This commit is contained in:
Isaac 2024-05-26 15:51:45 +04:00
commit 6ff4cd3681
19 changed files with 290 additions and 67 deletions

View file

@ -54,7 +54,8 @@ final class HashtagSearchControllerNode: ASDisplayNode {
self.shimmerNode.allowsGroupOpacity = true
self.recentListNode = HashtagSearchRecentListNode(context: context)
self.recentListNode.alpha = 0.0
let navigationController = controller.navigationController as? NavigationController
if let peer {
self.currentController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(id: peer.id), subject: nil, botStart: nil, mode: .inline(navigationController))
@ -150,6 +151,10 @@ final class HashtagSearchControllerNode: ASDisplayNode {
}
self.searchContentNode.query = query
self.updateSearchQuery(query)
Queue.mainQueue().after(0.4) {
let _ = addRecentHashtagSearchQuery(engine: context.engine, string: query).startStandalone()
}
}
self.currentController?.isSelectingMessagesUpdated = { [weak self] isSelecting in
@ -303,9 +308,20 @@ final class HashtagSearchControllerNode: ASDisplayNode {
self.insertSubnode(self.recentListNode, aboveSubnode: self.shimmerNode)
}
self.recentListNode.frame = CGRect(origin: .zero, size: layout.size)
transition.updateFrame(node: self.recentListNode, frame: CGRect(origin: .zero, size: layout.size))
self.recentListNode.updateLayout(layout: ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: insets.top - 35.0, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.safeInsets.right), safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: transition)
self.recentListNode.isHidden = !self.query.isEmpty
let recentTransition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
if self.query.isEmpty {
recentTransition.updateAlpha(node: self.recentListNode, alpha: 1.0)
} else if self.recentListNode.alpha > 0.0 {
Queue.mainQueue().after(0.1, {
if !self.query.isEmpty {
recentTransition.updateAlpha(node: self.recentListNode, alpha: 0.0)
}
})
}
if !self.hasValidLayout {
self.hasValidLayout = true

View file

@ -94,7 +94,7 @@ final class HashtagSearchGlobalChatContents: ChatCustomContentsProtocol {
}
func loadMore() {
guard self.historyViewDisposable == nil, let currentSearchState = self.currentSearchState else {
guard self.historyViewDisposable == nil, let currentSearchState = self.currentSearchState, let currentHistoryView = self.sourceHistoryView, currentHistoryView.holeEarlier else {
return
}
@ -114,7 +114,7 @@ final class HashtagSearchGlobalChatContents: ChatCustomContentsProtocol {
let updateType: ViewUpdateType = .FillHole
let historyView = MessageHistoryView(tag: nil, namespaces: .just(Set([Namespaces.Message.Cloud])), entries: result.0.messages.reversed().map { MessageHistoryEntry(message: $0, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false)) }, holeEarlier: false, holeLater: false, isLoading: false)
let historyView = MessageHistoryView(tag: nil, namespaces: .just(Set([Namespaces.Message.Cloud])), entries: result.0.messages.reversed().map { MessageHistoryEntry(message: $0, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false)) }, holeEarlier: !result.0.completed, holeLater: false, isLoading: false)
self.sourceHistoryView = historyView
self.updateHistoryView(updateType: updateType)

View file

@ -768,10 +768,14 @@ public final class ChatInlineSearchResultsListComponent: Component {
}
let renderedPeer: EngineRenderedPeer
if let effectiveAuthor {
if let effectiveAuthor, !component.showEmptyResults {
renderedPeer = EngineRenderedPeer(peer: effectiveAuthor)
} else {
renderedPeer = EngineRenderedPeer(peerId: message.id.peerId, peers: [:], associatedMedia: [:])
var peers: [EnginePeer.Id: EnginePeer] = [:]
if let peer = message.peers[message.id.peerId] {
peers[message.id.peerId] = EnginePeer(peer)
}
renderedPeer = EngineRenderedPeer(peerId: message.id.peerId, peers: peers, associatedMedia: [:])
}
return ChatListItem(
@ -800,7 +804,7 @@ public final class ChatInlineSearchResultsListComponent: Component {
inputActivities: nil,
promoInfo: nil,
ignoreUnreadBadge: false,
displayAsMessage: component.peerId != component.context.account.peerId,
displayAsMessage: component.peerId != component.context.account.peerId && !component.showEmptyResults,
hasFailedMessages: false,
forumTopicData: nil,
topForumTopicItems: [],

View file

@ -75,11 +75,16 @@ private func messagesShouldBeMerged(accountPeerId: PeerId, _ lhs: Message, _ rhs
}
}
var sameChat = true
if lhs.id.peerId != rhs.id.peerId {
sameChat = false
}
var sameThread = true
if let lhsPeer = lhs.peers[lhs.id.peerId], let rhsPeer = rhs.peers[rhs.id.peerId], arePeersEqual(lhsPeer, rhsPeer), let channel = lhsPeer as? TelegramChannel, channel.flags.contains(.isForum), lhs.threadId != rhs.threadId {
sameThread = false
}
var sameAuthor = false
if lhsEffectiveAuthor?.id == rhsEffectiveAuthor?.id && lhs.effectivelyIncoming(accountPeerId) == rhs.effectivelyIncoming(accountPeerId) {
sameAuthor = true
@ -124,7 +129,7 @@ private func messagesShouldBeMerged(accountPeerId: PeerId, _ lhs: Message, _ rhs
}
}
if abs(lhsEffectiveTimestamp - rhsEffectiveTimestamp) < Int32(10 * 60) && sameAuthor && sameThread {
if abs(lhsEffectiveTimestamp - rhsEffectiveTimestamp) < Int32(10 * 60) && sameChat && sameAuthor && sameThread {
if let channel = lhs.peers[lhs.id.peerId] as? TelegramChannel, case .group = channel.info, lhsEffectiveAuthor?.id == channel.id, !lhs.effectivelyIncoming(accountPeerId) {
return .none
}

View file

@ -22,6 +22,7 @@ public final class GiftAvatarComponent: Component {
let theme: PresentationTheme
let peers: [EnginePeer]
let photo: TelegramMediaWebFile?
let starsPeer: StarsContext.State.Transaction.Peer?
let isVisible: Bool
let hasIdleAnimations: Bool
let hasScaleAnimation: Bool
@ -29,11 +30,24 @@ public final class GiftAvatarComponent: Component {
let color: UIColor?
let offset: CGFloat?
public init(context: AccountContext, theme: PresentationTheme, peers: [EnginePeer], photo: TelegramMediaWebFile? = nil, isVisible: Bool, hasIdleAnimations: Bool, hasScaleAnimation: Bool = true, avatarSize: CGFloat = 100.0, color: UIColor? = nil, offset: CGFloat? = nil) {
public init(
context: AccountContext,
theme: PresentationTheme,
peers: [EnginePeer],
photo: TelegramMediaWebFile? = nil,
starsPeer: StarsContext.State.Transaction.Peer? = nil,
isVisible: Bool,
hasIdleAnimations: Bool,
hasScaleAnimation: Bool = true,
avatarSize: CGFloat = 100.0,
color: UIColor? = nil,
offset: CGFloat? = nil
) {
self.context = context
self.theme = theme
self.peers = peers
self.photo = photo
self.starsPeer = starsPeer
self.isVisible = isVisible
self.hasIdleAnimations = hasIdleAnimations
self.hasScaleAnimation = hasScaleAnimation
@ -76,6 +90,9 @@ public final class GiftAvatarComponent: Component {
private var mergedAvatarsNode: MergedAvatarsNode?
private var imageNode: TransformImageNode?
private var iconBackgroundView: UIImageView?
private var iconView: UIImageView?
private let badgeBackground = ComponentView<Empty>()
private let badge = ComponentView<Empty>()
@ -321,6 +338,82 @@ public final class GiftAvatarComponent: Component {
imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: imageSize.width / 2.0), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))()
self.avatarNode.isHidden = true
} else if let starsPeer = component.starsPeer {
let iconBackgroundView: UIImageView
let iconView: UIImageView
if let currentBackground = self.iconBackgroundView, let current = self.iconView {
iconBackgroundView = currentBackground
iconView = current
} else {
iconBackgroundView = UIImageView()
iconView = UIImageView()
self.addSubview(iconBackgroundView)
self.addSubview(iconView)
self.iconBackgroundView = iconBackgroundView
self.iconView = iconView
let size = CGSize(width: component.avatarSize, height: component.avatarSize)
var iconInset: CGFloat = 9.0
var iconOffset: CGFloat = 0.0
switch starsPeer {
case .appStore:
iconBackgroundView.image = generateGradientFilledCircleImage(
diameter: size.width,
colors: [
UIColor(rgb: 0x2a9ef1).cgColor,
UIColor(rgb: 0x72d5fd).cgColor
],
direction: .mirroredDiagonal
)
iconView.image = UIImage(bundleImageName: "Premium/Stars/Apple")
case .playMarket:
iconBackgroundView.image = generateGradientFilledCircleImage(
diameter: size.width,
colors: [
UIColor(rgb: 0x54cb68).cgColor,
UIColor(rgb: 0xa0de7e).cgColor
],
direction: .mirroredDiagonal
)
iconView.image = UIImage(bundleImageName: "Premium/Stars/Google")
case .fragment:
iconBackgroundView.image = generateFilledCircleImage(diameter: size.width, color: UIColor(rgb: 0x1b1f24))
iconView.image = UIImage(bundleImageName: "Premium/Stars/Fragment")
iconOffset = 5.0
case .premiumBot:
iconInset = 15.0
iconBackgroundView.image = generateGradientFilledCircleImage(
diameter: size.width,
colors: [
UIColor(rgb: 0x6b93ff).cgColor,
UIColor(rgb: 0x6b93ff).cgColor,
UIColor(rgb: 0x8d77ff).cgColor,
UIColor(rgb: 0xb56eec).cgColor,
UIColor(rgb: 0xb56eec).cgColor
],
direction: .mirroredDiagonal
)
iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon"), color: .white)
case .peer, .unsupported:
iconInset = 15.0
iconBackgroundView.image = generateGradientFilledCircleImage(
diameter: size.width,
colors: [
UIColor(rgb: 0xb1b1b1).cgColor,
UIColor(rgb: 0xcdcdcd).cgColor
],
direction: .mirroredDiagonal
)
iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon"), color: .white)
}
let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - size.width) / 2.0), y: 113.0 - size.height / 2.0), size: size)
iconBackgroundView.frame = imageFrame
iconView.frame = imageFrame.insetBy(dx: iconInset, dy: iconInset).offsetBy(dx: 0.0, dy: iconOffset)
}
} else if component.peers.count > 1 {
let avatarSize = CGSize(width: 60.0, height: 60.0)

View file

@ -38,6 +38,7 @@ swift_library(
"//submodules/Components/SolidRoundedButtonComponent",
"//submodules/TelegramUI/Components/AnimatedTextComponent",
"//submodules/AvatarNode",
"//submodules/PhotoResources",
],
visibility = [
"//visibility:public",

View file

@ -170,6 +170,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
let date: Int32
let via: String?
let toPeer: EnginePeer?
let transactionPeer: StarsContext.State.Transaction.Peer?
let photo: TelegramMediaWebFile?
var delayedCloseOnOpenPeer = true
@ -205,6 +206,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
} else {
toPeer = nil
}
transactionPeer = transaction.peer
photo = transaction.photo
case let .receipt(receipt):
titleText = receipt.invoiceMedia.title
@ -218,6 +220,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
} else {
toPeer = nil
}
transactionPeer = nil
photo = receipt.invoiceMedia.photo
delayedCloseOnOpenPeer = false
}
@ -252,6 +255,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
theme: theme,
peers: toPeer.flatMap { [$0] } ?? [],
photo: photo,
starsPeer: transactionPeer,
isVisible: true,
hasIdleAnimations: true,
hasScaleAnimation: false,
@ -1090,11 +1094,30 @@ private final class TransactionCellComponent: Component {
containerSize: CGSize(width: availableSize.width, height: availableSize.height)
)
func brokenLine(_ string: String) -> String {
let middleIndex = string.index(string.startIndex, offsetBy: string.count / 2)
var newString = string
newString.insert("\n", at: middleIndex)
return newString
}
let text: String
if availableSize.width > 230.0 {
text = component.transactionId
} else {
text = brokenLine(component.transactionId)
}
let textSize = self.text.update(
transition: .immediate,
component: AnyComponent(
MultilineTextComponent(
text: .plain(NSAttributedString(string: component.transactionId, font: Font.monospace(15.0), textColor: component.textColor, paragraphAlignment: .left)),
text: .plain(NSAttributedString(
string: text,
font: Font.monospace(15.0),
textColor: component.textColor,
paragraphAlignment: .left
)),
maximumNumberOfLines: 0,
lineSpacing: 0.2
)
@ -1103,9 +1126,9 @@ private final class TransactionCellComponent: Component {
containerSize: CGSize(width: availableSize.width - buttonSize.width - spacing, height: availableSize.height)
)
let size = CGSize(width: textSize.width + spacing + buttonSize.width, height: textSize.height)
let size = CGSize(width: availableSize.width, height: textSize.height)
let buttonFrame = CGRect(origin: CGPoint(x: textSize.width + spacing, y: floorToScreenPixels((size.height - buttonSize.height) / 2.0)), size: buttonSize)
let buttonFrame = CGRect(origin: CGPoint(x: availableSize.width - buttonSize.width - 2.0, y: floorToScreenPixels((size.height - buttonSize.height) / 2.0)), size: buttonSize)
if let buttonView = self.button.view {
if buttonView.superview == nil {
self.addSubview(buttonView)

View file

@ -14,6 +14,7 @@ import ListActionItemComponent
import TelegramStringFormatting
import AvatarNode
import BundleIconComponent
import PhotoResources
final class StarsTransactionsListPanelComponent: Component {
typealias EnvironmentType = StarsTransactionsPanelEnvironment
@ -150,7 +151,7 @@ final class StarsTransactionsListPanelComponent: Component {
if #available(iOS 13.0, *) {
self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false
}
self.scrollView.showsVerticalScrollIndicator = true
self.scrollView.showsVerticalScrollIndicator = false
self.scrollView.showsHorizontalScrollIndicator = false
self.scrollView.alwaysBounceHorizontal = false
self.scrollView.scrollsToTop = false
@ -215,8 +216,13 @@ final class StarsTransactionsListPanelComponent: Component {
let itemDate: String
switch item.transaction.peer {
case let .peer(peer):
itemTitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)
itemSubtitle = item.transaction.title
if let title = item.transaction.title {
itemTitle = title
itemSubtitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)
} else {
itemTitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)
itemSubtitle = nil
}
case .appStore:
itemTitle = environment.strings.Stars_Intro_Transaction_AppleTopUp_Title
itemSubtitle = environment.strings.Stars_Intro_Transaction_AppleTopUp_Subtitle
@ -286,7 +292,7 @@ final class StarsTransactionsListPanelComponent: Component {
theme: environment.theme,
title: AnyComponent(VStack(titleComponents, alignment: .left, spacing: 2.0)),
contentInsets: UIEdgeInsets(top: 9.0, left: environment.containerInsets.left, bottom: 8.0, right: environment.containerInsets.right),
leftIcon: .custom(AnyComponentWithIdentity(id: "avatar", component: AnyComponent(AvatarComponent(context: component.context, theme: environment.theme, peer: item.transaction.peer))), false),
leftIcon: .custom(AnyComponentWithIdentity(id: "avatar", component: AnyComponent(AvatarComponent(context: component.context, theme: environment.theme, peer: item.transaction.peer, photo: item.transaction.photo))), false),
icon: nil,
accessory: .custom(ListActionItemComponent.CustomAccessory(component: AnyComponentWithIdentity(id: "label", component: AnyComponent(LabelComponent(text: itemLabel))), insets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 16.0))),
action: { [weak self] _ in
@ -442,11 +448,13 @@ private final class AvatarComponent: Component {
let context: AccountContext
let theme: PresentationTheme
let peer: StarsContext.State.Transaction.Peer
let photo: TelegramMediaWebFile?
init(context: AccountContext, theme: PresentationTheme, peer: StarsContext.State.Transaction.Peer) {
init(context: AccountContext, theme: PresentationTheme, peer: StarsContext.State.Transaction.Peer, photo: TelegramMediaWebFile?) {
self.context = context
self.theme = theme
self.peer = peer
self.photo = photo
}
static func ==(lhs: AvatarComponent, rhs: AvatarComponent) -> Bool {
@ -459,6 +467,9 @@ private final class AvatarComponent: Component {
if lhs.peer != rhs.peer {
return false
}
if lhs.photo != rhs.photo {
return false
}
return true
}
@ -466,6 +477,9 @@ private final class AvatarComponent: Component {
private let avatarNode: AvatarNode
private let backgroundView = UIImageView()
private let iconView = UIImageView()
private var imageNode: TransformImageNode?
private let fetchDisposable = MetaDisposable()
private var component: AvatarComponent?
private weak var state: EmptyComponentState?
@ -486,6 +500,10 @@ private final class AvatarComponent: Component {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.fetchDisposable.dispose()
}
func update(component: AvatarComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
self.component = component
self.state = state
@ -496,15 +514,36 @@ private final class AvatarComponent: Component {
switch component.peer {
case let .peer(peer):
self.avatarNode.setPeer(
context: component.context,
theme: component.theme,
peer: peer,
synchronousLoad: true
)
self.backgroundView.isHidden = true
self.iconView.isHidden = true
self.avatarNode.isHidden = false
if let photo = component.photo {
let imageNode: TransformImageNode
if let current = self.imageNode {
imageNode = current
} else {
imageNode = TransformImageNode()
self.addSubview(imageNode.view)
self.imageNode = imageNode
imageNode.setSignal(chatWebFileImage(account: component.context.account, file: photo))
self.fetchDisposable.set(chatMessageWebFileInteractiveFetched(account: component.context.account, userLocation: .other, image: photo).startStrict())
}
imageNode.frame = CGRect(origin: .zero, size: size)
imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: size.width / 2.0), imageSize: size, boundingSize: size, intrinsicInsets: UIEdgeInsets()))()
self.backgroundView.isHidden = true
self.iconView.isHidden = true
self.avatarNode.isHidden = true
} else {
self.avatarNode.setPeer(
context: component.context,
theme: component.theme,
peer: peer,
synchronousLoad: true
)
self.backgroundView.isHidden = true
self.iconView.isHidden = true
self.avatarNode.isHidden = false
}
case .appStore:
self.backgroundView.image = generateGradientFilledCircleImage(
diameter: size.width,

View file

@ -725,7 +725,7 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer {
let resultController = UndoOverlayController(
presentationData: presentationData,
content: .image(
image: UIImage(bundleImageName: "Premium/Stars/StarMedium")!,
image: UIImage(bundleImageName: "Premium/Stars/StarLarge")!,
title: presentationData.strings.Stars_Intro_PurchasedTitle,
text: presentationData.strings.Stars_Intro_PurchasedText(presentationData.strings.Stars_Intro_PurchasedText_Stars(Int32(stars))).string,
round: false,
@ -740,6 +740,10 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer {
}
self.starsContext.load()
Queue.mainQueue().after(0.5, {
self.starsContext.loadMore()
})
}
required public init(coder aDecoder: NSCoder) {

View file

@ -341,7 +341,7 @@ private final class SheetContent: CombinedComponent {
transition: .immediate
)
let balanceIcon = balanceIcon.update(
component: BundleIconComponent(name: "Premium/Stars/StarMedium", tintColor: nil),
component: BundleIconComponent(name: "Premium/Stars/StarLarge", tintColor: nil),
availableSize: context.availableSize,
transition: .immediate
)

View file

@ -1,7 +1,7 @@
{
"images" : [
{
"filename" : "Star20.pdf",
"filename" : "Star20 (3).pdf",
"idiom" : "universal"
}
],

View file

@ -1,7 +1,7 @@
{
"images" : [
{
"filename" : "star_18.pdf",
"filename" : "star_18 (3).pdf",
"idiom" : "universal"
}
],

View file

@ -1,7 +1,7 @@
{
"images" : [
{
"filename" : "star_16 (2).pdf",
"filename" : "star_16 (3).pdf",
"idiom" : "universal"
}
],

View file

@ -3035,10 +3035,17 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
if self.chatHistoryLocationValue == historyView.locationInput {
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Navigation(index: .upperBound, anchorIndex: .upperBound, count: historyMessageCount, highlight: false), id: self.takeNextHistoryLocationId())
}
} else if mathesLast && historyView.originalView.earlierId != nil {
} else if mathesLast {
let locationInput: ChatHistoryLocation = .Navigation(index: .message(firstEntry.index), anchorIndex: .message(firstEntry.index), count: historyMessageCount, highlight: false)
if self.chatHistoryLocationValue?.content != locationInput {
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: locationInput, id: self.takeNextHistoryLocationId())
if historyView.originalView.earlierId != nil {
if self.chatHistoryLocationValue?.content != locationInput {
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: locationInput, id: self.takeNextHistoryLocationId())
}
} else if case let .customChatContents(customChatContents) = self.subject, case .hashTagSearch = customChatContents.kind {
if self.chatHistoryLocationValue?.content != locationInput {
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: locationInput, id: self.takeNextHistoryLocationId())
customChatContents.loadMore()
}
}
}
}
@ -4260,37 +4267,58 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
loop: for i in 0 ..< historyView.filteredEntries.count {
switch historyView.filteredEntries[i] {
case let .MessageEntry(message, presentationData, read, location, selection, attributes):
if message.id == id {
let index = historyView.filteredEntries.count - 1 - i
let item: ListViewItem
switch self.mode {
case .bubbles:
item = ChatMessageItemImpl(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, associatedData: associatedData, controllerInteraction: self.controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes, location: location), disableDate: disableFloatingDateHeaders)
case let .list(_, _, _, displayHeaders, hintLinks, isGlobalSearch):
let displayHeader: Bool
switch displayHeaders {
case .none:
displayHeader = false
case .all:
displayHeader = true
case .allButLast:
displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != historyView.lastHeaderId
}
item = ListMessageItem(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: self.controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch)
case let .MessageEntry(message, presentationData, read, location, selection, attributes):
if message.id == id {
let index = historyView.filteredEntries.count - 1 - i
let item: ListViewItem
switch self.mode {
case .bubbles:
item = ChatMessageItemImpl(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, associatedData: associatedData, controllerInteraction: self.controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes, location: location), disableDate: disableFloatingDateHeaders)
case let .list(_, _, _, displayHeaders, hintLinks, isGlobalSearch):
let displayHeader: Bool
switch displayHeaders {
case .none:
displayHeader = false
case .all:
displayHeader = true
case .allButLast:
displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != historyView.lastHeaderId
}
let updateItem = ListViewUpdateItem(index: index, previousIndex: index, item: item, directionHint: nil)
var scrollToItem: ListViewScrollToItem?
if scroll {
scrollToItem = ListViewScrollToItem(index: index, position: .center(.top), animated: true, curve: .Spring(duration: 0.4), directionHint: .Down, displayLink: true)
}
self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [updateItem], options: [.AnimateInsertion], scrollToItem: scrollToItem, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
break loop
item = ListMessageItem(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: self.controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch)
}
default:
break
let updateItem = ListViewUpdateItem(index: index, previousIndex: index, item: item, directionHint: nil)
var scrollToItem: ListViewScrollToItem?
if scroll {
scrollToItem = ListViewScrollToItem(index: index, position: .center(.top), animated: true, curve: .Spring(duration: 0.4), directionHint: .Down, displayLink: true)
}
self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [updateItem], options: [.AnimateInsertion], scrollToItem: scrollToItem, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
break loop
}
case let .MessageGroupEntry(_, messages, presentationData):
if messages.contains(where: { $0.0.id == id }) {
let index = historyView.filteredEntries.count - 1 - i
let item: ListViewItem
switch self.mode {
case .bubbles:
item = ChatMessageItemImpl(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, associatedData: associatedData, controllerInteraction: self.controllerInteraction, content: .group(messages: messages), disableDate: disableFloatingDateHeaders)
case .list:
assertionFailure()
item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: messages[0].0, selection: .none, displayHeader: false)
}
let updateItem = ListViewUpdateItem(index: index, previousIndex: index, item: item, directionHint: nil)
var scrollToItem: ListViewScrollToItem?
if scroll {
scrollToItem = ListViewScrollToItem(index: index, position: .center(.top), animated: true, curve: .Spring(duration: 0.4), directionHint: .Down, displayLink: true)
}
self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [updateItem], options: [.AnimateInsertion], scrollToItem: scrollToItem, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
break loop
}
default:
break
}
}
}

View file

@ -281,10 +281,12 @@ private func canViewReadStats(message: Message, participantCount: Int?, isMessag
}
func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, accountPeerId: PeerId) -> Bool {
if case let .customChatContents(contents) = chatPresentationInterfaceState.subject, case .hashTagSearch = contents.kind {
return false
}
if case .customChatContents = chatPresentationInterfaceState.chatLocation {
return true
}
guard let peer = chatPresentationInterfaceState.renderedPeer?.peer else {
return false
}

View file

@ -228,7 +228,15 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
if let currentId = results.currentId, let index = results.messageIndices.firstIndex(where: { $0.id == currentId }) {
canChangeListMode = true
if params.interfaceState.displayHistoryFilterAsList || self.alwaysShowTotalMessagesCount {
if self.alwaysShowTotalMessagesCount {
let value = presentationStringsFormattedNumber(Int32(displayTotalCount), params.interfaceState.dateTimeFormat.groupingSeparator)
let suffix = params.interfaceState.strings.Chat_BottomSearchPanel_MessageCount(Int32(displayTotalCount))
resultsTextString = [AnimatedTextComponent.Item(
id: "text",
isUnbreakable: true,
content: .text(params.interfaceState.strings.Chat_BottomSearchPanel_MessageCountFormat(value, suffix).string)
)]
} else if params.interfaceState.displayHistoryFilterAsList {
resultsTextString = extractAnimatedTextString(string: params.interfaceState.strings.Chat_BottomSearchPanel_MessageCountFormat(
".",
"."