mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 22:55:00 +00:00
[WIP] Business
This commit is contained in:
@@ -47,6 +47,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/TimeSelectionActionSheet",
|
||||
"//submodules/TelegramUI/Components/ChatListHeaderComponent",
|
||||
"//submodules/AttachmentUI",
|
||||
"//submodules/SearchBarNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
||||
@@ -183,11 +183,7 @@ final class AutomaticBusinessMessageSetupChatContents: ChatCustomContentsProtoco
|
||||
|
||||
let initialShortcut: String
|
||||
switch kind {
|
||||
case .awayMessageInput:
|
||||
initialShortcut = "_away"
|
||||
case .greetingMessageInput:
|
||||
initialShortcut = "_greeting"
|
||||
case let .quickReplyMessageInput(shortcut):
|
||||
case let .quickReplyMessageInput(shortcut, _):
|
||||
initialShortcut = shortcut
|
||||
}
|
||||
|
||||
@@ -217,9 +213,12 @@ final class AutomaticBusinessMessageSetupChatContents: ChatCustomContentsProtoco
|
||||
}
|
||||
|
||||
func quickReplyUpdateShortcut(value: String) {
|
||||
self.kind = .quickReplyMessageInput(shortcut: value)
|
||||
self.impl.with { impl in
|
||||
impl.quickReplyUpdateShortcut(value: value)
|
||||
switch self.kind {
|
||||
case let .quickReplyMessageInput(_, shortcutType):
|
||||
self.kind = .quickReplyMessageInput(shortcut: value, shortcutType: shortcutType)
|
||||
self.impl.with { impl in
|
||||
impl.quickReplyUpdateShortcut(value: value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
private struct AdditionalPeerList {
|
||||
struct AdditionalPeerList {
|
||||
enum Category: Int {
|
||||
case newChats = 0
|
||||
case existingChats = 1
|
||||
@@ -148,6 +148,8 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component {
|
||||
|
||||
private var replyToMessages: Bool = true
|
||||
|
||||
private var inactivityDays: Int = 7
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.scrollView = ScrollView()
|
||||
self.scrollView.showsVerticalScrollIndicator = true
|
||||
@@ -182,6 +184,66 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component {
|
||||
}
|
||||
|
||||
func attemptNavigation(complete: @escaping () -> Void) -> Bool {
|
||||
guard let component = self.component else {
|
||||
return true
|
||||
}
|
||||
|
||||
var mappedCategories: TelegramBusinessRecipients.Categories = []
|
||||
if self.additionalPeerList.categories.contains(.existingChats) {
|
||||
mappedCategories.insert(.existingChats)
|
||||
}
|
||||
if self.additionalPeerList.categories.contains(.newChats) {
|
||||
mappedCategories.insert(.newChats)
|
||||
}
|
||||
if self.additionalPeerList.categories.contains(.contacts) {
|
||||
mappedCategories.insert(.contacts)
|
||||
}
|
||||
if self.additionalPeerList.categories.contains(.nonContacts) {
|
||||
mappedCategories.insert(.nonContacts)
|
||||
}
|
||||
let recipients = TelegramBusinessRecipients(
|
||||
categories: mappedCategories,
|
||||
additionalPeers: Set(self.additionalPeerList.peers.map(\.peer.id)),
|
||||
exclude: self.hasAccessToAllChatsByDefault
|
||||
)
|
||||
|
||||
switch component.mode {
|
||||
case .greeting:
|
||||
var greetingMessage: TelegramBusinessGreetingMessage?
|
||||
if self.isOn, let currentShortcut = self.currentShortcut {
|
||||
greetingMessage = TelegramBusinessGreetingMessage(
|
||||
shortcutId: currentShortcut.id,
|
||||
recipients: recipients,
|
||||
inactivityDays: self.inactivityDays
|
||||
)
|
||||
}
|
||||
let _ = component.context.engine.accountData.updateBusinessGreetingMessage(greetingMessage: greetingMessage).startStandalone()
|
||||
case .away:
|
||||
var awayMessage: TelegramBusinessAwayMessage?
|
||||
if self.isOn, let currentShortcut = self.currentShortcut {
|
||||
let mappedSchedule: TelegramBusinessAwayMessage.Schedule
|
||||
switch self.schedule {
|
||||
case .always:
|
||||
mappedSchedule = .always
|
||||
case .outsideBusinessHours:
|
||||
mappedSchedule = .outsideWorkingHours
|
||||
case .custom:
|
||||
if let customScheduleStart = self.customScheduleStart, let customScheduleEnd = self.customScheduleEnd {
|
||||
mappedSchedule = .custom(beginTimestamp: Int32(customScheduleStart.timeIntervalSince1970), endTimestamp: Int32(customScheduleEnd.timeIntervalSince1970))
|
||||
} else {
|
||||
//TODO:localize
|
||||
return false
|
||||
}
|
||||
}
|
||||
awayMessage = TelegramBusinessAwayMessage(
|
||||
shortcutId: currentShortcut.id,
|
||||
recipients: recipients,
|
||||
schedule: mappedSchedule
|
||||
)
|
||||
}
|
||||
let _ = component.context.engine.accountData.updateBusinessAwayMessage(awayMessage: awayMessage).startStandalone()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -236,14 +298,14 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component {
|
||||
let additionalCategories: [ChatListNodeAdditionalCategory] = [
|
||||
ChatListNodeAdditionalCategory(
|
||||
id: self.hasAccessToAllChatsByDefault ? AdditionalCategoryId.existingChats.rawValue : AdditionalCategoryId.newChats.rawValue,
|
||||
icon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Contact"), color: .white), cornerRadius: 12.0, color: .purple),
|
||||
smallIcon: generateAvatarImage(size: CGSize(width: 22.0, height: 22.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Contact"), color: .white), iconScale: 0.6, cornerRadius: 6.0, circleCorners: true, color: .purple),
|
||||
icon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: self.hasAccessToAllChatsByDefault ? "Chat List/Filters/Chats" : "Chat List/Filters/NewChats"), color: .white), cornerRadius: 12.0, color: .purple),
|
||||
smallIcon: generateAvatarImage(size: CGSize(width: 22.0, height: 22.0), icon: generateTintedImage(image: UIImage(bundleImageName: self.hasAccessToAllChatsByDefault ? "Chat List/Filters/Chats" : "Chat List/Filters/NewChats"), color: .white), iconScale: 0.6, cornerRadius: 6.0, circleCorners: true, color: .purple),
|
||||
title: self.hasAccessToAllChatsByDefault ? "Existing Chats" : "New Chats"
|
||||
),
|
||||
ChatListNodeAdditionalCategory(
|
||||
id: AdditionalCategoryId.contacts.rawValue,
|
||||
icon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/User"), color: .white), cornerRadius: 12.0, color: .blue),
|
||||
smallIcon: generateAvatarImage(size: CGSize(width: 22.0, height: 22.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/User"), color: .white), iconScale: 0.6, cornerRadius: 6.0, circleCorners: true, color: .blue),
|
||||
icon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Contact"), color: .white), cornerRadius: 12.0, color: .blue),
|
||||
smallIcon: generateAvatarImage(size: CGSize(width: 22.0, height: 22.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Contact"), color: .white), iconScale: 0.6, cornerRadius: 6.0, circleCorners: true, color: .blue),
|
||||
title: "Contacts"
|
||||
),
|
||||
ChatListNodeAdditionalCategory(
|
||||
@@ -354,16 +416,19 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component {
|
||||
}
|
||||
|
||||
let shortcutName: String
|
||||
let shortcutType: ChatQuickReplyShortcutType
|
||||
switch component.mode {
|
||||
case .greeting:
|
||||
shortcutName = "hello"
|
||||
shortcutType = .greeting
|
||||
case .away:
|
||||
shortcutName = "away"
|
||||
shortcutType = .away
|
||||
}
|
||||
|
||||
let contents = AutomaticBusinessMessageSetupChatContents(
|
||||
context: component.context,
|
||||
kind: .quickReplyMessageInput(shortcut: shortcutName),
|
||||
kind: .quickReplyMessageInput(shortcut: shortcutName, shortcutType: shortcutType),
|
||||
shortcutId: self.currentShortcut?.id
|
||||
)
|
||||
let chatController = component.context.sharedContext.makeChatController(
|
||||
@@ -471,13 +536,58 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component {
|
||||
if self.component == nil {
|
||||
self.accountPeer = component.initialData.accountPeer
|
||||
|
||||
var initialRecipients: TelegramBusinessRecipients?
|
||||
|
||||
let shortcutName: String
|
||||
switch component.mode {
|
||||
case .greeting:
|
||||
shortcutName = "hello"
|
||||
|
||||
if let greetingMessage = component.initialData.greetingMessage {
|
||||
self.isOn = true
|
||||
initialRecipients = greetingMessage.recipients
|
||||
|
||||
self.inactivityDays = greetingMessage.inactivityDays
|
||||
}
|
||||
case .away:
|
||||
shortcutName = "away"
|
||||
|
||||
if let awayMessage = component.initialData.awayMessage {
|
||||
self.isOn = true
|
||||
initialRecipients = awayMessage.recipients
|
||||
}
|
||||
}
|
||||
|
||||
if let initialRecipients {
|
||||
var mappedCategories = Set<AdditionalPeerList.Category>()
|
||||
if initialRecipients.categories.contains(.existingChats) {
|
||||
mappedCategories.insert(.existingChats)
|
||||
}
|
||||
if initialRecipients.categories.contains(.newChats) {
|
||||
mappedCategories.insert(.newChats)
|
||||
}
|
||||
if initialRecipients.categories.contains(.contacts) {
|
||||
mappedCategories.insert(.contacts)
|
||||
}
|
||||
if initialRecipients.categories.contains(.nonContacts) {
|
||||
mappedCategories.insert(.nonContacts)
|
||||
}
|
||||
|
||||
var additionalPeers: [AdditionalPeerList.Peer] = []
|
||||
for peerId in initialRecipients.additionalPeers {
|
||||
if let peer = component.initialData.additionalPeers[peerId] {
|
||||
additionalPeers.append(peer)
|
||||
}
|
||||
}
|
||||
|
||||
self.additionalPeerList = AdditionalPeerList(
|
||||
categories: mappedCategories,
|
||||
peers: additionalPeers
|
||||
)
|
||||
|
||||
self.hasAccessToAllChatsByDefault = initialRecipients.exclude
|
||||
}
|
||||
|
||||
self.currentShortcut = component.initialData.shortcutMessageList.items.first(where: { $0.shortcut == shortcutName })
|
||||
|
||||
self.currentShortcutDisposable = (component.context.engine.accountData.shortcutMessageList()
|
||||
@@ -532,9 +642,6 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component {
|
||||
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
|
||||
let sectionSpacing: CGFloat = 32.0
|
||||
|
||||
let _ = bottomContentInset
|
||||
let _ = sectionSpacing
|
||||
|
||||
var contentHeight: CGFloat = 0.0
|
||||
|
||||
contentHeight += environment.navigationHeight
|
||||
@@ -548,7 +655,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component {
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
)
|
||||
let iconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) * 0.5), y: contentHeight + 2.0), size: iconSize)
|
||||
let iconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) * 0.5), y: contentHeight + 8.0), size: iconSize)
|
||||
if let iconView = self.icon.view {
|
||||
if iconView.superview == nil {
|
||||
self.scrollView.addSubview(iconView)
|
||||
@@ -557,7 +664,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component {
|
||||
iconView.bounds = CGRect(origin: CGPoint(), size: iconFrame.size)
|
||||
}
|
||||
|
||||
contentHeight += 129.0
|
||||
contentHeight += 124.0
|
||||
|
||||
//TODO:localize
|
||||
let subtitleString = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(component.mode == .greeting ? "Greet customers when they message you the first time or after a period of no activity." : "Automatically reply with a message when you are away.", attributes: MarkdownAttributes(
|
||||
@@ -1163,6 +1270,19 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component {
|
||||
otherSectionsHeight += sectionSpacing
|
||||
|
||||
if case .greeting = component.mode {
|
||||
var selectedInactivityIndex = 0
|
||||
let valueList: [Int] = [
|
||||
7,
|
||||
14,
|
||||
21,
|
||||
28
|
||||
]
|
||||
for i in 0 ..< valueList.count {
|
||||
if valueList[i] <= self.inactivityDays {
|
||||
selectedInactivityIndex = i
|
||||
}
|
||||
}
|
||||
|
||||
let periodSectionSize = self.periodSection.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(ListSectionComponent(
|
||||
@@ -1192,12 +1312,14 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component {
|
||||
"21 days",
|
||||
"28 days"
|
||||
],
|
||||
selectedIndex: 0,
|
||||
selectedIndex: selectedInactivityIndex,
|
||||
selectedIndexUpdated: { [weak self] index in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let _ = self
|
||||
let index = max(0, min(valueList.count - 1, index))
|
||||
self.inactivityDays = valueList[index]
|
||||
self.state?.updated(transition: .immediate)
|
||||
}
|
||||
)))
|
||||
]
|
||||
@@ -1268,15 +1390,24 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component {
|
||||
|
||||
public final class AutomaticBusinessMessageSetupScreen: ViewControllerComponentContainer {
|
||||
public final class InitialData: AutomaticBusinessMessageSetupScreenInitialData {
|
||||
let accountPeer: EnginePeer?
|
||||
let shortcutMessageList: ShortcutMessageList
|
||||
fileprivate let accountPeer: EnginePeer?
|
||||
fileprivate let shortcutMessageList: ShortcutMessageList
|
||||
fileprivate let greetingMessage: TelegramBusinessGreetingMessage?
|
||||
fileprivate let awayMessage: TelegramBusinessAwayMessage?
|
||||
fileprivate let additionalPeers: [EnginePeer.Id: AutomaticBusinessMessageSetupScreenComponent.AdditionalPeerList.Peer]
|
||||
|
||||
init(
|
||||
fileprivate init(
|
||||
accountPeer: EnginePeer?,
|
||||
shortcutMessageList: ShortcutMessageList
|
||||
shortcutMessageList: ShortcutMessageList,
|
||||
greetingMessage: TelegramBusinessGreetingMessage?,
|
||||
awayMessage: TelegramBusinessAwayMessage?,
|
||||
additionalPeers: [EnginePeer.Id: AutomaticBusinessMessageSetupScreenComponent.AdditionalPeerList.Peer]
|
||||
) {
|
||||
self.accountPeer = accountPeer
|
||||
self.shortcutMessageList = shortcutMessageList
|
||||
self.greetingMessage = greetingMessage
|
||||
self.awayMessage = awayMessage
|
||||
self.additionalPeers = additionalPeers
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1334,16 +1465,48 @@ public final class AutomaticBusinessMessageSetupScreen: ViewControllerComponentC
|
||||
public static func initialData(context: AccountContext) -> Signal<AutomaticBusinessMessageSetupScreenInitialData, NoError> {
|
||||
return combineLatest(
|
||||
context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId),
|
||||
TelegramEngine.EngineData.Item.Peer.BusinessGreetingMessage(id: context.account.peerId),
|
||||
TelegramEngine.EngineData.Item.Peer.BusinessAwayMessage(id: context.account.peerId)
|
||||
),
|
||||
context.engine.accountData.shortcutMessageList()
|
||||
|> take(1)
|
||||
)
|
||||
|> map { accountPeer, shortcutMessageList -> AutomaticBusinessMessageSetupScreenInitialData in
|
||||
return InitialData(
|
||||
accountPeer: accountPeer,
|
||||
shortcutMessageList: shortcutMessageList
|
||||
|> mapToSignal { data, shortcutMessageList -> Signal<AutomaticBusinessMessageSetupScreenInitialData, NoError> in
|
||||
let (accountPeer, greetingMessage, awayMessage) = data
|
||||
|
||||
var additionalPeerIds = Set<EnginePeer.Id>()
|
||||
if let greetingMessage {
|
||||
additionalPeerIds.formUnion(greetingMessage.recipients.additionalPeers)
|
||||
}
|
||||
if let awayMessage {
|
||||
additionalPeerIds.formUnion(awayMessage.recipients.additionalPeers)
|
||||
}
|
||||
|
||||
return context.engine.data.get(
|
||||
EngineDataMap(additionalPeerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:))),
|
||||
EngineDataMap(additionalPeerIds.map(TelegramEngine.EngineData.Item.Peer.IsContact.init(id:)))
|
||||
)
|
||||
|> map { peers, isContacts -> AutomaticBusinessMessageSetupScreenInitialData in
|
||||
var additionalPeers: [EnginePeer.Id: AutomaticBusinessMessageSetupScreenComponent.AdditionalPeerList.Peer] = [:]
|
||||
for id in additionalPeerIds {
|
||||
guard let peer = peers[id], let peer else {
|
||||
continue
|
||||
}
|
||||
additionalPeers[id] = AutomaticBusinessMessageSetupScreenComponent.AdditionalPeerList.Peer(
|
||||
peer: peer,
|
||||
isContact: isContacts[id] ?? false
|
||||
)
|
||||
}
|
||||
|
||||
return InitialData(
|
||||
accountPeer: accountPeer,
|
||||
shortcutMessageList: shortcutMessageList,
|
||||
greetingMessage: greetingMessage,
|
||||
awayMessage: awayMessage,
|
||||
additionalPeers: additionalPeers
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import ChatListHeaderComponent
|
||||
import PlainButtonComponent
|
||||
import MultilineTextComponent
|
||||
import AttachmentUI
|
||||
import SearchBarNode
|
||||
|
||||
final class QuickReplySetupScreenComponent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
@@ -453,6 +454,8 @@ final class QuickReplySetupScreenComponent: Component {
|
||||
var options: ListViewDeleteAndInsertOptions = [.Synchronous, .LowLatency]
|
||||
if animated {
|
||||
options.insert(.AnimateInsertion)
|
||||
} else {
|
||||
options.insert(.PreferSynchronousResourceLoading)
|
||||
}
|
||||
|
||||
self.transaction(
|
||||
@@ -476,6 +479,8 @@ final class QuickReplySetupScreenComponent: Component {
|
||||
private let navigationBarView = ComponentView<Empty>()
|
||||
private var navigationHeight: CGFloat?
|
||||
|
||||
private var searchBarNode: SearchBarNode?
|
||||
|
||||
private var selectionPanel: ComponentView<Empty>?
|
||||
|
||||
private var isUpdating: Bool = false
|
||||
@@ -492,10 +497,14 @@ final class QuickReplySetupScreenComponent: Component {
|
||||
|
||||
private var isEditing: Bool = false
|
||||
private var isSearchDisplayControllerActive: Bool = false
|
||||
private var searchQuery: String = ""
|
||||
private let searchQueryComponentSeparationCharacterSet: CharacterSet
|
||||
|
||||
private var accountPeer: EnginePeer?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.searchQueryComponentSeparationCharacterSet = CharacterSet(charactersIn: " _.:/")
|
||||
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
@@ -528,9 +537,18 @@ final class QuickReplySetupScreenComponent: Component {
|
||||
}
|
||||
|
||||
if let shortcut {
|
||||
let shortcutType: ChatQuickReplyShortcutType
|
||||
if shortcut == "hello" {
|
||||
shortcutType = .greeting
|
||||
} else if shortcut == "away" {
|
||||
shortcutType = .away
|
||||
} else {
|
||||
shortcutType = .generic
|
||||
}
|
||||
|
||||
let contents = AutomaticBusinessMessageSetupChatContents(
|
||||
context: component.context,
|
||||
kind: .quickReplyMessageInput(shortcut: shortcut),
|
||||
kind: .quickReplyMessageInput(shortcut: shortcut, shortcutType: shortcutType),
|
||||
shortcutId: shortcutId
|
||||
)
|
||||
let chatController = component.context.sharedContext.makeChatController(
|
||||
@@ -779,9 +797,8 @@ final class QuickReplySetupScreenComponent: Component {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = self
|
||||
//self.isSearchDisplayControllerActive = true
|
||||
//self.state?.updated(transition: .spring(duration: 0.4))
|
||||
self.isSearchDisplayControllerActive = true
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
},
|
||||
openStatusSetup: { _ in
|
||||
},
|
||||
@@ -952,6 +969,84 @@ final class QuickReplySetupScreenComponent: Component {
|
||||
)
|
||||
self.navigationHeight = navigationHeight
|
||||
|
||||
var removedSearchBar: SearchBarNode?
|
||||
if self.isSearchDisplayControllerActive {
|
||||
let searchBarNode: SearchBarNode
|
||||
var searchBarTransition = transition
|
||||
if let current = self.searchBarNode {
|
||||
searchBarNode = current
|
||||
} else {
|
||||
searchBarTransition = .immediate
|
||||
let searchBarTheme = SearchBarNodeTheme(theme: environment.theme, hasSeparator: false)
|
||||
searchBarNode = SearchBarNode(
|
||||
theme: searchBarTheme,
|
||||
strings: environment.strings,
|
||||
fieldStyle: .modern,
|
||||
displayBackground: false
|
||||
)
|
||||
searchBarNode.placeholderString = NSAttributedString(string: environment.strings.Common_Search, font: Font.regular(17.0), textColor: searchBarTheme.placeholder)
|
||||
self.searchBarNode = searchBarNode
|
||||
searchBarNode.cancel = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.isSearchDisplayControllerActive = false
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
}
|
||||
searchBarNode.textUpdated = { [weak self] query, _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if self.searchQuery != query {
|
||||
self.searchQuery = query.lowercased().trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
self.state?.updated(transition: .immediate)
|
||||
}
|
||||
}
|
||||
DispatchQueue.main.async { [weak self, weak searchBarNode] in
|
||||
guard let self, let searchBarNode, self.searchBarNode === searchBarNode else {
|
||||
return
|
||||
}
|
||||
searchBarNode.activate()
|
||||
|
||||
if let controller = self.environment?.controller() as? QuickReplySetupScreen {
|
||||
controller.requestAttachmentMenuExpansion()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight - 54.0 + 2.0), size: CGSize(width: availableSize.width, height: 54.0))
|
||||
if isModal {
|
||||
searchBarFrame.origin.y += 2.0
|
||||
}
|
||||
searchBarNode.updateLayout(boundingSize: searchBarFrame.size, leftInset: environment.safeInsets.left + 6.0, rightInset: environment.safeInsets.right, transition: searchBarTransition.containedViewLayoutTransition)
|
||||
searchBarTransition.setFrame(view: searchBarNode.view, frame: searchBarFrame)
|
||||
if searchBarNode.view.superview == nil {
|
||||
self.addSubview(searchBarNode.view)
|
||||
|
||||
if case let .curve(duration, curve) = transition.animation, let navigationBarView = self.navigationBarView.view as? ChatListNavigationBar.View, let placeholderNode = navigationBarView.searchContentNode?.placeholderNode {
|
||||
let timingFunction: String
|
||||
switch curve {
|
||||
case .easeInOut:
|
||||
timingFunction = CAMediaTimingFunctionName.easeOut.rawValue
|
||||
case .linear:
|
||||
timingFunction = CAMediaTimingFunctionName.linear.rawValue
|
||||
case .spring:
|
||||
timingFunction = kCAMediaTimingFunctionSpring
|
||||
case .custom:
|
||||
timingFunction = kCAMediaTimingFunctionSpring
|
||||
}
|
||||
|
||||
searchBarNode.animateIn(from: placeholderNode, duration: duration, timingFunction: timingFunction)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.searchQuery = ""
|
||||
if let searchBarNode = self.searchBarNode {
|
||||
self.searchBarNode = nil
|
||||
removedSearchBar = searchBarNode
|
||||
}
|
||||
}
|
||||
|
||||
if !self.selectedIds.isEmpty {
|
||||
let selectionPanel: ComponentView<Empty>
|
||||
var selectionPanelTransition = transition
|
||||
@@ -1058,11 +1153,25 @@ final class QuickReplySetupScreenComponent: Component {
|
||||
if let shortcutMessageList = self.shortcutMessageList, let accountPeer = self.accountPeer {
|
||||
switch component.mode {
|
||||
case .manage:
|
||||
entries.append(.add)
|
||||
if self.searchQuery.isEmpty {
|
||||
entries.append(.add)
|
||||
}
|
||||
case .select:
|
||||
break
|
||||
}
|
||||
for item in shortcutMessageList.items {
|
||||
if !self.searchQuery.isEmpty {
|
||||
var matches = false
|
||||
inner: for nameComponent in item.shortcut.lowercased().components(separatedBy: self.searchQueryComponentSeparationCharacterSet) {
|
||||
if nameComponent.lowercased().hasPrefix(self.searchQuery) {
|
||||
matches = true
|
||||
break inner
|
||||
}
|
||||
}
|
||||
if !matches {
|
||||
continue
|
||||
}
|
||||
}
|
||||
entries.append(.item(item: item, accountPeer: accountPeer, sortIndex: entries.count, isEditing: self.isEditing, isSelected: self.selectedIds.contains(item.id)))
|
||||
}
|
||||
}
|
||||
@@ -1081,6 +1190,17 @@ final class QuickReplySetupScreenComponent: Component {
|
||||
navigationBarComponentView.applyCurrentScroll(transition: transition)
|
||||
}
|
||||
|
||||
if let removedSearchBar {
|
||||
if !transition.animation.isImmediate, let navigationBarView = self.navigationBarView.view as? ChatListNavigationBar.View, let placeholderNode =
|
||||
navigationBarView.searchContentNode?.placeholderNode {
|
||||
removedSearchBar.transitionOut(to: placeholderNode, transition: transition.containedViewLayoutTransition, completion: { [weak removedSearchBar] in
|
||||
removedSearchBar?.view.removeFromSuperview()
|
||||
})
|
||||
} else {
|
||||
removedSearchBar.view.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user