Comment [WIP]

This commit is contained in:
Ali 2020-09-26 19:00:57 +03:00
parent 4076d3dc4d
commit e2f8229ae2
21 changed files with 3949 additions and 3774 deletions

View File

@ -1556,6 +1556,7 @@
"Channel.ErrorAccessDenied" = "Sorry, this channel is private.";
"Group.ErrorAccessDenied" = "Sorry, this group is private.";
"CommentsGroup.ErrorAccessDenied" = "Sorry, you can't access this chat because you were banned by an admin";
"Conversation.InputTextBroadcastPlaceholder" = "Broadcast";
"Channel.NotificationLoading" = "Loading...";
@ -5810,3 +5811,6 @@ Any member of this group will be able to see messages in the channel.";
"Channel.CommentsGroup.Header" = "Select a group chat that will be used to host comments from your channel.";
"Channel.CommentsGroup.HeaderSet" = "%@ is selected as the group that will be used to host comments for your channel.";
"Channel.CommentsGroup.HeaderGroupSet" = "%@ is linking the group as it's discussion board.";
"RepliesChat.DescriptionTitle" = "What is it?";
"RepliesChat.DescriptionText" = "This is a chat with replies.";

View File

@ -149,7 +149,9 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
}
let status: ContactsPeerItemStatus
if let user = primaryPeer as? TelegramUser {
if primaryPeer.id.isReplies {
status = .none
} else if let user = primaryPeer as? TelegramUser {
let servicePeer = isServicePeer(primaryPeer)
if user.flags.contains(.isSupport) && !servicePeer {
status = .custom(string: strings.Bot_GenericSupportStatus, multiline: false)

View File

@ -1843,7 +1843,9 @@ private func statusStringForPeerType(accountPeerId: PeerId, strings: Presentatio
}
}
if let user = peer as? TelegramUser {
if peer.id.isReplies {
return nil
} else if let user = peer as? TelegramUser {
if user.botInfo != nil || user.flags.contains(.isSupport) {
return (strings.ChatList_PeerTypeBot, false)
} else if isContact {

View File

@ -425,6 +425,9 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo
} else if peer.flags.contains(.isSupport), !servicePeer {
statusText = item.presentationData.strings.Bot_GenericSupportStatus
statusColor = item.presentationData.theme.list.itemSecondaryTextColor
} else if peer.id.isReplies {
statusText = ""
statusColor = item.presentationData.theme.list.itemPrimaryTextColor
} else if let _ = peer.botInfo {
statusText = item.presentationData.strings.Bot_GenericBotStatus
statusColor = item.presentationData.theme.list.itemSecondaryTextColor

View File

@ -139,6 +139,7 @@ private final class OverlayStatusControllerImpl: ViewController, StandalonePrese
private let type: OverlayStatusControllerType
private var animatedDidAppear = false
private var isDismissed = false
private var controllerNode: OverlayStatusControllerNode {
return self.displayNode as! OverlayStatusControllerNode
@ -181,8 +182,12 @@ private final class OverlayStatusControllerImpl: ViewController, StandalonePrese
}
override public func dismiss(completion: (() -> Void)? = nil) {
if self.isDismissed {
completion?()
return
}
self.isDismissed = true
self.controllerNode.dismiss()
completion?()
}
}

View File

@ -0,0 +1,80 @@
import Foundation
public struct PeerChatThreadId: Hashable {
public var peerId: PeerId
public var threadId: Int64
public init(peerId: PeerId, threadId: Int64) {
self.peerId = peerId
self.threadId = threadId
}
}
final class PeerChatThreadInterfaceStateTable: Table {
static func tableSpec(_ id: Int32) -> ValueBoxTable {
return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: false)
}
private var states: [PeerChatThreadId: PeerChatInterfaceState?] = [:]
private var peerIdsWithUpdatedStates = Set<PeerChatThreadId>()
private let sharedKey = ValueBoxKey(length: 8 + 8)
private func key(_ peerId: PeerChatThreadId, sharedKey: ValueBoxKey = ValueBoxKey(length: 8)) -> ValueBoxKey {
sharedKey.setInt64(0, value: peerId.peerId.toInt64())
sharedKey.setInt64(8, value: peerId.threadId)
return sharedKey
}
func get(_ peerId: PeerChatThreadId) -> PeerChatInterfaceState? {
if let cachedValue = self.states[peerId] {
return cachedValue
} else if let value = self.valueBox.get(self.table, key: self.key(peerId, sharedKey: self.sharedKey)), let state = PostboxDecoder(buffer: value).decodeRootObject() as? PeerChatInterfaceState {
self.states[peerId] = state
return state
} else {
self.states[peerId] = nil
return nil
}
}
func set(_ peerId: PeerChatThreadId, state: PeerChatInterfaceState?) -> Bool {
let currentState = self.get(peerId)
var updated = false
if let currentState = currentState, let state = state {
if !currentState.isEqual(to: state) {
updated = true
}
} else if (currentState != nil) != (state != nil) {
updated = true
}
if updated {
self.states[peerId] = state
self.peerIdsWithUpdatedStates.insert(peerId)
}
return updated
}
override func clearMemoryCache() {
self.states.removeAll()
self.peerIdsWithUpdatedStates.removeAll()
}
override func beforeCommit() {
if !self.peerIdsWithUpdatedStates.isEmpty {
let sharedEncoder = PostboxEncoder()
for peerId in self.peerIdsWithUpdatedStates {
if let state = self.states[peerId] {
if let state = state {
sharedEncoder.reset()
sharedEncoder.encodeRootObject(state)
self.valueBox.set(self.table, key: self.key(peerId, sharedKey: self.sharedKey), value: sharedEncoder.readBufferNoCopy())
} else {
self.valueBox.remove(self.table, key: self.key(peerId, sharedKey: self.sharedKey), secure: false)
}
}
}
self.peerIdsWithUpdatedStates.removeAll()
}
}
}

View File

@ -274,6 +274,11 @@ public final class Transaction {
self.postbox?.updatePeerChatInterfaceState(id, update: update)
}
public func updatePeerChatThreadInterfaceState(_ id: PeerId, threadId: Int64, update: (PeerChatInterfaceState?) -> (PeerChatInterfaceState?)) {
assert(!self.disposed)
self.postbox?.updatePeerChatThreadInterfaceState(id, threadId: threadId, update: update)
}
public func getPeer(_ id: PeerId) -> Peer? {
assert(!self.disposed)
return self.postbox?.peerTable.get(id)
@ -1294,6 +1299,7 @@ public final class Postbox {
let itemCollectionItemTable: ItemCollectionItemTable
let itemCollectionReverseIndexTable: ReverseIndexReferenceTable<ItemCollectionItemReverseIndexReference>
let peerChatInterfaceStateTable: PeerChatInterfaceStateTable
let peerChatThreadInterfaceStateTable: PeerChatThreadInterfaceStateTable
let itemCacheMetaTable: ItemCacheMetaTable
let itemCacheTable: ItemCacheTable
let peerNameTokenIndexTable: ReverseIndexReferenceTable<PeerIdReverseIndexReference>
@ -1394,6 +1400,7 @@ public final class Postbox {
self.itemCollectionReverseIndexTable = ReverseIndexReferenceTable<ItemCollectionItemReverseIndexReference>(valueBox: self.valueBox, table: ReverseIndexReferenceTable<ItemCollectionItemReverseIndexReference>.tableSpec(36))
self.itemCollectionItemTable = ItemCollectionItemTable(valueBox: self.valueBox, table: ItemCollectionItemTable.tableSpec(22), reverseIndexTable: self.itemCollectionReverseIndexTable)
self.peerChatInterfaceStateTable = PeerChatInterfaceStateTable(valueBox: self.valueBox, table: PeerChatInterfaceStateTable.tableSpec(23))
self.peerChatThreadInterfaceStateTable = PeerChatThreadInterfaceStateTable(valueBox: self.valueBox, table: PeerChatThreadInterfaceStateTable.tableSpec(64))
self.itemCacheMetaTable = ItemCacheMetaTable(valueBox: self.valueBox, table: ItemCacheMetaTable.tableSpec(24))
self.itemCacheTable = ItemCacheTable(valueBox: self.valueBox, table: ItemCacheTable.tableSpec(25))
self.chatListIndexTable = ChatListIndexTable(valueBox: self.valueBox, table: ChatListIndexTable.tableSpec(8), peerNameIndexTable: self.peerNameIndexTable, metadataTable: self.messageHistoryMetadataTable, readStateTable: self.readStateTable, notificationSettingsTable: self.peerNotificationSettingsTable)
@ -1445,6 +1452,7 @@ public final class Postbox {
tables.append(self.itemCollectionItemTable)
tables.append(self.itemCollectionReverseIndexTable)
tables.append(self.peerChatInterfaceStateTable)
tables.append(self.peerChatThreadInterfaceStateTable)
tables.append(self.itemCacheMetaTable)
tables.append(self.itemCacheTable)
tables.append(self.peerNameIndexTable)
@ -2119,6 +2127,11 @@ public final class Postbox {
}
}
fileprivate func updatePeerChatThreadInterfaceState(_ id: PeerId, threadId: Int64, update: (PeerChatInterfaceState?) -> (PeerChatInterfaceState?)) {
let updatedState = update(self.peerChatThreadInterfaceStateTable.get(PeerChatThreadId(peerId: id, threadId: threadId)))
let _ = self.peerChatThreadInterfaceStateTable.set(PeerChatThreadId(peerId: id, threadId: threadId), state: updatedState)
}
fileprivate func replaceRemoteContactCount(_ count: Int32) {
self.metadataTable.setRemoteContactCount(count)
self.currentReplaceRemoteContactCount = count
@ -2666,11 +2679,11 @@ public final class Postbox {
let initialData: InitialMessageHistoryData
switch peerIds {
case let .single(peerId):
initialData = self.initialMessageHistoryData(peerId: peerId)
initialData = self.initialMessageHistoryData(peerId: peerId, threadId: nil)
case let .associated(peerId, _):
initialData = self.initialMessageHistoryData(peerId: peerId)
initialData = self.initialMessageHistoryData(peerId: peerId, threadId: nil)
case let .external(input):
initialData = self.initialMessageHistoryData(peerId: input.peerId)
initialData = self.initialMessageHistoryData(peerId: input.peerId, threadId: input.threadId)
}
subscriber.putNext((MessageHistoryView(mutableView), initialUpdateType, initialData))
@ -2687,17 +2700,30 @@ public final class Postbox {
}
}
private func initialMessageHistoryData(peerId: PeerId) -> InitialMessageHistoryData {
let chatInterfaceState = self.peerChatInterfaceStateTable.get(peerId)
var associatedMessages: [MessageId: Message] = [:]
if let chatInterfaceState = chatInterfaceState {
for id in chatInterfaceState.associatedMessageIds {
if let message = self.getMessage(id) {
associatedMessages[message.id] = message
private func initialMessageHistoryData(peerId: PeerId, threadId: Int64?) -> InitialMessageHistoryData {
if let threadId = threadId {
let chatInterfaceState = self.peerChatThreadInterfaceStateTable.get(PeerChatThreadId(peerId: peerId, threadId: threadId))
var associatedMessages: [MessageId: Message] = [:]
if let chatInterfaceState = chatInterfaceState {
for id in chatInterfaceState.associatedMessageIds {
if let message = self.getMessage(id) {
associatedMessages[message.id] = message
}
}
}
return InitialMessageHistoryData(peer: self.peerTable.get(peerId), chatInterfaceState: chatInterfaceState, associatedMessages: associatedMessages)
} else {
let chatInterfaceState = self.peerChatInterfaceStateTable.get(peerId)
var associatedMessages: [MessageId: Message] = [:]
if let chatInterfaceState = chatInterfaceState {
for id in chatInterfaceState.associatedMessageIds {
if let message = self.getMessage(id) {
associatedMessages[message.id] = message
}
}
}
return InitialMessageHistoryData(peer: self.peerTable.get(peerId), chatInterfaceState: chatInterfaceState, associatedMessages: associatedMessages)
}
return InitialMessageHistoryData(peer: self.peerTable.get(peerId), chatInterfaceState: chatInterfaceState, associatedMessages: associatedMessages)
}
public func messageIndexAtId(_ id: MessageId) -> Signal<MessageIndex?, NoError> {

View File

@ -159,7 +159,7 @@ struct FetchMessageHistoryHoleResult: Equatable {
var actualThreadId: Int64?
}
func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryHoleSource, postbox: Postbox, peerInput: FetchMessageHistoryHoleThreadInput, namespace: MessageId.Namespace, direction: MessageHistoryViewRelativeHoleDirection, space: MessageHistoryHoleSpace, count rawCount: Int) -> Signal<FetchMessageHistoryHoleResult, NoError> {
func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryHoleSource, postbox: Postbox, peerInput: FetchMessageHistoryHoleThreadInput, namespace: MessageId.Namespace, direction: MessageHistoryViewRelativeHoleDirection, space: MessageHistoryHoleSpace, count rawCount: Int) -> Signal<FetchMessageHistoryHoleResult?, NoError> {
let count = min(100, rawCount)
return postbox.stateView()
@ -171,7 +171,7 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH
}
}
|> take(1)
|> mapToSignal { _ -> Signal<FetchMessageHistoryHoleResult, NoError> in
|> mapToSignal { _ -> Signal<FetchMessageHistoryHoleResult?, NoError> in
return postbox.transaction { transaction -> (Api.InputPeer?, Int32) in
switch peerInput {
case let .direct(peerId, _):
@ -180,7 +180,7 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH
return (transaction.getPeer(channelMessageId.peerId).flatMap(forceApiInputPeer), 0)
}
}
|> mapToSignal { (inputPeer, hash) -> Signal<FetchMessageHistoryHoleResult, NoError> in
|> mapToSignal { (inputPeer, hash) -> Signal<FetchMessageHistoryHoleResult?, NoError> in
guard let inputPeer = inputPeer else {
return .single(FetchMessageHistoryHoleResult(removedIndices: IndexSet(), strictRemovedIndices: IndexSet(), actualPeerId: nil, actualThreadId: nil))
}
@ -404,8 +404,27 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH
}
return request
|> retryRequest
|> mapToSignal { result -> Signal<FetchMessageHistoryHoleResult, NoError> in
|> retry(retryOnError: { error in
if error.errorDescription == "CHANNEL_PRIVATE" {
switch peerInput {
case let .direct(_, threadId):
if threadId != nil {
return false
}
case .threadFromChannel:
return false
}
}
return true
}, delayIncrement: 0.1, maxDelay: 2.0, maxRetries: 0, onQueue: .concurrentDefaultQueue())
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.messages.Messages?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<FetchMessageHistoryHoleResult?, NoError> in
guard let result = result else {
return .single(nil)
}
let messages: [Api.Message]
let chats: [Api.Chat]
let users: [Api.User]

View File

@ -225,7 +225,7 @@ private class ReplyThreadHistoryContextImpl {
self.currentHole?.1.dispose()
if let entry = entry {
self.currentHole = (entry, self.fetchHole(entry: entry).start(next: { [weak self] removedHoleIndices in
guard let strongSelf = self else {
guard let strongSelf = self, let removedHoleIndices = removedHoleIndices else {
return
}
if var currentHoles = strongSelf.stateValue?.holeIndices[Namespaces.Message.Cloud] {
@ -239,7 +239,7 @@ private class ReplyThreadHistoryContextImpl {
}
}
private func fetchHole(entry: MessageHistoryHolesViewEntry) -> Signal<FetchMessageHistoryHoleResult, NoError> {
private func fetchHole(entry: MessageHistoryHolesViewEntry) -> Signal<FetchMessageHistoryHoleResult?, NoError> {
switch entry.hole {
case let .peer(hole):
let fetchCount = min(entry.count, 100)
@ -386,8 +386,9 @@ public struct ChatReplyThreadMessage: Equatable {
public var maxReadOutgoingMessageId: MessageId?
public var initialFilledHoles: IndexSet
public var initialAnchor: Anchor
public var isNotAvailable: Bool
fileprivate init(messageId: MessageId, isChannelPost: Bool, maxMessage: MessageId?, maxReadIncomingMessageId: MessageId?, maxReadOutgoingMessageId: MessageId?, initialFilledHoles: IndexSet, initialAnchor: Anchor) {
fileprivate init(messageId: MessageId, isChannelPost: Bool, maxMessage: MessageId?, maxReadIncomingMessageId: MessageId?, maxReadOutgoingMessageId: MessageId?, initialFilledHoles: IndexSet, initialAnchor: Anchor, isNotAvailable: Bool) {
self.messageId = messageId
self.isChannelPost = isChannelPost
self.maxMessage = maxMessage
@ -395,6 +396,7 @@ public struct ChatReplyThreadMessage: Equatable {
self.maxReadOutgoingMessageId = maxReadOutgoingMessageId
self.initialFilledHoles = initialFilledHoles
self.initialAnchor = initialAnchor
self.isNotAvailable = isNotAvailable
}
}
@ -590,11 +592,11 @@ public func fetchChannelReplyThreadMessage(account: Account, messageId: MessageI
}
let preloadedHistory = preloadedHistoryPosition
|> mapToSignal { peerInput, commentsPeerId, threadMessageId, anchor, maxMessageId -> Signal<(FetchMessageHistoryHoleResult, ChatReplyThreadMessage.Anchor), FetchChannelReplyThreadMessageError> in
|> mapToSignal { peerInput, commentsPeerId, threadMessageId, anchor, maxMessageId -> Signal<(FetchMessageHistoryHoleResult?, ChatReplyThreadMessage.Anchor), FetchChannelReplyThreadMessageError> in
guard let maxMessageId = maxMessageId else {
return .single((FetchMessageHistoryHoleResult(removedIndices: IndexSet(integersIn: 1 ..< Int(Int32.max - 1)), strictRemovedIndices: IndexSet()), .automatic))
}
return account.postbox.transaction { transaction -> Signal<(FetchMessageHistoryHoleResult, ChatReplyThreadMessage.Anchor), FetchChannelReplyThreadMessageError> in
return account.postbox.transaction { transaction -> Signal<(FetchMessageHistoryHoleResult?, ChatReplyThreadMessage.Anchor), FetchChannelReplyThreadMessageError> in
if let threadMessageId = threadMessageId {
var holes = transaction.getThreadIndexHoles(peerId: threadMessageId.peerId, threadId: makeMessageThreadId(threadMessageId), namespace: Namespaces.Message.Cloud)
holes.remove(integersIn: Int(maxMessageId.id + 1) ..< Int(Int32.max))
@ -660,8 +662,11 @@ public func fetchChannelReplyThreadMessage(account: Account, messageId: MessageI
}
return fetchMessageHistoryHole(accountPeerId: account.peerId, source: .network(account.network), postbox: account.postbox, peerInput: peerInput, namespace: Namespaces.Message.Cloud, direction: direction, space: .everywhere, count: 40)
|> castError(FetchChannelReplyThreadMessageError.self)
|> mapToSignal { result -> Signal<(FetchMessageHistoryHoleResult, ChatReplyThreadMessage.Anchor), FetchChannelReplyThreadMessageError> in
return account.postbox.transaction { transaction -> (FetchMessageHistoryHoleResult, ChatReplyThreadMessage.Anchor) in
|> mapToSignal { result -> Signal<(FetchMessageHistoryHoleResult?, ChatReplyThreadMessage.Anchor), FetchChannelReplyThreadMessageError> in
return account.postbox.transaction { transaction -> (FetchMessageHistoryHoleResult?, ChatReplyThreadMessage.Anchor) in
guard let result = result else {
return (nil, .automatic)
}
let initialAnchor: ChatReplyThreadMessage.Anchor
switch anchor {
case .lowerBound:
@ -700,8 +705,10 @@ public func fetchChannelReplyThreadMessage(account: Account, messageId: MessageI
}
let (initialFilledHoles, initialAnchor) = initialFilledHolesAndInitialAnchor
return account.postbox.transaction { transaction -> Signal<ChatReplyThreadMessage, FetchChannelReplyThreadMessageError> in
for range in initialFilledHoles.strictRemovedIndices.rangeView {
transaction.removeThreadIndexHole(peerId: discussionMessage.messageId.peerId, threadId: makeMessageThreadId(discussionMessage.messageId), namespace: Namespaces.Message.Cloud, space: .everywhere, range: Int32(range.lowerBound) ... Int32(range.upperBound))
if let initialFilledHoles = initialFilledHoles {
for range in initialFilledHoles.strictRemovedIndices.rangeView {
transaction.removeThreadIndexHole(peerId: discussionMessage.messageId.peerId, threadId: makeMessageThreadId(discussionMessage.messageId), namespace: Namespaces.Message.Cloud, space: .everywhere, range: Int32(range.lowerBound) ... Int32(range.upperBound))
}
}
return .single(ChatReplyThreadMessage(
@ -710,8 +717,9 @@ public func fetchChannelReplyThreadMessage(account: Account, messageId: MessageI
maxMessage: discussionMessage.maxMessage,
maxReadIncomingMessageId: discussionMessage.maxReadIncomingMessageId,
maxReadOutgoingMessageId: discussionMessage.maxReadOutgoingMessageId,
initialFilledHoles: initialFilledHoles.removedIndices,
initialAnchor: initialAnchor
initialFilledHoles: initialFilledHoles?.removedIndices ?? IndexSet(),
initialAnchor: initialAnchor,
isNotAvailable: initialFilledHoles == nil
))
}
|> castError(FetchChannelReplyThreadMessageError.self)

View File

@ -4,18 +4,24 @@ import SwiftSignalKit
import SyncCore
public func updatePeerChatInterfaceState(account: Account, peerId: PeerId, state: SynchronizeableChatInterfaceState) -> Signal<Void, NoError> {
public func updatePeerChatInterfaceState(account: Account, peerId: PeerId, threadId: Int64?, state: SynchronizeableChatInterfaceState) -> Signal<Void, NoError> {
return account.postbox.transaction { transaction -> Void in
let currentInputState = (transaction.getPeerChatInterfaceState(peerId) as? SynchronizeableChatInterfaceState)?.synchronizeableInputState
let updatedInputState = state.synchronizeableInputState
if currentInputState != updatedInputState {
if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.CloudChannel || peerId.namespace == Namespaces.Peer.CloudGroup {
addSynchronizeChatInputStateOperation(transaction: transaction, peerId: peerId)
if let threadId = threadId {
transaction.updatePeerChatThreadInterfaceState(peerId, threadId: threadId, update: { _ in
return state
})
} else {
let currentInputState = (transaction.getPeerChatInterfaceState(peerId) as? SynchronizeableChatInterfaceState)?.synchronizeableInputState
let updatedInputState = state.synchronizeableInputState
if currentInputState != updatedInputState {
if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.CloudChannel || peerId.namespace == Namespaces.Peer.CloudGroup {
addSynchronizeChatInputStateOperation(transaction: transaction, peerId: peerId)
}
}
transaction.updatePeerChatInterfaceState(peerId, update: { _ in
return state
})
}
transaction.updatePeerChatInterfaceState(peerId, update: { _ in
return state
})
}
}

View File

@ -17,11 +17,13 @@ private let messageBoldItalicFont = Font.semiboldItalic(17.0)
private let messageFixedFont = UIFont(name: "Menlo-Regular", size: 16.0) ?? UIFont.systemFont(ofSize: 17.0)
final class ChatBotInfoItem: ListViewItem {
fileprivate let title: String
fileprivate let text: String
fileprivate let controllerInteraction: ChatControllerInteraction
fileprivate let presentationData: ChatPresentationData
init(text: String, controllerInteraction: ChatControllerInteraction, presentationData: ChatPresentationData) {
init(title: String, text: String, controllerInteraction: ChatControllerInteraction, presentationData: ChatPresentationData) {
self.title = title
self.text = text
self.controllerInteraction = controllerInteraction
self.presentationData = presentationData
@ -163,7 +165,7 @@ final class ChatBotInfoItemNode: ListViewItemNode {
let verticalItemInset: CGFloat = 10.0
let verticalContentInset: CGFloat = 8.0
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Bot_DescriptionTitle, font: messageBoldFont, textColor: item.presentationData.theme.theme.chat.message.infoPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - horizontalEdgeInset * 2.0 - horizontalContentInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: messageBoldFont, textColor: item.presentationData.theme.theme.chat.message.infoPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - horizontalEdgeInset * 2.0 - horizontalContentInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - horizontalEdgeInset * 2.0 - horizontalContentInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
@ -174,7 +176,7 @@ final class ChatBotInfoItemNode: ListViewItemNode {
let titleFrame = CGRect(origin: CGPoint(x: backgroundFrame.origin.x + horizontalContentInset, y: backgroundFrame.origin.y + verticalContentInset), size: titleLayout.size)
let textFrame = CGRect(origin: CGPoint(x: backgroundFrame.origin.x + horizontalContentInset, y: backgroundFrame.origin.y + verticalContentInset + titleLayout.size.height + textSpacing), size: textLayout.size)
let itemLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: textLayout.size.height + verticalItemInset * 2.0 + verticalContentInset * 2.0 + 4.0), insets: UIEdgeInsets())
let itemLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: textLayout.size.height + verticalItemInset * 2.0 + verticalContentInset * 2.0 + 18.0), insets: UIEdgeInsets())
return (itemLayout, { _ in
if let strongSelf = self {
strongSelf.theme = item.presentationData.theme

View File

@ -3231,14 +3231,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let strongSelf = self, let combinedInitialData = combinedInitialData else {
return
}
if var interfaceState = combinedInitialData.initialData?.chatInterfaceState as? ChatInterfaceState {
switch strongSelf.chatLocation {
case .peer:
break
default:
interfaceState = ChatInterfaceState()
}
if let interfaceState = combinedInitialData.initialData?.chatInterfaceState as? ChatInterfaceState {
var pinnedMessageId: MessageId?
var peerIsBlocked: Bool = false
var callsAvailable: Bool = true
@ -5715,16 +5708,24 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
private func saveInterfaceState(includeScrollState: Bool = true) {
if case let .peer(peerId) = self.chatLocation {
let timestamp = Int32(Date().timeIntervalSince1970)
var interfaceState = self.presentationInterfaceState.interfaceState.withUpdatedTimestamp(timestamp)
if includeScrollState {
let scrollState = self.chatDisplayNode.historyNode.immediateScrollState()
interfaceState = interfaceState.withUpdatedHistoryScrollState(scrollState)
}
interfaceState = interfaceState.withUpdatedInputLanguage(self.chatDisplayNode.currentTextInputLanguage)
let _ = updatePeerChatInterfaceState(account: self.context.account, peerId: peerId, state: interfaceState).start()
var peerId: PeerId
var threadId: Int64?
switch self.chatLocation {
case let .peer(peerIdValue):
peerId = peerIdValue
case let .replyThread(replyThreadMessage):
peerId = replyThreadMessage.messageId.peerId
threadId = makeMessageThreadId(replyThreadMessage.messageId)
}
let timestamp = Int32(Date().timeIntervalSince1970)
var interfaceState = self.presentationInterfaceState.interfaceState.withUpdatedTimestamp(timestamp)
if includeScrollState && threadId == nil {
let scrollState = self.chatDisplayNode.historyNode.immediateScrollState()
interfaceState = interfaceState.withUpdatedHistoryScrollState(scrollState)
}
interfaceState = interfaceState.withUpdatedInputLanguage(self.chatDisplayNode.currentTextInputLanguage)
let _ = updatePeerChatInterfaceState(account: self.context.account, peerId: peerId, threadId: threadId, state: interfaceState).start()
}
override public func viewDidDisappear(_ animated: Bool) {
@ -8359,7 +8360,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
controllerInteraction.currentMessageWithLoadingReplyThread = displayProgressInMessage
strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(displayProgressInMessage)
if let previousId = previousId {
strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(displayProgressInMessage)
strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(previousId)
}
}
@ -8368,7 +8369,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction else {
return
}
if let displayProgressInMessage = displayProgressInMessage, controllerInteraction.currentMessageWithLoadingReplyThread == messageId {
if let displayProgressInMessage = displayProgressInMessage, controllerInteraction.currentMessageWithLoadingReplyThread == displayProgressInMessage {
controllerInteraction.currentMessageWithLoadingReplyThread = nil
strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(displayProgressInMessage)
}

View File

@ -1964,7 +1964,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
if let peer = chatPresentationInterfaceState.renderedPeer?.peer, let restrictionTextValue = peer.restrictionText(platform: "ios", contentSettings: self.context.currentContentSettings.with { $0 }), !restrictionTextValue.isEmpty {
restrictionText = restrictionTextValue
} else if chatPresentationInterfaceState.isNotAccessible {
if let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, case .broadcast = peer.info {
if case .replyThread = self.chatLocation {
restrictionText = chatPresentationInterfaceState.strings.CommentsGroup_ErrorAccessDenied
} else if let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, case .broadcast = peer.info {
restrictionText = chatPresentationInterfaceState.strings.Channel_ErrorAccessDenied
} else {
restrictionText = chatPresentationInterfaceState.strings.Group_ErrorAccessDenied

View File

@ -182,7 +182,7 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView,
}
if includeChatInfoEntry {
if view.earlierId == nil {
if view.earlierId == nil, !view.isLoading {
var cachedPeerData: CachedPeerData?
for entry in view.additionalData {
if case let .cachedPeerData(_, data) = entry {
@ -190,8 +190,10 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView,
break
}
}
if let cachedPeerData = cachedPeerData as? CachedUserData, let botInfo = cachedPeerData.botInfo, !botInfo.description.isEmpty {
entries.insert(.ChatInfoEntry(botInfo.description, presentationData), at: 0)
if case let .peer(peerId) = location, peerId.isReplies {
entries.insert(.ChatInfoEntry(presentationData.strings.RepliesChat_DescriptionTitle, presentationData.strings.RepliesChat_DescriptionText, presentationData), at: 0)
} else if let cachedPeerData = cachedPeerData as? CachedUserData, let botInfo = cachedPeerData.botInfo, !botInfo.description.isEmpty {
entries.insert(.ChatInfoEntry(presentationData.strings.Bot_DescriptionTitle, botInfo.description, presentationData), at: 0)
} else {
var isEmpty = true
if entries.count <= 3 {

View File

@ -38,7 +38,7 @@ enum ChatHistoryEntry: Identifiable, Comparable {
case MessageGroupEntry(MessageGroupInfo, [(Message, Bool, ChatHistoryMessageSelection, ChatMessageEntryAttributes)], ChatPresentationData)
case UnreadEntry(MessageIndex, ChatPresentationData)
case ReplyCountEntry(MessageIndex, Bool, Int, ChatPresentationData)
case ChatInfoEntry(String, ChatPresentationData)
case ChatInfoEntry(String, String, ChatPresentationData)
case SearchEntry(PresentationTheme, PresentationStrings)
var stableId: UInt64 {
@ -193,8 +193,8 @@ enum ChatHistoryEntry: Identifiable, Comparable {
} else {
return false
}
case let .ChatInfoEntry(lhsText, lhsPresentationData):
if case let .ChatInfoEntry(rhsText, rhsPresentationData) = rhs, lhsText == rhsText, lhsPresentationData === rhsPresentationData {
case let .ChatInfoEntry(lhsTitle, lhsText, lhsPresentationData):
if case let .ChatInfoEntry(rhsTitle, rhsText, rhsPresentationData) = rhs, lhsTitle == rhsTitle, lhsText == rhsText, lhsPresentationData === rhsPresentationData {
return true
} else {
return false

View File

@ -277,8 +277,8 @@ private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLoca
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatUnreadItem(index: entry.entry.index, presentationData: presentationData, context: context), directionHint: entry.directionHint)
case let .ReplyCountEntry(_, isComments, count, presentationData):
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatReplyCountItem(index: entry.entry.index, isComments: isComments, count: count, presentationData: presentationData, context: context), directionHint: entry.directionHint)
case let .ChatInfoEntry(text, presentationData):
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatBotInfoItem(text: text, controllerInteraction: controllerInteraction, presentationData: presentationData), directionHint: entry.directionHint)
case let .ChatInfoEntry(title, text, presentationData):
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatBotInfoItem(title: title, text: text, controllerInteraction: controllerInteraction, presentationData: presentationData), directionHint: entry.directionHint)
case let .SearchEntry(theme, strings):
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSearchItem(theme: theme, placeholder: strings.Common_Search, activate: {
controllerInteraction.openSearch()
@ -322,8 +322,8 @@ private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLoca
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatUnreadItem(index: entry.entry.index, presentationData: presentationData, context: context), directionHint: entry.directionHint)
case let .ReplyCountEntry(_, isComments, count, presentationData):
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatReplyCountItem(index: entry.entry.index, isComments: isComments, count: count, presentationData: presentationData, context: context), directionHint: entry.directionHint)
case let .ChatInfoEntry(text, presentationData):
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatBotInfoItem(text: text, controllerInteraction: controllerInteraction, presentationData: presentationData), directionHint: entry.directionHint)
case let .ChatInfoEntry(title, text, presentationData):
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatBotInfoItem(title: title, text: text, controllerInteraction: controllerInteraction, presentationData: presentationData), directionHint: entry.directionHint)
case let .SearchEntry(theme, strings):
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSearchItem(theme: theme, placeholder: strings.Common_Search, activate: {
controllerInteraction.openSearch()

View File

@ -348,6 +348,16 @@ func fetchAndPreloadReplyThreadInfo(context: AccountContext, subject: ReplyThrea
scrollToLowerBoundMessage = index
}
if replyThreadMessage.isNotAvailable {
return .single(ReplyThreadInfo(
message: replyThreadMessage,
isChannelPost: replyThreadMessage.isChannelPost,
isEmpty: false,
scrollToLowerBoundMessage: nil,
contextHolder: chatLocationContextHolder
))
}
let preloadSignal = preloadedChatHistoryViewForLocation(
input,
context: context,

View File

@ -449,12 +449,12 @@ public final class WalletStrings: Equatable {
public var Wallet_Send_ConfirmationConfirm: String { return self._s[218]! }
public var Wallet_Created_ExportErrorTitle: String { return self._s[219]! }
public var Wallet_Info_TransactionPendingHeader: String { return self._s[220]! }
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
let form = getPluralizationForm(self.lc, value)
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
return String(format: self._ps[0 * 6 + Int(form.rawValue)]!, stringValue)
}
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
let form = getPluralizationForm(self.lc, value)
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, stringValue)