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."; "Channel.ErrorAccessDenied" = "Sorry, this channel is private.";
"Group.ErrorAccessDenied" = "Sorry, this group 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"; "Conversation.InputTextBroadcastPlaceholder" = "Broadcast";
"Channel.NotificationLoading" = "Loading..."; "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.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.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."; "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 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) let servicePeer = isServicePeer(primaryPeer)
if user.flags.contains(.isSupport) && !servicePeer { if user.flags.contains(.isSupport) && !servicePeer {
status = .custom(string: strings.Bot_GenericSupportStatus, multiline: false) 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) { if user.botInfo != nil || user.flags.contains(.isSupport) {
return (strings.ChatList_PeerTypeBot, false) return (strings.ChatList_PeerTypeBot, false)
} else if isContact { } else if isContact {

View File

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

View File

@ -139,6 +139,7 @@ private final class OverlayStatusControllerImpl: ViewController, StandalonePrese
private let type: OverlayStatusControllerType private let type: OverlayStatusControllerType
private var animatedDidAppear = false private var animatedDidAppear = false
private var isDismissed = false
private var controllerNode: OverlayStatusControllerNode { private var controllerNode: OverlayStatusControllerNode {
return self.displayNode as! OverlayStatusControllerNode return self.displayNode as! OverlayStatusControllerNode
@ -181,8 +182,12 @@ private final class OverlayStatusControllerImpl: ViewController, StandalonePrese
} }
override public func dismiss(completion: (() -> Void)? = nil) { override public func dismiss(completion: (() -> Void)? = nil) {
if self.isDismissed {
completion?()
return
}
self.isDismissed = true
self.controllerNode.dismiss() 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) 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? { public func getPeer(_ id: PeerId) -> Peer? {
assert(!self.disposed) assert(!self.disposed)
return self.postbox?.peerTable.get(id) return self.postbox?.peerTable.get(id)
@ -1294,6 +1299,7 @@ public final class Postbox {
let itemCollectionItemTable: ItemCollectionItemTable let itemCollectionItemTable: ItemCollectionItemTable
let itemCollectionReverseIndexTable: ReverseIndexReferenceTable<ItemCollectionItemReverseIndexReference> let itemCollectionReverseIndexTable: ReverseIndexReferenceTable<ItemCollectionItemReverseIndexReference>
let peerChatInterfaceStateTable: PeerChatInterfaceStateTable let peerChatInterfaceStateTable: PeerChatInterfaceStateTable
let peerChatThreadInterfaceStateTable: PeerChatThreadInterfaceStateTable
let itemCacheMetaTable: ItemCacheMetaTable let itemCacheMetaTable: ItemCacheMetaTable
let itemCacheTable: ItemCacheTable let itemCacheTable: ItemCacheTable
let peerNameTokenIndexTable: ReverseIndexReferenceTable<PeerIdReverseIndexReference> 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.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.itemCollectionItemTable = ItemCollectionItemTable(valueBox: self.valueBox, table: ItemCollectionItemTable.tableSpec(22), reverseIndexTable: self.itemCollectionReverseIndexTable)
self.peerChatInterfaceStateTable = PeerChatInterfaceStateTable(valueBox: self.valueBox, table: PeerChatInterfaceStateTable.tableSpec(23)) 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.itemCacheMetaTable = ItemCacheMetaTable(valueBox: self.valueBox, table: ItemCacheMetaTable.tableSpec(24))
self.itemCacheTable = ItemCacheTable(valueBox: self.valueBox, table: ItemCacheTable.tableSpec(25)) 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) 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.itemCollectionItemTable)
tables.append(self.itemCollectionReverseIndexTable) tables.append(self.itemCollectionReverseIndexTable)
tables.append(self.peerChatInterfaceStateTable) tables.append(self.peerChatInterfaceStateTable)
tables.append(self.peerChatThreadInterfaceStateTable)
tables.append(self.itemCacheMetaTable) tables.append(self.itemCacheMetaTable)
tables.append(self.itemCacheTable) tables.append(self.itemCacheTable)
tables.append(self.peerNameIndexTable) 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) { fileprivate func replaceRemoteContactCount(_ count: Int32) {
self.metadataTable.setRemoteContactCount(count) self.metadataTable.setRemoteContactCount(count)
self.currentReplaceRemoteContactCount = count self.currentReplaceRemoteContactCount = count
@ -2666,11 +2679,11 @@ public final class Postbox {
let initialData: InitialMessageHistoryData let initialData: InitialMessageHistoryData
switch peerIds { switch peerIds {
case let .single(peerId): case let .single(peerId):
initialData = self.initialMessageHistoryData(peerId: peerId) initialData = self.initialMessageHistoryData(peerId: peerId, threadId: nil)
case let .associated(peerId, _): case let .associated(peerId, _):
initialData = self.initialMessageHistoryData(peerId: peerId) initialData = self.initialMessageHistoryData(peerId: peerId, threadId: nil)
case let .external(input): 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)) subscriber.putNext((MessageHistoryView(mutableView), initialUpdateType, initialData))
@ -2687,17 +2700,30 @@ public final class Postbox {
} }
} }
private func initialMessageHistoryData(peerId: PeerId) -> InitialMessageHistoryData { private func initialMessageHistoryData(peerId: PeerId, threadId: Int64?) -> InitialMessageHistoryData {
let chatInterfaceState = self.peerChatInterfaceStateTable.get(peerId) if let threadId = threadId {
var associatedMessages: [MessageId: Message] = [:] let chatInterfaceState = self.peerChatThreadInterfaceStateTable.get(PeerChatThreadId(peerId: peerId, threadId: threadId))
if let chatInterfaceState = chatInterfaceState { var associatedMessages: [MessageId: Message] = [:]
for id in chatInterfaceState.associatedMessageIds { if let chatInterfaceState = chatInterfaceState {
if let message = self.getMessage(id) { for id in chatInterfaceState.associatedMessageIds {
associatedMessages[message.id] = message 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> { public func messageIndexAtId(_ id: MessageId) -> Signal<MessageIndex?, NoError> {

View File

@ -159,7 +159,7 @@ struct FetchMessageHistoryHoleResult: Equatable {
var actualThreadId: Int64? 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) let count = min(100, rawCount)
return postbox.stateView() return postbox.stateView()
@ -171,7 +171,7 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH
} }
} }
|> take(1) |> take(1)
|> mapToSignal { _ -> Signal<FetchMessageHistoryHoleResult, NoError> in |> mapToSignal { _ -> Signal<FetchMessageHistoryHoleResult?, NoError> in
return postbox.transaction { transaction -> (Api.InputPeer?, Int32) in return postbox.transaction { transaction -> (Api.InputPeer?, Int32) in
switch peerInput { switch peerInput {
case let .direct(peerId, _): case let .direct(peerId, _):
@ -180,7 +180,7 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH
return (transaction.getPeer(channelMessageId.peerId).flatMap(forceApiInputPeer), 0) 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 { guard let inputPeer = inputPeer else {
return .single(FetchMessageHistoryHoleResult(removedIndices: IndexSet(), strictRemovedIndices: IndexSet(), actualPeerId: nil, actualThreadId: nil)) return .single(FetchMessageHistoryHoleResult(removedIndices: IndexSet(), strictRemovedIndices: IndexSet(), actualPeerId: nil, actualThreadId: nil))
} }
@ -404,8 +404,27 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH
} }
return request return request
|> retryRequest |> retry(retryOnError: { error in
|> mapToSignal { result -> Signal<FetchMessageHistoryHoleResult, NoError> 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 messages: [Api.Message]
let chats: [Api.Chat] let chats: [Api.Chat]
let users: [Api.User] let users: [Api.User]

View File

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

View File

@ -4,18 +4,24 @@ import SwiftSignalKit
import SyncCore 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 return account.postbox.transaction { transaction -> Void in
let currentInputState = (transaction.getPeerChatInterfaceState(peerId) as? SynchronizeableChatInterfaceState)?.synchronizeableInputState if let threadId = threadId {
let updatedInputState = state.synchronizeableInputState transaction.updatePeerChatThreadInterfaceState(peerId, threadId: threadId, update: { _ in
return state
if currentInputState != updatedInputState { })
if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.CloudChannel || peerId.namespace == Namespaces.Peer.CloudGroup { } else {
addSynchronizeChatInputStateOperation(transaction: transaction, peerId: peerId) 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) private let messageFixedFont = UIFont(name: "Menlo-Regular", size: 16.0) ?? UIFont.systemFont(ofSize: 17.0)
final class ChatBotInfoItem: ListViewItem { final class ChatBotInfoItem: ListViewItem {
fileprivate let title: String
fileprivate let text: String fileprivate let text: String
fileprivate let controllerInteraction: ChatControllerInteraction fileprivate let controllerInteraction: ChatControllerInteraction
fileprivate let presentationData: ChatPresentationData 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.text = text
self.controllerInteraction = controllerInteraction self.controllerInteraction = controllerInteraction
self.presentationData = presentationData self.presentationData = presentationData
@ -163,7 +165,7 @@ final class ChatBotInfoItemNode: ListViewItemNode {
let verticalItemInset: CGFloat = 10.0 let verticalItemInset: CGFloat = 10.0
let verticalContentInset: CGFloat = 8.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())) 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 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 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 return (itemLayout, { _ in
if let strongSelf = self { if let strongSelf = self {
strongSelf.theme = item.presentationData.theme 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 { guard let strongSelf = self, let combinedInitialData = combinedInitialData else {
return return
} }
if var interfaceState = combinedInitialData.initialData?.chatInterfaceState as? ChatInterfaceState { if let interfaceState = combinedInitialData.initialData?.chatInterfaceState as? ChatInterfaceState {
switch strongSelf.chatLocation {
case .peer:
break
default:
interfaceState = ChatInterfaceState()
}
var pinnedMessageId: MessageId? var pinnedMessageId: MessageId?
var peerIsBlocked: Bool = false var peerIsBlocked: Bool = false
var callsAvailable: Bool = true var callsAvailable: Bool = true
@ -5715,16 +5708,24 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
private func saveInterfaceState(includeScrollState: Bool = true) { private func saveInterfaceState(includeScrollState: Bool = true) {
if case let .peer(peerId) = self.chatLocation { var peerId: PeerId
let timestamp = Int32(Date().timeIntervalSince1970) var threadId: Int64?
var interfaceState = self.presentationInterfaceState.interfaceState.withUpdatedTimestamp(timestamp) switch self.chatLocation {
if includeScrollState { case let .peer(peerIdValue):
let scrollState = self.chatDisplayNode.historyNode.immediateScrollState() peerId = peerIdValue
interfaceState = interfaceState.withUpdatedHistoryScrollState(scrollState) case let .replyThread(replyThreadMessage):
} peerId = replyThreadMessage.messageId.peerId
interfaceState = interfaceState.withUpdatedInputLanguage(self.chatDisplayNode.currentTextInputLanguage) threadId = makeMessageThreadId(replyThreadMessage.messageId)
let _ = updatePeerChatInterfaceState(account: self.context.account, peerId: peerId, state: interfaceState).start()
} }
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) { override public func viewDidDisappear(_ animated: Bool) {
@ -8359,7 +8360,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
controllerInteraction.currentMessageWithLoadingReplyThread = displayProgressInMessage controllerInteraction.currentMessageWithLoadingReplyThread = displayProgressInMessage
strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(displayProgressInMessage) strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(displayProgressInMessage)
if let previousId = previousId { 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 { guard let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction else {
return return
} }
if let displayProgressInMessage = displayProgressInMessage, controllerInteraction.currentMessageWithLoadingReplyThread == messageId { if let displayProgressInMessage = displayProgressInMessage, controllerInteraction.currentMessageWithLoadingReplyThread == displayProgressInMessage {
controllerInteraction.currentMessageWithLoadingReplyThread = nil controllerInteraction.currentMessageWithLoadingReplyThread = nil
strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(displayProgressInMessage) 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 { if let peer = chatPresentationInterfaceState.renderedPeer?.peer, let restrictionTextValue = peer.restrictionText(platform: "ios", contentSettings: self.context.currentContentSettings.with { $0 }), !restrictionTextValue.isEmpty {
restrictionText = restrictionTextValue restrictionText = restrictionTextValue
} else if chatPresentationInterfaceState.isNotAccessible { } 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 restrictionText = chatPresentationInterfaceState.strings.Channel_ErrorAccessDenied
} else { } else {
restrictionText = chatPresentationInterfaceState.strings.Group_ErrorAccessDenied restrictionText = chatPresentationInterfaceState.strings.Group_ErrorAccessDenied

View File

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

View File

@ -38,7 +38,7 @@ enum ChatHistoryEntry: Identifiable, Comparable {
case MessageGroupEntry(MessageGroupInfo, [(Message, Bool, ChatHistoryMessageSelection, ChatMessageEntryAttributes)], ChatPresentationData) case MessageGroupEntry(MessageGroupInfo, [(Message, Bool, ChatHistoryMessageSelection, ChatMessageEntryAttributes)], ChatPresentationData)
case UnreadEntry(MessageIndex, ChatPresentationData) case UnreadEntry(MessageIndex, ChatPresentationData)
case ReplyCountEntry(MessageIndex, Bool, Int, ChatPresentationData) case ReplyCountEntry(MessageIndex, Bool, Int, ChatPresentationData)
case ChatInfoEntry(String, ChatPresentationData) case ChatInfoEntry(String, String, ChatPresentationData)
case SearchEntry(PresentationTheme, PresentationStrings) case SearchEntry(PresentationTheme, PresentationStrings)
var stableId: UInt64 { var stableId: UInt64 {
@ -193,8 +193,8 @@ enum ChatHistoryEntry: Identifiable, Comparable {
} else { } else {
return false return false
} }
case let .ChatInfoEntry(lhsText, lhsPresentationData): case let .ChatInfoEntry(lhsTitle, lhsText, lhsPresentationData):
if case let .ChatInfoEntry(rhsText, rhsPresentationData) = rhs, lhsText == rhsText, lhsPresentationData === rhsPresentationData { if case let .ChatInfoEntry(rhsTitle, rhsText, rhsPresentationData) = rhs, lhsTitle == rhsTitle, lhsText == rhsText, lhsPresentationData === rhsPresentationData {
return true return true
} else { } else {
return false 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) 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): 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) 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): case let .ChatInfoEntry(title, text, presentationData):
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatBotInfoItem(text: text, controllerInteraction: controllerInteraction, presentationData: presentationData), directionHint: entry.directionHint) 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): case let .SearchEntry(theme, strings):
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSearchItem(theme: theme, placeholder: strings.Common_Search, activate: { return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSearchItem(theme: theme, placeholder: strings.Common_Search, activate: {
controllerInteraction.openSearch() 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) 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): 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) 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): case let .ChatInfoEntry(title, text, presentationData):
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatBotInfoItem(text: text, controllerInteraction: controllerInteraction, presentationData: presentationData), directionHint: entry.directionHint) 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): case let .SearchEntry(theme, strings):
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSearchItem(theme: theme, placeholder: strings.Common_Search, activate: { return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSearchItem(theme: theme, placeholder: strings.Common_Search, activate: {
controllerInteraction.openSearch() controllerInteraction.openSearch()

View File

@ -348,6 +348,16 @@ func fetchAndPreloadReplyThreadInfo(context: AccountContext, subject: ReplyThrea
scrollToLowerBoundMessage = index scrollToLowerBoundMessage = index
} }
if replyThreadMessage.isNotAvailable {
return .single(ReplyThreadInfo(
message: replyThreadMessage,
isChannelPost: replyThreadMessage.isChannelPost,
isEmpty: false,
scrollToLowerBoundMessage: nil,
contextHolder: chatLocationContextHolder
))
}
let preloadSignal = preloadedChatHistoryViewForLocation( let preloadSignal = preloadedChatHistoryViewForLocation(
input, input,
context: context, 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_Send_ConfirmationConfirm: String { return self._s[218]! }
public var Wallet_Created_ExportErrorTitle: String { return self._s[219]! } public var Wallet_Created_ExportErrorTitle: String { return self._s[219]! }
public var Wallet_Info_TransactionPendingHeader: String { return self._s[220]! } 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 form = getPluralizationForm(self.lc, value)
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator) let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
return String(format: self._ps[0 * 6 + Int(form.rawValue)]!, stringValue) 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 form = getPluralizationForm(self.lc, value)
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator) let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, stringValue) return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, stringValue)