Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2020-10-06 23:51:16 +04:00
commit 77a7ac8882
35 changed files with 1581 additions and 1279 deletions

View File

@ -3,7 +3,7 @@
include Utils.makefile include Utils.makefile
APP_VERSION="7.1" APP_VERSION="7.1.1"
CORE_COUNT=$(shell sysctl -n hw.logicalcpu) CORE_COUNT=$(shell sysctl -n hw.logicalcpu)
CORE_COUNT_MINUS_ONE=$(shell expr ${CORE_COUNT} \- 1) CORE_COUNT_MINUS_ONE=$(shell expr ${CORE_COUNT} \- 1)

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

@ -1 +1 @@
11.5 12.0.1

View File

@ -5,7 +5,7 @@ set -e
BUILD_TELEGRAM_VERSION="1" BUILD_TELEGRAM_VERSION="1"
MACOS_VERSION="10.15" MACOS_VERSION="10.15"
XCODE_VERSION="11.5" XCODE_VERSION="12.0.1"
GUEST_SHELL="bash" GUEST_SHELL="bash"
VM_BASE_NAME="macos$(echo $MACOS_VERSION | sed -e 's/\.'/_/g)_Xcode$(echo $XCODE_VERSION | sed -e 's/\.'/_/g)" VM_BASE_NAME="macos$(echo $MACOS_VERSION | sed -e 's/\.'/_/g)_Xcode$(echo $XCODE_VERSION | sed -e 's/\.'/_/g)"

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

