Business fixes

This commit is contained in:
Isaac 2024-03-01 15:18:46 +04:00
parent 23103a97d1
commit 1db0db3929
6 changed files with 149 additions and 44 deletions

View File

@ -23,6 +23,7 @@ import PlainButtonComponent
import MultilineTextComponent import MultilineTextComponent
import AttachmentUI import AttachmentUI
import SearchBarNode import SearchBarNode
import BalancedTextComponent
final class QuickReplySetupScreenComponent: Component { final class QuickReplySetupScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -474,6 +475,7 @@ final class QuickReplySetupScreenComponent: Component {
final class View: UIView { final class View: UIView {
private var emptyState: ComponentView<Empty>? private var emptyState: ComponentView<Empty>?
private var contentListNode: ContentListNode? private var contentListNode: ContentListNode?
private var emptySearchState: ComponentView<Empty>?
private let navigationBarView = ComponentView<Empty>() private let navigationBarView = ComponentView<Empty>()
private var navigationHeight: CGFloat? private var navigationHeight: CGFloat?
@ -1171,6 +1173,41 @@ final class QuickReplySetupScreenComponent: Component {
} }
contentListNode.setEntries(entries: entries, animated: !transition.animation.isImmediate) contentListNode.setEntries(entries: entries, animated: !transition.animation.isImmediate)
if !self.searchQuery.isEmpty && entries.isEmpty {
var emptySearchStateTransition = transition
let emptySearchState: ComponentView<Empty>
if let current = self.emptySearchState {
emptySearchState = current
} else {
emptySearchStateTransition = emptySearchStateTransition.withAnimation(.none)
emptySearchState = ComponentView()
self.emptySearchState = emptySearchState
}
let emptySearchStateSize = emptySearchState.update(
transition: .immediate,
component: AnyComponent(BalancedTextComponent(
text: .plain(NSAttributedString(string: environment.strings.Conversation_SearchNoResults, font: Font.regular(17.0), textColor: environment.theme.list.freeTextColor, paragraphAlignment: .center)),
horizontalAlignment: .center,
maximumNumberOfLines: 0
)),
environment: {},
containerSize: CGSize(width: availableSize.width - 16.0 * 2.0, height: availableSize.height)
)
var emptySearchStateBottomInset = listBottomInset
emptySearchStateBottomInset = max(emptySearchStateBottomInset, environment.inputHeight)
let emptySearchStateFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - emptySearchStateSize.width) * 0.5), y: navigationHeight + floor((availableSize.height - emptySearchStateBottomInset - navigationHeight) * 0.5)), size: emptySearchStateSize)
if let emptySearchStateView = emptySearchState.view {
if emptySearchStateView.superview == nil {
self.addSubview(emptySearchStateView)
}
emptySearchStateTransition.containedViewLayoutTransition.updatePosition(layer: emptySearchStateView.layer, position: emptySearchStateFrame.center)
emptySearchStateView.bounds = CGRect(origin: CGPoint(), size: emptySearchStateFrame.size)
}
} else if let emptySearchState = self.emptySearchState {
self.emptySearchState = nil
emptySearchState.view?.removeFromSuperview()
}
if let shortcutMessageList = self.shortcutMessageList, !shortcutMessageList.items.isEmpty { if let shortcutMessageList = self.shortcutMessageList, !shortcutMessageList.items.isEmpty {
contentListNode.isHidden = false contentListNode.isHidden = false
} else { } else {

View File

@ -27,7 +27,9 @@ private func wrappedMinuteRange(range: Range<Int>, dayIndexOffset: Int = 0) -> I
var result = IndexSet() var result = IndexSet()
if mappedRange.upperBound > 7 * 24 * 60 { if mappedRange.upperBound > 7 * 24 * 60 {
result.insert(integersIn: mappedRange.lowerBound ..< 7 * 24 * 60) if mappedRange.lowerBound < 7 * 24 * 60 {
result.insert(integersIn: mappedRange.lowerBound ..< 7 * 24 * 60)
}
result.insert(integersIn: 0 ..< (mappedRange.upperBound - 7 * 24 * 60)) result.insert(integersIn: 0 ..< (mappedRange.upperBound - 7 * 24 * 60))
} else { } else {
result.insert(integersIn: mappedRange) result.insert(integersIn: mappedRange)

View File

@ -23,6 +23,8 @@ swift_library(
"//submodules/PresentationDataUtils", "//submodules/PresentationDataUtils",
"//submodules/SearchBarNode", "//submodules/SearchBarNode",
"//submodules/TelegramUIPreferences", "//submodules/TelegramUIPreferences",
"//submodules/ComponentFlow",
"//submodules/Components/BalancedTextComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -13,6 +13,8 @@ import AccountContext
import SearchBarNode import SearchBarNode
import SearchUI import SearchUI
import TelegramUIPreferences import TelegramUIPreferences
import ComponentFlow
import BalancedTextComponent
private struct TimezoneListEntry: Comparable, Identifiable { private struct TimezoneListEntry: Comparable, Identifiable {
var id: String var id: String
@ -60,6 +62,7 @@ private struct TimezoneListSearchContainerTransition {
let insertions: [ListViewInsertItem] let insertions: [ListViewInsertItem]
let updates: [ListViewUpdateItem] let updates: [ListViewUpdateItem]
let isSearching: Bool let isSearching: Bool
let isEmptyResult: Bool
} }
private func preparedLanguageListSearchContainerTransition(presentationData: PresentationData, from fromEntries: [TimezoneListEntry], to toEntries: [TimezoneListEntry], action: @escaping (String) -> Void, isSearching: Bool, forceUpdate: Bool) -> TimezoneListSearchContainerTransition { private func preparedLanguageListSearchContainerTransition(presentationData: PresentationData, from fromEntries: [TimezoneListEntry], to toEntries: [TimezoneListEntry], action: @escaping (String) -> Void, isSearching: Bool, forceUpdate: Bool) -> TimezoneListSearchContainerTransition {
@ -69,7 +72,7 @@ private func preparedLanguageListSearchContainerTransition(presentationData: Pre
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(presentationData: presentationData, searchMode: true, action: action), directionHint: nil) } let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(presentationData: presentationData, searchMode: true, action: action), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(presentationData: presentationData, searchMode: true, action: action), directionHint: nil) } let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(presentationData: presentationData, searchMode: true, action: action), directionHint: nil) }
return TimezoneListSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, isSearching: isSearching) return TimezoneListSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, isSearching: isSearching, isEmptyResult: isSearching && toEntries.isEmpty)
} }
private final class TimezoneListSearchContainerNode: SearchDisplayControllerContentNode { private final class TimezoneListSearchContainerNode: SearchDisplayControllerContentNode {
@ -77,6 +80,8 @@ private final class TimezoneListSearchContainerNode: SearchDisplayControllerCont
private let dimNode: ASDisplayNode private let dimNode: ASDisplayNode
private let listNode: ListView private let listNode: ListView
private var notFoundText: ComponentView<Empty>?
private var enqueuedTransitions: [TimezoneListSearchContainerTransition] = [] private var enqueuedTransitions: [TimezoneListSearchContainerTransition] = []
private var hasValidLayout = false private var hasValidLayout = false
@ -88,6 +93,9 @@ private final class TimezoneListSearchContainerNode: SearchDisplayControllerCont
private let presentationDataPromise: Promise<PresentationData> private let presentationDataPromise: Promise<PresentationData>
private var isEmptyResult: Bool = false
private var currentLayout: (layout: ContainerViewLayout, navigationBarHeight: CGFloat)?
public override var hasDim: Bool { public override var hasDim: Bool {
return true return true
} }
@ -220,14 +228,26 @@ private final class TimezoneListSearchContainerNode: SearchDisplayControllerCont
options.insert(.PreferSynchronousDrawing) options.insert(.PreferSynchronousDrawing)
let isSearching = transition.isSearching let isSearching = transition.isSearching
let isEmptyResult = transition.isEmptyResult
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in
self?.listNode.isHidden = !isSearching guard let self else {
self?.dimNode.isHidden = isSearching return
}
self.listNode.isHidden = !isSearching
self.dimNode.isHidden = isSearching
self.isEmptyResult = isEmptyResult
if let currentLayout = self.currentLayout {
self.containerLayoutUpdated(currentLayout.layout, navigationBarHeight: currentLayout.navigationBarHeight, transition: .immediate)
}
}) })
} }
} }
override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
self.currentLayout = (layout, navigationBarHeight)
super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
let topInset = navigationBarHeight let topInset = navigationBarHeight
@ -244,6 +264,36 @@ private final class TimezoneListSearchContainerNode: SearchDisplayControllerCont
self.dequeueTransitions() self.dequeueTransitions()
} }
} }
if self.isEmptyResult {
let notFoundText: ComponentView<Empty>
if let current = self.notFoundText {
notFoundText = current
} else {
notFoundText = ComponentView()
self.notFoundText = notFoundText
}
let notFoundTextSize = notFoundText.update(
transition: .immediate,
component: AnyComponent(BalancedTextComponent(
text: .plain(NSAttributedString(string: self.presentationData.strings.Conversation_SearchNoResults, font: Font.regular(17.0), textColor: self.presentationData.theme.list.freeTextColor, paragraphAlignment: .center)),
horizontalAlignment: .center,
maximumNumberOfLines: 0
)),
environment: {},
containerSize: CGSize(width: layout.size.width - 16.0 * 2.0, height: layout.size.height)
)
let notFoundTextFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - notFoundTextSize.width) * 0.5), y: navigationBarHeight + floor((layout.size.height - navigationBarHeight - notFoundTextSize.height) * 0.5)), size: notFoundTextSize)
if let notFoundTextView = notFoundText.view {
if notFoundTextView.superview == nil {
self.view.addSubview(notFoundTextView)
}
notFoundTextView.frame = notFoundTextFrame
}
} else if let notFoundText = self.notFoundText {
self.notFoundText = nil
notFoundText.view?.removeFromSuperview()
}
} }
@objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) { @objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) {

View File

@ -28,10 +28,6 @@ private func inputQueryResultPriority(_ result: ChatPresentationInputQueryResult
} }
func inputContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatInputContextPanelNode?, controllerInteraction: ChatControllerInteraction, interfaceInteraction: ChatPanelInterfaceInteraction?, chatPresentationContext: ChatPresentationContext) -> ChatInputContextPanelNode? { func inputContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatInputContextPanelNode?, controllerInteraction: ChatControllerInteraction, interfaceInteraction: ChatPanelInterfaceInteraction?, chatPresentationContext: ChatPresentationContext) -> ChatInputContextPanelNode? {
guard let _ = chatPresentationInterfaceState.renderedPeer?.peer else {
return nil
}
if chatPresentationInterfaceState.showCommands, let renderedPeer = chatPresentationInterfaceState.renderedPeer { if chatPresentationInterfaceState.showCommands, let renderedPeer = chatPresentationInterfaceState.renderedPeer {
if let currentPanel = currentPanel as? CommandMenuChatInputContextPanelNode { if let currentPanel = currentPanel as? CommandMenuChatInputContextPanelNode {
return currentPanel return currentPanel

View File

@ -15,9 +15,6 @@ import ChatPresentationInterfaceState
import ChatContextQuery import ChatContextQuery
func contextQueryResultStateForChatInterfacePresentationState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentQueryStates: inout [ChatPresentationInputQueryKind: (ChatPresentationInputQuery, Disposable)], requestBotLocationStatus: @escaping (PeerId) -> Void) -> [ChatPresentationInputQueryKind: ChatContextQueryUpdate] { func contextQueryResultStateForChatInterfacePresentationState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentQueryStates: inout [ChatPresentationInputQueryKind: (ChatPresentationInputQuery, Disposable)], requestBotLocationStatus: @escaping (PeerId) -> Void) -> [ChatPresentationInputQueryKind: ChatContextQueryUpdate] {
guard let peer = chatPresentationInterfaceState.renderedPeer?.peer else {
return [:]
}
let inputQueries = inputContextQueriesForChatPresentationIntefaceState(chatPresentationInterfaceState).filter({ query in let inputQueries = inputContextQueriesForChatPresentationIntefaceState(chatPresentationInterfaceState).filter({ query in
if chatPresentationInterfaceState.editMessageState != nil { if chatPresentationInterfaceState.editMessageState != nil {
switch query { switch query {
@ -36,7 +33,7 @@ func contextQueryResultStateForChatInterfacePresentationState(_ chatPresentation
for query in inputQueries { for query in inputQueries {
let previousQuery = currentQueryStates[query.kind]?.0 let previousQuery = currentQueryStates[query.kind]?.0
if previousQuery != query { if previousQuery != query {
let signal = updatedContextQueryResultStateForQuery(context: context, peer: peer, chatLocation: chatPresentationInterfaceState.chatLocation, inputQuery: query, previousQuery: previousQuery, requestBotLocationStatus: requestBotLocationStatus) let signal = updatedContextQueryResultStateForQuery(context: context, peer: chatPresentationInterfaceState.renderedPeer?.peer, chatLocation: chatPresentationInterfaceState.chatLocation, inputQuery: query, previousQuery: previousQuery, requestBotLocationStatus: requestBotLocationStatus)
updates[query.kind] = .update(query, signal) updates[query.kind] = .update(query, signal)
} }
} }
@ -57,7 +54,7 @@ func contextQueryResultStateForChatInterfacePresentationState(_ chatPresentation
return updates return updates
} }
private func updatedContextQueryResultStateForQuery(context: AccountContext, peer: Peer, chatLocation: ChatLocation, inputQuery: ChatPresentationInputQuery, previousQuery: ChatPresentationInputQuery?, requestBotLocationStatus: @escaping (PeerId) -> Void) -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> { private func updatedContextQueryResultStateForQuery(context: AccountContext, peer: Peer?, chatLocation: ChatLocation, inputQuery: ChatPresentationInputQuery, previousQuery: ChatPresentationInputQuery?, requestBotLocationStatus: @escaping (PeerId) -> Void) -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> {
switch inputQuery { switch inputQuery {
case let .emoji(query): case let .emoji(query):
var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = .complete() var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = .complete()
@ -154,46 +151,64 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
let inlineBots: Signal<[(EnginePeer, Double)], NoError> = types.contains(.contextBots) ? context.engine.peers.recentlyUsedInlineBots() : .single([]) let inlineBots: Signal<[(EnginePeer, Double)], NoError> = types.contains(.contextBots) ? context.engine.peers.recentlyUsedInlineBots() : .single([])
let strings = context.sharedContext.currentPresentationData.with({ $0 }).strings let strings = context.sharedContext.currentPresentationData.with({ $0 }).strings
let participants = combineLatest(inlineBots, searchPeerMembers(context: context, peerId: peer.id, chatLocation: chatLocation, query: query, scope: .mention))
|> map { inlineBots, peers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in let participants: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError>
let filteredInlineBots = inlineBots.sorted(by: { $0.1 > $1.1 }).filter { peer, rating in if let peer {
if rating < 0.14 { participants = combineLatest(
inlineBots,
searchPeerMembers(context: context, peerId: peer.id, chatLocation: chatLocation, query: query, scope: .mention)
)
|> map { inlineBots, peers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
let filteredInlineBots = inlineBots.sorted(by: { $0.1 > $1.1 }).filter { peer, rating in
if rating < 0.14 {
return false
}
if peer.indexName.matchesByTokens(normalizedQuery) {
return true
}
if let addressName = peer.addressName, addressName.lowercased().hasPrefix(normalizedQuery) {
return true
}
return false return false
} }.map { $0.0 }
if peer.indexName.matchesByTokens(normalizedQuery) {
let inlineBotPeerIds = Set(filteredInlineBots.map { $0.id })
let filteredPeers = peers.filter { peer in
if inlineBotPeerIds.contains(peer.id) {
return false
}
if !types.contains(.accountPeer) && peer.id == context.account.peerId {
return false
}
return true return true
} }
if let addressName = peer.addressName, addressName.lowercased().hasPrefix(normalizedQuery) { var sortedPeers = filteredInlineBots
return true sortedPeers.append(contentsOf: filteredPeers.sorted(by: { lhs, rhs in
let result = lhs.indexName.stringRepresentation(lastNameFirst: true).compare(rhs.indexName.stringRepresentation(lastNameFirst: true))
return result == .orderedAscending
}))
sortedPeers = sortedPeers.filter { peer in
return !peer.displayTitle(strings: strings, displayOrder: .firstLast).isEmpty
} }
return false return { _ in return .mentions(sortedPeers) }
}.map { $0.0 }
let inlineBotPeerIds = Set(filteredInlineBots.map { $0.id })
let filteredPeers = peers.filter { peer in
if inlineBotPeerIds.contains(peer.id) {
return false
}
if !types.contains(.accountPeer) && peer.id == context.account.peerId {
return false
}
return true
} }
var sortedPeers = filteredInlineBots |> castError(ChatContextQueryError.self)
sortedPeers.append(contentsOf: filteredPeers.sorted(by: { lhs, rhs in } else {
let result = lhs.indexName.stringRepresentation(lastNameFirst: true).compare(rhs.indexName.stringRepresentation(lastNameFirst: true)) participants = .single({ _ in return nil })
return result == .orderedAscending
}))
sortedPeers = sortedPeers.filter { peer in
return !peer.displayTitle(strings: strings, displayOrder: .firstLast).isEmpty
}
return { _ in return .mentions(sortedPeers) }
} }
|> castError(ChatContextQueryError.self)
return signal |> then(participants) return signal |> then(participants)
case let .command(query): case let .command(query):
guard let peer else {
return .single({ _ in return .commands(ChatInputQueryCommandsResult(
commands: [],
accountPeer: nil,
hasShortcuts: false,
query: ""
)) })
}
let normalizedQuery = query.lowercased() let normalizedQuery = query.lowercased()
var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = .complete() var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = .complete()
@ -258,6 +273,9 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
|> castError(ChatContextQueryError.self) |> castError(ChatContextQueryError.self)
return signal |> then(commands) return signal |> then(commands)
case let .contextRequest(addressName, query): case let .contextRequest(addressName, query):
guard let peer else {
return .single({ _ in return .contextRequestResult(nil, nil) })
}
var delayRequest = true var delayRequest = true
var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = .complete() var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = .complete()
if let previousQuery = previousQuery { if let previousQuery = previousQuery {