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
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)

View File

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

View File

@ -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";

View File

@ -1 +1 @@
11.5
12.0.1

View File

@ -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)"

View File

@ -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()

View File

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

View File

@ -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];

View File

@ -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

View File

@ -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) {

View File

@ -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 {

View File

@ -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)
}

View File

@ -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 {

View File

@ -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) }

View File

@ -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 {

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()
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>) {

View File

@ -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):

View File

@ -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
})

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?
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

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 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)
}

View File

@ -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)

View File

@ -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 {

View File

@ -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
}) })
})

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
let context = self.context
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 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 {

View File

@ -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

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 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] = []

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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)
}

View File

@ -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

View File

@ -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
}

View File

@ -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)
}