@ -180,7 +180,6 @@
- (void)cancel - (void)cancel
{ {
[self cleanup]; [self cleanup];
[self fail];
} }
- (void)complete { - (void)complete {

View File

@ -109,9 +109,9 @@ static NSDictionary *selectPublicKey(NSArray *fingerprints, NSArray<NSDictionary
{ {
for (NSDictionary *keyDesc in publicKeys) for (NSDictionary *keyDesc in publicKeys)
{ {
int64_t keyFingerprint = [[keyDesc objectForKey:@"fingerprint"] longLongValue]; uint64_t keyFingerprint = [[keyDesc objectForKey:@"fingerprint"] unsignedLongLongValue];
if ([nFingerprint longLongValue] == keyFingerprint) if ([nFingerprint unsignedLongLongValue] == keyFingerprint)
return keyDesc; return keyDesc;
} }
} }
@ -132,7 +132,6 @@ typedef enum {
id<EncryptionProvider> _encryptionProvider; id<EncryptionProvider> _encryptionProvider;
bool _tempAuth; bool _tempAuth;
MTSessionInfo *_sessionInfo;
MTDatacenterAuthStage _stage; MTDatacenterAuthStage _stage;
int64_t _currentStageMessageId; int64_t _currentStageMessageId;
@ -165,7 +164,6 @@ typedef enum {
{ {
_encryptionProvider = context.encryptionProvider; _encryptionProvider = context.encryptionProvider;
_tempAuth = tempAuth; _tempAuth = tempAuth;
_sessionInfo = [[MTSessionInfo alloc] initWithRandomSessionIdAndContext:context];
} }
return self; return self;
} }
@ -267,7 +265,7 @@ typedef enum {
[reqDhBuffer appendInt64:_dhPublicKeyFingerprint]; [reqDhBuffer appendInt64:_dhPublicKeyFingerprint];
[reqDhBuffer appendTLBytes:_dhEncryptedData]; [reqDhBuffer appendTLBytes:_dhEncryptedData];
NSString *messageDescription = [NSString stringWithFormat:@"reqDh nonce:%@ serverNonce:%@ p:%@ q:%@ fingerprint:%llx", _nonce, _serverNonce, _dhP, _dhQ, _dhPublicKeyFingerprint]; NSString *messageDescription = [NSString stringWithFormat:@"reqDh nonce:%@ serverNonce:%@ p:%@ q:%@ fingerprint:%llx dhEncryptedData:%d bytes", _nonce, _serverNonce, _dhP, _dhQ, _dhPublicKeyFingerprint, (int)_dhEncryptedData.length];
MTOutgoingMessage *message = [[MTOutgoingMessage alloc] initWithData:reqDhBuffer.data metadata:messageDescription additionalDebugDescription:nil shortMetadata:messageDescription messageId:_currentStageMessageId messageSeqNo:_currentStageMessageSeqNo]; MTOutgoingMessage *message = [[MTOutgoingMessage alloc] initWithData:reqDhBuffer.data metadata:messageDescription additionalDebugDescription:nil shortMetadata:messageDescription messageId:_currentStageMessageId messageSeqNo:_currentStageMessageSeqNo];
return [[MTMessageTransaction alloc] initWithMessagePayload:@[message] prepared:nil failed:nil completion:^(NSDictionary *messageInternalIdToTransactionId, NSDictionary *messageInternalIdToPreparedMessage, __unused NSDictionary *messageInternalIdToQuickAckId) return [[MTMessageTransaction alloc] initWithMessagePayload:@[message] prepared:nil failed:nil completion:^(NSDictionary *messageInternalIdToTransactionId, NSDictionary *messageInternalIdToPreparedMessage, __unused NSDictionary *messageInternalIdToQuickAckId)
{ {
@ -402,12 +400,10 @@ typedef enum {
arc4random_buf(&random, 1); arc4random_buf(&random, 1);
[dataWithHash appendBytes:&random length:1]; [dataWithHash appendBytes:&random length:1];
} }
#if DEBUG
assert(dataWithHash.length == 255);
#endif
NSData *encryptedData = MTRsaEncrypt(_encryptionProvider, [publicKey objectForKey:@"key"], dataWithHash); NSData *encryptedData = MTRsaEncrypt(_encryptionProvider, [publicKey objectForKey:@"key"], dataWithHash);
if (MTLogEnabled()) { if (MTLogEnabled()) {
MTLog(@"[MTDatacenterAuthMessageService#%p encryptedData.length = %d]", self, encryptedData.length); MTLog(@"[MTDatacenterAuthMessageService#%p encryptedData length %d dataWithHash length %d]", self, (int)encryptedData.length, (int)dataWithHash.length);
} }
if (encryptedData.length < 256) if (encryptedData.length < 256)
{ {
@ -447,8 +443,11 @@ typedef enum {
arc4random_buf(&random, 1); arc4random_buf(&random, 1);
[dataWithHash appendBytes:&random length:1]; [dataWithHash appendBytes:&random length:1];
} }
NSData *encryptedData = MTRsaEncrypt(_encryptionProvider, [publicKey objectForKey:@"key"], dataWithHash); NSData *encryptedData = MTRsaEncrypt(_encryptionProvider, [publicKey objectForKey:@"key"], dataWithHash);
if (MTLogEnabled()) {
MTLog(@"[MTDatacenterAuthMessageService#%p encryptedData length %d dataWithHash length %d]", self, (int)encryptedData.length, (int)dataWithHash.length);
}
if (encryptedData.length < 256) if (encryptedData.length < 256)
{ {
NSMutableData *newEncryptedData = [[NSMutableData alloc] init]; NSMutableData *newEncryptedData = [[NSMutableData alloc] init];

View File

@ -292,12 +292,13 @@ NSData *MTAesDecrypt(NSData *data, NSData *key, NSData *iv)
NSData *MTRsaEncrypt(id<EncryptionProvider> provider, NSString *publicKey, NSData *data) NSData *MTRsaEncrypt(id<EncryptionProvider> provider, NSString *publicKey, NSData *data)
{ {
#if TARGET_OS_IOS #if TARGET_OS_IOS
NSMutableData *updatedData = [[NSMutableData alloc] initWithData:data]; return [provider rsaEncryptWithPublicKey:publicKey data:data];
/*NSMutableData *updatedData = [[NSMutableData alloc] initWithData:data];
while (updatedData.length < 256) { while (updatedData.length < 256) {
uint8_t zero = 0; uint8_t zero = 0;
[updatedData replaceBytesInRange:NSMakeRange(0, 0) withBytes:&zero length:1]; [updatedData replaceBytesInRange:NSMakeRange(0, 0) withBytes:&zero length:1];
} }
return [MTRsa encryptData:updatedData publicKey:publicKey]; return [MTRsa encryptData:updatedData publicKey:publicKey];*/
#else #else
return [provider macosRSAEncrypt:publicKey data:data]; return [provider macosRSAEncrypt:publicKey data:data];
#endif #endif

View File

@ -1726,8 +1726,13 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64;
{ {
[[MTProto managerQueue] dispatchOnQueue:^ [[MTProto managerQueue] dispatchOnQueue:^
{ {
if (transport != _transport || completion == nil) if (transport != _transport || completion == nil) {
return; return;
}
if (_useUnauthorizedMode) {
return;
}
MTDatacenterAuthKey *authKey = [self getAuthKeyForCurrentScheme:scheme createIfNeeded:false authInfoSelector:nil]; MTDatacenterAuthKey *authKey = [self getAuthKeyForCurrentScheme:scheme createIfNeeded:false authInfoSelector:nil];
if (authKey == nil) { if (authKey == nil) {
@ -2038,11 +2043,18 @@ static NSString *dumpHexString(NSData *data, int maxLength) {
- (void)handleMissingKey:(MTTransportScheme *)scheme { - (void)handleMissingKey:(MTTransportScheme *)scheme {
NSAssert([[MTProto managerQueue] isCurrentQueue], @"invalid queue"); NSAssert([[MTProto managerQueue] isCurrentQueue], @"invalid queue");
if (_useUnauthorizedMode) {
if (MTLogEnabled()) {
MTLog(@"[MTProto#%p@%p don't handleMissingKey when useUnauthorizedMode]", self, _context);
}
return;
}
MTDatacenterAuthInfoSelector authInfoSelector; MTDatacenterAuthInfoSelector authInfoSelector;
[self getAuthKeyForCurrentScheme:scheme createIfNeeded:false authInfoSelector:&authInfoSelector]; [self getAuthKeyForCurrentScheme:scheme createIfNeeded:false authInfoSelector:&authInfoSelector];
if (MTLogEnabled()) { if (MTLogEnabled()) {
MTLog(@"[MTProto#%p@%p missing key %lld selector]", self, _context, _validAuthInfo.authInfo.authKeyId, authInfoSelector); MTLog(@"[MTProto#%p@%p missing key %lld selector %d]", self, _context, _validAuthInfo.authInfo.authKeyId, authInfoSelector);
} }
if (_useExplicitAuthKey != nil) { if (_useExplicitAuthKey != nil) {
@ -2618,7 +2630,7 @@ static NSString *dumpHexString(NSData *data, int maxLength) {
{ {
[_context setGlobalTimeDifference:timeDifference]; [_context setGlobalTimeDifference:timeDifference];
if (saltList != nil) if (!_useUnauthorizedMode && saltList != nil)
{ {
if (_useExplicitAuthKey) { if (_useExplicitAuthKey) {
if (_validAuthInfo != nil && _validAuthInfo.selector == authInfoSelector) { if (_validAuthInfo != nil && _validAuthInfo.selector == authInfoSelector) {

View File

@ -560,9 +560,15 @@ public func channelDiscussionGroupSetupController(context: AccountContext, peerI
var isEmptyState = false var isEmptyState = false
var displayGroupList = false var displayGroupList = false
if let cachedData = view.cachedData as? CachedChannelData { if let cachedData = view.cachedData as? CachedChannelData {
let isEmpty = cachedData.linkedDiscussionPeerId == nil var isEmpty = true
switch cachedData.linkedDiscussionPeerId {
case .unknown:
isEmpty = true
case let .known(value):
isEmpty = value == nil
}
if let peer = view.peers[view.peerId] as? TelegramChannel, case .broadcast = peer.info { if let peer = view.peers[view.peerId] as? TelegramChannel, case .broadcast = peer.info {
if cachedData.linkedDiscussionPeerId == nil { if isEmpty {
if groups == nil { if groups == nil {
isEmptyState = true isEmptyState = true
} else { } else {
@ -570,13 +576,13 @@ public func channelDiscussionGroupSetupController(context: AccountContext, peerI
} }
} }
} }
if let wasEmpty = wasEmpty, wasEmpty != isEmpty {
crossfade = true
}
wasEmpty = isEmpty
} else { } else {
isEmptyState = true isEmptyState = true
} }
if let wasEmpty = wasEmpty, wasEmpty != isEmptyState {
crossfade = true
}
wasEmpty = isEmptyState
var emptyStateItem: ItemListControllerEmptyStateItem? var emptyStateItem: ItemListControllerEmptyStateItem?
if isEmptyState { if isEmptyState {

View File

@ -5,20 +5,23 @@ 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 {
if case .replyThread = chatLocation { case memberSuggestion
return .single([]) case mention
} else if peerId.namespace == Namespaces.Peer.CloudChannel { }
public func searchPeerMembers(context: AccountContext, peerId: PeerId, chatLocation: ChatLocation, query: String, scope: SearchPeerMembersScope) -> Signal<[Peer], NoError> {
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 case .peer = chatLocation, 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 +40,7 @@ public func searchPeerMembers(context: AccountContext, peerId: PeerId, chatLocat
return nil return nil
} }
}) }, true))
} }
}) })
@ -49,22 +52,70 @@ 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 switch chatLocation {
if case .ready = state.loadingState { case let .peer(peerId):
subscriber.putNext(state.list.compactMap { participant 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 participant.peer.isDeleted { if case .ready = state.loadingState {
return nil subscriber.putNext((state.list.compactMap { participant in
} if participant.peer.isDeleted {
return participant.peer return nil
}) }
return participant.peer
}, true))
}
})
return ActionDisposable {
disposable.dispose()
}
case let .replyThread(replyThreadMessage):
let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.mentions(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, threadMessageId: replyThreadMessage.messageId, searchQuery: query.isEmpty ? nil : query, updated: { state in
if case .ready = state.loadingState {
subscriber.putNext((state.list.compactMap { participant in
if participant.peer.isDeleted {
return nil
}
return participant.peer
}, true))
}
})
return ActionDisposable {
disposable.dispose()
} }
})
return ActionDisposable {
disposable.dispose()
} }
} |> 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

@ -4,21 +4,27 @@ public struct NetworkSettings: PreferencesEntry, Equatable {
public var reducedBackupDiscoveryTimeout: Bool public var reducedBackupDiscoveryTimeout: Bool
public var applicationUpdateUrlPrefix: String? public var applicationUpdateUrlPrefix: String?
public var backupHostOverride: String? public var backupHostOverride: String?
public var defaultEnableTempKeys: Bool
public var userEnableTempKeys: Bool?
public static var defaultSettings: NetworkSettings { public static var defaultSettings: NetworkSettings {
return NetworkSettings(reducedBackupDiscoveryTimeout: false, applicationUpdateUrlPrefix: nil, backupHostOverride: nil) return NetworkSettings(reducedBackupDiscoveryTimeout: false, applicationUpdateUrlPrefix: nil, backupHostOverride: nil, defaultEnableTempKeys: false, userEnableTempKeys: nil)
} }
public init(reducedBackupDiscoveryTimeout: Bool, applicationUpdateUrlPrefix: String?, backupHostOverride: String?) { public init(reducedBackupDiscoveryTimeout: Bool, applicationUpdateUrlPrefix: String?, backupHostOverride: String?, defaultEnableTempKeys: Bool, userEnableTempKeys: Bool?) {
self.reducedBackupDiscoveryTimeout = reducedBackupDiscoveryTimeout self.reducedBackupDiscoveryTimeout = reducedBackupDiscoveryTimeout
self.applicationUpdateUrlPrefix = applicationUpdateUrlPrefix self.applicationUpdateUrlPrefix = applicationUpdateUrlPrefix
self.backupHostOverride = backupHostOverride self.backupHostOverride = backupHostOverride
self.defaultEnableTempKeys = defaultEnableTempKeys
self.userEnableTempKeys = userEnableTempKeys
} }
public init(decoder: PostboxDecoder) { public init(decoder: PostboxDecoder) {
self.reducedBackupDiscoveryTimeout = decoder.decodeInt32ForKey("reducedBackupDiscoveryTimeout", orElse: 0) != 0 self.reducedBackupDiscoveryTimeout = decoder.decodeInt32ForKey("reducedBackupDiscoveryTimeout", orElse: 0) != 0
self.applicationUpdateUrlPrefix = decoder.decodeOptionalStringForKey("applicationUpdateUrlPrefix") self.applicationUpdateUrlPrefix = decoder.decodeOptionalStringForKey("applicationUpdateUrlPrefix")
self.backupHostOverride = decoder.decodeOptionalStringForKey("backupHostOverride") self.backupHostOverride = decoder.decodeOptionalStringForKey("backupHostOverride")
self.defaultEnableTempKeys = decoder.decodeBoolForKey("defaultEnableTempKeys", orElse: false)
self.userEnableTempKeys = decoder.decodeOptionalBoolForKey("userEnableTempKeys")
} }
public func encode(_ encoder: PostboxEncoder) { public func encode(_ encoder: PostboxEncoder) {
@ -33,6 +39,12 @@ public struct NetworkSettings: PreferencesEntry, Equatable {
} else { } else {
encoder.encodeNil(forKey: "backupHostOverride") encoder.encodeNil(forKey: "backupHostOverride")
} }
encoder.encodeBool(self.defaultEnableTempKeys, forKey: "defaultEnableTempKeys")
if let userEnableTempKeys = self.userEnableTempKeys {
encoder.encodeBool(userEnableTempKeys, forKey: "userEnableTempKeys")
} else {
encoder.encodeNil(forKey: "userEnableTempKeys")
}
} }
public func isEqual(to: PreferencesEntry) -> Bool { public func isEqual(to: PreferencesEntry) -> Bool {

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[-531931925] = { 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(flags: Int32, q: String?, topMsgId: Int32?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
@ -10036,6 +10037,14 @@ public extension Api {
} }
serializeString(q, buffer: buffer, boxed: false) serializeString(q, buffer: buffer, boxed: false)
break break
case .channelParticipantsMentions(let flags, let q, let topMsgId):
if boxed {
buffer.appendInt32(-531931925)
}
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeString(q!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)}
break
} }
} }
@ -10055,6 +10064,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 flags, let q, let topMsgId):
return ("channelParticipantsMentions", [("flags", flags), ("q", q), ("topMsgId", topMsgId)])
} }
} }
@ -10111,6 +10122,23 @@ public extension Api {
return nil return nil
} }
} }
public static func parse_channelParticipantsMentions(_ reader: BufferReader) -> ChannelParticipantsFilter? {
var _1: Int32?
_1 = reader.readInt32()
var _2: String?
if Int(_1!) & Int(1 << 0) != 0 {_2 = parseString(reader) }
var _3: Int32?
if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt32() }
let _c1 = _1 != nil
let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
if _c1 && _c2 && _c3 {
return Api.ChannelParticipantsFilter.channelParticipantsMentions(flags: _1!, q: _2, topMsgId: _3)
}
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

@ -18,6 +18,7 @@ public enum ChannelMembersCategory {
case bots(ChannelMembersCategoryFilter) case bots(ChannelMembersCategoryFilter)
case restricted(ChannelMembersCategoryFilter) case restricted(ChannelMembersCategoryFilter)
case banned(ChannelMembersCategoryFilter) case banned(ChannelMembersCategoryFilter)
case mentions(threadId: MessageId?, filter: ChannelMembersCategoryFilter)
} }
public func channelMembers(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, category: ChannelMembersCategory = .recent(.all), offset: Int32 = 0, limit: Int32 = 64, hash: Int32 = 0) -> Signal<[RenderedChannelParticipant]?, NoError> { public func channelMembers(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, category: ChannelMembersCategory = .recent(.all), offset: Int32 = 0, limit: Int32 = 64, hash: Int32 = 0) -> Signal<[RenderedChannelParticipant]?, NoError> {
@ -32,6 +33,24 @@ public func channelMembers(postbox: Postbox, network: Network, accountPeerId: Pe
case let .search(query): case let .search(query):
apiFilter = .channelParticipantsSearch(q: query) apiFilter = .channelParticipantsSearch(q: query)
} }
case let .mentions(threadId, filter):
switch filter {
case .all:
var flags: Int32 = 0
if threadId != nil {
flags |= 1 << 1
}
apiFilter = .channelParticipantsMentions(flags: flags, q: nil, topMsgId: threadId?.id)
case let .search(query):
var flags: Int32 = 0
if threadId != nil {
flags |= 1 << 1
}
if !query.isEmpty {
flags |= 1 << 0
}
apiFilter = .channelParticipantsMentions(flags: flags, q: query.isEmpty ? nil : query, topMsgId: threadId?.id)
}
case .admins: case .admins:
apiFilter = .channelParticipantsAdmins apiFilter = .channelParticipantsAdmins
case let .contacts(filter): case let .contacts(filter):

View File

@ -35,10 +35,14 @@ func managedConfigurationUpdates(accountManager: AccountManager, postbox: Postbo
} }
let blockedMode = (config.flags & 8) != 0 let blockedMode = (config.flags & 8) != 0
let defaultEnableTempKeys = (config.flags & (1 << 13)) != 0
updateNetworkSettingsInteractively(transaction: transaction, network: network, { settings in updateNetworkSettingsInteractively(transaction: transaction, network: network, { settings in
var settings = settings var settings = settings
settings.reducedBackupDiscoveryTimeout = blockedMode settings.reducedBackupDiscoveryTimeout = blockedMode
settings.applicationUpdateUrlPrefix = config.autoupdateUrlPrefix settings.applicationUpdateUrlPrefix = config.autoupdateUrlPrefix
settings.defaultEnableTempKeys = defaultEnableTempKeys
return settings return settings
}) })

View File

@ -474,6 +474,17 @@ func initializedNetwork(accountId: AccountRecordId, arguments: NetworkInitializa
} }
} }
let useTempAuthKeys: Bool
if let networkSettings = networkSettings {
if let userEnableTempKeys = networkSettings.userEnableTempKeys {
useTempAuthKeys = userEnableTempKeys
} else {
useTempAuthKeys = networkSettings.defaultEnableTempKeys
}
} else {
useTempAuthKeys = true
}
var contextValue: MTContext? var contextValue: MTContext?
sharedContexts.with { store in sharedContexts.with { store in
let key = SharedContextStore.Key(accountId: accountId) let key = SharedContextStore.Key(accountId: accountId)
@ -483,7 +494,7 @@ func initializedNetwork(accountId: AccountRecordId, arguments: NetworkInitializa
context = current context = current
context.updateApiEnvironment({ _ in return apiEnvironment}) context.updateApiEnvironment({ _ in return apiEnvironment})
} else { } else {
context = MTContext(serialization: serialization, encryptionProvider: arguments.encryptionProvider, apiEnvironment: apiEnvironment, isTestingEnvironment: testingEnvironment, useTempAuthKeys: true)! context = MTContext(serialization: serialization, encryptionProvider: arguments.encryptionProvider, apiEnvironment: apiEnvironment, isTestingEnvironment: testingEnvironment, useTempAuthKeys: useTempAuthKeys)!
store.contexts[key] = context store.contexts[key] = context
} }
contextValue = context contextValue = context

View File

@ -17,7 +17,7 @@ extension NetworkSettings {
} }
} }
public func updateNetworkSettingsInteractively(transaction: Transaction, network: Network, _ f: @escaping (NetworkSettings) -> NetworkSettings) { public func updateNetworkSettingsInteractively(transaction: Transaction, network: Network?, _ f: @escaping (NetworkSettings) -> NetworkSettings) {
var updateNetwork = false var updateNetwork = false
var updatedSettings: NetworkSettings? var updatedSettings: NetworkSettings?
transaction.updatePreferencesEntry(key: PreferencesKeys.networkSettings, { current in transaction.updatePreferencesEntry(key: PreferencesKeys.networkSettings, { current in
@ -33,7 +33,7 @@ public func updateNetworkSettingsInteractively(transaction: Transaction, network
return updated return updated
}) })
if updateNetwork, let updatedSettings = updatedSettings { if let network = network, updateNetwork, let updatedSettings = updatedSettings {
network.context.updateApiEnvironment { current in network.context.updateApiEnvironment { current in
return current?.withUpdatedNetworkSettings(updatedSettings.mtNetworkSettings) return current?.withUpdatedNetworkSettings(updatedSettings.mtNetworkSettings)
} }

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

@ -58,7 +58,7 @@ class UpdateMessageService: NSObject, MTMessageService {
self.putNext(groups) self.putNext(groups)
} }
case let .updateShortChatMessage(flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyHeader, entities): case let .updateShortChatMessage(flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyHeader, entities):
let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: .peerChat(chatId: fromId), peerId: Api.Peer.peerChat(chatId: chatId), fwdFrom: fwdFrom, viaBotId: viaBotId, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, restrictionReason: nil) let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: .peerUser(userId: fromId), peerId: Api.Peer.peerChat(chatId: chatId), fwdFrom: fwdFrom, viaBotId: viaBotId, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, restrictionReason: nil)
let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount) let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount)
let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil)
if groups.count != 0 { if groups.count != 0 {

View File

@ -3277,13 +3277,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)
}
} }
} }
@ -3394,7 +3396,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
@ -3422,22 +3479,31 @@ 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
} }
} if let pinnedMessageId = pinnedMessageId {
if let message = messages?[pinnedMessageId] {
var pinnedMessage: Message? pinnedMessage = ChatPinnedMessage(message: message, isLatest: true)
if let pinnedMessageId = pinnedMessageId { }
pinnedMessage = messages?[pinnedMessageId] }
} else {
if let pinnedMessageId = pinnedMessageId {
if let message = messages?[pinnedMessageId] {
pinnedMessage = ChatPinnedMessage(message: message, isLatest: true)
}
}
//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) {
@ -3446,7 +3512,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)
@ -4743,7 +4809,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
@ -4795,7 +4861,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 {
interfaceInteraction.navigateToMessage(message.id, false)
}*/
} else { } else {
interfaceInteraction.navigateToMessage(message.id, false) interfaceInteraction.navigateToMessage(message.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

@ -189,9 +189,6 @@ final class ChatSearchInputPanelNode: ChatInputPanelNode {
canSearchMembers = false canSearchMembers = false
} }
} }
if case .replyThread = interfaceState.chatLocation {
canSearchMembers = false
}
self.membersButton.isHidden = (!(interfaceState.search?.query.isEmpty ?? true)) || self.displayActivity || !canSearchMembers self.membersButton.isHidden = (!(interfaceState.search?.query.isEmpty ?? true)) || self.displayActivity || !canSearchMembers
let resultsEnabled = (resultCount ?? 0) > 0 let resultsEnabled = (resultCount ?? 0) > 0

