[WIP] Pinned messages

This commit is contained in:
Ali 2020-10-06 15:04:01 +01:00
parent 4ed47db191
commit de039d7303
21 changed files with 1477 additions and 1281 deletions

View File

@ -3,7 +3,7 @@
@implementation Serialization @implementation Serialization
- (NSUInteger)currentLayer { - (NSUInteger)currentLayer {
return 119; return 120;
} }
- (id _Nullable)parseMessage:(NSData * _Nullable)data { - (id _Nullable)parseMessage:(NSData * _Nullable)data {

View File

@ -1921,6 +1921,7 @@
"Conversation.Unpin" = "Unpin"; "Conversation.Unpin" = "Unpin";
"Conversation.Report" = "Report Spam"; "Conversation.Report" = "Report Spam";
"Conversation.PinnedMessage" = "Pinned Message"; "Conversation.PinnedMessage" = "Pinned Message";
"Conversation.PinnedPreviousMessage" = "Previous Message";
"Conversation.Moderate.Delete" = "Delete Message"; "Conversation.Moderate.Delete" = "Delete Message";
"Conversation.Moderate.Ban" = "Ban User"; "Conversation.Moderate.Ban" = "Ban User";

View File

@ -14,7 +14,7 @@ public func legacySuggestionContext(context: AccountContext, peerId: PeerId, cha
suggestionContext.userListSignal = { query in suggestionContext.userListSignal = { query in
return SSignal { subscriber in return SSignal { subscriber in
if let query = query { if let query = query {
let disposable = searchPeerMembers(context: context, peerId: peerId, chatLocation: chatLocation, query: query).start(next: { peers in let disposable = searchPeerMembers(context: context, peerId: peerId, chatLocation: chatLocation, query: query, scope: .mention).start(next: { peers in
let users = NSMutableArray() let users = NSMutableArray()
for peer in peers { for peer in peers {
let user = TGUser() let user = TGUser()

View File

@ -5,20 +5,25 @@ import SyncCore
import SwiftSignalKit import SwiftSignalKit
import AccountContext import AccountContext
public func searchPeerMembers(context: AccountContext, peerId: PeerId, chatLocation: ChatLocation, query: String) -> Signal<[Peer], NoError> { public enum SearchPeerMembersScope {
case memberSuggestion
case mention
}
public func searchPeerMembers(context: AccountContext, peerId: PeerId, chatLocation: ChatLocation, query: String, scope: SearchPeerMembersScope) -> Signal<[Peer], NoError> {
if case .replyThread = chatLocation { if case .replyThread = chatLocation {
return .single([]) return .single([])
} else if peerId.namespace == Namespaces.Peer.CloudChannel { } else if peerId.namespace == Namespaces.Peer.CloudChannel {
return context.account.postbox.transaction { transaction -> CachedChannelData? in return context.account.postbox.transaction { transaction -> CachedChannelData? in
return transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData return transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData
} }
|> mapToSignal { cachedData -> Signal<[Peer], NoError> in |> mapToSignal { cachedData -> Signal<([Peer], Bool), NoError> in
if let cachedData = cachedData, let memberCount = cachedData.participantsSummary.memberCount, memberCount <= 64 { if let cachedData = cachedData, let memberCount = cachedData.participantsSummary.memberCount, memberCount <= 64 {
return Signal { subscriber in return Signal { subscriber in
let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: nil, requestUpdate: false, updated: { state in let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: nil, requestUpdate: false, updated: { state in
if case .ready = state.loadingState { if case .ready = state.loadingState {
let normalizedQuery = query.lowercased() let normalizedQuery = query.lowercased()
subscriber.putNext(state.list.compactMap { participant -> Peer? in subscriber.putNext((state.list.compactMap { participant -> Peer? in
if participant.peer.isDeleted { if participant.peer.isDeleted {
return nil return nil
} }
@ -37,7 +42,7 @@ public func searchPeerMembers(context: AccountContext, peerId: PeerId, chatLocat
return nil return nil
} }
}) }, true))
} }
}) })
@ -51,12 +56,12 @@ public func searchPeerMembers(context: AccountContext, peerId: PeerId, chatLocat
return Signal { subscriber in return Signal { subscriber in
let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: query.isEmpty ? nil : query, updated: { state in let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: query.isEmpty ? nil : query, updated: { state in
if case .ready = state.loadingState { if case .ready = state.loadingState {
subscriber.putNext(state.list.compactMap { participant in subscriber.putNext((state.list.compactMap { participant in
if participant.peer.isDeleted { if participant.peer.isDeleted {
return nil return nil
} }
return participant.peer return participant.peer
}) }, true))
} }
}) })
@ -65,6 +70,36 @@ public func searchPeerMembers(context: AccountContext, peerId: PeerId, chatLocat
} }
} |> runOn(Queue.mainQueue()) } |> runOn(Queue.mainQueue())
} }
|> mapToSignal { result, isReady -> Signal<[Peer], NoError> in
switch scope {
case .mention:
return .single(result)
case .memberSuggestion:
return context.account.postbox.transaction { transaction -> [Peer] in
var result = result
let normalizedQuery = query.lowercased()
if isReady {
if let channel = transaction.getPeer(peerId) as? TelegramChannel, case .group = channel.info {
var matches = false
if normalizedQuery.isEmpty {
matches = true
} else {
if channel.indexName.matchesByTokens(normalizedQuery) {
matches = true
}
if let addressName = channel.addressName, addressName.lowercased().hasPrefix(normalizedQuery) {
matches = true
}
}
if matches {
result.insert(channel, at: 0)
}
}
}
return result
}
}
}
} else { } else {
return searchGroupMembers(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, query: query) return searchGroupMembers(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, query: query)
} }

View File

@ -322,6 +322,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[106343499] = { return Api.ChannelParticipantsFilter.parse_channelParticipantsSearch($0) } dict[106343499] = { return Api.ChannelParticipantsFilter.parse_channelParticipantsSearch($0) }
dict[-1548400251] = { return Api.ChannelParticipantsFilter.parse_channelParticipantsKicked($0) } dict[-1548400251] = { return Api.ChannelParticipantsFilter.parse_channelParticipantsKicked($0) }
dict[-1150621555] = { return Api.ChannelParticipantsFilter.parse_channelParticipantsContacts($0) } dict[-1150621555] = { return Api.ChannelParticipantsFilter.parse_channelParticipantsContacts($0) }
dict[915357814] = { return Api.ChannelParticipantsFilter.parse_channelParticipantsMentions($0) }
dict[-350980120] = { return Api.WebPage.parse_webPageEmpty($0) } dict[-350980120] = { return Api.WebPage.parse_webPageEmpty($0) }
dict[-981018084] = { return Api.WebPage.parse_webPagePending($0) } dict[-981018084] = { return Api.WebPage.parse_webPagePending($0) }
dict[-392411726] = { return Api.WebPage.parse_webPage($0) } dict[-392411726] = { return Api.WebPage.parse_webPage($0) }

View File

@ -9991,6 +9991,7 @@ public extension Api {
case channelParticipantsSearch(q: String) case channelParticipantsSearch(q: String)
case channelParticipantsKicked(q: String) case channelParticipantsKicked(q: String)
case channelParticipantsContacts(q: String) case channelParticipantsContacts(q: String)
case channelParticipantsMentions(q: String)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
@ -10036,6 +10037,12 @@ public extension Api {
} }
serializeString(q, buffer: buffer, boxed: false) serializeString(q, buffer: buffer, boxed: false)
break break
case .channelParticipantsMentions(let q):
if boxed {
buffer.appendInt32(915357814)
}
serializeString(q, buffer: buffer, boxed: false)
break
} }
} }
@ -10055,6 +10062,8 @@ public extension Api {
return ("channelParticipantsKicked", [("q", q)]) return ("channelParticipantsKicked", [("q", q)])
case .channelParticipantsContacts(let q): case .channelParticipantsContacts(let q):
return ("channelParticipantsContacts", [("q", q)]) return ("channelParticipantsContacts", [("q", q)])
case .channelParticipantsMentions(let q):
return ("channelParticipantsMentions", [("q", q)])
} }
} }
@ -10111,6 +10120,17 @@ public extension Api {
return nil return nil
} }
} }
public static func parse_channelParticipantsMentions(_ reader: BufferReader) -> ChannelParticipantsFilter? {
var _1: String?
_1 = parseString(reader)
let _c1 = _1 != nil
if _c1 {
return Api.ChannelParticipantsFilter.channelParticipantsMentions(q: _1!)
}
else {
return nil
}
}
} }
public enum WebPage: TypeConstructorDescription { public enum WebPage: TypeConstructorDescription {

View File

@ -3737,9 +3737,26 @@ public extension Api {
}) })
} }
public static func search(flags: Int32, peer: Api.InputPeer, q: String, fromId: Api.InputUser?, topMsgId: Int32?, filter: Api.MessagesFilter, minDate: Int32, maxDate: Int32, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.Messages>) { public static func setTyping(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?, action: Api.SendMessageAction) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(1310163211) buffer.appendInt32(1486110434)
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)}
action.serialize(buffer, true)
return (FunctionDescription(name: "messages.setTyping", parameters: [("flags", flags), ("peer", peer), ("topMsgId", topMsgId), ("action", action)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Bool
}
return result
})
}
public static func search(flags: Int32, peer: Api.InputPeer, q: String, fromId: Api.InputPeer?, topMsgId: Int32?, filter: Api.MessagesFilter, minDate: Int32, maxDate: Int32, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.Messages>) {
let buffer = Buffer()
buffer.appendInt32(204812012)
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true) peer.serialize(buffer, true)
serializeString(q, buffer: buffer, boxed: false) serializeString(q, buffer: buffer, boxed: false)
@ -3763,23 +3780,6 @@ public extension Api {
return result return result
}) })
} }
public static func setTyping(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?, action: Api.SendMessageAction) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(1486110434)
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)}
action.serialize(buffer, true)
return (FunctionDescription(name: "messages.setTyping", parameters: [("flags", flags), ("peer", peer), ("topMsgId", topMsgId), ("action", action)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Bool
}
return result
})
}
} }
public struct channels { public struct channels {
public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) { public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {

View File

@ -221,11 +221,11 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q
guard let inputPeer = apiInputPeer(peer) else { guard let inputPeer = apiInputPeer(peer) else {
return .single((nil, nil)) return .single((nil, nil))
} }
var fromInputUser: Api.InputUser? = nil var fromInputPeer: Api.InputPeer? = nil
var flags: Int32 = 0 var flags: Int32 = 0
if let from = values.from { if let from = values.from {
fromInputUser = apiInputUser(from) fromInputPeer = apiInputPeer(from)
if let _ = fromInputUser { if let _ = fromInputPeer {
flags |= (1 << 0) flags |= (1 << 0)
} }
} }
@ -241,7 +241,7 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q
if peer.id.namespace == Namespaces.Peer.CloudChannel && query.isEmpty && fromId == nil && tags == nil && minDate == nil && maxDate == nil { if peer.id.namespace == Namespaces.Peer.CloudChannel && query.isEmpty && fromId == nil && tags == nil && minDate == nil && maxDate == nil {
signal = account.network.request(Api.functions.messages.getHistory(peer: inputPeer, offsetId: lowerBound?.id.id ?? 0, offsetDate: 0, addOffset: 0, limit: limit, maxId: Int32.max - 1, minId: 0, hash: 0)) signal = account.network.request(Api.functions.messages.getHistory(peer: inputPeer, offsetId: lowerBound?.id.id ?? 0, offsetDate: 0, addOffset: 0, limit: limit, maxId: Int32.max - 1, minId: 0, hash: 0))
} else { } else {
signal = account.network.request(Api.functions.messages.search(flags: flags, peer: inputPeer, q: query, fromId: fromInputUser, topMsgId: topMsgId?.id, filter: filter, minDate: minDate ?? 0, maxDate: maxDate ?? (Int32.max - 1), offsetId: lowerBound?.id.id ?? 0, addOffset: 0, limit: limit, maxId: Int32.max - 1, minId: 0, hash: 0)) signal = account.network.request(Api.functions.messages.search(flags: flags, peer: inputPeer, q: query, fromId: fromInputPeer, topMsgId: topMsgId?.id, filter: filter, minDate: minDate ?? 0, maxDate: maxDate ?? (Int32.max - 1), offsetId: lowerBound?.id.id ?? 0, addOffset: 0, limit: limit, maxId: Int32.max - 1, minId: 0, hash: 0))
} }
peerMessages = signal peerMessages = signal
|> map(Optional.init) |> map(Optional.init)
@ -257,7 +257,7 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q
additionalPeerMessages = .single(nil) additionalPeerMessages = .single(nil)
} else if mainCompleted || !hasAdditional { } else if mainCompleted || !hasAdditional {
let lowerBound = state?.additional?.messages.last.flatMap({ $0.index }) let lowerBound = state?.additional?.messages.last.flatMap({ $0.index })
additionalPeerMessages = account.network.request(Api.functions.messages.search(flags: flags, peer: inputPeer, q: query, fromId: fromInputUser, topMsgId: topMsgId?.id, filter: filter, minDate: minDate ?? 0, maxDate: maxDate ?? (Int32.max - 1), offsetId: lowerBound?.id.id ?? 0, addOffset: 0, limit: limit, maxId: Int32.max - 1, minId: 0, hash: 0)) additionalPeerMessages = account.network.request(Api.functions.messages.search(flags: flags, peer: inputPeer, q: query, fromId: fromInputPeer, topMsgId: topMsgId?.id, filter: filter, minDate: minDate ?? 0, maxDate: maxDate ?? (Int32.max - 1), offsetId: lowerBound?.id.id ?? 0, addOffset: 0, limit: limit, maxId: Int32.max - 1, minId: 0, hash: 0))
|> map(Optional.init) |> map(Optional.init)
|> `catch` { _ -> Signal<Api.messages.Messages?, NoError> in |> `catch` { _ -> Signal<Api.messages.Messages?, NoError> in
return .single(nil) return .single(nil)

View File

@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
public class Serialization: NSObject, MTSerialization { public class Serialization: NSObject, MTSerialization {
public func currentLayer() -> UInt { public func currentLayer() -> UInt {
return 119 return 120
} }
public func parseMessage(_ data: Data!) -> Any! { public func parseMessage(_ data: Data!) -> Any! {

View File

@ -3268,13 +3268,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
if case let .replyThread(replyThreadMessageId) = strongSelf.chatLocation { if case let .replyThread(replyThreadMessageId) = strongSelf.chatLocation {
pinnedMessageId = replyThreadMessageId.messageId pinnedMessageId = replyThreadMessageId.effectiveTopId
} }
var pinnedMessage: Message? var pinnedMessage: ChatPinnedMessage?
if let pinnedMessageId = pinnedMessageId { if let pinnedMessageId = pinnedMessageId {
if let cachedDataMessages = combinedInitialData.cachedDataMessages { if let cachedDataMessages = combinedInitialData.cachedDataMessages {
pinnedMessage = cachedDataMessages[pinnedMessageId] if let message = cachedDataMessages[pinnedMessageId] {
pinnedMessage = ChatPinnedMessage(message: message, isLatest: true)
}
} }
} }
@ -3385,7 +3387,62 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let isTopReplyThreadMessageShown: Signal<Bool, NoError> = self.chatDisplayNode.historyNode.isTopReplyThreadMessageShown.get() let isTopReplyThreadMessageShown: Signal<Bool, NoError> = self.chatDisplayNode.historyNode.isTopReplyThreadMessageShown.get()
|> distinctUntilChanged |> distinctUntilChanged
self.cachedDataDisposable = combineLatest(queue: .mainQueue(), self.chatDisplayNode.historyNode.cachedPeerDataAndMessages, hasPendingMessages, isTopReplyThreadMessageShown).start(next: { [weak self] cachedDataAndMessages, hasPendingMessages, isTopReplyThreadMessageShown in let topPinnedMessage: Signal<ChatPinnedMessage?, NoError>
switch self.chatLocation {
case let .peer(peerId):
let replyHistory: Signal<ChatHistoryViewUpdate, NoError> = (chatHistoryViewForLocation(ChatHistoryLocationInput(content: .Initial(count: 100), id: 0), context: self.context, chatLocation: .peer(peerId), chatLocationContextHolder: Atomic<ChatLocationContextHolder?>(value: nil), scheduled: false, fixedCombinedReadStates: nil, tagMask: MessageTags.photoOrVideo, additionalData: [])
|> castError(Bool.self)
|> mapToSignal { update -> Signal<ChatHistoryViewUpdate, Bool> in
switch update {
case let .Loading(_, type):
if case .Generic(.FillHole) = type {
return .fail(true)
}
case let .HistoryView(_, type, _, _, _, _, _):
if case .Generic(.FillHole) = type {
return .fail(true)
}
}
return .single(update)
})
|> restartIfError
topPinnedMessage = combineLatest(
replyHistory,
self.chatDisplayNode.historyNode.topVisibleMessage.get()
)
|> map { update, topVisibleMessage -> ChatPinnedMessage? in
var message: ChatPinnedMessage?
switch update {
case .Loading:
break
case let .HistoryView(view, _, _, _, _, _, _):
for i in 0 ..< view.entries.count {
let entry = view.entries[i]
var matches = false
if message == nil {
matches = true
} else if let topVisibleMessage = topVisibleMessage {
if entry.message.id < topVisibleMessage.id {
matches = true
}
} else {
matches = true
}
if matches {
message = ChatPinnedMessage(message: entry.message, isLatest: i == view.entries.count - 1)
}
}
break
}
return message
}
|> distinctUntilChanged
case .replyThread:
topPinnedMessage = .single(nil)
}
self.cachedDataDisposable = combineLatest(queue: .mainQueue(), self.chatDisplayNode.historyNode.cachedPeerDataAndMessages, hasPendingMessages, isTopReplyThreadMessageShown, topPinnedMessage).start(next: { [weak self] cachedDataAndMessages, hasPendingMessages, isTopReplyThreadMessageShown, topPinnedMessage in
if let strongSelf = self { if let strongSelf = self {
let (cachedData, messages) = cachedDataAndMessages let (cachedData, messages) = cachedDataAndMessages
@ -3413,22 +3470,26 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} else if let _ = cachedData as? CachedSecretChatData { } else if let _ = cachedData as? CachedSecretChatData {
} }
var pinnedMessage: ChatPinnedMessage?
if case let .replyThread(replyThreadMessage) = strongSelf.chatLocation { if case let .replyThread(replyThreadMessage) = strongSelf.chatLocation {
if isTopReplyThreadMessageShown { if isTopReplyThreadMessageShown {
pinnedMessageId = nil pinnedMessageId = nil
} else { } else {
pinnedMessageId = replyThreadMessage.messageId pinnedMessageId = replyThreadMessage.effectiveTopId
} }
}
var pinnedMessage: Message?
if let pinnedMessageId = pinnedMessageId { if let pinnedMessageId = pinnedMessageId {
pinnedMessage = messages?[pinnedMessageId] if let message = messages?[pinnedMessageId] {
pinnedMessage = ChatPinnedMessage(message: message, isLatest: true)
}
}
} else {
pinnedMessageId = topPinnedMessage?.message.id
pinnedMessage = topPinnedMessage
} }
var pinnedMessageUpdated = false var pinnedMessageUpdated = false
if let current = strongSelf.presentationInterfaceState.pinnedMessage, let updated = pinnedMessage { if let current = strongSelf.presentationInterfaceState.pinnedMessage, let updated = pinnedMessage {
if current.id != updated.id || current.stableVersion != updated.stableVersion { if current != updated {
pinnedMessageUpdated = true pinnedMessageUpdated = true
} }
} else if (strongSelf.presentationInterfaceState.pinnedMessage != nil) != (pinnedMessage != nil) { } else if (strongSelf.presentationInterfaceState.pinnedMessage != nil) != (pinnedMessage != nil) {
@ -3437,7 +3498,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let callsDataUpdated = strongSelf.presentationInterfaceState.callsAvailable != callsAvailable || strongSelf.presentationInterfaceState.callsPrivate != callsPrivate let callsDataUpdated = strongSelf.presentationInterfaceState.callsAvailable != callsAvailable || strongSelf.presentationInterfaceState.callsPrivate != callsPrivate
if strongSelf.presentationInterfaceState.pinnedMessageId != pinnedMessageId || strongSelf.presentationInterfaceState.pinnedMessage?.stableVersion != pinnedMessage?.stableVersion || strongSelf.presentationInterfaceState.peerIsBlocked != peerIsBlocked || pinnedMessageUpdated || callsDataUpdated || strongSelf.presentationInterfaceState.slowmodeState != slowmodeState { if strongSelf.presentationInterfaceState.pinnedMessageId != pinnedMessageId || strongSelf.presentationInterfaceState.pinnedMessage != pinnedMessage || strongSelf.presentationInterfaceState.peerIsBlocked != peerIsBlocked || pinnedMessageUpdated || callsDataUpdated || strongSelf.presentationInterfaceState.slowmodeState != slowmodeState {
strongSelf.updateChatPresentationInterfaceState(animated: strongSelf.willAppear, interactive: strongSelf.willAppear, { state in strongSelf.updateChatPresentationInterfaceState(animated: strongSelf.willAppear, interactive: strongSelf.willAppear, { state in
return state return state
.updatedPinnedMessageId(pinnedMessageId) .updatedPinnedMessageId(pinnedMessageId)
@ -4734,7 +4795,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})]), in: .window(.root)) })]), in: .window(.root))
} }
} else { } else {
if let pinnedMessageId = strongSelf.presentationInterfaceState.pinnedMessage?.id { if let pinnedMessageId = strongSelf.presentationInterfaceState.pinnedMessage?.message.id {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
return $0.updatedInterfaceState({ $0.withUpdatedMessageActionsState({ value in return $0.updatedInterfaceState({ $0.withUpdatedMessageActionsState({ value in
var value = value var value = value
@ -4786,7 +4847,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
return $0.updatedInterfaceState({ $0.withUpdatedMessageActionsState({ value in return $0.updatedInterfaceState({ $0.withUpdatedMessageActionsState({ value in
var value = value var value = value
value.closedPinnedMessageId = pinnedMessage.id value.closedPinnedMessageId = pinnedMessage.message.id
return value return value
}) }) }) })
}) })

View File

@ -997,7 +997,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
} }
} }
if let pinnedMessage = self.chatPresentationInterfaceState.pinnedMessage, self.context.sharedContext.immediateExperimentalUISettings.playerEmbedding, self.context.sharedContext.immediateExperimentalUISettings.playlistPlayback, self.embeddedTitleContentNode == nil, let url = extractExperimentalPlaylistUrl(pinnedMessage.text), self.didProcessExperimentalEmbedUrl != url { if let pinnedMessage = self.chatPresentationInterfaceState.pinnedMessage, self.context.sharedContext.immediateExperimentalUISettings.playerEmbedding, self.context.sharedContext.immediateExperimentalUISettings.playlistPlayback, self.embeddedTitleContentNode == nil, let url = extractExperimentalPlaylistUrl(pinnedMessage.message.text), self.didProcessExperimentalEmbedUrl != url {
self.didProcessExperimentalEmbedUrl = url self.didProcessExperimentalEmbedUrl = url
let context = self.context let context = self.context
let baseNavigationController = self.controller?.navigationController as? NavigationController let baseNavigationController = self.controller?.navigationController as? NavigationController

View File

@ -24,6 +24,11 @@ extension ChatReplyThreadMessage {
} }
} }
struct ChatTopVisibleMessage: Equatable {
var id: MessageId
var isLast: Bool
}
private class ChatHistoryListSelectionRecognizer: UIPanGestureRecognizer { private class ChatHistoryListSelectionRecognizer: UIPanGestureRecognizer {
private let selectionGestureActivationThreshold: CGFloat = 5.0 private let selectionGestureActivationThreshold: CGFloat = 5.0
@ -557,6 +562,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
private var loadedMessagesFromCachedDataDisposable: Disposable? private var loadedMessagesFromCachedDataDisposable: Disposable?
let isTopReplyThreadMessageShown = ValuePromise<Bool>(false, ignoreRepeated: true) let isTopReplyThreadMessageShown = ValuePromise<Bool>(false, ignoreRepeated: true)
let topVisibleMessage = ValuePromise<ChatTopVisibleMessage?>(nil, ignoreRepeated: true)
private let clientId: Atomic<Int32> private let clientId: Atomic<Int32>
@ -1151,6 +1157,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
private func processDisplayedItemRangeChanged(displayedRange: ListViewDisplayedItemRange, transactionState: ChatHistoryTransactionOpaqueState) { private func processDisplayedItemRangeChanged(displayedRange: ListViewDisplayedItemRange, transactionState: ChatHistoryTransactionOpaqueState) {
let historyView = transactionState.historyView let historyView = transactionState.historyView
var isTopReplyThreadMessageShownValue = false var isTopReplyThreadMessageShownValue = false
var topVisibleMessage: ChatTopVisibleMessage?
if let visible = displayedRange.visibleRange { if let visible = displayedRange.visibleRange {
let indexRange = (historyView.filteredEntries.count - 1 - visible.lastIndex, historyView.filteredEntries.count - 1 - visible.firstIndex) let indexRange = (historyView.filteredEntries.count - 1 - visible.lastIndex, historyView.filteredEntries.count - 1 - visible.firstIndex)
if indexRange.0 > indexRange.1 { if indexRange.0 > indexRange.1 {
@ -1225,6 +1232,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
if case let .replyThread(replyThreadMessage) = self.chatLocation, replyThreadMessage.effectiveTopId == message.id { if case let .replyThread(replyThreadMessage) = self.chatLocation, replyThreadMessage.effectiveTopId == message.id {
isTopReplyThreadMessageShownValue = true isTopReplyThreadMessageShownValue = true
} }
topVisibleMessage = ChatTopVisibleMessage(id: message.id, isLast: i == historyView.filteredEntries.count - 1)
case let .MessageGroupEntry(_, messages, _): case let .MessageGroupEntry(_, messages, _):
for (message, _, _, _) in messages { for (message, _, _, _) in messages {
var hasUnconsumedMention = false var hasUnconsumedMention = false
@ -1255,6 +1263,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
if case let .replyThread(replyThreadMessage) = self.chatLocation, replyThreadMessage.effectiveTopId == message.id { if case let .replyThread(replyThreadMessage) = self.chatLocation, replyThreadMessage.effectiveTopId == message.id {
isTopReplyThreadMessageShownValue = true isTopReplyThreadMessageShownValue = true
} }
topVisibleMessage = ChatTopVisibleMessage(id: message.id, isLast: i == historyView.filteredEntries.count - 1)
} }
default: default:
break break
@ -1371,6 +1380,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
} }
} }
self.isTopReplyThreadMessageShown.set(isTopReplyThreadMessageShownValue) self.isTopReplyThreadMessageShown.set(isTopReplyThreadMessageShownValue)
self.topVisibleMessage.set(topVisibleMessage)
if let loaded = displayedRange.loadedRange, let firstEntry = historyView.filteredEntries.first, let lastEntry = historyView.filteredEntries.last { if let loaded = displayedRange.loadedRange, let firstEntry = historyView.filteredEntries.first, let lastEntry = historyView.filteredEntries.last {
if loaded.firstIndex < 5 && historyView.originalView.laterId != nil { if loaded.firstIndex < 5 && historyView.originalView.laterId != nil {

View File

@ -643,7 +643,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
} }
if data.canPin, case .peer = chatPresentationInterfaceState.chatLocation { if data.canPin, case .peer = chatPresentationInterfaceState.chatLocation {
if chatPresentationInterfaceState.pinnedMessage?.id != messages[0].id { if chatPresentationInterfaceState.pinnedMessage?.message.id != messages[0].id {
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_Pin, icon: { theme in actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_Pin, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Pin"), color: theme.actionSheet.primaryTextColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Pin"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in }, action: { _, f in

View File

@ -145,7 +145,7 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
} }
let inlineBots: Signal<[(Peer, Double)], NoError> = types.contains(.contextBots) ? recentlyUsedInlineBots(postbox: context.account.postbox) : .single([]) let inlineBots: Signal<[(Peer, Double)], NoError> = types.contains(.contextBots) ? recentlyUsedInlineBots(postbox: context.account.postbox) : .single([])
let participants = combineLatest(inlineBots, searchPeerMembers(context: context, peerId: peer.id, chatLocation: chatLocation, query: query)) let participants = combineLatest(inlineBots, searchPeerMembers(context: context, peerId: peer.id, chatLocation: chatLocation, query: query, scope: .mention))
|> map { inlineBots, peers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in |> map { inlineBots, peers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
let filteredInlineBots = inlineBots.sorted(by: { $0.1 > $1.1 }).filter { peer, rating in let filteredInlineBots = inlineBots.sorted(by: { $0.1 > $1.1 }).filter { peer, rating in
if rating < 0.14 { if rating < 0.14 {
@ -347,7 +347,7 @@ func searchQuerySuggestionResultStateForChatInterfacePresentationState(_ chatPre
} }
} }
let participants = searchPeerMembers(context: context, peerId: peer.id, chatLocation: chatPresentationInterfaceState.chatLocation, query: query) let participants = searchPeerMembers(context: context, peerId: peer.id, chatLocation: chatPresentationInterfaceState.chatLocation, query: query, scope: .memberSuggestion)
|> map { peers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in |> map { peers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
let filteredPeers = peers let filteredPeers = peers
var sortedPeers: [Peer] = [] var sortedPeers: [Peer] = []

View File

@ -19,7 +19,7 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat
loop: for context in chatPresentationInterfaceState.titlePanelContexts.reversed() { loop: for context in chatPresentationInterfaceState.titlePanelContexts.reversed() {
switch context { switch context {
case .pinnedMessage: case .pinnedMessage:
if let pinnedMessage = chatPresentationInterfaceState.pinnedMessage, pinnedMessage.id != chatPresentationInterfaceState.interfaceState.messageActionsState.closedPinnedMessageId { if let pinnedMessage = chatPresentationInterfaceState.pinnedMessage, pinnedMessage.message.id != chatPresentationInterfaceState.interfaceState.messageActionsState.closedPinnedMessageId {
selectedContext = context selectedContext = context
break loop break loop
} }

View File

@ -13,10 +13,18 @@ import StickerResources
import PhotoResources import PhotoResources
import TelegramStringFormatting import TelegramStringFormatting
private enum PinnedMessageAnimation {
case slideToTop
case slideToBottom
}
final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
private let context: AccountContext private let context: AccountContext
private let tapButton: HighlightTrackingButtonNode private let tapButton: HighlightTrackingButtonNode
private let closeButton: HighlightableButtonNode private let closeButton: HighlightableButtonNode
private let clippingContainer: ASDisplayNode
private let contentContainer: ASDisplayNode
private let lineNode: ASImageNode private let lineNode: ASImageNode
private let titleNode: TextNode private let titleNode: TextNode
private let textNode: TextNode private let textNode: TextNode
@ -25,7 +33,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
private let separatorNode: ASDisplayNode private let separatorNode: ASDisplayNode
private var currentLayout: (CGFloat, CGFloat, CGFloat)? private var currentLayout: (CGFloat, CGFloat, CGFloat)?
private var currentMessage: Message? private var currentMessage: ChatPinnedMessage?
private var previousMediaReference: AnyMediaReference? private var previousMediaReference: AnyMediaReference?
private var isReplyThread: Bool = false private var isReplyThread: Bool = false
@ -46,6 +54,11 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
self.separatorNode = ASDisplayNode() self.separatorNode = ASDisplayNode()
self.separatorNode.isLayerBacked = true self.separatorNode.isLayerBacked = true
self.clippingContainer = ASDisplayNode()
self.clippingContainer.clipsToBounds = true
self.contentContainer = ASDisplayNode()
self.lineNode = ASImageNode() self.lineNode = ASImageNode()
self.lineNode.displayWithoutProcessing = true self.lineNode.displayWithoutProcessing = true
self.lineNode.displaysAsynchronously = false self.lineNode.displaysAsynchronously = false
@ -87,10 +100,12 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
self.closeButton.addTarget(self, action: #selector(self.closePressed), forControlEvents: [.touchUpInside]) self.closeButton.addTarget(self, action: #selector(self.closePressed), forControlEvents: [.touchUpInside])
self.addSubnode(self.closeButton) self.addSubnode(self.closeButton)
self.addSubnode(self.clippingContainer)
self.clippingContainer.addSubnode(self.contentContainer)
self.addSubnode(self.lineNode) self.addSubnode(self.lineNode)
self.addSubnode(self.titleNode) self.contentContainer.addSubnode(self.titleNode)
self.addSubnode(self.textNode) self.contentContainer.addSubnode(self.textNode)
self.addSubnode(self.imageNode) self.contentContainer.addSubnode(self.imageNode)
self.tapButton.addTarget(self, action: #selector(self.tapped), forControlEvents: [.touchUpInside]) self.tapButton.addTarget(self, action: #selector(self.tapped), forControlEvents: [.touchUpInside])
self.addSubnode(self.tapButton) self.addSubnode(self.tapButton)
@ -128,10 +143,18 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
self.closeButton.isHidden = isReplyThread self.closeButton.isHidden = isReplyThread
var messageUpdated = false var messageUpdated = false
var messageUpdatedAnimation: PinnedMessageAnimation?
if let currentMessage = self.currentMessage, let pinnedMessage = interfaceState.pinnedMessage { if let currentMessage = self.currentMessage, let pinnedMessage = interfaceState.pinnedMessage {
if currentMessage.id != pinnedMessage.id || currentMessage.stableVersion != pinnedMessage.stableVersion { if currentMessage != pinnedMessage {
messageUpdated = true messageUpdated = true
} }
if currentMessage.message.id != pinnedMessage.message.id {
if currentMessage.message.id < pinnedMessage.message.id {
messageUpdatedAnimation = .slideToTop
} else {
messageUpdatedAnimation = .slideToBottom
}
}
} else if (self.currentMessage != nil) != (interfaceState.pinnedMessage != nil) { } else if (self.currentMessage != nil) != (interfaceState.pinnedMessage != nil) {
messageUpdated = true messageUpdated = true
} }
@ -141,7 +164,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
self.currentMessage = interfaceState.pinnedMessage self.currentMessage = interfaceState.pinnedMessage
if let currentMessage = currentMessage, let currentLayout = self.currentLayout { if let currentMessage = currentMessage, let currentLayout = self.currentLayout {
self.enqueueTransition(width: currentLayout.0, leftInset: currentLayout.1, rightInset: currentLayout.2, transition: .immediate, message: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, nameDisplayOrder: interfaceState.nameDisplayOrder, accountPeerId: self.context.account.peerId, firstTime: previousMessageWasNil, isReplyThread: isReplyThread) self.enqueueTransition(width: currentLayout.0, leftInset: currentLayout.1, rightInset: currentLayout.2, transition: .immediate, animation: messageUpdatedAnimation, pinnedMessage: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, nameDisplayOrder: interfaceState.nameDisplayOrder, accountPeerId: self.context.account.peerId, firstTime: previousMessageWasNil, isReplyThread: isReplyThread)
} }
} }
@ -156,18 +179,43 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelHeight - UIScreenPixel), size: CGSize(width: width, height: UIScreenPixel))) transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelHeight - UIScreenPixel), size: CGSize(width: width, height: UIScreenPixel)))
self.tapButton.frame = CGRect(origin: CGPoint(), size: CGSize(width: width - rightInset - closeButtonSize.width - 4.0, height: panelHeight)) self.tapButton.frame = CGRect(origin: CGPoint(), size: CGSize(width: width - rightInset - closeButtonSize.width - 4.0, height: panelHeight))
self.clippingContainer.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: panelHeight))
self.contentContainer.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: panelHeight))
if self.currentLayout?.0 != width || self.currentLayout?.1 != leftInset || self.currentLayout?.2 != rightInset { if self.currentLayout?.0 != width || self.currentLayout?.1 != leftInset || self.currentLayout?.2 != rightInset {
self.currentLayout = (width, leftInset, rightInset) self.currentLayout = (width, leftInset, rightInset)
if let currentMessage = self.currentMessage { if let currentMessage = self.currentMessage {
self.enqueueTransition(width: width, leftInset: leftInset, rightInset: rightInset, transition: .immediate, message: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, nameDisplayOrder: interfaceState.nameDisplayOrder, accountPeerId: interfaceState.accountPeerId, firstTime: true, isReplyThread: isReplyThread) self.enqueueTransition(width: width, leftInset: leftInset, rightInset: rightInset, transition: .immediate, animation: .none, pinnedMessage: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, nameDisplayOrder: interfaceState.nameDisplayOrder, accountPeerId: interfaceState.accountPeerId, firstTime: true, isReplyThread: isReplyThread)
} }
} }
return panelHeight return panelHeight
} }
private func enqueueTransition(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, message: Message, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, accountPeerId: PeerId, firstTime: Bool, isReplyThread: Bool) { private func enqueueTransition(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, animation: PinnedMessageAnimation?, pinnedMessage: ChatPinnedMessage, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, accountPeerId: PeerId, firstTime: Bool, isReplyThread: Bool) {
let message = pinnedMessage.message
if let animation = animation {
let offset: CGFloat
switch animation {
case .slideToTop:
offset = -40.0
case .slideToBottom:
offset = 40.0
}
if let copyView = self.contentContainer.view.snapshotView(afterScreenUpdates: false) {
copyView.frame = self.contentContainer.frame
self.clippingContainer.view.addSubview(copyView)
copyView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: offset), duration: 0.2, removeOnCompletion: false, additive: true)
copyView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak copyView] _ in
copyView?.removeFromSuperview()
})
self.contentContainer.layer.animatePosition(from: CGPoint(x: 0.0, y: -offset), to: CGPoint(), duration: 0.2, additive: true)
self.contentContainer.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
}
let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeTextLayout = TextNode.asyncLayout(self.textNode) let makeTextLayout = TextNode.asyncLayout(self.textNode)
let imageNodeLayout = self.imageNode.asyncLayout() let imageNodeLayout = self.imageNode.asyncLayout()
@ -191,7 +239,12 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
var updatedMediaReference: AnyMediaReference? var updatedMediaReference: AnyMediaReference?
var imageDimensions: CGSize? var imageDimensions: CGSize?
var titleString = strings.Conversation_PinnedMessage var titleString: String
if pinnedMessage.isLatest {
titleString = strings.Conversation_PinnedMessage
} else {
titleString = strings.Conversation_PinnedPreviousMessage
}
for media in message.media { for media in message.media {
if let image = media as? TelegramMediaImage { if let image = media as? TelegramMediaImage {
@ -300,13 +353,8 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
if let interfaceInteraction = self.interfaceInteraction, let message = self.currentMessage { if let interfaceInteraction = self.interfaceInteraction, let message = self.currentMessage {
if self.isReplyThread { if self.isReplyThread {
interfaceInteraction.scrollToTop() interfaceInteraction.scrollToTop()
/*if let sourceReference = message.sourceReference {
interfaceInteraction.navigateToMessage(sourceReference.messageId, true)
} else { } else {
interfaceInteraction.navigateToMessage(message.id, false) interfaceInteraction.navigateToMessage(message.message.id, false)
}*/
} else {
interfaceInteraction.navigateToMessage(message.id, false)
} }
} }
} }

View File

@ -257,6 +257,32 @@ struct ChatSlowmodeState: Equatable {
var variant: ChatSlowmodeVariant var variant: ChatSlowmodeVariant
} }
final class ChatPinnedMessage: Equatable {
let message: Message
let isLatest: Bool
init(message: Message, isLatest: Bool) {
self.message = message
self.isLatest = isLatest
}
static func ==(lhs: ChatPinnedMessage, rhs: ChatPinnedMessage) -> Bool {
if lhs === rhs {
return true
}
if lhs.message.id != rhs.message.id {
return false
}
if lhs.message.stableVersion != rhs.message.stableVersion {
return false
}
if lhs.isLatest != rhs.isLatest {
return false
}
return true
}
}
final class ChatPresentationInterfaceState: Equatable { final class ChatPresentationInterfaceState: Equatable {
let interfaceState: ChatInterfaceState let interfaceState: ChatInterfaceState
let chatLocation: ChatLocation let chatLocation: ChatLocation
@ -274,7 +300,7 @@ final class ChatPresentationInterfaceState: Equatable {
let titlePanelContexts: [ChatTitlePanelContext] let titlePanelContexts: [ChatTitlePanelContext]
let keyboardButtonsMessage: Message? let keyboardButtonsMessage: Message?
let pinnedMessageId: MessageId? let pinnedMessageId: MessageId?
let pinnedMessage: Message? let pinnedMessage: ChatPinnedMessage?
let peerIsBlocked: Bool let peerIsBlocked: Bool
let peerIsMuted: Bool let peerIsMuted: Bool
let peerDiscussionId: PeerId? let peerDiscussionId: PeerId?
@ -348,7 +374,7 @@ final class ChatPresentationInterfaceState: Equatable {
self.peerNearbyData = peerNearbyData self.peerNearbyData = peerNearbyData
} }
init(interfaceState: ChatInterfaceState, chatLocation: ChatLocation, renderedPeer: RenderedPeer?, isNotAccessible: Bool, explicitelyCanPinMessages: Bool, contactStatus: ChatContactStatus?, hasBots: Bool, isArchived: Bool, inputTextPanelState: ChatTextInputPanelState, editMessageState: ChatEditInterfaceMessageState?, recordedMediaPreview: ChatRecordedMediaPreview?, inputQueryResults: [ChatPresentationInputQueryKind: ChatPresentationInputQueryResult], inputMode: ChatInputMode, titlePanelContexts: [ChatTitlePanelContext], keyboardButtonsMessage: Message?, pinnedMessageId: MessageId?, pinnedMessage: Message?, peerIsBlocked: Bool, peerIsMuted: Bool, peerDiscussionId: PeerId?, peerGeoLocation: PeerGeoLocation?, callsAvailable: Bool, callsPrivate: Bool, slowmodeState: ChatSlowmodeState?, chatHistoryState: ChatHistoryNodeHistoryState?, botStartPayload: String?, urlPreview: (String, TelegramMediaWebpage)?, editingUrlPreview: (String, TelegramMediaWebpage)?, search: ChatSearchData?, searchQuerySuggestionResult: ChatPresentationInputQueryResult?, chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, limitsConfiguration: LimitsConfiguration, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, accountPeerId: PeerId, mode: ChatControllerPresentationMode, hasScheduledMessages: Bool, isScheduledMessages: Bool, peerNearbyData: ChatPeerNearbyData?) { init(interfaceState: ChatInterfaceState, chatLocation: ChatLocation, renderedPeer: RenderedPeer?, isNotAccessible: Bool, explicitelyCanPinMessages: Bool, contactStatus: ChatContactStatus?, hasBots: Bool, isArchived: Bool, inputTextPanelState: ChatTextInputPanelState, editMessageState: ChatEditInterfaceMessageState?, recordedMediaPreview: ChatRecordedMediaPreview?, inputQueryResults: [ChatPresentationInputQueryKind: ChatPresentationInputQueryResult], inputMode: ChatInputMode, titlePanelContexts: [ChatTitlePanelContext], keyboardButtonsMessage: Message?, pinnedMessageId: MessageId?, pinnedMessage: ChatPinnedMessage?, peerIsBlocked: Bool, peerIsMuted: Bool, peerDiscussionId: PeerId?, peerGeoLocation: PeerGeoLocation?, callsAvailable: Bool, callsPrivate: Bool, slowmodeState: ChatSlowmodeState?, chatHistoryState: ChatHistoryNodeHistoryState?, botStartPayload: String?, urlPreview: (String, TelegramMediaWebpage)?, editingUrlPreview: (String, TelegramMediaWebpage)?, search: ChatSearchData?, searchQuerySuggestionResult: ChatPresentationInputQueryResult?, chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, limitsConfiguration: LimitsConfiguration, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, accountPeerId: PeerId, mode: ChatControllerPresentationMode, hasScheduledMessages: Bool, isScheduledMessages: Bool, peerNearbyData: ChatPeerNearbyData?) {
self.interfaceState = interfaceState self.interfaceState = interfaceState
self.chatLocation = chatLocation self.chatLocation = chatLocation
self.renderedPeer = renderedPeer self.renderedPeer = renderedPeer
@ -447,14 +473,7 @@ final class ChatPresentationInterfaceState: Equatable {
if lhs.pinnedMessageId != rhs.pinnedMessageId { if lhs.pinnedMessageId != rhs.pinnedMessageId {
return false return false
} }
if let lhsMessage = lhs.pinnedMessage, let rhsMessage = rhs.pinnedMessage { if lhs.pinnedMessage != rhs.pinnedMessage {
if lhsMessage.id != rhsMessage.id {
return false
}
if lhsMessage.stableVersion != rhsMessage.stableVersion {
return false
}
} else if (lhs.pinnedMessage != nil) != (rhs.pinnedMessage != nil) {
return false return false
} }
if lhs.callsAvailable != rhs.callsAvailable { if lhs.callsAvailable != rhs.callsAvailable {
@ -613,7 +632,7 @@ final class ChatPresentationInterfaceState: Equatable {
return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, isScheduledMessages: self.isScheduledMessages, peerNearbyData: self.peerNearbyData) return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, isScheduledMessages: self.isScheduledMessages, peerNearbyData: self.peerNearbyData)
} }
func updatedPinnedMessage(_ pinnedMessage: Message?) -> ChatPresentationInterfaceState { func updatedPinnedMessage(_ pinnedMessage: ChatPinnedMessage?) -> ChatPresentationInterfaceState {
return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, isScheduledMessages: self.isScheduledMessages, peerNearbyData: self.peerNearbyData) return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, isScheduledMessages: self.isScheduledMessages, peerNearbyData: self.peerNearbyData)
} }

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)