mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
77a7ac8882
2
Makefile
2
Makefile
@ -3,7 +3,7 @@
|
||||
include Utils.makefile
|
||||
|
||||
|
||||
APP_VERSION="7.1"
|
||||
APP_VERSION="7.1.1"
|
||||
CORE_COUNT=$(shell sysctl -n hw.logicalcpu)
|
||||
CORE_COUNT_MINUS_ONE=$(shell expr ${CORE_COUNT} \- 1)
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
@implementation Serialization
|
||||
|
||||
- (NSUInteger)currentLayer {
|
||||
return 119;
|
||||
return 120;
|
||||
}
|
||||
|
||||
- (id _Nullable)parseMessage:(NSData * _Nullable)data {
|
||||
|
@ -1921,6 +1921,7 @@
|
||||
"Conversation.Unpin" = "Unpin";
|
||||
"Conversation.Report" = "Report Spam";
|
||||
"Conversation.PinnedMessage" = "Pinned Message";
|
||||
"Conversation.PinnedPreviousMessage" = "Previous Message";
|
||||
|
||||
"Conversation.Moderate.Delete" = "Delete Message";
|
||||
"Conversation.Moderate.Ban" = "Ban User";
|
||||
|
@ -1 +1 @@
|
||||
11.5
|
||||
12.0.1
|
||||
|
@ -5,7 +5,7 @@ set -e
|
||||
BUILD_TELEGRAM_VERSION="1"
|
||||
|
||||
MACOS_VERSION="10.15"
|
||||
XCODE_VERSION="11.5"
|
||||
XCODE_VERSION="12.0.1"
|
||||
GUEST_SHELL="bash"
|
||||
|
||||
VM_BASE_NAME="macos$(echo $MACOS_VERSION | sed -e 's/\.'/_/g)_Xcode$(echo $XCODE_VERSION | sed -e 's/\.'/_/g)"
|
||||
|
@ -14,7 +14,7 @@ public func legacySuggestionContext(context: AccountContext, peerId: PeerId, cha
|
||||
suggestionContext.userListSignal = { query in
|
||||
return SSignal { subscriber in
|
||||
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()
|
||||
for peer in peers {
|
||||
let user = TGUser()
|
||||
|
@ -180,7 +180,6 @@
|
||||
- (void)cancel
|
||||
{
|
||||
[self cleanup];
|
||||
[self fail];
|
||||
}
|
||||
|
||||
- (void)complete {
|
||||
|
@ -109,9 +109,9 @@ static NSDictionary *selectPublicKey(NSArray *fingerprints, NSArray<NSDictionary
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -132,7 +132,6 @@ typedef enum {
|
||||
id<EncryptionProvider> _encryptionProvider;
|
||||
|
||||
bool _tempAuth;
|
||||
MTSessionInfo *_sessionInfo;
|
||||
|
||||
MTDatacenterAuthStage _stage;
|
||||
int64_t _currentStageMessageId;
|
||||
@ -165,7 +164,6 @@ typedef enum {
|
||||
{
|
||||
_encryptionProvider = context.encryptionProvider;
|
||||
_tempAuth = tempAuth;
|
||||
_sessionInfo = [[MTSessionInfo alloc] initWithRandomSessionIdAndContext:context];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@ -267,7 +265,7 @@ typedef enum {
|
||||
[reqDhBuffer appendInt64:_dhPublicKeyFingerprint];
|
||||
[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];
|
||||
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);
|
||||
[dataWithHash appendBytes:&random length:1];
|
||||
}
|
||||
#if DEBUG
|
||||
assert(dataWithHash.length == 255);
|
||||
#endif
|
||||
|
||||
NSData *encryptedData = MTRsaEncrypt(_encryptionProvider, [publicKey objectForKey:@"key"], dataWithHash);
|
||||
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)
|
||||
{
|
||||
@ -447,8 +443,11 @@ typedef enum {
|
||||
arc4random_buf(&random, 1);
|
||||
[dataWithHash appendBytes:&random length:1];
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
{
|
||||
NSMutableData *newEncryptedData = [[NSMutableData alloc] init];
|
||||
|
@ -292,12 +292,13 @@ NSData *MTAesDecrypt(NSData *data, NSData *key, NSData *iv)
|
||||
NSData *MTRsaEncrypt(id<EncryptionProvider> provider, NSString *publicKey, NSData *data)
|
||||
{
|
||||
#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) {
|
||||
uint8_t zero = 0;
|
||||
[updatedData replaceBytesInRange:NSMakeRange(0, 0) withBytes:&zero length:1];
|
||||
}
|
||||
return [MTRsa encryptData:updatedData publicKey:publicKey];
|
||||
return [MTRsa encryptData:updatedData publicKey:publicKey];*/
|
||||
#else
|
||||
return [provider macosRSAEncrypt:publicKey data:data];
|
||||
#endif
|
||||
|
@ -1726,8 +1726,13 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64;
|
||||
{
|
||||
[[MTProto managerQueue] dispatchOnQueue:^
|
||||
{
|
||||
if (transport != _transport || completion == nil)
|
||||
if (transport != _transport || completion == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_useUnauthorizedMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
MTDatacenterAuthKey *authKey = [self getAuthKeyForCurrentScheme:scheme createIfNeeded:false authInfoSelector:nil];
|
||||
if (authKey == nil) {
|
||||
@ -2038,11 +2043,18 @@ static NSString *dumpHexString(NSData *data, int maxLength) {
|
||||
- (void)handleMissingKey:(MTTransportScheme *)scheme {
|
||||
NSAssert([[MTProto managerQueue] isCurrentQueue], @"invalid queue");
|
||||
|
||||
if (_useUnauthorizedMode) {
|
||||
if (MTLogEnabled()) {
|
||||
MTLog(@"[MTProto#%p@%p don't handleMissingKey when useUnauthorizedMode]", self, _context);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
MTDatacenterAuthInfoSelector authInfoSelector;
|
||||
[self getAuthKeyForCurrentScheme:scheme createIfNeeded:false authInfoSelector:&authInfoSelector];
|
||||
|
||||
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) {
|
||||
@ -2618,7 +2630,7 @@ static NSString *dumpHexString(NSData *data, int maxLength) {
|
||||
{
|
||||
[_context setGlobalTimeDifference:timeDifference];
|
||||
|
||||
if (saltList != nil)
|
||||
if (!_useUnauthorizedMode && saltList != nil)
|
||||
{
|
||||
if (_useExplicitAuthKey) {
|
||||
if (_validAuthInfo != nil && _validAuthInfo.selector == authInfoSelector) {
|
||||
|
@ -560,9 +560,15 @@ public func channelDiscussionGroupSetupController(context: AccountContext, peerI
|
||||
var isEmptyState = false
|
||||
var displayGroupList = false
|
||||
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 cachedData.linkedDiscussionPeerId == nil {
|
||||
if isEmpty {
|
||||
if groups == nil {
|
||||
isEmptyState = true
|
||||
} else {
|
||||
@ -570,13 +576,13 @@ public func channelDiscussionGroupSetupController(context: AccountContext, peerI
|
||||
}
|
||||
}
|
||||
}
|
||||
if let wasEmpty = wasEmpty, wasEmpty != isEmpty {
|
||||
crossfade = true
|
||||
}
|
||||
wasEmpty = isEmpty
|
||||
} else {
|
||||
isEmptyState = true
|
||||
}
|
||||
if let wasEmpty = wasEmpty, wasEmpty != isEmptyState {
|
||||
crossfade = true
|
||||
}
|
||||
wasEmpty = isEmptyState
|
||||
|
||||
var emptyStateItem: ItemListControllerEmptyStateItem?
|
||||
if isEmptyState {
|
||||
|
@ -5,20 +5,23 @@ import SyncCore
|
||||
import SwiftSignalKit
|
||||
import AccountContext
|
||||
|
||||
public func searchPeerMembers(context: AccountContext, peerId: PeerId, chatLocation: ChatLocation, query: String) -> Signal<[Peer], NoError> {
|
||||
if case .replyThread = chatLocation {
|
||||
return .single([])
|
||||
} else if peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
public enum SearchPeerMembersScope {
|
||||
case memberSuggestion
|
||||
case mention
|
||||
}
|
||||
|
||||
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 transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData
|
||||
}
|
||||
|> mapToSignal { cachedData -> Signal<[Peer], NoError> in
|
||||
if let cachedData = cachedData, let memberCount = cachedData.participantsSummary.memberCount, memberCount <= 64 {
|
||||
|> mapToSignal { cachedData -> Signal<([Peer], Bool), NoError> in
|
||||
if case .peer = chatLocation, let cachedData = cachedData, let memberCount = cachedData.participantsSummary.memberCount, memberCount <= 64 {
|
||||
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
|
||||
if case .ready = state.loadingState {
|
||||
let normalizedQuery = query.lowercased()
|
||||
subscriber.putNext(state.list.compactMap { participant -> Peer? in
|
||||
subscriber.putNext((state.list.compactMap { participant -> Peer? in
|
||||
if participant.peer.isDeleted {
|
||||
return nil
|
||||
}
|
||||
@ -37,7 +40,7 @@ public func searchPeerMembers(context: AccountContext, peerId: PeerId, chatLocat
|
||||
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}, true))
|
||||
}
|
||||
})
|
||||
|
||||
@ -49,22 +52,70 @@ public func searchPeerMembers(context: AccountContext, peerId: PeerId, chatLocat
|
||||
}
|
||||
|
||||
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
|
||||
if case .ready = state.loadingState {
|
||||
subscriber.putNext(state.list.compactMap { participant in
|
||||
if participant.peer.isDeleted {
|
||||
return nil
|
||||
}
|
||||
return participant.peer
|
||||
})
|
||||
switch chatLocation {
|
||||
case let .peer(peerId):
|
||||
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 {
|
||||
subscriber.putNext((state.list.compactMap { participant in
|
||||
if participant.peer.isDeleted {
|
||||
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())
|
||||
}
|
||||
|> 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 {
|
||||
return searchGroupMembers(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, query: query)
|
||||
}
|
||||
|
@ -4,21 +4,27 @@ public struct NetworkSettings: PreferencesEntry, Equatable {
|
||||
public var reducedBackupDiscoveryTimeout: Bool
|
||||
public var applicationUpdateUrlPrefix: String?
|
||||
public var backupHostOverride: String?
|
||||
public var defaultEnableTempKeys: Bool
|
||||
public var userEnableTempKeys: Bool?
|
||||
|
||||
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.applicationUpdateUrlPrefix = applicationUpdateUrlPrefix
|
||||
self.backupHostOverride = backupHostOverride
|
||||
self.defaultEnableTempKeys = defaultEnableTempKeys
|
||||
self.userEnableTempKeys = userEnableTempKeys
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.reducedBackupDiscoveryTimeout = decoder.decodeInt32ForKey("reducedBackupDiscoveryTimeout", orElse: 0) != 0
|
||||
self.applicationUpdateUrlPrefix = decoder.decodeOptionalStringForKey("applicationUpdateUrlPrefix")
|
||||
self.backupHostOverride = decoder.decodeOptionalStringForKey("backupHostOverride")
|
||||
self.defaultEnableTempKeys = decoder.decodeBoolForKey("defaultEnableTempKeys", orElse: false)
|
||||
self.userEnableTempKeys = decoder.decodeOptionalBoolForKey("userEnableTempKeys")
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
@ -33,6 +39,12 @@ public struct NetworkSettings: PreferencesEntry, Equatable {
|
||||
} else {
|
||||
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 {
|
||||
|
@ -322,6 +322,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[106343499] = { return Api.ChannelParticipantsFilter.parse_channelParticipantsSearch($0) }
|
||||
dict[-1548400251] = { return Api.ChannelParticipantsFilter.parse_channelParticipantsKicked($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[-981018084] = { return Api.WebPage.parse_webPagePending($0) }
|
||||
dict[-392411726] = { return Api.WebPage.parse_webPage($0) }
|
||||
|
@ -9991,6 +9991,7 @@ public extension Api {
|
||||
case channelParticipantsSearch(q: String)
|
||||
case channelParticipantsKicked(q: String)
|
||||
case channelParticipantsContacts(q: String)
|
||||
case channelParticipantsMentions(flags: Int32, q: String?, topMsgId: Int32?)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
@ -10036,6 +10037,14 @@ public extension Api {
|
||||
}
|
||||
serializeString(q, buffer: buffer, boxed: false)
|
||||
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)])
|
||||
case .channelParticipantsContacts(let 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
|
||||
}
|
||||
}
|
||||
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 {
|
||||
|
@ -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()
|
||||
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)
|
||||
peer.serialize(buffer, true)
|
||||
serializeString(q, buffer: buffer, boxed: false)
|
||||
@ -3763,23 +3780,6 @@ public extension Api {
|
||||
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 static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
|
@ -18,6 +18,7 @@ public enum ChannelMembersCategory {
|
||||
case bots(ChannelMembersCategoryFilter)
|
||||
case restricted(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> {
|
||||
@ -32,6 +33,24 @@ public func channelMembers(postbox: Postbox, network: Network, accountPeerId: Pe
|
||||
case let .search(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:
|
||||
apiFilter = .channelParticipantsAdmins
|
||||
case let .contacts(filter):
|
||||
|
@ -35,10 +35,14 @@ func managedConfigurationUpdates(accountManager: AccountManager, postbox: Postbo
|
||||
}
|
||||
|
||||
let blockedMode = (config.flags & 8) != 0
|
||||
|
||||
let defaultEnableTempKeys = (config.flags & (1 << 13)) != 0
|
||||
|
||||
updateNetworkSettingsInteractively(transaction: transaction, network: network, { settings in
|
||||
var settings = settings
|
||||
settings.reducedBackupDiscoveryTimeout = blockedMode
|
||||
settings.applicationUpdateUrlPrefix = config.autoupdateUrlPrefix
|
||||
settings.defaultEnableTempKeys = defaultEnableTempKeys
|
||||
return settings
|
||||
})
|
||||
|
||||
|
@ -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?
|
||||
sharedContexts.with { store in
|
||||
let key = SharedContextStore.Key(accountId: accountId)
|
||||
@ -483,7 +494,7 @@ func initializedNetwork(accountId: AccountRecordId, arguments: NetworkInitializa
|
||||
context = current
|
||||
context.updateApiEnvironment({ _ in return apiEnvironment})
|
||||
} 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
|
||||
}
|
||||
contextValue = context
|
||||
|
@ -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 updatedSettings: NetworkSettings?
|
||||
transaction.updatePreferencesEntry(key: PreferencesKeys.networkSettings, { current in
|
||||
@ -33,7 +33,7 @@ public func updateNetworkSettingsInteractively(transaction: Transaction, network
|
||||
return updated
|
||||
})
|
||||
|
||||
if updateNetwork, let updatedSettings = updatedSettings {
|
||||
if let network = network, updateNetwork, let updatedSettings = updatedSettings {
|
||||
network.context.updateApiEnvironment { current in
|
||||
return current?.withUpdatedNetworkSettings(updatedSettings.mtNetworkSettings)
|
||||
}
|
||||
|
@ -221,11 +221,11 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q
|
||||
guard let inputPeer = apiInputPeer(peer) else {
|
||||
return .single((nil, nil))
|
||||
}
|
||||
var fromInputUser: Api.InputUser? = nil
|
||||
var fromInputPeer: Api.InputPeer? = nil
|
||||
var flags: Int32 = 0
|
||||
if let from = values.from {
|
||||
fromInputUser = apiInputUser(from)
|
||||
if let _ = fromInputUser {
|
||||
fromInputPeer = apiInputPeer(from)
|
||||
if let _ = fromInputPeer {
|
||||
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 {
|
||||
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 {
|
||||
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
|
||||
|> map(Optional.init)
|
||||
@ -257,7 +257,7 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q
|
||||
additionalPeerMessages = .single(nil)
|
||||
} else if mainCompleted || !hasAdditional {
|
||||
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)
|
||||
|> `catch` { _ -> Signal<Api.messages.Messages?, NoError> in
|
||||
return .single(nil)
|
||||
|
@ -58,7 +58,7 @@ class UpdateMessageService: NSObject, MTMessageService {
|
||||
self.putNext(groups)
|
||||
}
|
||||
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 groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil)
|
||||
if groups.count != 0 {
|
||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -3277,13 +3277,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
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 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()
|
||||
|> 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 {
|
||||
let (cachedData, messages) = cachedDataAndMessages
|
||||
|
||||
@ -3422,22 +3479,31 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
} else if let _ = cachedData as? CachedSecretChatData {
|
||||
}
|
||||
|
||||
var pinnedMessage: ChatPinnedMessage?
|
||||
if case let .replyThread(replyThreadMessage) = strongSelf.chatLocation {
|
||||
if isTopReplyThreadMessageShown {
|
||||
pinnedMessageId = nil
|
||||
} else {
|
||||
pinnedMessageId = replyThreadMessage.messageId
|
||||
pinnedMessageId = replyThreadMessage.effectiveTopId
|
||||
}
|
||||
}
|
||||
|
||||
var pinnedMessage: Message?
|
||||
if let pinnedMessageId = pinnedMessageId {
|
||||
pinnedMessage = messages?[pinnedMessageId]
|
||||
if let pinnedMessageId = pinnedMessageId {
|
||||
if let message = messages?[pinnedMessageId] {
|
||||
pinnedMessage = ChatPinnedMessage(message: message, isLatest: true)
|
||||
}
|
||||
}
|
||||
} 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
|
||||
if let current = strongSelf.presentationInterfaceState.pinnedMessage, let updated = pinnedMessage {
|
||||
if current.id != updated.id || current.stableVersion != updated.stableVersion {
|
||||
if current != updated {
|
||||
pinnedMessageUpdated = true
|
||||
}
|
||||
} 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
|
||||
|
||||
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
|
||||
return state
|
||||
.updatedPinnedMessageId(pinnedMessageId)
|
||||
@ -4743,7 +4809,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
})]), in: .window(.root))
|
||||
}
|
||||
} else {
|
||||
if let pinnedMessageId = strongSelf.presentationInterfaceState.pinnedMessage?.id {
|
||||
if let pinnedMessageId = strongSelf.presentationInterfaceState.pinnedMessage?.message.id {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||
return $0.updatedInterfaceState({ $0.withUpdatedMessageActionsState({ value in
|
||||
var value = value
|
||||
@ -4795,7 +4861,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||
return $0.updatedInterfaceState({ $0.withUpdatedMessageActionsState({ value in
|
||||
var value = value
|
||||
value.closedPinnedMessageId = pinnedMessage.id
|
||||
value.closedPinnedMessageId = pinnedMessage.message.id
|
||||
return value
|
||||
}) })
|
||||
})
|
||||
|
@ -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
|
||||
let context = self.context
|
||||
let baseNavigationController = self.controller?.navigationController as? NavigationController
|
||||
|
@ -24,6 +24,11 @@ extension ChatReplyThreadMessage {
|
||||
}
|
||||
}
|
||||
|
||||
struct ChatTopVisibleMessage: Equatable {
|
||||
var id: MessageId
|
||||
var isLast: Bool
|
||||
}
|
||||
|
||||
private class ChatHistoryListSelectionRecognizer: UIPanGestureRecognizer {
|
||||
private let selectionGestureActivationThreshold: CGFloat = 5.0
|
||||
|
||||
@ -557,6 +562,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
private var loadedMessagesFromCachedDataDisposable: Disposable?
|
||||
|
||||
let isTopReplyThreadMessageShown = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
let topVisibleMessage = ValuePromise<ChatTopVisibleMessage?>(nil, ignoreRepeated: true)
|
||||
|
||||
private let clientId: Atomic<Int32>
|
||||
|
||||
@ -1151,6 +1157,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
private func processDisplayedItemRangeChanged(displayedRange: ListViewDisplayedItemRange, transactionState: ChatHistoryTransactionOpaqueState) {
|
||||
let historyView = transactionState.historyView
|
||||
var isTopReplyThreadMessageShownValue = false
|
||||
var topVisibleMessage: ChatTopVisibleMessage?
|
||||
if let visible = displayedRange.visibleRange {
|
||||
let indexRange = (historyView.filteredEntries.count - 1 - visible.lastIndex, historyView.filteredEntries.count - 1 - visible.firstIndex)
|
||||
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 {
|
||||
isTopReplyThreadMessageShownValue = true
|
||||
}
|
||||
topVisibleMessage = ChatTopVisibleMessage(id: message.id, isLast: i == historyView.filteredEntries.count - 1)
|
||||
case let .MessageGroupEntry(_, messages, _):
|
||||
for (message, _, _, _) in messages {
|
||||
var hasUnconsumedMention = false
|
||||
@ -1255,6 +1263,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
if case let .replyThread(replyThreadMessage) = self.chatLocation, replyThreadMessage.effectiveTopId == message.id {
|
||||
isTopReplyThreadMessageShownValue = true
|
||||
}
|
||||
topVisibleMessage = ChatTopVisibleMessage(id: message.id, isLast: i == historyView.filteredEntries.count - 1)
|
||||
}
|
||||
default:
|
||||
break
|
||||
@ -1371,6 +1380,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
}
|
||||
}
|
||||
self.isTopReplyThreadMessageShown.set(isTopReplyThreadMessageShownValue)
|
||||
self.topVisibleMessage.set(topVisibleMessage)
|
||||
|
||||
if let loaded = displayedRange.loadedRange, let firstEntry = historyView.filteredEntries.first, let lastEntry = historyView.filteredEntries.last {
|
||||
if loaded.firstIndex < 5 && historyView.originalView.laterId != nil {
|
||||
|
@ -643,7 +643,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
|
||||
}
|
||||
|
||||
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
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Pin"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
|
@ -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 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
|
||||
let filteredInlineBots = inlineBots.sorted(by: { $0.1 > $1.1 }).filter { peer, rating in
|
||||
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
|
||||
let filteredPeers = peers
|
||||
var sortedPeers: [Peer] = []
|
||||
|
@ -19,7 +19,7 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat
|
||||
loop: for context in chatPresentationInterfaceState.titlePanelContexts.reversed() {
|
||||
switch context {
|
||||
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
|
||||
break loop
|
||||
}
|
||||
|
@ -13,10 +13,18 @@ import StickerResources
|
||||
import PhotoResources
|
||||
import TelegramStringFormatting
|
||||
|
||||
private enum PinnedMessageAnimation {
|
||||
case slideToTop
|
||||
case slideToBottom
|
||||
}
|
||||
|
||||
final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
private let context: AccountContext
|
||||
private let tapButton: HighlightTrackingButtonNode
|
||||
private let closeButton: HighlightableButtonNode
|
||||
|
||||
private let clippingContainer: ASDisplayNode
|
||||
private let contentContainer: ASDisplayNode
|
||||
private let lineNode: ASImageNode
|
||||
private let titleNode: TextNode
|
||||
private let textNode: TextNode
|
||||
@ -25,7 +33,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
private let separatorNode: ASDisplayNode
|
||||
|
||||
private var currentLayout: (CGFloat, CGFloat, CGFloat)?
|
||||
private var currentMessage: Message?
|
||||
private var currentMessage: ChatPinnedMessage?
|
||||
private var previousMediaReference: AnyMediaReference?
|
||||
|
||||
private var isReplyThread: Bool = false
|
||||
@ -46,6 +54,11 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
self.separatorNode = ASDisplayNode()
|
||||
self.separatorNode.isLayerBacked = true
|
||||
|
||||
self.clippingContainer = ASDisplayNode()
|
||||
self.clippingContainer.clipsToBounds = true
|
||||
|
||||
self.contentContainer = ASDisplayNode()
|
||||
|
||||
self.lineNode = ASImageNode()
|
||||
self.lineNode.displayWithoutProcessing = true
|
||||
self.lineNode.displaysAsynchronously = false
|
||||
@ -87,10 +100,12 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
self.closeButton.addTarget(self, action: #selector(self.closePressed), forControlEvents: [.touchUpInside])
|
||||
self.addSubnode(self.closeButton)
|
||||
|
||||
self.addSubnode(self.clippingContainer)
|
||||
self.clippingContainer.addSubnode(self.contentContainer)
|
||||
self.addSubnode(self.lineNode)
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.imageNode)
|
||||
self.contentContainer.addSubnode(self.titleNode)
|
||||
self.contentContainer.addSubnode(self.textNode)
|
||||
self.contentContainer.addSubnode(self.imageNode)
|
||||
|
||||
self.tapButton.addTarget(self, action: #selector(self.tapped), forControlEvents: [.touchUpInside])
|
||||
self.addSubnode(self.tapButton)
|
||||
@ -128,10 +143,18 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
self.closeButton.isHidden = isReplyThread
|
||||
|
||||
var messageUpdated = false
|
||||
var messageUpdatedAnimation: PinnedMessageAnimation?
|
||||
if let currentMessage = self.currentMessage, let pinnedMessage = interfaceState.pinnedMessage {
|
||||
if currentMessage.id != pinnedMessage.id || currentMessage.stableVersion != pinnedMessage.stableVersion {
|
||||
if currentMessage != pinnedMessage {
|
||||
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) {
|
||||
messageUpdated = true
|
||||
}
|
||||
@ -141,7 +164,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
self.currentMessage = interfaceState.pinnedMessage
|
||||
|
||||
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)))
|
||||
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 {
|
||||
self.currentLayout = (width, leftInset, rightInset)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 makeTextLayout = TextNode.asyncLayout(self.textNode)
|
||||
let imageNodeLayout = self.imageNode.asyncLayout()
|
||||
@ -191,7 +239,12 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
var updatedMediaReference: AnyMediaReference?
|
||||
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 {
|
||||
if let image = media as? TelegramMediaImage {
|
||||
@ -300,13 +353,8 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
if let interfaceInteraction = self.interfaceInteraction, let message = self.currentMessage {
|
||||
if self.isReplyThread {
|
||||
interfaceInteraction.scrollToTop()
|
||||
/*if let sourceReference = message.sourceReference {
|
||||
interfaceInteraction.navigateToMessage(sourceReference.messageId, true)
|
||||
} else {
|
||||
interfaceInteraction.navigateToMessage(message.id, false)
|
||||
}*/
|
||||
} else {
|
||||
interfaceInteraction.navigateToMessage(message.id, false)
|
||||
interfaceInteraction.navigateToMessage(message.message.id, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -257,6 +257,32 @@ struct ChatSlowmodeState: Equatable {
|
||||
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 {
|
||||
let interfaceState: ChatInterfaceState
|
||||
let chatLocation: ChatLocation
|
||||
@ -274,7 +300,7 @@ final class ChatPresentationInterfaceState: Equatable {
|
||||
let titlePanelContexts: [ChatTitlePanelContext]
|
||||
let keyboardButtonsMessage: Message?
|
||||
let pinnedMessageId: MessageId?
|
||||
let pinnedMessage: Message?
|
||||
let pinnedMessage: ChatPinnedMessage?
|
||||
let peerIsBlocked: Bool
|
||||
let peerIsMuted: Bool
|
||||
let peerDiscussionId: PeerId?
|
||||
@ -348,7 +374,7 @@ final class ChatPresentationInterfaceState: Equatable {
|
||||
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.chatLocation = chatLocation
|
||||
self.renderedPeer = renderedPeer
|
||||
@ -447,14 +473,7 @@ final class ChatPresentationInterfaceState: Equatable {
|
||||
if lhs.pinnedMessageId != rhs.pinnedMessageId {
|
||||
return false
|
||||
}
|
||||
if let lhsMessage = lhs.pinnedMessage, let rhsMessage = rhs.pinnedMessage {
|
||||
if lhsMessage.id != rhsMessage.id {
|
||||
return false
|
||||
}
|
||||
if lhsMessage.stableVersion != rhsMessage.stableVersion {
|
||||
return false
|
||||
}
|
||||
} else if (lhs.pinnedMessage != nil) != (rhs.pinnedMessage != nil) {
|
||||
if lhs.pinnedMessage != rhs.pinnedMessage {
|
||||
return false
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -189,9 +189,6 @@ final class ChatSearchInputPanelNode: ChatInputPanelNode {
|
||||
canSearchMembers = false
|
||||
}
|
||||
}
|
||||
if case .replyThread = interfaceState.chatLocation {
|
||||
canSearchMembers = false
|
||||
}
|
||||
self.membersButton.isHidden = (!(interfaceState.search?.query.isEmpty ?? true)) || self.displayActivity || !canSearchMembers
|
||||
|
||||
let resultsEnabled = (resultCount ?? 0) > 0
|
||||
|
@ -64,6 +64,7 @@ public struct ChannelMemberListState {
|
||||
enum ChannelMemberListCategory {
|
||||
case recent
|
||||
case recentSearch(String)
|
||||
case mentions(MessageId?, String?)
|
||||
case admins(String?)
|
||||
case contacts(String?)
|
||||
case bots(String?)
|
||||
@ -211,6 +212,12 @@ private final class ChannelMemberSingleCategoryListContext: ChannelMemberCategor
|
||||
requestCategory = .recent(.all)
|
||||
case let .recentSearch(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):
|
||||
requestCategory = .admins
|
||||
adminQuery = query
|
||||
@ -521,6 +528,8 @@ private final class ChannelMemberSingleCategoryListContext: ChannelMemberCategor
|
||||
}
|
||||
}
|
||||
}
|
||||
case .mentions:
|
||||
break
|
||||
}
|
||||
}
|
||||
if updatedList {
|
||||
@ -728,7 +737,7 @@ final class PeerChannelMemberCategoriesContext {
|
||||
emptyTimeout = 0.0
|
||||
}
|
||||
switch key {
|
||||
case .recent, .recentSearch, .admins, .contacts, .bots:
|
||||
case .recent, .recentSearch, .admins, .contacts, .bots, .mentions:
|
||||
let mappedCategory: ChannelMemberListCategory
|
||||
switch key {
|
||||
case .recent:
|
||||
@ -741,6 +750,8 @@ final class PeerChannelMemberCategoriesContext {
|
||||
mappedCategory = .contacts(query)
|
||||
case let .bots(query):
|
||||
mappedCategory = .bots(query)
|
||||
case let .mentions(threadId, query):
|
||||
mappedCategory = .mentions(threadId, query)
|
||||
default:
|
||||
mappedCategory = .recent
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import TelegramStringFormatting
|
||||
enum PeerChannelMemberContextKey: Equatable, Hashable {
|
||||
case recent
|
||||
case recentSearch(String)
|
||||
case mentions(threadId: MessageId?, query: String?)
|
||||
case admins(String?)
|
||||
case contacts(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)
|
||||
}
|
||||
|
||||
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?) {
|
||||
return self.getContext(postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: .admins(searchQuery), requestUpdate: true, updated: updated)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user