View File

@ -64,6 +64,7 @@ public struct ChannelMemberListState {
enum ChannelMemberListCategory { enum ChannelMemberListCategory {
case recent case recent
case recentSearch(String) case recentSearch(String)
case mentions(MessageId?, String?)
case admins(String?) case admins(String?)
case contacts(String?) case contacts(String?)
case bots(String?) case bots(String?)
@ -211,6 +212,12 @@ private final class ChannelMemberSingleCategoryListContext: ChannelMemberCategor
requestCategory = .recent(.all) requestCategory = .recent(.all)
case let .recentSearch(query): case let .recentSearch(query):
requestCategory = .recent(.search(query)) requestCategory = .recent(.search(query))
case let .mentions(threadId, query):
if let query = query, !query.isEmpty {
requestCategory = .mentions(threadId: threadId, filter: .search(query))
} else {
requestCategory = .mentions(threadId: threadId, filter: .all)
}
case let .admins(query): case let .admins(query):
requestCategory = .admins requestCategory = .admins
adminQuery = query adminQuery = query
@ -521,6 +528,8 @@ private final class ChannelMemberSingleCategoryListContext: ChannelMemberCategor
} }
} }
} }
case .mentions:
break
} }
} }
if updatedList { if updatedList {
@ -728,7 +737,7 @@ final class PeerChannelMemberCategoriesContext {
emptyTimeout = 0.0 emptyTimeout = 0.0
} }
switch key { switch key {
case .recent, .recentSearch, .admins, .contacts, .bots: case .recent, .recentSearch, .admins, .contacts, .bots, .mentions:
let mappedCategory: ChannelMemberListCategory let mappedCategory: ChannelMemberListCategory
switch key { switch key {
case .recent: case .recent:
@ -741,6 +750,8 @@ final class PeerChannelMemberCategoriesContext {
mappedCategory = .contacts(query) mappedCategory = .contacts(query)
case let .bots(query): case let .bots(query):
mappedCategory = .bots(query) mappedCategory = .bots(query)
case let .mentions(threadId, query):
mappedCategory = .mentions(threadId, query)
default: default:
mappedCategory = .recent mappedCategory = .recent
} }

View File

@ -8,6 +8,7 @@ import TelegramStringFormatting
enum PeerChannelMemberContextKey: Equatable, Hashable { enum PeerChannelMemberContextKey: Equatable, Hashable {
case recent case recent
case recentSearch(String) case recentSearch(String)
case mentions(threadId: MessageId?, query: String?)
case admins(String?) case admins(String?)
case contacts(String?) case contacts(String?)
case bots(String?) case bots(String?)
@ -321,6 +322,11 @@ public final class PeerChannelMemberCategoriesContextsManager {
return self.getContext(postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: key, requestUpdate: requestUpdate, updated: updated) return self.getContext(postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: key, requestUpdate: requestUpdate, updated: updated)
} }
public func mentions(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, threadMessageId: MessageId?, searchQuery: String? = nil, requestUpdate: Bool = true, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) {
let key: PeerChannelMemberContextKey = .mentions(threadId: threadMessageId, query: searchQuery)
return self.getContext(postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: key, requestUpdate: requestUpdate, updated: updated)
}
public func admins(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { public func admins(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) {
return self.getContext(postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: .admins(searchQuery), requestUpdate: true, updated: updated) return self.getContext(postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: .admins(searchQuery), requestUpdate: true, updated: updated)
} }