Invite Links Improvements

This commit is contained in:
Ilya Laktyushin 2021-02-10 18:21:59 +04:00
commit a7b81d68ae
1990 changed files with 487093 additions and 5876 deletions
.bazelrc.gitmodulesBUILDIntents.intentdefinition
Telegram
build-system/bazel-rules
submodules
AccountContext/Sources
CameraUI/Sources
ChatListFilterSettingsHeaderItem/Sources
ContextUI/Sources
DatePickerNode/Sources
FFMpegBinding/Sources
InviteLinksUI/Sources
ItemListUI/Sources
LegacyComponents
PublicHeaders/LegacyComponents
Sources
PeerInfoUI/Sources
Postbox/Sources
SSignalKit/SSignalKit/Source/SSignalKit
SegmentedControlNode/Sources
SettingsUI/Sources
SyncCore/Sources
TelegramApi/Sources
TelegramCallsUI/Sources
TelegramCore/Sources
TelegramNotices/Sources
TelegramPresentationData/Sources
TelegramStringFormatting/Sources
TelegramUI

@ -14,4 +14,6 @@ build --features=swift.enable_vfsoverlays
build --strategy=Genrule=standalone
build --spawn_strategy=standalone
build --strategy=SwiftCompile=standalone
build --strategy=SwiftCompile=standalone
build --define RULES_SWIFT_BUILD_DUMMY_WORKER=1

6
.gitmodules vendored

@ -17,12 +17,12 @@ url=https://github.com/ali-fareed/rules_swift.git
[submodule "build-system/tulsi"]
path = build-system/tulsi
url=https://github.com/bazelbuild/tulsi.git
[submodule "third-party/webrtc/webrtc-ios"]
path = third-party/webrtc/webrtc-ios
url=https://github.com/ali-fareed/webrtc-ios.git
[submodule "submodules/TgVoipWebrtc/tgcalls"]
path = submodules/TgVoipWebrtc/tgcalls
url=../tgcalls.git
[submodule "third-party/libvpx/libvpx"]
path = third-party/libvpx/libvpx
url = https://github.com/webmproject/libvpx.git
[submodule "third-party/webrtc/webrtc"]
path = third-party/webrtc/webrtc
url = https://github.com/ali-fareed/webrtc.git

10
BUILD Normal file

@ -0,0 +1,10 @@
load("@build_bazel_rules_apple//apple:resources.bzl",
"swift_intent_library",
)
swift_intent_library(
name = "GeneratedSources",
src = "Intents.intentdefinition",
visibility = ["//visibility:public"],
)

@ -75,66 +75,6 @@
<string>SelectFriends</string>
<key>INIntentParameters</key>
<array>
<dict>
<key>INIntentParameterConfigurable</key>
<true/>
<key>INIntentParameterDisplayName</key>
<string>Contents</string>
<key>INIntentParameterDisplayNameID</key>
<string>WAiyZm</string>
<key>INIntentParameterDisplayPriority</key>
<integer>1</integer>
<key>INIntentParameterEnumType</key>
<string>Contents</string>
<key>INIntentParameterEnumTypeNamespace</key>
<string>p74MWb</string>
<key>INIntentParameterMetadata</key>
<dict>
<key>INIntentParameterMetadataDefaultValue</key>
<string>recent</string>
</dict>
<key>INIntentParameterName</key>
<string>contents</string>
<key>INIntentParameterPromptDialogs</key>
<array>
<dict>
<key>INIntentParameterPromptDialogCustom</key>
<true/>
<key>INIntentParameterPromptDialogType</key>
<string>Configuration</string>
</dict>
<dict>
<key>INIntentParameterPromptDialogCustom</key>
<true/>
<key>INIntentParameterPromptDialogType</key>
<string>Primary</string>
</dict>
<dict>
<key>INIntentParameterPromptDialogCustom</key>
<true/>
<key>INIntentParameterPromptDialogFormatString</key>
<string>There are ${count} options matching ${contents}.</string>
<key>INIntentParameterPromptDialogFormatStringID</key>
<string>oSRWBb</string>
<key>INIntentParameterPromptDialogType</key>
<string>DisambiguationIntroduction</string>
</dict>
<dict>
<key>INIntentParameterPromptDialogCustom</key>
<true/>
<key>INIntentParameterPromptDialogFormatString</key>
<string>Just to confirm, you wanted ${contents}?</string>
<key>INIntentParameterPromptDialogFormatStringID</key>
<string>jvYJCG</string>
<key>INIntentParameterPromptDialogType</key>
<string>Confirmation</string>
</dict>
</array>
<key>INIntentParameterTag</key>
<integer>5</integer>
<key>INIntentParameterType</key>
<string>Integer</string>
</dict>
<dict>
<key>INIntentParameterArraySizes</key>
<array>
@ -146,7 +86,7 @@
</dict>
<dict>
<key>INIntentParameterArraySizeSize</key>
<integer>8</integer>
<integer>2</integer>
<key>INIntentParameterArraySizeSizeClass</key>
<string>Medium</string>
</dict>
@ -160,11 +100,11 @@
<key>INIntentParameterConfigurable</key>
<true/>
<key>INIntentParameterDisplayName</key>
<string>Chats</string>
<string> </string>
<key>INIntentParameterDisplayNameID</key>
<string>WIf4LD</string>
<key>INIntentParameterDisplayPriority</key>
<integer>2</integer>
<integer>1</integer>
<key>INIntentParameterFixedSizeArray</key>
<integer>1</integer>
<key>INIntentParameterName</key>
@ -178,6 +118,10 @@
<dict>
<key>INIntentParameterPromptDialogCustom</key>
<true/>
<key>INIntentParameterPromptDialogFormatString</key>
<string>Search</string>
<key>INIntentParameterPromptDialogFormatStringID</key>
<string>ORCbLf</string>
<key>INIntentParameterPromptDialogType</key>
<string>Configuration</string>
</dict>
@ -190,8 +134,6 @@
</array>
<key>INIntentParameterRelationship</key>
<dict>
<key>INIntentParameterRelationshipParentName</key>
<string>contents</string>
<key>INIntentParameterRelationshipPredicateName</key>
<string>EnumHasExactValue</string>
<key>INIntentParameterRelationshipPredicateValue</key>
@ -201,6 +143,8 @@
<true/>
<key>INIntentParameterSupportsMultipleValues</key>
<true/>
<key>INIntentParameterSupportsSearch</key>
<true/>
<key>INIntentParameterTag</key>
<integer>19</integer>
<key>INIntentParameterType</key>

@ -8,6 +8,10 @@ load("@build_bazel_rules_apple//apple:ios.bzl",
"ios_framework",
)
load("@build_bazel_rules_apple//apple:resources.bzl",
"swift_intent_library",
)
load("@build_bazel_rules_apple//apple:watchos.bzl",
"watchos_application",
"watchos_extension",
@ -1158,7 +1162,7 @@ plist_fragment(
)
swift_library(
name = "GeneratedSources",
name = "GeneratedSources1",
module_name = "GeneratedSources",
srcs = glob([
"Generated/**/*.swift",
@ -1166,6 +1170,12 @@ swift_library(
visibility = ["//visibility:public"],
)
'''swift_intent_library(
name = "GeneratedSources",
src = "SiriIntents/Intents.intentdefinition",
visibility = ["//visibility:public"],
)'''
swift_library(
name = "WidgetExtensionLib",
module_name = "WidgetExtensionLib",
@ -1173,7 +1183,7 @@ swift_library(
"WidgetKitWidget/**/*.swift",
]),
data = [
"SiriIntents/Intents.intentdefinition",
#"SiriIntents/Intents.intentdefinition",
],
deps = [
"//submodules/BuildConfig:BuildConfig",
@ -1185,7 +1195,7 @@ swift_library(
"//submodules/TelegramCore:TelegramCore",
"//submodules/SyncCore:SyncCore",
"//submodules/OpenSSLEncryptionProvider:OpenSSLEncryptionProvider",
":GeneratedSources",
"//:GeneratedSources",
],
)
@ -1267,7 +1277,7 @@ swift_library(
"SiriIntents/**/*.swift",
]),
data = [
"SiriIntents/Intents.intentdefinition",
#"SiriIntents/Intents.intentdefinition",
],
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
@ -1278,7 +1288,7 @@ swift_library(
"//submodules/BuildConfig:BuildConfig",
"//submodules/OpenSSLEncryptionProvider:OpenSSLEncryptionProvider",
"//submodules/AppLockState:AppLockState",
":GeneratedSources",
"//:GeneratedSources",
],
)
@ -1578,7 +1588,7 @@ ios_application(
":NotificationContentExtension",
":NotificationServiceExtension",
":IntentsExtension",
# ":WidgetExtension",
":WidgetExtension",
],
}),
watch_application = select({

@ -1,36 +0,0 @@
//
// Contents.swift
//
// This file was automatically generated and should not be edited.
//
#if canImport(Intents)
import Intents
@available(iOS 12.0, macOS 10.16, watchOS 5.0, *) @available(tvOS, unavailable)
@objc public enum Contents: Int {
case `unknown` = 0
case `recent` = 1
case `custom` = 2
}
@available(iOS 13.0, macOS 10.16, watchOS 6.0, *) @available(tvOS, unavailable)
@objc(ContentsResolutionResult)
public class ContentsResolutionResult: INEnumResolutionResult {
// This resolution result is for when the app extension wants to tell Siri to proceed, with a given Contents. The resolvedValue can be different than the original Contents. This allows app extensions to apply business logic constraints.
// Use notRequired() to continue with a 'nil' value.
@objc(successWithResolvedContents:)
public class func success(with resolvedValue: Contents) -> Self {
return __success(withResolvedValue: resolvedValue.rawValue)
}
// This resolution result is to ask Siri to confirm if this is the value with which the user wants to continue.
@objc(confirmationRequiredWithContentsToConfirm:)
public class func confirmationRequired(with valueToConfirm: Contents) -> Self {
return __confirmationRequiredWithValue(toConfirm: valueToConfirm.rawValue)
}
}
#endif

@ -1,60 +0,0 @@
//
// Friend.swift
//
// This file was automatically generated and should not be edited.
//
#if canImport(Intents)
import Intents
@available(iOS 12.0, macOS 10.16, watchOS 5.0, *) @available(tvOS, unavailable)
@objc(Friend)
public class Friend: INObject {
@available(iOS 13.0, macOS 10.16, watchOS 6.0, *)
@NSManaged public var subtitle: String?
}
@available(iOS 13.0, macOS 10.16, watchOS 6.0, *) @available(tvOS, unavailable)
@objc(FriendResolutionResult)
public class FriendResolutionResult: INObjectResolutionResult {
// This resolution result is for when the app extension wants to tell Siri to proceed, with a given Friend. The resolvedValue can be different than the original Friend. This allows app extensions to apply business logic constraints.
// Use notRequired() to continue with a 'nil' value.
@objc(successWithResolvedFriend:)
public class func success(with resolvedObject: Friend) -> Self {
return super.success(with: resolvedObject) as! Self
}
// This resolution result is to ask Siri to disambiguate between the provided Friend.
@objc(disambiguationWithFriendsToDisambiguate:)
public class func disambiguation(with objectsToDisambiguate: [Friend]) -> Self {
return super.disambiguation(with: objectsToDisambiguate) as! Self
}
// This resolution result is to ask Siri to confirm if this is the value with which the user wants to continue.
@objc(confirmationRequiredWithFriendToConfirm:)
public class func confirmationRequired(with objectToConfirm: Friend?) -> Self {
return super.confirmationRequired(with: objectToConfirm) as! Self
}
@available(*, unavailable)
override public class func success(with resolvedObject: INObject) -> Self {
fatalError()
}
@available(*, unavailable)
override public class func disambiguation(with objectsToDisambiguate: [INObject]) -> Self {
fatalError()
}
@available(*, unavailable)
override public class func confirmationRequired(with objectToConfirm: INObject?) -> Self {
fatalError()
}
}
#endif

@ -1,120 +0,0 @@
//
// SelectFriendsIntent.swift
//
// This file was automatically generated and should not be edited.
//
#if canImport(Intents)
import Intents
@available(iOS 12.0, macOS 10.16, watchOS 5.0, *) @available(tvOS, unavailable)
@objc(SelectFriendsIntent)
public class SelectFriendsIntent: INIntent {
@NSManaged public var contents: Contents
@NSManaged public var friends: [Friend]?
}
/*!
@abstract Protocol to declare support for handling a SelectFriendsIntent. By implementing this protocol, a class can provide logic for resolving, confirming and handling the intent.
@discussion The minimum requirement for an implementing class is that it should be able to handle the intent. The confirmation method is optional. The handling method is always called last, after confirming the intent.
*/
@available(iOS 12.0, macOS 10.16, watchOS 5.0, *) @available(tvOS, unavailable)
@objc(SelectFriendsIntentHandling)
public protocol SelectFriendsIntentHandling: NSObjectProtocol {
/*!
@abstract Dynamic options methods - provide options for the parameter at runtime
@discussion Called to query dynamic options for the parameter and this intent in its current form.
@param intent The input intent
@param completion The response block contains options for the parameter
*/
@available(iOS 14.0, macOS 10.16, watchOS 7.0, *)
@objc(provideFriendsOptionsCollectionForSelectFriends:withCompletion:)
func provideFriendsOptionsCollection(for intent: SelectFriendsIntent, with completion: @escaping (INObjectCollection<Friend>?, Error?) -> Swift.Void)
/*!
@abstract Confirmation method - Validate that this intent is ready for the next step (i.e. handling)
@discussion Called prior to asking the app to handle the intent. The app should return a response object that contains additional information about the intent, which may be relevant for the system to show the user prior to handling. If unimplemented, the system will assume the intent is valid, and will assume there is no additional information relevant to this intent.
@param intent The input intent
@param completion The response block contains a SelectFriendsIntentResponse containing additional details about the intent that may be relevant for the system to show the user prior to handling.
@see SelectFriendsIntentResponse
*/
@objc(confirmSelectFriends:completion:)
optional func confirm(intent: SelectFriendsIntent, completion: @escaping (SelectFriendsIntentResponse) -> Swift.Void)
/*!
@abstract Handling method - Execute the task represented by the SelectFriendsIntent that's passed in
@discussion Called to actually execute the intent. The app must return a response for this intent.
@param intent The input intent
@param completion The response handling block takes a SelectFriendsIntentResponse containing the details of the result of having executed the intent
@see SelectFriendsIntentResponse
*/
@objc(handleSelectFriends:completion:)
optional func handle(intent: SelectFriendsIntent, completion: @escaping (SelectFriendsIntentResponse) -> Swift.Void)
/*!
@abstract Default values for parameters with dynamic options
@discussion Called to query the parameter default value.
*/
@available(iOS 14.0, macOS 10.16, watchOS 7.0, *)
@objc(defaultFriendsForSelectFriends:)
optional func defaultFriends(for intent: SelectFriendsIntent) -> [Friend]?
/*!
@abstract Deprecated dynamic options methods.
*/
@available(iOS, introduced: 13.0, deprecated: 14.0, message: "")
@available(watchOS, introduced: 6.0, deprecated: 7.0, message: "")
@objc(provideFriendsOptionsForSelectFriends:withCompletion:)
optional func provideFriendsOptions(for intent: SelectFriendsIntent, with completion: @escaping ([Friend]?, Error?) -> Swift.Void)
}
/*!
@abstract Constants indicating the state of the response.
*/
@available(iOS 12.0, macOS 10.16, watchOS 5.0, *) @available(tvOS, unavailable)
@objc public enum SelectFriendsIntentResponseCode: Int {
case unspecified = 0
case ready
case continueInApp
case inProgress
case success
case failure
case failureRequiringAppLaunch
}
@available(iOS 12.0, macOS 10.16, watchOS 5.0, *) @available(tvOS, unavailable)
@objc(SelectFriendsIntentResponse)
public class SelectFriendsIntentResponse: INIntentResponse {
/*!
@abstract The response code indicating your success or failure in confirming or handling the intent.
*/
@objc public fileprivate(set) var code: SelectFriendsIntentResponseCode = .unspecified
/*!
@abstract Initializes the response object with the specified code and user activity object.
@discussion The app extension has the option of capturing its private state as an NSUserActivity and returning it as the 'currentActivity'. If the app is launched, an NSUserActivity will be passed in with the private state. The NSUserActivity may also be used to query the app's UI extension (if provided) for a view controller representing the current intent handling state. In the case of app launch, the NSUserActivity will have its activityType set to the name of the intent. This intent object will also be available in the NSUserActivity.interaction property.
@param code The response code indicating your success or failure in confirming or handling the intent.
@param userActivity The user activity object to use when launching your app. Provide an object if you want to add information that is specific to your app. If you specify nil, the system automatically creates a user activity object for you, sets its type to the class name of the intent being handled, and fills it with an INInteraction object containing the intent and your response.
*/
@objc(initWithCode:userActivity:)
public convenience init(code: SelectFriendsIntentResponseCode, userActivity: NSUserActivity?) {
self.init()
self.code = code
self.userActivity = userActivity
}
}
#endif

@ -56,10 +56,15 @@ enum IntentHandlingError {
@objc(IntentHandler)
class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessagesIntentHandling, INSetMessageAttributeIntentHandling, INStartAudioCallIntentHandling, INSearchCallHistoryIntentHandling, SelectFriendsIntentHandling {
private let accountPromise = Promise<Account?>()
private let allAccounts = Promise<[(AccountRecordId, PeerId)]>()
private let resolvePersonsDisposable = MetaDisposable()
private let actionDisposable = MetaDisposable()
private let searchDisposable = MetaDisposable()
private var rootPath: String?
private var accountManager: AccountManager?
private var encryptionParameters: ValueBoxEncryptionParameters?
private var appGroupUrl: URL?
override init() {
@ -88,6 +93,8 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
let rootPath = rootPathForBasePath(appGroupUrl.path)
performAppGroupUpgrades(appGroupPath: appGroupUrl.path, rootPath: rootPath)
self.rootPath = rootPath
TempBox.initializeShared(basePath: rootPath, processType: "siri", launchSpecificId: arc4random64())
let logsPath = rootPath + "/siri-logs"
@ -97,16 +104,61 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown"
initializeAccountManagement()
let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata")
self.accountManager = accountManager
let deviceSpecificEncryptionParameters = BuildConfig.deviceSpecificEncryptionParameters(rootPath, baseAppBundleId: baseAppBundleId)
let encryptionParameters = ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: deviceSpecificEncryptionParameters.key)!, salt: ValueBoxEncryptionParameters.Salt(data: deviceSpecificEncryptionParameters.salt)!)
self.encryptionParameters = encryptionParameters
self.allAccounts.set(accountManager.accountRecords()
|> take(1)
|> map { view -> [(AccountRecordId, PeerId)] in
var result: [(AccountRecordId, Int, PeerId)] = []
for record in view.records {
let isLoggedOut = record.attributes.contains(where: { attribute in
return attribute is LoggedOutAccountAttribute
})
if isLoggedOut {
continue
}
/*let isTestingEnvironment = record.attributes.contains(where: { attribute in
if let attribute = attribute as? AccountEnvironmentAttribute, case .test = attribute.environment {
return true
} else {
return false
}
})*/
var backupData: AccountBackupData?
var sortIndex: Int32 = 0
for attribute in record.attributes {
if let attribute = attribute as? AccountSortOrderAttribute {
sortIndex = attribute.order
} else if let attribute = attribute as? AccountBackupDataAttribute {
backupData = attribute.data
}
}
if let backupData = backupData {
result.append((record.id, Int(sortIndex), PeerId(backupData.peerId)))
}
}
result.sort(by: { lhs, rhs in
if lhs.1 != rhs.1 {
return lhs.1 < rhs.1
} else {
return lhs.0 < rhs.0
}
})
return result.map { record -> (AccountRecordId, PeerId) in
return (record.0, record.2)
}
})
let account: Signal<Account?, NoError>
if let accountCache = accountCache {
account = .single(accountCache)
} else {
initializeAccountManagement()
let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata")
let deviceSpecificEncryptionParameters = BuildConfig.deviceSpecificEncryptionParameters(rootPath, baseAppBundleId: baseAppBundleId)
let encryptionParameters = ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: deviceSpecificEncryptionParameters.key)!, salt: ValueBoxEncryptionParameters.Salt(data: deviceSpecificEncryptionParameters.salt)!)
account = currentAccount(allocateIfNotExists: false, networkArguments: NetworkInitializationArguments(apiId: apiId, apiHash: apiHash, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider()), supplementary: true, manager: accountManager, rootPath: rootPath, auxiliaryMethods: accountAuxiliaryMethods, encryptionParameters: encryptionParameters)
|> mapToSignal { account -> Signal<Account?, NoError> in
if let account = account {
@ -139,6 +191,7 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
deinit {
self.resolvePersonsDisposable.dispose()
self.actionDisposable.dispose()
self.searchDisposable.dispose()
}
override public func handler(for intent: INIntent) -> Any {
@ -701,45 +754,181 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
}
@available(iOSApplicationExtension 14.0, iOS 14.0, *)
func provideFriendsOptionsCollection(for intent: SelectFriendsIntent, with completion: @escaping (INObjectCollection<Friend>?, Error?) -> Void) {
let _ = (self.accountPromise.get()
func provideFriendsOptionsCollection(for intent: SelectFriendsIntent, searchTerm: String?, with completion: @escaping (INObjectCollection<Friend>?, Error?) -> Void) {
guard let rootPath = self.rootPath, let _ = self.accountManager, let encryptionParameters = self.encryptionParameters else {
completion(nil, nil)
return
}
if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: rootPath))), let state = try? JSONDecoder().decode(LockState.self, from: data) {
if state.isManuallyLocked || state.autolockTimeout != nil {
let error = NSError(domain: "Locked", code: 1, userInfo: [
NSLocalizedDescriptionKey: "Open Telegram and enter passcode to edit widget."
])
completion(nil, error)
return
}
}
self.searchDisposable.set((self.allAccounts.get()
|> castError(Error.self)
|> take(1)
|> mapToSignal { accounts -> Signal<INObjectCollection<Friend>, Error> in
var accountResults: [Signal<INObjectSection<Friend>, Error>] = []
for (accountId, accountPeerId) in accounts {
accountResults.append(accountTransaction(rootPath: rootPath, id: accountId, encryptionParameters: encryptionParameters, transaction: { postbox, transaction -> INObjectSection<Friend> in
var accountTitle: String = ""
if let peer = transaction.getPeer(accountPeerId) as? TelegramUser {
if let username = peer.username, !username.isEmpty {
accountTitle = "@\(username)"
} else {
accountTitle = peer.debugDisplayTitle
}
}
var peers: [Peer] = []
if let searchTerm = searchTerm {
if !searchTerm.isEmpty {
for renderedPeer in transaction.searchPeers(query: searchTerm) {
if let peer = renderedPeer.peer, !(peer is TelegramSecretChat) {
peers.append(peer)
}
}
if peers.count > 30 {
peers = Array(peers.dropLast(peers.count - 30))
}
}
} else {
for renderedPeer in transaction.getTopChatListEntries(groupId: .root, count: 50) {
if let peer = renderedPeer.peer, !(peer is TelegramSecretChat) {
peers.append(peer)
}
}
}
var items: [Friend] = []
for peer in peers {
let profileImage = smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in
return postbox.mediaBox.resourcePath(representation.resource)
}.flatMap { path -> INImage? in
if let data = try? Data(contentsOf: URL(fileURLWithPath: path)) {
return INImage(imageData: data)
} else {
return nil
}
}
items.append(Friend(identifier: "\(accountId.int64):\(peer.id.toInt64())", display: peer.debugDisplayTitle, subtitle: nil, image: nil))
}
return INObjectSection<Friend>(title: accountTitle, items: items)
})
|> castError(Error.self))
}
return combineLatest(accountResults)
|> map { accountResults -> INObjectCollection<Friend> in
let filteredSections = accountResults.filter { section in
return !section.items.isEmpty
}
if filteredSections.count == 1 {
return INObjectCollection<Friend>(items: filteredSections[0].items)
} else {
return INObjectCollection<Friend>(sections: filteredSections)
}
}
}).start(next: { result in
completion(result, nil)
}, error: { error in
completion(nil, error)
}))
/*let _ = (self.accountPromise.get()
|> take(1)
|> mapToSignal { account -> Signal<[Friend], NoError> in
guard let account = account else {
return .single([])
}
return account.postbox.transaction { transaction -> [Friend] in
var peers: [Peer] = []
outer: for peerId in transaction.getContactPeerIds() {
if let peer = transaction.getPeer(peerId) as? TelegramUser {
peers.append(peer)
if let searchTerm = searchTerm {
if !searchTerm.isEmpty {
return account.postbox.searchPeers(query: searchTerm)
|> map { renderedPeers -> [Friend] in
var peers: [Peer] = []
for renderedPeer in renderedPeers {
if let peer = renderedPeer.peer, !(peer is TelegramSecretChat) {
peers.append(peer)
}
}
peers.sort(by: { lhs, rhs in
return lhs.debugDisplayTitle < rhs.debugDisplayTitle
})
if peers.count > 30 {
peers = Array(peers.dropLast(peers.count - 30))
}
var result: [Friend] = []
for peer in peers {
let profileImage = smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in
return account.postbox.mediaBox.resourcePath(representation.resource)
}.flatMap { path -> INImage? in
if let data = try? Data(contentsOf: URL(fileURLWithPath: path)) {
return INImage(imageData: data)
} else {
return nil
}
}
result.append(Friend(identifier: "\(peer.id.toInt64())", display: peer.debugDisplayTitle, subtitle: nil, image: nil))
}
return result
}
} else {
return .single([])
}
peers.sort(by: { lhs, rhs in
return lhs.debugDisplayTitle < rhs.debugDisplayTitle
})
var result: [Friend] = []
for peer in peers {
let profileImage = smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in
return account.postbox.mediaBox.resourcePath(representation.resource)
}.flatMap { path -> INImage? in
if let data = try? Data(contentsOf: URL(fileURLWithPath: path)) {
return INImage(imageData: data)
} else {
return nil
} else {
return account.postbox.transaction { transaction -> [Friend] in
var peers: [Peer] = []
for peerId in transaction.getContactPeerIds() {
if let peer = transaction.getPeer(peerId) as? TelegramUser {
peers.append(peer)
}
}
result.append(Friend(identifier: "\(peer.id.toInt64())", display: peer.debugDisplayTitle, subtitle: nil, image: nil))
peers.sort(by: { lhs, rhs in
return lhs.debugDisplayTitle < rhs.debugDisplayTitle
})
if peers.count > 50 {
peers = Array(peers.dropLast(peers.count - 50))
}
var result: [Friend] = []
for peer in peers {
let profileImage = smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in
return account.postbox.mediaBox.resourcePath(representation.resource)
}.flatMap { path -> INImage? in
if let data = try? Data(contentsOf: URL(fileURLWithPath: path)) {
return INImage(imageData: data)
} else {
return nil
}
}
result.append(Friend(identifier: "\(peer.id.toInt64())", display: peer.debugDisplayTitle, subtitle: nil, image: nil))
}
return result
}
return result
}
}
|> deliverOnMainQueue).start(next: { result in
let collection = INObjectCollection(items: result)
completion(collection, nil)
})
})*/
}
}

@ -2187,7 +2187,7 @@ Unused sets are archived when you add more.";
"Widget.AuthRequired" = "Log in to Telegram";
"Widget.NoUsers" = "Start messaging to see your friends here";
"Widget.GalleryTitle" = "Telegram";
"Widget.GalleryDescription" = "See your friends here";
"Widget.GalleryDescription" = "Select chats";
"ShareMenu.CopyShareLinkGame" = "Copy link to game";
@ -6056,3 +6056,13 @@ Sorry for the inconvenience.";
"Report.Report" = "Report";
"Report.Succeed" = "Telegram moderators will study your report. Thank you!";
"Conversation.AutoremoveRemainingTime" = "auto-delete in %@";
"Conversation.AutoremoveRemainingDays_1" = "auto-delete in %@ day";
"Conversation.AutoremoveRemainingDays_any" = "auto-delete in %@ days";
"PeerInfo.AutoremoveMessages" = "Auto-Delete Messages";
"PeerInfo.AutoremoveMessagesDisabled" = "Never";
"Conversation.AutoremoveChanged" = "Auto-Delete timer set to %@";

@ -94,11 +94,11 @@ private func avatarViewLettersImage(size: CGSize, peerId: Int64, accountPeerId:
private let avatarSize = CGSize(width: 50.0, height: 50.0)
func avatarImage(accountPeerId: Int64, peer: WidgetDataPeer, size: CGSize) -> UIImage {
if let path = peer.avatarPath, let image = UIImage(contentsOfFile: path), let roundImage = avatarRoundImage(size: size, source: image) {
func avatarImage(accountPeerId: Int64?, peer: WidgetDataPeer?, size: CGSize) -> UIImage {
if let path = peer?.avatarPath, let image = UIImage(contentsOfFile: path), let roundImage = avatarRoundImage(size: size, source: image) {
return roundImage
} else {
return avatarViewLettersImage(size: size, peerId: peer.id, accountPeerId: accountPeerId, letters: peer.letters)!
return avatarViewLettersImage(size: size, peerId: peer?.id ?? 1, accountPeerId: accountPeerId ?? 1, letters: peer?.letters ?? [" "])!
}
}

@ -17,6 +17,29 @@ import WidgetItemsUtils
import GeneratedSources
struct ParsedPeer {
var accountId: Int64
var accountPeerId: Int64
var peer: WidgetDataPeer
}
struct ParsedPeers {
var peers: [ParsedPeer]
var updateTimestamp: Int32
}
private extension ParsedPeers {
init(accountId: Int64, peers: WidgetDataPeers) {
self.init(peers: peers.peers.map { peer -> ParsedPeer in
return ParsedPeer(
accountId: accountId,
accountPeerId: peers.accountPeerId,
peer: peer
)
}, updateTimestamp: peers.updateTimestamp)
}
}
private var installedSharedLogger = false
private func setupSharedLogger(rootPath: String, path: String) {
@ -64,14 +87,7 @@ struct Provider: IntentTimelineProvider {
}
func getSnapshot(for configuration: SelectFriendsIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let contents: SimpleEntry.Contents
switch configuration.contents {
case .unknown, .recent:
contents = .recent
case .custom:
contents = .recent
}
let entry = SimpleEntry(date: Date(), contents: contents)
let entry = SimpleEntry(date: Date(), contents: .peers(ParsedPeers(accountId: 0, peers: WidgetDataPeers(accountPeerId: 0, peers: [], updateTimestamp: 0))))
completion(entry)
}
@ -79,111 +95,209 @@ struct Provider: IntentTimelineProvider {
let currentDate = Date()
let entryDate = Calendar.current.date(byAdding: .hour, value: 0, to: currentDate)!
switch configuration.contents {
case .unknown, .recent:
guard let appBundleIdentifier = Bundle.main.bundleIdentifier, let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else {
completion(Timeline(entries: [SimpleEntry(date: entryDate, contents: .recent)], policy: .atEnd))
case .custom:
guard let appBundleIdentifier = Bundle.main.bundleIdentifier, let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else {
completion(Timeline(entries: [SimpleEntry(date: entryDate, contents: .recent)], policy: .atEnd))
return
return
}
let baseAppBundleId = String(appBundleIdentifier[..<lastDotRange.lowerBound])
let appGroupName = "group.\(baseAppBundleId)"
let maybeAppGroupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName)
guard let appGroupUrl = maybeAppGroupUrl else {
completion(Timeline(entries: [SimpleEntry(date: entryDate, contents: .recent)], policy: .atEnd))
return
}
let rootPath = rootPathForBasePath(appGroupUrl.path)
let dataPath = rootPath + "/widget-data"
guard let data = try? Data(contentsOf: URL(fileURLWithPath: dataPath)), let widgetData = try? JSONDecoder().decode(WidgetData.self, from: data), case let .peers(widgetPeers) = widgetData.content else {
completion(Timeline(entries: [SimpleEntry(date: entryDate, contents: .recent)], policy: .atEnd))
return
}
TempBox.initializeShared(basePath: rootPath, processType: "widget", launchSpecificId: arc4random64())
let logsPath = rootPath + "/widget-logs"
let _ = try? FileManager.default.createDirectory(atPath: logsPath, withIntermediateDirectories: true, attributes: nil)
setupSharedLogger(rootPath: rootPath, path: logsPath)
initializeAccountManagement()
let deviceSpecificEncryptionParameters = BuildConfig.deviceSpecificEncryptionParameters(rootPath, baseAppBundleId: baseAppBundleId)
let encryptionParameters = ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: deviceSpecificEncryptionParameters.key)!, salt: ValueBoxEncryptionParameters.Salt(data: deviceSpecificEncryptionParameters.salt)!)
var itemsByAccount: [Int64: [(Int64, Friend)]] = [:]
var itemOrder: [(Int64, Int64)] = []
if let friends = configuration.friends {
for item in friends {
guard let identifier = item.identifier else {
continue
}
guard let index = identifier.firstIndex(of: ":") else {
continue
}
guard let accountId = Int64(identifier[identifier.startIndex ..< index]) else {
continue
}
guard let peerId = Int64(identifier[identifier.index(after: index)...]) else {
continue
}
if itemsByAccount[accountId] == nil {
itemsByAccount[accountId] = []
}
itemsByAccount[accountId]?.append((peerId, item))
itemOrder.append((accountId, peerId))
}
let baseAppBundleId = String(appBundleIdentifier[..<lastDotRange.lowerBound])
let appGroupName = "group.\(baseAppBundleId)"
let maybeAppGroupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName)
guard let appGroupUrl = maybeAppGroupUrl else {
completion(Timeline(entries: [SimpleEntry(date: entryDate, contents: .recent)], policy: .atEnd))
return
}
let rootPath = rootPathForBasePath(appGroupUrl.path)
let dataPath = rootPath + "/widget-data"
guard let data = try? Data(contentsOf: URL(fileURLWithPath: dataPath)), let widgetData = try? JSONDecoder().decode(WidgetData.self, from: data), case let .peers(widgetPeers) = widgetData.content else {
completion(Timeline(entries: [SimpleEntry(date: entryDate, contents: .recent)], policy: .atEnd))
return
}
TempBox.initializeShared(basePath: rootPath, processType: "widget", launchSpecificId: arc4random64())
let logsPath = rootPath + "/widget-logs"
let _ = try? FileManager.default.createDirectory(atPath: logsPath, withIntermediateDirectories: true, attributes: nil)
setupSharedLogger(rootPath: rootPath, path: logsPath)
initializeAccountManagement()
let deviceSpecificEncryptionParameters = BuildConfig.deviceSpecificEncryptionParameters(rootPath, baseAppBundleId: baseAppBundleId)
let encryptionParameters = ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: deviceSpecificEncryptionParameters.key)!, salt: ValueBoxEncryptionParameters.Salt(data: deviceSpecificEncryptionParameters.salt)!)
let _ = (accountTransaction(rootPath: rootPath, id: AccountRecordId(rawValue: widgetData.accountId), encryptionParameters: encryptionParameters, transaction: { postbox, transaction -> WidgetDataPeers in
var peers: [WidgetDataPeer] = []
if let items = configuration.friends {
for item in items {
guard let identifier = item.identifier, let peerIdValue = Int64(identifier) else {
continue
}
var friendsByAccount: [Signal<[ParsedPeer], NoError>] = []
for (accountId, items) in itemsByAccount {
friendsByAccount.append(accountTransaction(rootPath: rootPath, id: AccountRecordId(rawValue: accountId), encryptionParameters: encryptionParameters, transaction: { postbox, transaction -> [ParsedPeer] in
guard let state = transaction.getState() as? AuthorizedAccountState else {
return []
}
var result: [ParsedPeer] = []
for (peerId, _) in items {
guard let peer = transaction.getPeer(PeerId(peerId)) else {
continue
}
var name: String = ""
var lastName: String?
if let user = peer as? TelegramUser {
if let firstName = user.firstName {
name = firstName
lastName = user.lastName
} else if let lastName = user.lastName {
name = lastName
} else if let phone = user.phone, !phone.isEmpty {
name = phone
}
guard let peer = transaction.getPeer(PeerId(peerIdValue)) else {
continue
} else {
name = peer.debugDisplayTitle
}
var badge: WidgetDataPeer.Badge?
if let readState = transaction.getCombinedPeerReadState(peer.id), readState.count > 0 {
var isMuted = false
if let notificationSettings = transaction.getPeerNotificationSettings(peer.id) as? TelegramPeerNotificationSettings {
isMuted = notificationSettings.isRemovedFromTotalUnreadCount(default: false)
}
var name: String = ""
var lastName: String?
if let user = peer as? TelegramUser {
if let firstName = user.firstName {
name = firstName
lastName = user.lastName
} else if let lastName = user.lastName {
name = lastName
} else if let phone = user.phone, !phone.isEmpty {
name = phone
}
} else {
name = peer.debugDisplayTitle
badge = WidgetDataPeer.Badge(
count: Int(readState.count),
isMuted: isMuted
)
}
var mappedMessage: WidgetDataPeer.Message?
if let index = transaction.getTopPeerMessageIndex(peerId: peer.id) {
if let message = transaction.getMessage(index.id) {
mappedMessage = WidgetDataPeer.Message(message: message)
}
var badge: WidgetDataPeer.Badge?
if let readState = transaction.getCombinedPeerReadState(peer.id), readState.count > 0 {
var isMuted = false
if let notificationSettings = transaction.getPeerNotificationSettings(peer.id) as? TelegramPeerNotificationSettings {
isMuted = notificationSettings.isRemovedFromTotalUnreadCount(default: false)
}
badge = WidgetDataPeer.Badge(
count: Int(readState.count),
isMuted: isMuted
)
}
let widgetPeer = WidgetDataPeer(id: peer.id.toInt64(), name: name, lastName: lastName, letters: peer.displayLetters, avatarPath: smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in
return postbox.mediaBox.resourcePath(representation.resource)
}, badge: badge, message: mappedMessage)
result.append(ParsedPeer(accountId: accountId, accountPeerId: state.peerId.toInt64(), peer: widgetPeer))
}
return result
}))
}
let _ = combineLatest(friendsByAccount).start(next: { allPeers in
var orderedPeers: [ParsedPeer] = []
outer: for (accountId, peerId) in itemOrder {
for peerSet in allPeers {
for peer in peerSet {
if peer.accountId == accountId && peer.peer.id == peerId {
orderedPeers.append(peer)
continue outer
}
var mappedMessage: WidgetDataPeer.Message?
if let index = transaction.getTopPeerMessageIndex(peerId: peer.id) {
if let message = transaction.getMessage(index.id) {
mappedMessage = WidgetDataPeer.Message(message: message)
}
}
peers.append(WidgetDataPeer(id: peer.id.toInt64(), name: name, lastName: lastName, letters: peer.displayLetters, avatarPath: smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in
return postbox.mediaBox.resourcePath(representation.resource)
}, badge: badge, message: mappedMessage))
}
}
return WidgetDataPeers(accountPeerId: widgetPeers.accountPeerId, peers: peers)
})
|> deliverOnMainQueue).start(next: { peers in
completion(Timeline(entries: [SimpleEntry(date: entryDate, contents: .peers(peers))], policy: .atEnd))
})
}
}
let result = ParsedPeers(peers: orderedPeers, updateTimestamp: Int32(Date().timeIntervalSince1970))
completion(Timeline(entries: [SimpleEntry(date: entryDate, contents: .peers(result))], policy: .atEnd))
})
/*let _ = (accountTransaction(rootPath: rootPath, id: AccountRecordId(rawValue: widgetData.accountId), encryptionParameters: encryptionParameters, transaction: { postbox, transaction -> ParsedPeers in
var peers: [ParsedPeer] = []
if let items = configuration.friends {
for item in items {
guard let identifier = item.identifier, let peerIdValue = Int64(identifier) else {
continue
}
guard let peer = transaction.getPeer(PeerId(peerIdValue)) else {
continue
}
var name: String = ""
var lastName: String?
if let user = peer as? TelegramUser {
if let firstName = user.firstName {
name = firstName
lastName = user.lastName
} else if let lastName = user.lastName {
name = lastName
} else if let phone = user.phone, !phone.isEmpty {
name = phone
}
} else {
name = peer.debugDisplayTitle
}
var badge: WidgetDataPeer.Badge?
if let readState = transaction.getCombinedPeerReadState(peer.id), readState.count > 0 {
var isMuted = false
if let notificationSettings = transaction.getPeerNotificationSettings(peer.id) as? TelegramPeerNotificationSettings {
isMuted = notificationSettings.isRemovedFromTotalUnreadCount(default: false)
}
badge = WidgetDataPeer.Badge(
count: Int(readState.count),
isMuted: isMuted
)
}
var mappedMessage: WidgetDataPeer.Message?
if let index = transaction.getTopPeerMessageIndex(peerId: peer.id) {
if let message = transaction.getMessage(index.id) {
mappedMessage = WidgetDataPeer.Message(message: message)
}
}
peers.append(WidgetDataPeer(id: peer.id.toInt64(), name: name, lastName: lastName, letters: peer.displayLetters, avatarPath: smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in
return postbox.mediaBox.resourcePath(representation.resource)
}, badge: badge, message: mappedMessage))
}
}
return ParsedPeers(peers: peers, updateTimestamp: Int32(Date().timeIntervalSince1970))
})
|> deliverOnMainQueue).start(next: { peers in
completion(Timeline(entries: [SimpleEntry(date: entryDate, contents: .peers(peers))], policy: .atEnd))
})*/
}
}
struct SimpleEntry: TimelineEntry {
enum Contents {
case recent
case peers(WidgetDataPeers)
case peers(ParsedPeers)
}
let date: Date
@ -191,27 +305,32 @@ struct SimpleEntry: TimelineEntry {
}
enum PeersWidgetData {
case placeholder
case empty
case locked
case peers(WidgetDataPeers)
case peers(ParsedPeers)
}
extension PeersWidgetData {
static let previewData = PeersWidgetData.placeholder
static let previewData = PeersWidgetData.empty
}
struct AvatarItemView: View {
var accountPeerId: Int64
var peer: WidgetDataPeer
var peer: ParsedPeer?
var itemSize: CGFloat
var displayBadge: Bool = true
var body: some View {
return ZStack {
Image(uiImage: avatarImage(accountPeerId: accountPeerId, peer: peer, size: CGSize(width: itemSize, height: itemSize)))
if let peer = peer {
Image(uiImage: avatarImage(accountPeerId: peer.accountPeerId, peer: peer.peer, size: CGSize(width: itemSize, height: itemSize)))
.clipShape(Circle())
if displayBadge, let badge = peer.badge, badge.count > 0 {
} else {
Image(uiImage: avatarImage(accountPeerId: nil, peer: nil, size: CGSize(width: itemSize, height: itemSize)))
//Rectangle()
.frame(width: itemSize, height: itemSize)
.clipShape(Circle())
.redacted(reason: .placeholder)
}
/*if let peer = peer, displayBadge, let badge = peer.badge, badge.count > 0 {
Text("\(badge.count)")
.font(Font.system(size: 16.0))
.multilineTextAlignment(.center)
@ -223,7 +342,7 @@ struct AvatarItemView: View {
.frame(minWidth: 20, idealWidth: 20, maxWidth: .infinity, minHeight: 20, idealHeight: 20, maxHeight: 20.0, alignment: .center)
)
.position(x: floor(0.84 * itemSize), y: floor(0.16 * itemSize))
}
}*/
}
}
}
@ -259,7 +378,7 @@ struct WidgetView: View {
}
}
func peersView(geometry: GeometryProxy, peers: WidgetDataPeers) -> some View {
func peersView(geometry: GeometryProxy, peers: ParsedPeers) -> some View {
let columnCount: Int
let rowCount: Int
@ -308,9 +427,8 @@ struct WidgetView: View {
return ZStack {
ForEach(0 ..< min(peers.peers.count, columnCount * rowCount), content: { i in
Link(destination: URL(string: linkForPeer(id: peers.peers[i].id))!, label: {
Link(destination: URL(string: linkForPeer(id: peers.peers[i].peer.id))!, label: {
AvatarItemView(
accountPeerId: peers.accountPeerId,
peer: peers.peers[i],
itemSize: itemSize
).frame(width: itemSize, height: itemSize)
@ -322,18 +440,10 @@ struct WidgetView: View {
func peerViews() -> AnyView {
switch data {
case .placeholder:
case .empty:
return AnyView(GeometryReader { geometry in
placeholder(geometry: geometry)
})
case .empty:
return AnyView(VStack {
Text(presentationData.applicationStartRequiredString)
})
case .locked:
return AnyView(VStack {
Text(presentationData.applicationLockedString)
})
case let .peers(peers):
return AnyView(GeometryReader { geometry in
peersView(geometry: geometry, peers: peers)
@ -348,23 +458,50 @@ struct WidgetView: View {
.padding(0.0)
}
func chatTopLine(_ peer: WidgetDataPeer) -> some View {
func chatTopLine(_ peer: ParsedPeer?) -> some View {
let dateText: String
if let message = peer.message {
dateText = DateFormatter.localizedString(from: Date(timeIntervalSince1970: Double(message.timestamp)), dateStyle: .none, timeStyle: .short)
let chatTitle: Text
let date: Text
if let peer = peer {
if let message = peer.peer.message {
dateText = DateFormatter.localizedString(from: Date(timeIntervalSince1970: Double(message.timestamp)), dateStyle: .none, timeStyle: .short)
} else {
dateText = ""
}
chatTitle = Text(peer.peer.name).font(Font.system(size: 16.0, weight: .medium, design: .default)).foregroundColor(.primary)
date = Text(dateText)
.font(Font.system(size: 14.0, weight: .regular, design: .default)).foregroundColor(.secondary)
} else {
dateText = ""
dateText = "10:00"
chatTitle = Text("Chat Title").font(Font.system(size: 16.0, weight: .medium, design: .default)).foregroundColor(.primary)
date = Text(dateText)
.font(Font.system(size: 14.0, weight: .regular, design: .default)).foregroundColor(.secondary)
}
return HStack(alignment: .center, spacing: 0.0, content: {
Text(peer.name).font(Font.system(size: 16.0, weight: .medium, design: .default)).foregroundColor(.primary)
if peer != nil {
chatTitle
} else {
chatTitle.redacted(reason: .placeholder)
}
Spacer()
Text(dateText).font(Font.system(size: 14.0, weight: .regular, design: .default)).foregroundColor(.secondary)
if peer != nil {
date
} else {
date.redacted(reason: .placeholder)
}
})
.padding(0.0)
}
func chatBottomLine(_ peer: WidgetDataPeer) -> some View {
var text = peer.message?.text ?? ""
if let message = peer.message {
func chatBottomLine(_ peer: ParsedPeer?) -> AnyView {
var text = peer?.peer.message?.text ?? ""
text += "\n"
if peer == nil {
text = "First Line Of Text Here\nSecond line fwqefeqwfqwef qwef wq"
}
if let message = peer?.peer.message {
//TODO:localize
switch message.content {
case .text:
@ -406,17 +543,47 @@ struct WidgetView: View {
case let .poll(poll):
text = "📊 \(poll.title)"
}
if let author = message.author {
if author.isMe {
text = "You: \(text)"
} else {
text = "\(author.title): \(text)"
}
}
}
var hasBadge = false
if let badge = peer.badge, badge.count > 0 {
if let peer = peer, let badge = peer.peer.badge, badge.count > 0 {
hasBadge = true
}
return HStack(alignment: .center, spacing: hasBadge ? 6.0 : 0.0, content: {
Text(text).lineLimit(nil).font(Font.system(size: 15.0, weight: .regular, design: .default)).foregroundColor(.secondary).multilineTextAlignment(.leading).frame(maxHeight: .infinity, alignment: .topLeading)
Spacer()
if let badge = peer.badge, badge.count > 0 {
let textView = Text(text)
.lineLimit(2)
.font(Font.system(size: 15.0, weight: .regular, design: .default))
.foregroundColor(.secondary)
.multilineTextAlignment(.leading)
.padding(0.0)
//.frame(maxHeight: .infinity, alignment: .topLeading)
//.background(Rectangle().foregroundColor(.gray))
if peer != nil {
return AnyView(textView)
} else {
return AnyView(
textView
.redacted(reason: .placeholder)
)
}
/*return HStack(alignment: .center, spacing: hasBadge ? 6.0 : 0.0, content: {
if peer != nil {
textView
} else {
textView.redacted(reason: .placeholder)
}
//Spacer()
/*if let peer = peer, let badge = peer.badge, badge.count > 0 {
VStack {
Spacer()
Text("\(badge.count)")
@ -431,39 +598,116 @@ struct WidgetView: View {
)
.padding(EdgeInsets(top: 0.0, leading: 0.0, bottom: 6.0, trailing: 3.0))
}
}
})
}*/
})*/
}
func chatContent(_ peer: WidgetDataPeer) -> some View {
return VStack(alignment: .leading, spacing: 2.0, content: {
func chatContent(_ peer: ParsedPeer?) -> some View {
return VStack(alignment: .leading, spacing: 0.0, content: {
chatTopLine(peer)
chatBottomLine(peer).frame(maxHeight: .infinity)
chatBottomLine(peer)
})
}
func chatContentView(_ index: Int) -> AnyView {
let peers: WidgetDataPeers
func chatContentView(_ index: Int, size: CGSize) -> AnyView {
let peers: ParsedPeers?
switch data {
case let .peers(peersValue):
peers = peersValue
if peers.peers.count <= index {
return AnyView(Spacer())
if peersValue.peers.count <= index {
peers = nil
} else {
peers = peersValue
}
default:
return AnyView(Spacer())
peers = nil
}
let itemHeight = (size.height - 22.0) / 2.0
let url: URL
if let peers = peers {
url = URL(string: linkForPeer(id: peers.peers[index].peer.id))!
} else {
url = URL(string: "\(buildConfig.appSpecificUrlScheme)://")!
}
return AnyView(
Link(destination: URL(string: linkForPeer(id: peers.peers[index].id))!, label: {
Link(destination: url, label: {
HStack(alignment: .center, spacing: 0.0, content: {
AvatarItemView(accountPeerId: peers.accountPeerId, peer: peers.peers[index], itemSize: 60.0, displayBadge: false).frame(width: 60.0, height: 60.0, alignment: .leading).padding(EdgeInsets(top: 0.0, leading: 10.0, bottom: 0.0, trailing: 10.0))
chatContent(peers.peers[index]).frame(maxWidth: .infinity).padding(EdgeInsets(top: 10.0, leading: 0.0, bottom: 10.0, trailing: 10.0))
AvatarItemView(peer: peers?.peers[index], itemSize: 54.0, displayBadge: false).frame(width: 54.0, height: 54.0, alignment: .leading).padding(EdgeInsets(top: 0.0, leading: 10.0, bottom: 0.0, trailing: 10.0))
chatContent(peers?.peers[index]).frame(maxWidth: .infinity).padding(EdgeInsets(top: 0.0, leading: 0.0, bottom: 0.0, trailing: 10.0))
})
})
.frame(width: size.width, height: itemHeight, alignment: .leading)
)
}
func chatSeparatorView(size: CGSize) -> some View {
return HStack(alignment: .center, spacing: 0.0, content: {
Spacer()
Rectangle()
.foregroundColor(getSeparatorColor())
.frame(width: size.width - 54.0 - 20.0, height: 0.5, alignment: .leading)
})
.frame(width: size.width, height: 1.0, alignment: .leading)
/*let separatorWidth = size.width - 54.0 - 20.0
let itemHeight = (size.height - 22.0) / 2.0
return Rectangle().foregroundColor(getSeparatorColor())
//.position(x: (54.0 + 20.0 + separatorWidth) / 2.0, y: itemHeight / 2.0)
.frame(width: size.width, height: 1.0, alignment: .leading)*/
}
func chatsUpdateBackgroundView(size: CGSize) -> some View {
return Rectangle().foregroundColor(getUpdatedBackgroundColor())
//.position(x: size.width / 2.0, y: size.height - 11.0)
.frame(width: size.width, height: 22.0, alignment: .center)
}
func chatUpdateView(size: CGSize) -> some View {
return ZStack(alignment: Alignment(horizontal: .center, vertical: .center), content: {
Rectangle().foregroundColor(getUpdatedBackgroundColor())
chatsUpdateTimestampView(size: size)
})
.frame(width: size.width, height: 22.0 - 1.0, alignment: .center)
}
func chatsUpdateTimestampView(size: CGSize) -> some View {
let text: String
switch data {
case let .peers(peersValue):
if peersValue.peers.isEmpty {
text = "Long tap to edit widget"
} else {
let date = Date(timeIntervalSince1970: Double(peersValue.updateTimestamp))
let calendar = Calendar.current
//TODO:localize
if !calendar.isDate(Date(), inSameDayAs: date) {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .none
text = "updated on \(formatter.string(from: date))"
} else {
let formatter = DateFormatter()
formatter.dateStyle = .none
formatter.timeStyle = .short
text = "updated at \(formatter.string(from: date))"
}
}
default:
text = ""
}
return Text(text)
.font(Font.system(size: 12.0))
.foregroundColor(getUpdatedTextColor())
/*return HStack(alignment: .center, spacing: 0.0, content: {
Text(text)
.font(Font.system(size: 12.0))
.foregroundColor(getUpdatedTextColor())
}).position(x: size.width / 2.0, y: size.height - 11.0).frame(width: size.width, height: 22.0, alignment: .leading)*/
}
func getSeparatorColor() -> Color {
switch colorScheme {
case .light:
@ -475,13 +719,36 @@ struct WidgetView: View {
}
}
func getUpdatedBackgroundColor() -> Color {
switch colorScheme {
case .light:
return Color(.sRGB, red: 242.0 / 255.0, green: 242.0 / 255.0, blue: 247.0 / 255.0, opacity: 1.0)
case .dark:
return Color(.sRGB, red: 21.0 / 255.0, green: 21.0 / 255.0, blue: 21.0 / 255.0, opacity: 1.0)
@unknown default:
return .secondary
}
}
func getUpdatedTextColor() -> Color {
switch colorScheme {
case .light:
return Color(.sRGB, red: 142.0 / 255.0, green: 142.0 / 255.0, blue: 146.0 / 255.0, opacity: 1.0)
case .dark:
return Color(.sRGB, red: 142.0 / 255.0, green: 142.0 / 255.0, blue: 146.0 / 255.0, opacity: 1.0)
@unknown default:
return .secondary
}
}
var body: some View {
GeometryReader(content: { geometry in
ZStack {
chatContentView(0).position(x: geometry.size.width / 2.0, y: geometry.size.height / 4.0).frame(width: geometry.size.width, height: geometry.size.height / 2.0, alignment: .leading)
chatContentView(1).position(x: geometry.size.width / 2.0, y: geometry.size.height / 2.0 + geometry.size.height / 4.0).frame(width: geometry.size.width, height: geometry.size.height / 2.0, alignment: .leading)
Rectangle().foregroundColor(getSeparatorColor()).position(x: geometry.size.width / 2.0, y: geometry.size.height / 4.0).frame(width: geometry.size.width, height: 0.33, alignment: .leading)
}
return VStack(alignment: .center, spacing: 0.0, content: {
chatContentView(0, size: geometry.size)
chatSeparatorView(size: geometry.size)
chatContentView(1, size: geometry.size)
chatUpdateView(size: geometry.size)
})
})
.padding(0.0)
}
@ -535,7 +802,7 @@ func getWidgetData(contents: SimpleEntry.Contents) -> PeersWidgetData {
case .recent:
let appBundleIdentifier = Bundle.main.bundleIdentifier!
guard let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else {
return .placeholder
return .empty
}
let baseAppBundleId = String(appBundleIdentifier[..<lastDotRange.lowerBound])
@ -543,28 +810,28 @@ func getWidgetData(contents: SimpleEntry.Contents) -> PeersWidgetData {
let maybeAppGroupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName)
guard let appGroupUrl = maybeAppGroupUrl else {
return .placeholder
return .empty
}
let rootPath = rootPathForBasePath(appGroupUrl.path)
if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: rootPath))), let state = try? JSONDecoder().decode(LockState.self, from: data), isAppLocked(state: state) {
return .locked
}
/*if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: rootPath))), let state = try? JSONDecoder().decode(LockState.self, from: data) {
if state.isManuallyLocked || state.autolockTimeout != nil {
return .empty
}
}*/
let dataPath = rootPath + "/widget-data"
if let data = try? Data(contentsOf: URL(fileURLWithPath: dataPath)), let widgetData = try? JSONDecoder().decode(WidgetData.self, from: data) {
switch widgetData.content {
case let .peers(peers):
return .peers(peers)
case .disabled:
return .placeholder
case .notAuthorized:
return .locked
return .peers(ParsedPeers(accountId: widgetData.accountId, peers: peers))
case .empty:
return .empty
}
} else {
return .placeholder
return .empty
}
case let .peers(peers):
return .peers(peers)

@ -1 +1 @@
Subproject commit f7f2b6d7c952f3cf6bdcedce6a0a2a40a27ff596
Subproject commit c1f83903e864d753477e51d66d3ada6c2c6d096f

@ -1 +1 @@
Subproject commit e2b1e1e4399bd168a00d1c3125eaa3ae52835340
Subproject commit fe541dc3d45a4389d8bf973f7a16803429c5e212

@ -1 +1 @@
Subproject commit bfb54953cee1bc985ba8113dd2e635e1d294abdb
Subproject commit e80b9795db5e35f86d643e0fba7776f1b1f71066

@ -96,6 +96,7 @@ public final class PresentationCallVideoView {
case rotation270
}
public let holder: AnyObject
public let view: UIView
public let setOnFirstFrameReceived: (((Float) -> Void)?) -> Void
@ -105,6 +106,7 @@ public final class PresentationCallVideoView {
public let setOnIsMirroredUpdated: (((Bool) -> Void)?) -> Void
public init(
holder: AnyObject,
view: UIView,
setOnFirstFrameReceived: @escaping (((Float) -> Void)?) -> Void,
getOrientation: @escaping () -> Orientation,
@ -112,6 +114,7 @@ public final class PresentationCallVideoView {
setOnOrientationUpdated: @escaping (((Orientation, CGFloat) -> Void)?) -> Void,
setOnIsMirroredUpdated: @escaping (((Bool) -> Void)?) -> Void
) {
self.holder = holder
self.view = view
self.setOnFirstFrameReceived = setOnFirstFrameReceived
self.getOrientation = getOrientation
@ -277,13 +280,15 @@ public protocol PresentationGroupCall: class {
var internalId: CallSessionInternalId { get }
var peerId: PeerId { get }
var isVideo: Bool { get }
var audioOutputState: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> { get }
var canBeRemoved: Signal<Bool, NoError> { get }
var state: Signal<PresentationGroupCallState, NoError> { get }
var summaryState: Signal<PresentationGroupCallSummaryState?, NoError> { get }
var members: Signal<PresentationGroupCallMembers?, NoError> { get }
var audioLevels: Signal<[(PeerId, Float, Bool)], NoError> { get }
var audioLevels: Signal<[(PeerId, UInt32, Float, Bool)], NoError> { get }
var myAudioLevel: Signal<Float, NoError> { get }
var isMuted: Signal<Bool, NoError> { get }
@ -293,8 +298,11 @@ public protocol PresentationGroupCall: class {
func toggleIsMuted()
func setIsMuted(action: PresentationGroupCallMuteAction)
func requestVideo()
func disableVideo()
func updateDefaultParticipantsAreMuted(isMuted: Bool)
func setVolume(peerId: PeerId, volume: Int32, sync: Bool)
func setFullSizeVideo(peerId: PeerId?)
func setCurrentAudioOutput(_ output: AudioSessionOutput)
func updateMuteState(peerId: PeerId, isMuted: Bool) -> GroupCallParticipantsContext.Participant.MuteState?
@ -303,6 +311,10 @@ public protocol PresentationGroupCall: class {
func removedPeer(_ peerId: PeerId)
var invitedPeers: Signal<[PeerId], NoError> { get }
var incomingVideoSources: Signal<[PeerId: UInt32], NoError> { get }
func makeIncomingVideoView(source: UInt32, completion: @escaping (PresentationCallVideoView?) -> Void)
func loadMoreMembers(token: String)
}

@ -0,0 +1,11 @@
#import <UIKit/UIKit.h>
//! Project version number for Camera.
FOUNDATION_EXPORT double CameraVersionNumber;
//! Project version string for Camera.
FOUNDATION_EXPORT const unsigned char CameraVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <Camera/PublicHeader.h>

@ -13,6 +13,7 @@ public enum ChatListFilterSettingsHeaderAnimation {
case folders
case newFolder
case discussionGroupSetup
case autoRemove
}
public class ChatListFilterSettingsHeaderItem: ListViewItem, ItemListItem {
@ -106,7 +107,26 @@ class ChatListFilterSettingsHeaderItemNode: ListViewItemNode {
return { item, params, neighbors in
let leftInset: CGFloat = 32.0 + params.leftInset
let topInset: CGFloat = 92.0
let animationName: String
var size = 192
var insetDifference = 100
var playbackMode: AnimatedStickerPlaybackMode = .once
switch item.animation {
case .folders:
animationName = "ChatListFolders"
case .newFolder:
animationName = "ChatListNewFolder"
case .discussionGroupSetup:
animationName = "DiscussionGroupSetup"
case .autoRemove:
animationName = "MessageAutoRemove"
size = 260
insetDifference = 120
playbackMode = .loop
}
let topInset: CGFloat = CGFloat(size - insetDifference)
let attributedText = NSAttributedString(string: item.text, font: titleFont, textColor: item.theme.list.freeTextColor)
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
@ -119,17 +139,8 @@ class ChatListFilterSettingsHeaderItemNode: ListViewItemNode {
return (layout, { [weak self] in
if let strongSelf = self {
if strongSelf.item == nil {
let animationName: String
switch item.animation {
case .folders:
animationName = "ChatListFolders"
case .newFolder:
animationName = "ChatListNewFolder"
case .discussionGroupSetup:
animationName = "DiscussionGroupSetup"
}
if let path = getAppBundle().path(forResource: animationName, ofType: "tgs") {
strongSelf.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 192, height: 192, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
strongSelf.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: size, height: size, playbackMode: playbackMode, mode: .direct(cachePathPrefix: nil))
strongSelf.animationNode.visibility = true
}
}
@ -137,7 +148,7 @@ class ChatListFilterSettingsHeaderItemNode: ListViewItemNode {
strongSelf.item = item
strongSelf.accessibilityLabel = attributedText.string
let iconSize = CGSize(width: 96.0, height: 96.0)
let iconSize = CGSize(width: CGFloat(size) / 2.0, height: CGFloat(size) / 2.0)
strongSelf.animationNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - iconSize.width) / 2.0), y: -10.0), size: iconSize)
strongSelf.animationNode.updateLayout(size: iconSize)

@ -84,17 +84,27 @@ private final class InnerActionsContainerNode: ASDisplayNode {
switch items[i] {
case let .action(action):
itemNodes.append(.action(ContextActionNode(presentationData: presentationData, action: action, getController: getController, actionSelected: actionSelected)))
if i != items.count - 1, case .action = items[i + 1] {
let separatorNode = ASDisplayNode()
separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor
itemNodes.append(.itemSeparator(separatorNode))
if i != items.count - 1 {
switch items[i + 1] {
case .action, .custom:
let separatorNode = ASDisplayNode()
separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor
itemNodes.append(.itemSeparator(separatorNode))
default:
break
}
}
case let .custom(item, _):
itemNodes.append(.custom(item.node(presentationData: presentationData, getController: getController, actionSelected: actionSelected)))
if i != items.count - 1, case .action = items[i + 1] {
let separatorNode = ASDisplayNode()
separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor
itemNodes.append(.itemSeparator(separatorNode))
if i != items.count - 1 {
switch items[i + 1] {
case .action, .custom:
let separatorNode = ASDisplayNode()
separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor
itemNodes.append(.itemSeparator(separatorNode))
default:
break
}
}
case .separator:
let separatorNode = ASDisplayNode()

@ -15,8 +15,9 @@ public final class DatePickerTheme: Equatable {
public let selectionColor: UIColor
public let selectionTextColor: UIColor
public let separatorColor: UIColor
public let segmentedControlTheme: SegmentedControlTheme
public init(backgroundColor: UIColor, textColor: UIColor, secondaryTextColor: UIColor, accentColor: UIColor, disabledColor: UIColor, selectionColor: UIColor, selectionTextColor: UIColor, separatorColor: UIColor) {
public init(backgroundColor: UIColor, textColor: UIColor, secondaryTextColor: UIColor, accentColor: UIColor, disabledColor: UIColor, selectionColor: UIColor, selectionTextColor: UIColor, separatorColor: UIColor, segmentedControlTheme: SegmentedControlTheme) {
self.backgroundColor = backgroundColor
self.textColor = textColor
self.secondaryTextColor = secondaryTextColor
@ -25,6 +26,7 @@ public final class DatePickerTheme: Equatable {
self.selectionColor = selectionColor
self.selectionTextColor = selectionTextColor
self.separatorColor = separatorColor
self.segmentedControlTheme = segmentedControlTheme
}
public static func ==(lhs: DatePickerTheme, rhs: DatePickerTheme) -> Bool {
@ -55,7 +57,7 @@ public final class DatePickerTheme: Equatable {
public extension DatePickerTheme {
convenience init(theme: PresentationTheme) {
self.init(backgroundColor: theme.list.itemBlocksBackgroundColor, textColor: theme.list.itemPrimaryTextColor, secondaryTextColor: theme.list.itemSecondaryTextColor, accentColor: theme.list.itemAccentColor, disabledColor: theme.list.itemDisabledTextColor, selectionColor: theme.list.itemCheckColors.fillColor, selectionTextColor: theme.list.itemCheckColors.foregroundColor, separatorColor: theme.list.itemBlocksSeparatorColor)
self.init(backgroundColor: theme.list.itemBlocksBackgroundColor, textColor: theme.list.itemPrimaryTextColor, secondaryTextColor: theme.list.itemSecondaryTextColor, accentColor: theme.list.itemAccentColor, disabledColor: theme.list.itemDisabledTextColor, selectionColor: theme.list.itemCheckColors.fillColor, selectionTextColor: theme.list.itemCheckColors.foregroundColor, separatorColor: theme.list.itemBlocksSeparatorColor, segmentedControlTheme: SegmentedControlTheme(theme: theme))
}
}
@ -278,7 +280,7 @@ public final class DatePickerNode: ASDisplayNode {
private let strings: PresentationStrings
private let timeTitleNode: ImmediateTextNode
private let timeFieldNode: ASImageNode
private let timePickerNode: TimePickerNode
private let timeSeparatorNode: ASDisplayNode
private let dayNodes: [ImmediateTextNode]
@ -357,7 +359,7 @@ public final class DatePickerNode: ASDisplayNode {
}
}
public init(theme: DatePickerTheme, strings: PresentationStrings) {
public init(theme: DatePickerTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat) {
self.theme = theme
self.strings = strings
self.state = State(minDate: telegramReleaseDate, maxDate: upperLimitDate, date: Date(), displayingMonthSelection: false, selectedMonth: monthForDate(Date()))
@ -365,9 +367,7 @@ public final class DatePickerNode: ASDisplayNode {
self.timeTitleNode = ImmediateTextNode()
self.timeTitleNode.attributedText = NSAttributedString(string: "Time", font: Font.regular(17.0), textColor: theme.textColor)
self.timeFieldNode = ASImageNode()
self.timeFieldNode.displaysAsynchronously = false
self.timeFieldNode.displayWithoutProcessing = true
self.timePickerNode = TimePickerNode(theme: theme, dateTimeFormat: dateTimeFormat, date: self.state.date)
self.timeSeparatorNode = ASDisplayNode()
self.timeSeparatorNode.backgroundColor = theme.separatorColor
@ -403,7 +403,7 @@ public final class DatePickerNode: ASDisplayNode {
self.backgroundColor = theme.backgroundColor
self.addSubnode(self.timeTitleNode)
self.addSubnode(self.timeFieldNode)
self.addSubnode(self.timePickerNode)
self.addSubnode(self.contentNode)
@ -421,7 +421,9 @@ public final class DatePickerNode: ASDisplayNode {
self.monthArrowNode.image = generateSmallArrowImage(color: theme.accentColor)
self.previousButtonNode.setImage(generateNavigationArrowImage(color: theme.accentColor, mirror: true), for: .normal)
self.previousButtonNode.setImage(generateNavigationArrowImage(color: theme.disabledColor, mirror: true), for: .disabled)
self.nextButtonNode.setImage(generateNavigationArrowImage(color: theme.accentColor, mirror: false), for: .normal)
self.nextButtonNode.setImage(generateNavigationArrowImage(color: theme.disabledColor, mirror: false), for: .disabled)
self.setupItems()
@ -445,6 +447,15 @@ public final class DatePickerNode: ASDisplayNode {
self.previousButtonNode.addTarget(self, action: #selector(self.previousButtonPressed), forControlEvents: .touchUpInside)
self.nextButtonNode.addTarget(self, action: #selector(self.nextButtonPressed), forControlEvents: .touchUpInside)
self.timePickerNode.valueChanged = { [weak self] date in
if let strongSelf = self {
let updatedState = State(minDate: strongSelf.state.minDate, maxDate: strongSelf.state.maxDate, date: date, displayingMonthSelection: strongSelf.state.displayingMonthSelection, selectedMonth: strongSelf.state.selectedMonth)
strongSelf.updateState(updatedState, animated: false)
strongSelf.valueUpdated?(date)
}
}
monthChangedImpl = { [weak self] date in
if let strongSelf = self {
let updatedState = State(minDate: strongSelf.state.minDate, maxDate: strongSelf.state.maxDate, date: date, displayingMonthSelection: strongSelf.state.displayingMonthSelection, selectedMonth: monthForDate(date))
@ -523,7 +534,7 @@ public final class DatePickerNode: ASDisplayNode {
return
}
self.theme = theme
self.backgroundColor = self.theme.backgroundColor
self.monthArrowNode.image = generateSmallArrowImage(color: theme.accentColor)
self.previousButtonNode.setImage(generateNavigationArrowImage(color: theme.accentColor, mirror: true), for: .normal)
@ -668,9 +679,12 @@ public final class DatePickerNode: ASDisplayNode {
let timeTitleSize = self.timeTitleNode.updateLayout(size)
self.timeTitleNode.frame = CGRect(origin: CGPoint(x: 16.0, y: 14.0), size: timeTitleSize)
let timePickerSize = self.timePickerNode.updateLayout(size: size)
self.timePickerNode.frame = CGRect(origin: CGPoint(x: size.width - timePickerSize.width - 16.0, y: 6.0), size: timePickerSize)
self.timeSeparatorNode.frame = CGRect(x: 16.0, y: timeHeight, width: size.width - 16.0, height: UIScreenPixel)
self.monthTextNode.attributedText = NSAttributedString(string: stringForMonth(strings: self.strings, month: components.month.flatMap { Int32($0) - 1 } ?? 0, ofYear: components.year.flatMap { Int32($0) - 1900 } ?? 100), font: controlFont, textColor: theme.textColor)
self.monthTextNode.attributedText = NSAttributedString(string: stringForMonth(strings: self.strings, month: components.month.flatMap { Int32($0) - 1 } ?? 0, ofYear: components.year.flatMap { Int32($0) - 1900 } ?? 100), font: controlFont, textColor: self.state.displayingMonthSelection ? self.theme.accentColor : self.theme.textColor)
let monthSize = self.monthTextNode.updateLayout(size)
let monthTextFrame = CGRect(x: sideInset, y: 11.0 + timeHeight, width: monthSize.width, height: monthSize.height)
@ -684,7 +698,9 @@ public final class DatePickerNode: ASDisplayNode {
self.monthButtonNode.frame = monthTextFrame.inset(by: UIEdgeInsets(top: -6.0, left: -6.0, bottom: -6.0, right: -30.0))
self.previousButtonNode.isEnabled = self.currentIndex > 0
self.previousButtonNode.frame = CGRect(x: size.width - sideInset - 54.0, y: monthTextFrame.minY + 1.0, width: 10.0, height: 17.0)
self.nextButtonNode.isEnabled = self.currentIndex < self.months.count - 1
self.nextButtonNode.frame = CGRect(x: size.width - sideInset - 13.0, y: monthTextFrame.minY + 1.0, width: 10.0, height: 17.0)
let daysSideInset: CGFloat = 12.0
@ -752,6 +768,9 @@ private final class MonthPickerNode: ASDisplayNode, UIPickerViewDelegate, UIPick
}
}
var minDate: Date?
var maxDate: Date?
private let valueChanged: (Date) -> Void
private let pickerView: UIPickerView
@ -840,3 +859,85 @@ private final class MonthPickerNode: ASDisplayNode, UIPickerViewDelegate, UIPick
self.pickerView.frame = CGRect(origin: CGPoint(), size: CGSize(width: self.bounds.size.width, height: 180.0))
}
}
private final class TimePickerNode: ASDisplayNode {
private var theme: DatePickerTheme
private let dateTimeFormat: PresentationDateTimeFormat
private let backgroundNode: ASDisplayNode
private let textNode: ImmediateTextNode
private let amPMSelectorNode: SegmentedControlNode
var date: Date
var minDate: Date?
var maxDate: Date?
var valueChanged: ((Date) -> Void)?
init(theme: DatePickerTheme, dateTimeFormat: PresentationDateTimeFormat, date: Date) {
self.theme = theme
self.dateTimeFormat = dateTimeFormat
self.date = date
self.backgroundNode = ASDisplayNode()
self.backgroundNode.backgroundColor = theme.segmentedControlTheme.backgroundColor
self.backgroundNode.cornerRadius = 9.0
self.textNode = ImmediateTextNode()
let hours = calendar.component(.hour, from: date)
self.amPMSelectorNode = SegmentedControlNode(theme: theme.segmentedControlTheme, items: [SegmentedControlItem(title: "AM"), SegmentedControlItem(title: "PM")], selectedIndex: hours > 12 ? 1 : 0)
super.init()
self.addSubnode(self.backgroundNode)
self.addSubnode(self.textNode)
self.addSubnode(self.amPMSelectorNode)
self.amPMSelectorNode.selectedIndexChanged = { index in
let hours = calendar.component(.hour, from: date)
var components = calendar.dateComponents([.year, .month, .day, .hour, .minute], from: self.date)
if index == 0 && hours >= 12 {
components.hour = hours - 12
} else if index == 1 && hours < 12 {
components.hour = hours + 12
}
if let newDate = calendar.date(from: components) {
self.valueChanged?(newDate)
}
}
}
func updateTheme(_ theme: DatePickerTheme) {
self.theme = theme
self.backgroundNode.backgroundColor = theme.segmentedControlTheme.backgroundColor
}
func updateLayout(size: CGSize) -> CGSize {
self.backgroundNode.frame = CGRect(x: 0.0, y: 0.0, width: 75.0, height: 36.0)
var contentSize = CGSize()
let hours = Int32(calendar.component(.hour, from: self.date))
let minutes = Int32(calendar.component(.hour, from: self.date))
let string = stringForShortTimestamp(hours: hours, minutes: minutes, dateTimeFormat: self.dateTimeFormat).replacingOccurrences(of: " AM", with: "").replacingOccurrences(of: " PM", with: "")
self.textNode.attributedText = NSAttributedString(string: string, font: Font.with(size: 21.0, design: .monospace, weight: .regular, traits: []), textColor: self.theme.textColor)
let textSize = self.textNode.updateLayout(size)
self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((self.backgroundNode.frame.width - textSize.width) / 2.0), y: floorToScreenPixels((self.backgroundNode.frame.height - textSize.height) / 2.0)), size: textSize)
if self.dateTimeFormat.timeFormat == .military {
contentSize = self.backgroundNode.frame.size
self.amPMSelectorNode.isHidden = true
} else {
self.amPMSelectorNode.isHidden = false
let segmentedSize = self.amPMSelectorNode.updateLayout(.sizeToFit(maximumWidth: 120.0, minimumWidth: 80.0, height: 36.0), transition: .immediate)
self.amPMSelectorNode.frame = CGRect(x: 85.0, y: 0.0, width: segmentedSize.width, height: 36.0)
contentSize = CGSize(width: 85.0 + segmentedSize.width, height: 36.0)
}
return contentSize
}
}

@ -32,7 +32,7 @@
(int)sourceSampleRate,
0,
NULL);
_ratio = MAX(1, destinationSampleRate / sourceSampleRate) * MAX(1, destinationChannelCount / sourceChannelCount) * 2;
_ratio = MAX(1, destinationSampleRate / MAX(sourceSampleRate, 1)) * MAX(1, destinationChannelCount / sourceChannelCount) * 2;
swr_init(_context);
}
return self;

@ -51,7 +51,7 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
case timeHeader(PresentationTheme, String)
case timePicker(PresentationTheme, InviteLinkTimeLimit)
case timeExpiryDate(PresentationTheme, Int32?, Bool)
case timeCustomPicker(PresentationTheme, Int32?)
case timeCustomPicker(PresentationTheme, PresentationDateTimeFormat, Int32?)
case timeInfo(PresentationTheme, String)
case usageHeader(PresentationTheme, String)
@ -117,8 +117,8 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
} else {
return false
}
case let .timeCustomPicker(lhsTheme, lhsDate):
if case let .timeCustomPicker(rhsTheme, rhsDate) = rhs, lhsTheme === rhsTheme, lhsDate == rhsDate {
case let .timeCustomPicker(lhsTheme, lhsDateTimeFormat, lhsDate):
if case let .timeCustomPicker(rhsTheme, rhsDateTimeFormat, rhsDate) = rhs, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsDate == rhsDate {
return true
} else {
return false
@ -197,8 +197,8 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
return updatedState
}
})
case let .timeCustomPicker(_, date):
return ItemListDatePickerItem(presentationData: presentationData, date: date, sectionId: self.section, style: .blocks, updated: { date in
case let .timeCustomPicker(_, dateTimeFormat, date):
return ItemListDatePickerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, date: date, sectionId: self.section, style: .blocks, updated: { date in
arguments.updateState({ state in
var updatedState = state
updatedState.time = .custom(date)
@ -283,7 +283,7 @@ private func inviteLinkEditControllerEntries(invite: ExportedInvitation?, state:
}
entries.append(.timeExpiryDate(presentationData.theme, time, state.pickingTimeLimit))
if state.pickingTimeLimit {
entries.append(.timeCustomPicker(presentationData.theme, time ?? currentTime))
entries.append(.timeCustomPicker(presentationData.theme, presentationData.dateTimeFormat, time ?? currentTime))
}
entries.append(.timeInfo(presentationData.theme, presentationData.strings.InviteLink_Create_TimeLimitInfo))
@ -373,7 +373,14 @@ public func inviteLinkEditController(context: AccountContext, peerId: PeerId, in
let _ = (revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link)
|> timeout(10, queue: Queue.mainQueue(), alternate: .fail(.generic))
|> deliverOnMainQueue).start(next: { invite in
completion?(invite)
switch invite {
case .none:
completion?(nil)
case let .update(invitation):
completion?(invitation)
case let .replace(_, invitation):
completion?(invitation)
}
}, error: { _ in
updateState { state in
var updatedState = state

@ -467,16 +467,24 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad
}
}
if revoke {
// revokeLinkDisposable.set((revokePersistentPeerExportedInvitation(account: context.account, peerId: peerId) |> deliverOnMainQueue).start(completed: {
// updateState { state in
// var updatedState = state
// updatedState.revokingPrivateLink = false
// return updatedState
// }
//
// invitesContext.reload()
// revokedInvitesContext.reload()
// }))
revokeLinkDisposable.set((revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(next: { result in
updateState { state in
var updatedState = state
updatedState.revokingPrivateLink = false
return updatedState
}
if let result = result {
switch result {
case let .update(newInvite):
invitesContext.remove(newInvite)
revokedInvitesContext.add(newInvite)
case let .replace(previousInvite, newInvite):
revokedInvitesContext.add(previousInvite)
invitesContext.remove(previousInvite)
invitesContext.add(newInvite)
}
}
}))
}
})
]),
@ -512,7 +520,7 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextCopy, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.dismissWithoutContent)
f(.default)
dismissTooltipsImpl?()
@ -526,7 +534,7 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextShare, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.dismissWithoutContent)
f(.default)
let shareController = ShareController(context: context, subject: .url(invite.link))
presentControllerImpl?(shareController, nil)
@ -535,7 +543,7 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Wallet/QrIcon"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.dismissWithoutContent)
f(.default)
let controller = InviteLinkQRCodeController(context: context, invite: invite)
presentControllerImpl?(controller, nil)
@ -544,7 +552,7 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextEdit, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.dismissWithoutContent)
f(.default)
let controller = inviteLinkEditController(context: context, peerId: peerId, invite: invite, completion: { invite in
if let invite = invite {
@ -565,7 +573,7 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextDelete, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
}, action: { _, f in
f(.dismissWithoutContent)
f(.default)
let controller = ActionSheetController(presentationData: presentationData)
let dismissAction: () -> Void = { [weak controller] in
@ -578,7 +586,6 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad
dismissAction()
revokeLinkDisposable.set((deletePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: {
}))
revokedInvitesContext.remove(invite)
@ -592,7 +599,7 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextRevoke, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
}, action: { _, f in
f(.dismissWithoutContent)
f(.default)
let controller = ActionSheetController(presentationData: presentationData)
let dismissAction: () -> Void = { [weak controller] in

@ -10,6 +10,7 @@ import DatePickerNode
public class ItemListDatePickerItem: ListViewItem, ItemListItem {
let presentationData: ItemListPresentationData
let dateTimeFormat: PresentationDateTimeFormat
let date: Int32?
public let sectionId: ItemListSectionId
let style: ItemListStyle
@ -18,6 +19,7 @@ public class ItemListDatePickerItem: ListViewItem, ItemListItem {
public init(
presentationData: ItemListPresentationData,
dateTimeFormat: PresentationDateTimeFormat,
date: Int32?,
sectionId: ItemListSectionId,
style: ItemListStyle,
@ -25,6 +27,7 @@ public class ItemListDatePickerItem: ListViewItem, ItemListItem {
tag: ItemListItemTag? = nil
) {
self.presentationData = presentationData
self.dateTimeFormat = dateTimeFormat
self.date = date
self.sectionId = sectionId
self.style = style
@ -92,6 +95,7 @@ public class ItemListDatePickerItemNode: ListViewItemNode, ItemListItemNode {
self.backgroundNode.backgroundColor = .white
self.maskNode = ASImageNode()
self.maskNode.isUserInteractionEnabled = false
self.topStripeNode = ASDisplayNode()
self.topStripeNode.isLayerBacked = true
@ -100,15 +104,7 @@ public class ItemListDatePickerItemNode: ListViewItemNode, ItemListItemNode {
self.bottomStripeNode.isLayerBacked = true
super.init(layerBacked: false, dynamicBounce: false)
}
// @objc private func datePickerUpdated() {
// guard let datePicker = self.datePicker else {
// return
// }
// self.item?.updated?(Int32(datePicker.date.timeIntervalSince1970))
// }
public func asyncLayout() -> (_ item: ItemListDatePickerItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let currentItem = self.item
@ -128,7 +124,9 @@ public class ItemListDatePickerItemNode: ListViewItemNode, ItemListItemNode {
let leftInset = 16.0 + params.leftInset
let rightInset = 16.0 + params.rightInset
let height: CGFloat = 360.0
let width = min(390.0, params.width - params.leftInset - params.rightInset)
let cellSize = floor((width - 12.0 * 2.0) / 7.0)
let height: CGFloat = 122.0 + cellSize * 6.0
switch item.style {
case .plain:
@ -154,26 +152,7 @@ public class ItemListDatePickerItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.datePickerNode?.updateTheme(DatePickerTheme(theme: item.presentationData.theme))
}
let datePickerNode: DatePickerNode
if let current = strongSelf.datePickerNode {
datePickerNode = current
} else {
datePickerNode = DatePickerNode(theme: DatePickerTheme(theme: item.presentationData.theme), strings: item.presentationData.strings)
strongSelf.addSubnode(datePickerNode)
strongSelf.datePickerNode = datePickerNode
}
datePickerNode.valueUpdated = { date in
strongSelf.item?.updated?(Int32(date.timeIntervalSince1970))
}
datePickerNode.minimumDate = Date()
datePickerNode.date = item.date.flatMap { Date(timeIntervalSince1970: TimeInterval($0)) } ?? Date()
let datePickerSize = CGSize(width: contentSize.width - params.leftInset - params.rightInset, height: contentSize.height)
datePickerNode.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: datePickerSize)
datePickerNode.updateLayout(size: datePickerSize, transition: .immediate)
switch item.style {
case .plain:
if strongSelf.backgroundNode.supernode != nil {
@ -230,6 +209,25 @@ public class ItemListDatePickerItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight))
}
let datePickerNode: DatePickerNode
if let current = strongSelf.datePickerNode {
datePickerNode = current
} else {
datePickerNode = DatePickerNode(theme: DatePickerTheme(theme: item.presentationData.theme), strings: item.presentationData.strings, dateTimeFormat: item.dateTimeFormat)
strongSelf.insertSubnode(datePickerNode, belowSubnode: strongSelf.topStripeNode)
strongSelf.datePickerNode = datePickerNode
}
datePickerNode.valueUpdated = { date in
strongSelf.item?.updated?(Int32(date.timeIntervalSince1970))
}
datePickerNode.minimumDate = Date()
datePickerNode.date = item.date.flatMap { Date(timeIntervalSince1970: TimeInterval($0)) } ?? Date()
let datePickerSize = CGSize(width: width, height: contentSize.height)
datePickerNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((params.width - datePickerSize.width) / 2.0), y: 0.0), size: datePickerSize)
datePickerNode.updateLayout(size: datePickerSize, transition: .immediate)
}
})
}

@ -408,7 +408,6 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
} else {
titleText = " "
subtitleText = " "
self.iconNode.isHidden = true
}
let titleAttributedString = NSAttributedString(string: titleText, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)

@ -54,7 +54,7 @@ public final class ItemListControllerSegmentedTitleView: UIView {
super.layoutSubviews()
let size = self.bounds.size
let controlSize = self.segmentedControlNode.updateLayout(.sizeToFit(maximumWidth: size.width, minimumWidth: 160.0), transition: .immediate)
let controlSize = self.segmentedControlNode.updateLayout(.sizeToFit(maximumWidth: size.width, minimumWidth: 160.0, height: 32.0), transition: .immediate)
self.segmentedControlNode.frame = CGRect(origin: CGPoint(x: floor((size.width - controlSize.width) / 2.0), y: floor((size.height - controlSize.height) / 2.0)), size: controlSize)
}
}

@ -13,6 +13,7 @@
@property (nonatomic, assign) CGFloat startValue;
@property (nonatomic, assign) CGFloat value;
@property (nonatomic, assign) int minimumUndottedValue;
@property (nonatomic, assign) CGFloat markValue;

@ -40,6 +40,7 @@ const CGFloat TGPhotoEditorSliderViewInternalMargin = 7.0f;
_startValue = 0.0f;
_value = _startValue;
_dotSize = 10.5f;
_minimumUndottedValue = -1;
_lineSize = TGPhotoEditorSliderViewLineSize;
_knobPadding = TGPhotoEditorSliderViewInternalMargin;
@ -174,13 +175,59 @@ const CGFloat TGPhotoEditorSliderViewInternalMargin = 7.0f;
CGContextSetBlendMode(context, kCGBlendModeCopy);
}
CGContextSetFillColorWithColor(context, _backColor.CGColor);
[self drawRectangle:backFrame cornerRadius:self.trackCornerRadius context:context];
if (_minimumUndottedValue > -1 && self.positionsCount > 1) {
CGContextSetLineWidth(context, backFrame.size.height);
CGContextSetLineCap(context, kCGLineCapRound);
for (NSInteger i = 1; i < self.positionsCount; i++)
{
CGFloat previousX = margin + totalLength / (self.positionsCount - 1) * (i - 1);
CGFloat currentX = margin + totalLength / (self.positionsCount - 1) * i;
if (_minimumUndottedValue < i) {
CGFloat normalDashWidth = 16.0f;
CGFloat dashFraction = 0.6f;
CGFloat totalLineWidth = currentX - previousX;
int numberOfDashes = (int)floor((double)(totalLineWidth / normalDashWidth));
CGFloat dashWidth = (totalLineWidth / (CGFloat)numberOfDashes);
CGFloat innerWidth = dashWidth * dashFraction - 2.0f;
CGFloat innerOffset = (dashWidth - innerWidth) / 2.0f;
CGFloat dottedX = previousX;
while (dottedX + innerWidth < currentX) {
bool highlighted = dottedX + dashWidth / 2.0f < CGRectGetMaxX(trackFrame);
CGContextSetStrokeColorWithColor(context, highlighted ? _trackColor.CGColor : _backColor.CGColor);
CGContextMoveToPoint(context, dottedX + innerOffset, CGRectGetMidY(backFrame));
CGContextAddLineToPoint(context, dottedX + innerOffset + innerWidth, CGRectGetMidY(backFrame));
CGContextStrokePath(context);
dottedX += dashWidth;
}
} else {
bool highlighted = (previousX + (currentX - previousX) / 2.0f) < CGRectGetMaxX(trackFrame);
CGContextSetStrokeColorWithColor(context, highlighted ? _trackColor.CGColor : _backColor.CGColor);
CGContextMoveToPoint(context, previousX, CGRectGetMidY(backFrame));
CGContextAddLineToPoint(context, currentX, CGRectGetMidY(backFrame));
CGContextStrokePath(context);
}
}
} else {
CGContextSetFillColorWithColor(context, _backColor.CGColor);
[self drawRectangle:backFrame cornerRadius:self.trackCornerRadius context:context];
}
CGContextSetBlendMode(context, kCGBlendModeNormal);
CGContextSetFillColorWithColor(context, _trackColor.CGColor);
[self drawRectangle:trackFrame cornerRadius:self.trackCornerRadius context:context];
if (_minimumUndottedValue > -1) {
} else {
CGContextSetFillColorWithColor(context, _trackColor.CGColor);
[self drawRectangle:trackFrame cornerRadius:self.trackCornerRadius context:context];
}
if (!_startHidden || self.displayEdges)
{
@ -313,6 +360,13 @@ const CGFloat TGPhotoEditorSliderViewInternalMargin = 7.0f;
[self setNeedsDisplay];
}
- (void)setMinimumUndottedValue:(int)minimumUndottedValue {
if (_minimumUndottedValue != minimumUndottedValue) {
_minimumUndottedValue = minimumUndottedValue;
[self setNeedsDisplay];
}
}
#pragma mark - Properties
- (bool)isTracking

@ -989,22 +989,35 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: {
dismissAction()
var revoke = false
updateState { state in
if !state.revokingPrivateLink {
revoke = true
return state.withUpdatedRevokingPrivateLink(true)
} else {
return state
let _ = (context.account.postbox.transaction { transaction -> String? in
if let cachedData = transaction.getPeerCachedData(peerId: peerId) {
if let cachedData = cachedData as? CachedChannelData {
return cachedData.exportedInvitation?.link
} else if let cachedData = cachedData as? CachedGroupData {
return cachedData.exportedInvitation?.link
}
}
}
if revoke {
// revokeLinkDisposable.set((revokePersistentPeerExportedInvitation(account: context.account, peerId: peerId) |> deliverOnMainQueue).start(completed: {
// updateState {
// $0.withUpdatedRevokingPrivateLink(false)
// }
// }))
}
return nil
} |> deliverOnMainQueue).start(next: { link in
if let link = link {
var revoke = false
updateState { state in
if !state.revokingPrivateLink {
revoke = true
return state.withUpdatedRevokingPrivateLink(true)
} else {
return state
}
}
if revoke {
revokeLinkDisposable.set((revokePeerExportedInvitation(account: context.account, peerId: peerId, link: link) |> deliverOnMainQueue).start(completed: {
updateState {
$0.withUpdatedRevokingPrivateLink(false)
}
}))
}
}
})
})
]),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])

@ -0,0 +1,321 @@
import Foundation
import UIKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import SyncCore
import TelegramPresentationData
import ItemListUI
import PresentationDataUtils
import AccountContext
import ChatListFilterSettingsHeaderItem
private final class PeerAutoremoveSetupArguments {
let toggleGlobal: (Bool) -> Void
let updateValue: (Int32) -> Void
init(toggleGlobal: @escaping (Bool) -> Void, updateValue: @escaping (Int32) -> Void) {
self.toggleGlobal = toggleGlobal
self.updateValue = updateValue
}
}
private enum PeerAutoremoveSetupSection: Int32 {
case header
case time
case global
}
private enum PeerAutoremoveSetupEntry: ItemListNodeEntry {
case header
case timeHeader(String)
case timeValue(Int32, Int32)
case timeComment(String)
case globalSwitch(String, Bool)
var section: ItemListSectionId {
switch self {
case .header:
return PeerAutoremoveSetupSection.header.rawValue
case .timeHeader, .timeValue, .timeComment:
return PeerAutoremoveSetupSection.time.rawValue
case .globalSwitch:
return PeerAutoremoveSetupSection.global.rawValue
}
}
var stableId: Int32 {
switch self {
case .header:
return 0
case .timeHeader:
return 1
case .timeValue:
return 2
case .timeComment:
return 3
case .globalSwitch:
return 4
}
}
static func ==(lhs: PeerAutoremoveSetupEntry, rhs: PeerAutoremoveSetupEntry) -> Bool {
switch lhs {
case .header:
if case .header = rhs {
return true
} else {
return false
}
case let .timeHeader(lhsText):
if case let .timeHeader(rhsText) = rhs, lhsText == rhsText {
return true
} else {
return false
}
case let .timeValue(lhsValue, lhsMaxValue):
if case let .timeValue(rhsValue, rhsMaxValue) = rhs, lhsValue == rhsValue, lhsMaxValue == rhsMaxValue {
return true
} else {
return false
}
case let .timeComment(lhsText):
if case let .timeComment(rhsText) = rhs, lhsText == rhsText {
return true
} else {
return false
}
case let .globalSwitch(lhsText, lhsValue):
if case let .globalSwitch(rhsText, rhsValue) = rhs, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
}
}
static func <(lhs: PeerAutoremoveSetupEntry, rhs: PeerAutoremoveSetupEntry) -> Bool {
return lhs.stableId < rhs.stableId
}
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
let arguments = arguments as! PeerAutoremoveSetupArguments
switch self {
case .header:
return ChatListFilterSettingsHeaderItem(theme: presentationData.theme, text: "", animation: .autoRemove, sectionId: self.section)
case let .timeHeader(text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .timeValue(value, maxValue):
return PeerRemoveTimeoutItem(theme: presentationData.theme, value: value, maxValue: maxValue, enabled: true, sectionId: self.section, updated: { value in
arguments.updateValue(value)
}, tag: nil)
case let .timeComment(text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .globalSwitch(text, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.toggleGlobal(value)
})
}
}
}
private struct PeerAutoremoveSetupState: Equatable {
var changedValue: Int32?
var changedGlobalValue: Bool?
var applyingSetting: Bool = false
}
private func peerAutoremoveSetupEntries(peer: Peer?, presentationData: PresentationData, defaultMyValue: Int32, peerValue: Int32, defaultGlobalValue: Bool, state: PeerAutoremoveSetupState) -> [PeerAutoremoveSetupEntry] {
var entries: [PeerAutoremoveSetupEntry] = []
let globalValue = state.changedGlobalValue ?? defaultGlobalValue
let resolvedValue: Int32
let resolvedMaxValue: Int32
if peer is TelegramUser {
resolvedValue = state.changedValue ?? defaultMyValue
resolvedMaxValue = peerValue
} else {
resolvedValue = state.changedValue ?? peerValue
resolvedMaxValue = Int32.max
}
//TODO:localize
entries.append(.header)
entries.append(.timeHeader("AUTO-DELETE MESSAGES"))
entries.append(.timeValue(resolvedValue, resolvedMaxValue))
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
entries.append(.timeComment("Automatically delete messages sent in this channel after a certain period of time."))
} else {
if resolvedMaxValue != Int32.max {
entries.append(.timeComment("\(peer?.compactDisplayTitle ?? "") has set messages to auto-delete in \(timeIntervalString(strings: presentationData.strings, value: resolvedMaxValue)). You can't cancel it or make this interval longer."))
} else {
entries.append(.timeComment("Automatically delete messages sent in this chat after a certain period of time."))
}
}
if let user = peer as? TelegramUser {
entries.append(.globalSwitch("Also auto-delete for \(user.compactDisplayTitle)", globalValue))
}
return entries
}
public enum PeerAutoremoveSetupScreenResult {
public struct Updated {
public var myValue: Int32?
public var limitedByValue: Int32?
}
case unchanged
case updated(Updated)
}
public func peerAutoremoveSetupScreen(context: AccountContext, peerId: PeerId, completion: @escaping (PeerAutoremoveSetupScreenResult) -> Void = { _ in }) -> ViewController {
let statePromise = ValuePromise(PeerAutoremoveSetupState(), ignoreRepeated: true)
let stateValue = Atomic(value: PeerAutoremoveSetupState())
let updateState: ((PeerAutoremoveSetupState) -> PeerAutoremoveSetupState) -> Void = { f in
statePromise.set(stateValue.modify { f($0) })
}
var pushControllerImpl: ((ViewController) -> Void)?
var dismissImpl: (() -> Void)?
let actionsDisposable = DisposableSet()
let applyDisposable = MetaDisposable()
actionsDisposable.add(applyDisposable)
let arguments = PeerAutoremoveSetupArguments(toggleGlobal: { value in
updateState { state in
var state = state
state.changedGlobalValue = value
return state
}
}, updateValue: { value in
updateState { state in
var state = state
state.changedValue = value
return state
}
})
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get(), context.account.viewTracker.peerView(peerId))
|> deliverOnMainQueue
|> map { presentationData, state, view -> (ItemListControllerState, (ItemListNodeState, Any)) in
var defaultMyValue: Int32 = Int32.max
var peerValue: Int32 = Int32.max
var defaultGlobalValue = true
if let cachedData = view.cachedData as? CachedChannelData {
if case let .known(value) = cachedData.autoremoveTimeout {
defaultMyValue = value?.myValue ?? Int32.max
peerValue = value?.peerValue ?? Int32.max
defaultGlobalValue = value?.isGlobal ?? true
}
} else if let cachedData = view.cachedData as? CachedGroupData {
if case let .known(value) = cachedData.autoremoveTimeout {
defaultMyValue = value?.myValue ?? Int32.max
peerValue = value?.peerValue ?? Int32.max
defaultGlobalValue = value?.isGlobal ?? true
}
} else if let cachedData = view.cachedData as? CachedUserData {
if case let .known(value) = cachedData.autoremoveTimeout {
defaultMyValue = value?.myValue ?? Int32.max
peerValue = value?.peerValue ?? Int32.max
defaultGlobalValue = value?.isGlobal ?? true
}
}
let peer = view.peers[view.peerId]
let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
dismissImpl?()
})
var rightNavigationButton: ItemListNavigationButton?
if state.applyingSetting {
rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {})
} else {
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
var changedValue: Int32?
var globalValue: Bool?
updateState { state in
var state = state
state.applyingSetting = true
changedValue = state.changedValue
globalValue = state.changedGlobalValue
return state
}
let resolvedDefaultValue: Int32
if peer is TelegramUser {
resolvedDefaultValue = defaultMyValue
} else {
resolvedDefaultValue = peerValue
}
var updated = false
if let changedValue = changedValue, changedValue != resolvedDefaultValue {
updated = true
}
if let globalValue = globalValue, globalValue != defaultGlobalValue {
updated = true
}
if updated {
var resolvedValue: Int32? = changedValue ?? resolvedDefaultValue
if resolvedValue == Int32.max {
resolvedValue = nil
}
let resolvedMaxValue: Int32
if peer is TelegramUser {
resolvedMaxValue = peerValue
} else {
resolvedMaxValue = Int32.max
}
let resolvedGlobalValue = globalValue ?? defaultGlobalValue
let signal = setChatMessageAutoremoveTimeoutInteractively(account: context.account, peerId: peerId, timeout: resolvedValue, isGlobal: resolvedGlobalValue)
|> deliverOnMainQueue
applyDisposable.set((signal
|> deliverOnMainQueue).start(error: { _ in
}, completed: {
dismissImpl?()
if resolvedValue != resolvedDefaultValue {
completion(.updated(PeerAutoremoveSetupScreenResult.Updated(
myValue: resolvedValue,
limitedByValue: resolvedMaxValue == Int32.max ? nil : resolvedMaxValue
)))
} else {
completion(.unchanged)
}
}))
} else {
dismissImpl?()
completion(.unchanged)
}
})
}
//TODO:localize
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text("Auto-Deletion"), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: peerAutoremoveSetupEntries(peer: peer, presentationData: presentationData, defaultMyValue: defaultMyValue, peerValue: peerValue, defaultGlobalValue: defaultGlobalValue, state: state), style: .blocks)
return (controllerState, (listState, arguments))
}
|> afterDisposed {
actionsDisposable.dispose()
}
let controller = ItemListController(context: context, state: signal)
controller.navigationPresentation = .modal
dismissImpl = { [weak controller] in
controller?.view.endEditing(true)
controller?.dismiss()
}
pushControllerImpl = { [weak controller] c in
controller?.push(c)
}
return controller
}

@ -0,0 +1,343 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import SwiftSignalKit
import TelegramCore
import SyncCore
import TelegramPresentationData
import TelegramUIPreferences
import LegacyComponents
import ItemListUI
import PresentationDataUtils
import AppBundle
private func mapTimeoutToSliderValue(_ value: Int32) -> CGFloat {
switch value {
case 24 * 60 * 60:
return 0.0
case 7 * 24 * 60 * 60:
return 1.0
default:
return 2.0
}
}
private func mapSliderValueToTimeout(_ value: CGFloat) -> Int32 {
switch value {
case 0.0:
return 24 * 60 * 60
case 1.0:
return 7 * 24 * 60 * 60
default:
return Int32.max
}
}
class PeerRemoveTimeoutItem: ListViewItem, ItemListItem {
let theme: PresentationTheme
let value: Int32
let maxValue: Int32
let enabled: Bool
let sectionId: ItemListSectionId
let updated: (Int32) -> Void
let tag: ItemListItemTag?
init(theme: PresentationTheme, value: Int32, maxValue: Int32, enabled: Bool = true, sectionId: ItemListSectionId, updated: @escaping (Int32) -> Void, tag: ItemListItemTag? = nil) {
self.theme = theme
self.value = value
self.maxValue = maxValue
self.enabled = enabled
self.sectionId = sectionId
self.updated = updated
self.tag = tag
}
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
async {
let node = PeerRemoveTimeoutItemNode()
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
node.contentSize = layout.contentSize
node.insets = layout.insets
Queue.mainQueue().async {
completion(node, {
return (nil, { _ in apply() })
})
}
}
}
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
Queue.mainQueue().async {
if let nodeValue = node() as? PeerRemoveTimeoutItemNode {
let makeLayout = nodeValue.asyncLayout()
async {
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
Queue.mainQueue().async {
completion(layout, { _ in
apply()
})
}
}
}
}
}
}
private func generateKnobImage() -> UIImage? {
return generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setShadow(offset: CGSize(width: 0.0, height: -3.0), blur: 8.0, color: UIColor(white: 0.0, alpha: 0.15).cgColor)
context.setFillColor(UIColor.white.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: 6.0, y: 6.0), size: CGSize(width: 28.0, height: 28.0)))
})
}
class PeerRemoveTimeoutItemNode: ListViewItemNode, ItemListItemNode {
private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode
private let maskNode: ASImageNode
private var sliderView: TGPhotoEditorSliderView?
private let titleNodes: [TextNode]
private let disabledOverlayNode: ASDisplayNode
private var item: PeerRemoveTimeoutItem?
private var layoutParams: ListViewItemLayoutParams?
var tag: ItemListItemTag? {
return self.item?.tag
}
init() {
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true
self.topStripeNode = ASDisplayNode()
self.topStripeNode.isLayerBacked = true
self.bottomStripeNode = ASDisplayNode()
self.bottomStripeNode.isLayerBacked = true
self.maskNode = ASImageNode()
self.disabledOverlayNode = ASDisplayNode()
self.titleNodes = (0 ..< 3).map { _ in
return TextNode()
}
super.init(layerBacked: false, dynamicBounce: false)
self.titleNodes.forEach(self.addSubnode)
self.addSubnode(self.disabledOverlayNode)
}
override func didLoad() {
super.didLoad()
let sliderView = TGPhotoEditorSliderView()
sliderView.enablePanHandling = true
sliderView.trackCornerRadius = 1.0
sliderView.lineSize = 2.0
sliderView.dotSize = 5.0
sliderView.minimumValue = 0.0
sliderView.maximumValue = 2.0
sliderView.startValue = 0.0
sliderView.positionsCount = 3
sliderView.useLinesForPositions = true
sliderView.minimumUndottedValue = 0
sliderView.disablesInteractiveTransitionGestureRecognizer = true
if let item = self.item, let params = self.layoutParams {
sliderView.isUserInteractionEnabled = item.enabled
sliderView.minimumUndottedValue = 0
sliderView.value = mapTimeoutToSliderValue(item.value)
sliderView.minimumUndottedValue = Int32(mapTimeoutToSliderValue(item.maxValue))
sliderView.backgroundColor = item.theme.list.itemBlocksBackgroundColor
sliderView.backColor = item.theme.list.disclosureArrowColor
sliderView.trackColor = item.enabled ? item.theme.list.itemAccentColor : item.theme.list.itemDisabledTextColor
sliderView.knobImage = generateKnobImage()
let sliderInset: CGFloat = params.leftInset + 16.0
sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + sliderInset, y: 38.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - sliderInset * 2.0, height: 44.0))
}
self.view.insertSubview(sliderView, belowSubview: self.disabledOverlayNode.view)
sliderView.addTarget(self, action: #selector(self.sliderValueChanged), for: .valueChanged)
self.sliderView = sliderView
}
func asyncLayout() -> (_ item: PeerRemoveTimeoutItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let currentItem = self.item
let makeTitleNodeLayouts = self.titleNodes.map(TextNode.asyncLayout)
return { item, params, neighbors in
var themeUpdated = false
if currentItem?.theme !== item.theme {
themeUpdated = true
}
let contentSize: CGSize
var insets: UIEdgeInsets
let separatorHeight = UIScreenPixel
let titleLayouts = zip(0 ..< makeTitleNodeLayouts.count, makeTitleNodeLayouts).map { index, makeLayout -> (TextNodeLayout, () -> TextNode) in
let text: String
//TODO:localize
switch index {
case 0:
text = "After 24 hours"
case 1:
text = "After 7 days"
default:
text = "Never"
}
return makeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: text, font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor), maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: 100.0)))
}
contentSize = CGSize(width: params.width, height: 88.0)
insets = itemListNeighborsGroupedInsets(neighbors)
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
let layoutSize = layout.size
return (layout, { [weak self] in
if let strongSelf = self {
let firstTime = strongSelf.item == nil
strongSelf.item = item
strongSelf.layoutParams = params
let leftInset = 16.0 + params.leftInset
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
strongSelf.disabledOverlayNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4)
strongSelf.disabledOverlayNode.isHidden = item.enabled
strongSelf.disabledOverlayNode.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 8.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: 44.0))
if strongSelf.backgroundNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
}
if strongSelf.topStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
}
if strongSelf.bottomStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
}
if strongSelf.maskNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
}
let hasCorners = itemListHasRoundedBlockLayout(params)
var hasTopCorners = false
var hasBottomCorners = false
switch neighbors.top {
case .sameSection(false):
strongSelf.topStripeNode.isHidden = true
default:
hasTopCorners = true
strongSelf.topStripeNode.isHidden = hasCorners
}
let bottomStripeInset: CGFloat
let bottomStripeOffset: CGFloat
switch neighbors.bottom {
case .sameSection(false):
bottomStripeInset = params.leftInset + 16.0
bottomStripeOffset = -separatorHeight
default:
bottomStripeInset = 0.0
bottomStripeOffset = 0.0
hasBottomCorners = true
strongSelf.bottomStripeNode.isHidden = hasCorners
}
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
zip(0 ..< titleLayouts.count, titleLayouts).forEach { index, layoutAndApply in
let textNode = layoutAndApply.1()
let size = layoutAndApply.0.size
switch index {
case 0:
textNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 13.0), size: size)
case 1:
textNode.frame = CGRect(origin: CGPoint(x: floor((params.width - size.width) / 2.0), y: 13.0), size: size)
default:
textNode.frame = CGRect(origin: CGPoint(x: params.width - size.width - 16.0, y: 13.0), size: size)
}
}
if let sliderView = strongSelf.sliderView {
sliderView.isUserInteractionEnabled = item.enabled
sliderView.trackColor = item.enabled ? item.theme.list.itemAccentColor : item.theme.list.itemDisabledTextColor
sliderView.minimumUndottedValue = Int32(mapTimeoutToSliderValue(item.maxValue))
if themeUpdated {
sliderView.backgroundColor = item.theme.list.itemBlocksBackgroundColor
sliderView.backColor = item.theme.list.disclosureArrowColor
sliderView.knobImage = generateKnobImage()
}
let value: CGFloat
switch item.value {
case 24 * 60 * 60:
value = 0.0
case 7 * 24 * 60 * 60:
value = 1.0
default:
value = 2.0
}
if firstTime {
sliderView.value = value
}
let sliderInset: CGFloat = leftInset
sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + sliderInset, y: 38.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - sliderInset * 2.0, height: 44.0))
}
}
})
}
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
}
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
}
@objc func sliderValueChanged() {
guard let sliderView = self.sliderView else {
return
}
let value: Int32
switch sliderView.value {
case 0.0:
value = 24 * 60 * 60
case 1.0:
value = 7 * 24 * 60 * 60
default:
value = Int32.max
}
self.item?.updated(value)
}
}

@ -79,6 +79,17 @@ public enum PostboxAccessChallengeData: PostboxCoding, Equatable, Codable {
return true
}
}
public var lockId: String? {
switch self {
case .none:
return nil
case let .numericalPassword(value):
return "numericalPassword:\(value)"
case let .plaintextPassword(value):
return "plaintextPassword:\(value)"
}
}
}
public struct AuthAccountRecord: PostboxCoding, Codable {

@ -559,31 +559,31 @@ final class MutableChatListView {
return self.sampledState.hole
}
private func renderEntry(_ entry: MutableChatListEntry, postbox: Postbox, renderMessage: (IntermediateMessage) -> Message, getPeer: (PeerId) -> Peer?, getPeerNotificationSettings: (PeerId) -> PeerNotificationSettings?, getPeerPresence: (PeerId) -> PeerPresence?) -> MutableChatListEntry? {
private func renderEntry(_ entry: MutableChatListEntry, postbox: Postbox) -> MutableChatListEntry? {
switch entry {
case let .IntermediateMessageEntry(index, messageIndex):
var renderedMessages: [Message] = []
if let messageIndex = messageIndex {
if let messageGroup = postbox.messageHistoryTable.getMessageGroup(at: messageIndex, limit: 10) {
renderedMessages.append(contentsOf: messageGroup.compactMap(renderMessage))
renderedMessages.append(contentsOf: messageGroup.compactMap(postbox.renderIntermediateMessage))
}
}
var peers = SimpleDictionary<PeerId, Peer>()
var notificationSettings: PeerNotificationSettings?
var presence: PeerPresence?
var isContact: Bool = false
if let peer = getPeer(index.messageIndex.id.peerId) {
if let peer = postbox.peerTable.get(index.messageIndex.id.peerId) {
peers[peer.id] = peer
if let associatedPeerId = peer.associatedPeerId {
if let associatedPeer = getPeer(associatedPeerId) {
if let associatedPeer = postbox.peerTable.get(associatedPeerId) {
peers[associatedPeer.id] = associatedPeer
}
notificationSettings = getPeerNotificationSettings(associatedPeerId)
presence = getPeerPresence(associatedPeerId)
notificationSettings = postbox.peerNotificationSettingsTable.getEffective(associatedPeerId)
presence = postbox.peerPresenceTable.get(associatedPeerId)
isContact = postbox.contactsTable.isContact(peerId: associatedPeerId)
} else {
notificationSettings = getPeerNotificationSettings(index.messageIndex.id.peerId)
presence = getPeerPresence(index.messageIndex.id.peerId)
notificationSettings = postbox.peerNotificationSettingsTable.getEffective(index.messageIndex.id.peerId)
presence = postbox.peerPresenceTable.get(index.messageIndex.id.peerId)
isContact = postbox.contactsTable.isContact(peerId: peer.id)
}
}
@ -597,9 +597,9 @@ final class MutableChatListView {
}
}
func render(postbox: Postbox, renderMessage: (IntermediateMessage) -> Message, getPeer: (PeerId) -> Peer?, getPeerNotificationSettings: (PeerId) -> PeerNotificationSettings?, getPeerPresence: (PeerId) -> PeerPresence?) {
func render(postbox: Postbox) {
for i in 0 ..< self.additionalItemEntries.count {
if let updatedEntry = self.renderEntry(self.additionalItemEntries[i].entry, postbox: postbox, renderMessage: renderMessage, getPeer: getPeer, getPeerNotificationSettings: getPeerNotificationSettings, getPeerPresence: getPeerPresence) {
if let updatedEntry = self.renderEntry(self.additionalItemEntries[i].entry, postbox: postbox) {
self.additionalItemEntries[i].entry = updatedEntry
}
}

@ -15,7 +15,7 @@ final class MutableContactPeersView {
self.includePresences = includePresences
}
func replay(replacePeerIds: Set<PeerId>?, updatedPeerPresences: [PeerId: PeerPresence], getPeer: (PeerId) -> Peer?, getPeerPresence: (PeerId) -> PeerPresence?) -> Bool {
func replay(postbox: Postbox, replacePeerIds: Set<PeerId>?, updatedPeerPresences: [PeerId: PeerPresence]) -> Bool {
var updated = false
if let replacePeerIds = replacePeerIds {
let removedPeerIds = self.peerIds.subtracting(replacePeerIds)
@ -29,11 +29,11 @@ final class MutableContactPeersView {
}
for peerId in addedPeerIds {
if let peer = getPeer(peerId) {
if let peer = postbox.peerTable.get(peerId) {
self.peers[peerId] = peer
}
if self.includePresences {
if let presence = getPeerPresence(peerId) {
if let presence = postbox.peerPresenceTable.get(peerId) {
self.peerPresences[peerId] = presence
}
}

@ -493,6 +493,7 @@ public struct MessageForwardInfo: Equatable {
public protocol MessageAttribute: class, PostboxCoding {
var associatedPeerIds: [PeerId] { get }
var associatedMessageIds: [MessageId] { get }
var automaticTimestampBasedAttribute: (UInt16, Int32)? { get }
}
public extension MessageAttribute {
@ -503,6 +504,10 @@ public extension MessageAttribute {
var associatedMessageIds: [MessageId] {
return []
}
var automaticTimestampBasedAttribute: (UInt16, Int32)? {
return nil
}
}
public struct MessageGroupInfo: Equatable {

@ -83,13 +83,14 @@ final class MessageHistoryTable: Table {
let threadsTable: MessageHistoryThreadsTable
let globalTagsTable: GlobalMessageHistoryTagsTable
let localTagsTable: LocalMessageHistoryTagsTable
let timeBasedAttributesTable: TimestampBasedMessageAttributesTable
let readStateTable: MessageHistoryReadStateTable
let synchronizeReadStateTable: MessageHistorySynchronizeReadStateTable
let textIndexTable: MessageHistoryTextIndexTable
let summaryTable: MessageHistoryTagsSummaryTable
let pendingActionsTable: PendingMessageActionsTable
init(valueBox: ValueBox, table: ValueBoxTable, seedConfiguration: SeedConfiguration, messageHistoryIndexTable: MessageHistoryIndexTable, messageHistoryHoleIndexTable: MessageHistoryHoleIndexTable, messageMediaTable: MessageMediaTable, historyMetadataTable: MessageHistoryMetadataTable, globallyUniqueMessageIdsTable: MessageGloballyUniqueIdTable, unsentTable: MessageHistoryUnsentTable, failedTable: MessageHistoryFailedTable, tagsTable: MessageHistoryTagsTable, threadsTable: MessageHistoryThreadsTable, globalTagsTable: GlobalMessageHistoryTagsTable, localTagsTable: LocalMessageHistoryTagsTable, readStateTable: MessageHistoryReadStateTable, synchronizeReadStateTable: MessageHistorySynchronizeReadStateTable, textIndexTable: MessageHistoryTextIndexTable, summaryTable: MessageHistoryTagsSummaryTable, pendingActionsTable: PendingMessageActionsTable) {
init(valueBox: ValueBox, table: ValueBoxTable, seedConfiguration: SeedConfiguration, messageHistoryIndexTable: MessageHistoryIndexTable, messageHistoryHoleIndexTable: MessageHistoryHoleIndexTable, messageMediaTable: MessageMediaTable, historyMetadataTable: MessageHistoryMetadataTable, globallyUniqueMessageIdsTable: MessageGloballyUniqueIdTable, unsentTable: MessageHistoryUnsentTable, failedTable: MessageHistoryFailedTable, tagsTable: MessageHistoryTagsTable, threadsTable: MessageHistoryThreadsTable, globalTagsTable: GlobalMessageHistoryTagsTable, localTagsTable: LocalMessageHistoryTagsTable, timeBasedAttributesTable: TimestampBasedMessageAttributesTable, readStateTable: MessageHistoryReadStateTable, synchronizeReadStateTable: MessageHistorySynchronizeReadStateTable, textIndexTable: MessageHistoryTextIndexTable, summaryTable: MessageHistoryTagsSummaryTable, pendingActionsTable: PendingMessageActionsTable) {
self.seedConfiguration = seedConfiguration
self.messageHistoryIndexTable = messageHistoryIndexTable
self.messageHistoryHoleIndexTable = messageHistoryHoleIndexTable
@ -102,6 +103,7 @@ final class MessageHistoryTable: Table {
self.threadsTable = threadsTable
self.globalTagsTable = globalTagsTable
self.localTagsTable = localTagsTable
self.timeBasedAttributesTable = timeBasedAttributesTable
self.readStateTable = readStateTable
self.synchronizeReadStateTable = synchronizeReadStateTable
self.textIndexTable = textIndexTable
@ -175,7 +177,7 @@ final class MessageHistoryTable: Table {
return dict
}
private func processIndexOperationsCommitAccumulatedRemoveIndices(peerId: PeerId, accumulatedRemoveIndices: inout [MessageIndex], updatedCombinedState: inout CombinedPeerReadState?, invalidateReadState: inout Bool, unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], outputOperations: inout [MessageHistoryOperation], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation]) {
private func processIndexOperationsCommitAccumulatedRemoveIndices(peerId: PeerId, accumulatedRemoveIndices: inout [MessageIndex], updatedCombinedState: inout CombinedPeerReadState?, invalidateReadState: inout Bool, unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], outputOperations: inout [MessageHistoryOperation], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation]) {
if !accumulatedRemoveIndices.isEmpty {
let (combinedState, invalidate) = self.readStateTable.deleteMessages(peerId, indices: accumulatedRemoveIndices, incomingStatsInIndices: { peerId, namespace, indices in
return self.incomingMessageStatsInIndices(peerId, namespace: namespace, indices: indices)
@ -193,7 +195,7 @@ final class MessageHistoryTable: Table {
var globalIndicesWithMetadata: [(GlobalMessageTags, MessageIndex)] = []
for index in bucket {
let tagsAndGlobalTags = self.justRemove(index, unsentMessageOperations: &unsentMessageOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations)
let tagsAndGlobalTags = self.justRemove(index, unsentMessageOperations: &unsentMessageOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: &timestampBasedMessageAttributesOperations)
if let (tags, globalTags) = tagsAndGlobalTags {
indicesWithMetadata.append((index, tags))
@ -220,7 +222,7 @@ final class MessageHistoryTable: Table {
}
}
private func processIndexOperations(_ peerId: PeerId, operations: [MessageHistoryIndexOperation], processedOperationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation]) {
private func processIndexOperations(_ peerId: PeerId, operations: [MessageHistoryIndexOperation], processedOperationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation]) {
let sharedKey = self.key(MessageIndex(id: MessageId(peerId: PeerId(namespace: 0, id: 0), namespace: 0, id: 0), timestamp: 0))
let sharedBuffer = WriteBuffer()
let sharedEncoder = PostboxEncoder()
@ -251,7 +253,7 @@ final class MessageHistoryTable: Table {
for operation in operations {
switch operation {
case let .InsertMessage(storeMessage):
processIndexOperationsCommitAccumulatedRemoveIndices(peerId: peerId, accumulatedRemoveIndices: &accumulatedRemoveIndices, updatedCombinedState: &updatedCombinedState, invalidateReadState: &invalidateReadState, unsentMessageOperations: &unsentMessageOperations, outputOperations: &outputOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations)
processIndexOperationsCommitAccumulatedRemoveIndices(peerId: peerId, accumulatedRemoveIndices: &accumulatedRemoveIndices, updatedCombinedState: &updatedCombinedState, invalidateReadState: &invalidateReadState, unsentMessageOperations: &unsentMessageOperations, outputOperations: &outputOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: &timestampBasedMessageAttributesOperations)
let (message, updatedGroupInfos) = self.justInsertMessage(storeMessage, sharedKey: sharedKey, sharedBuffer: sharedBuffer, sharedEncoder: sharedEncoder, localTagsOperations: &localTagsOperations, updateExistingMedia: &updateExistingMedia)
outputOperations.append(.InsertMessage(message))
@ -301,15 +303,20 @@ final class MessageHistoryTable: Table {
if !message.localTags.isEmpty {
self.localTagsTable.set(id: message.id, tags: message.localTags, previousTags: [], operations: &localTagsOperations)
}
for attribute in MessageHistoryTable.renderMessageAttributes(message) {
if let (tag, timestamp) = attribute.automaticTimestampBasedAttribute {
self.timeBasedAttributesTable.set(tag: tag, id: message.id, timestamp: timestamp, operations: &timestampBasedMessageAttributesOperations)
}
}
if !message.flags.intersection(.IsIncomingMask).isEmpty {
accumulatedAddedIncomingMessageIndices.insert(message.index)
}
case let .InsertExistingMessage(storeMessage):
commitAccumulatedAddedIndices()
processIndexOperationsCommitAccumulatedRemoveIndices(peerId: peerId, accumulatedRemoveIndices: &accumulatedRemoveIndices, updatedCombinedState: &updatedCombinedState, invalidateReadState: &invalidateReadState, unsentMessageOperations: &unsentMessageOperations, outputOperations: &outputOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations)
processIndexOperationsCommitAccumulatedRemoveIndices(peerId: peerId, accumulatedRemoveIndices: &accumulatedRemoveIndices, updatedCombinedState: &updatedCombinedState, invalidateReadState: &invalidateReadState, unsentMessageOperations: &unsentMessageOperations, outputOperations: &outputOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: &timestampBasedMessageAttributesOperations)
var updatedGroupInfos: [MessageId: MessageGroupInfo] = [:]
if let (message, previousTags) = self.justUpdate(storeMessage.index, message: storeMessage, keepLocalTags: true, sharedKey: sharedKey, sharedBuffer: sharedBuffer, sharedEncoder: sharedEncoder, unsentMessageOperations: &unsentMessageOperations, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, updatedGroupInfos: &updatedGroupInfos, localTagsOperations: &localTagsOperations, updatedMedia: &updatedMedia) {
if let (message, previousTags) = self.justUpdate(storeMessage.index, message: storeMessage, keepLocalTags: true, sharedKey: sharedKey, sharedBuffer: sharedBuffer, sharedEncoder: sharedEncoder, unsentMessageOperations: &unsentMessageOperations, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, updatedGroupInfos: &updatedGroupInfos, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: &timestampBasedMessageAttributesOperations, updatedMedia: &updatedMedia) {
outputOperations.append(.Remove([(storeMessage.index, previousTags)]))
outputOperations.append(.InsertMessage(message))
if !updatedGroupInfos.isEmpty {
@ -321,10 +328,10 @@ final class MessageHistoryTable: Table {
accumulatedRemoveIndices.append(index)
case let .Update(index, storeMessage):
commitAccumulatedAddedIndices()
processIndexOperationsCommitAccumulatedRemoveIndices(peerId: peerId, accumulatedRemoveIndices: &accumulatedRemoveIndices, updatedCombinedState: &updatedCombinedState, invalidateReadState: &invalidateReadState, unsentMessageOperations: &unsentMessageOperations, outputOperations: &outputOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations)
processIndexOperationsCommitAccumulatedRemoveIndices(peerId: peerId, accumulatedRemoveIndices: &accumulatedRemoveIndices, updatedCombinedState: &updatedCombinedState, invalidateReadState: &invalidateReadState, unsentMessageOperations: &unsentMessageOperations, outputOperations: &outputOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: &timestampBasedMessageAttributesOperations)
var updatedGroupInfos: [MessageId: MessageGroupInfo] = [:]
if let (message, previousTags) = self.justUpdate(index, message: storeMessage, keepLocalTags: false, sharedKey: sharedKey, sharedBuffer: sharedBuffer, sharedEncoder: sharedEncoder, unsentMessageOperations: &unsentMessageOperations, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, updatedGroupInfos: &updatedGroupInfos, localTagsOperations: &localTagsOperations, updatedMedia: &updatedMedia) {
if let (message, previousTags) = self.justUpdate(index, message: storeMessage, keepLocalTags: false, sharedKey: sharedKey, sharedBuffer: sharedBuffer, sharedEncoder: sharedEncoder, unsentMessageOperations: &unsentMessageOperations, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, updatedGroupInfos: &updatedGroupInfos, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: &timestampBasedMessageAttributesOperations, updatedMedia: &updatedMedia) {
outputOperations.append(.Remove([(index, previousTags)]))
outputOperations.append(.InsertMessage(message))
if !updatedGroupInfos.isEmpty {
@ -340,10 +347,10 @@ final class MessageHistoryTable: Table {
}
case let .UpdateTimestamp(index, timestamp):
commitAccumulatedAddedIndices()
processIndexOperationsCommitAccumulatedRemoveIndices(peerId: peerId, accumulatedRemoveIndices: &accumulatedRemoveIndices, updatedCombinedState: &updatedCombinedState, invalidateReadState: &invalidateReadState, unsentMessageOperations: &unsentMessageOperations, outputOperations: &outputOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations)
processIndexOperationsCommitAccumulatedRemoveIndices(peerId: peerId, accumulatedRemoveIndices: &accumulatedRemoveIndices, updatedCombinedState: &updatedCombinedState, invalidateReadState: &invalidateReadState, unsentMessageOperations: &unsentMessageOperations, outputOperations: &outputOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: &timestampBasedMessageAttributesOperations)
var updatedGroupInfos: [MessageId: MessageGroupInfo] = [:]
let tagsAndGlobalTags = self.justUpdateTimestamp(index, timestamp: timestamp, unsentMessageOperations: &unsentMessageOperations, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, updatedGroupInfos: &updatedGroupInfos, localTagsOperations: &localTagsOperations, updatedMedia: &updatedMedia)
let tagsAndGlobalTags = self.justUpdateTimestamp(index, timestamp: timestamp, unsentMessageOperations: &unsentMessageOperations, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, updatedGroupInfos: &updatedGroupInfos, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: &timestampBasedMessageAttributesOperations, updatedMedia: &updatedMedia)
outputOperations.append(.UpdateTimestamp(index, timestamp))
if !updatedGroupInfos.isEmpty {
outputOperations.append(.UpdateGroupInfos(updatedGroupInfos))
@ -357,7 +364,7 @@ final class MessageHistoryTable: Table {
}
commitAccumulatedAddedIndices()
processIndexOperationsCommitAccumulatedRemoveIndices(peerId: peerId, accumulatedRemoveIndices: &accumulatedRemoveIndices, updatedCombinedState: &updatedCombinedState, invalidateReadState: &invalidateReadState, unsentMessageOperations: &unsentMessageOperations, outputOperations: &outputOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations)
processIndexOperationsCommitAccumulatedRemoveIndices(peerId: peerId, accumulatedRemoveIndices: &accumulatedRemoveIndices, updatedCombinedState: &updatedCombinedState, invalidateReadState: &invalidateReadState, unsentMessageOperations: &unsentMessageOperations, outputOperations: &outputOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: &timestampBasedMessageAttributesOperations)
if let updatedCombinedState = updatedCombinedState {
outputOperations.append(.UpdateReadState(peerId, updatedCombinedState))
@ -397,7 +404,7 @@ final class MessageHistoryTable: Table {
return internalStoreMessages
}
func addMessages(messages: [StoreMessage], operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], processMessages: (([PeerId : [StoreMessage]]) -> Void)?) -> [Int64: MessageId] {
func addMessages(messages: [StoreMessage], operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation], processMessages: (([PeerId : [StoreMessage]]) -> Void)?) -> [Int64: MessageId] {
let messagesByPeerId = self.messagesGroupedByPeerId(messages)
var globallyUniqueIdToMessageId: [Int64: MessageId] = [:]
var globalTagsInitialized = Set<GlobalMessageTags>()
@ -419,7 +426,7 @@ final class MessageHistoryTable: Table {
}
self.messageHistoryIndexTable.addMessages(internalPeerMessages, operations: &operations)
self.processIndexOperations(peerId, operations: operations, processedOperationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations)
self.processIndexOperations(peerId, operations: operations, processedOperationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: &timestampBasedMessageAttributesOperations)
}
processMessages?(messagesByPeerId)
@ -427,7 +434,7 @@ final class MessageHistoryTable: Table {
return globallyUniqueIdToMessageId
}
func removeMessages(_ messageIds: [MessageId], operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], forEachMedia: (Media) -> Void) {
func removeMessages(_ messageIds: [MessageId], operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation], forEachMedia: (Media) -> Void) {
for (peerId, messageIds) in self.messageIdsByPeerId(messageIds) {
var operations: [MessageHistoryIndexOperation] = []
@ -443,11 +450,11 @@ final class MessageHistoryTable: Table {
}
}
}
self.processIndexOperations(peerId, operations: operations, processedOperationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations)
self.processIndexOperations(peerId, operations: operations, processedOperationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: &timestampBasedMessageAttributesOperations)
}
}
func removeMessagesInRange(peerId: PeerId, namespace: MessageId.Namespace, minId: MessageId.Id, maxId: MessageId.Id, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], forEachMedia: (Media) -> Void) {
func removeMessagesInRange(peerId: PeerId, namespace: MessageId.Namespace, minId: MessageId.Id, maxId: MessageId.Id, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation], forEachMedia: (Media) -> Void) {
var operations: [MessageHistoryIndexOperation] = []
self.messageHistoryIndexTable.removeMessagesInRange(peerId: peerId, namespace: namespace, minId: minId, maxId: maxId, operations: &operations)
for operation in operations {
@ -460,20 +467,20 @@ final class MessageHistoryTable: Table {
}
}
self.processIndexOperations(peerId, operations: operations, processedOperationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations)
self.processIndexOperations(peerId, operations: operations, processedOperationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: &timestampBasedMessageAttributesOperations)
}
func clearHistory(peerId: PeerId, namespaces: MessageIdNamespaces, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], forEachMedia: (Media) -> Void) {
func clearHistory(peerId: PeerId, namespaces: MessageIdNamespaces, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation], forEachMedia: (Media) -> Void) {
let indices = self.allMessageIndices(peerId: peerId).filter { namespaces.contains($0.id.namespace) }
self.removeMessages(indices.map { $0.id }, operationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, forEachMedia: forEachMedia)
self.removeMessages(indices.map { $0.id }, operationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: &timestampBasedMessageAttributesOperations, forEachMedia: forEachMedia)
}
func removeAllMessagesWithAuthor(peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], forEachMedia: (Media) -> Void) {
func removeAllMessagesWithAuthor(peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation], forEachMedia: (Media) -> Void) {
let indices = self.allIndicesWithAuthor(peerId: peerId, authorId: authorId, namespace: namespace)
self.removeMessages(indices.map { $0.id }, operationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, forEachMedia: forEachMedia)
self.removeMessages(indices.map { $0.id }, operationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: &timestampBasedMessageAttributesOperations, forEachMedia: forEachMedia)
}
func removeAllMessagesWithGlobalTag(tag: GlobalMessageTags, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], forEachMedia: (Media) -> Void) {
func removeAllMessagesWithGlobalTag(tag: GlobalMessageTags, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation], forEachMedia: (Media) -> Void) {
var indices: [MessageIndex] = []
for entry in self.allIndicesWithGlobalTag(tag: tag) {
switch entry {
@ -483,24 +490,24 @@ final class MessageHistoryTable: Table {
break
}
}
self.removeMessages(indices.map { $0.id }, operationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, forEachMedia: forEachMedia)
self.removeMessages(indices.map { $0.id }, operationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: &timestampBasedMessageAttributesOperations, forEachMedia: forEachMedia)
}
func removeAllMessagesWithForwardAuthor(peerId: PeerId, forwardAuthorId: PeerId, namespace: MessageId.Namespace, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], forEachMedia: (Media) -> Void) {
func removeAllMessagesWithForwardAuthor(peerId: PeerId, forwardAuthorId: PeerId, namespace: MessageId.Namespace, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation], forEachMedia: (Media) -> Void) {
let indices = self.allIndicesWithForwardAuthor(peerId: peerId, forwardAuthorId: forwardAuthorId, namespace: namespace)
self.removeMessages(indices.map { $0.id }, operationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, forEachMedia: forEachMedia)
self.removeMessages(indices.map { $0.id }, operationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: &timestampBasedMessageAttributesOperations, forEachMedia: forEachMedia)
}
func updateMessage(_ id: MessageId, message: StoreMessage, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation]) {
func updateMessage(_ id: MessageId, message: StoreMessage, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation]) {
var operations: [MessageHistoryIndexOperation] = []
self.messageHistoryIndexTable.updateMessage(id, message: self.internalStoreMessages([message]).first!, operations: &operations)
self.processIndexOperations(id.peerId, operations: operations, processedOperationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations)
self.processIndexOperations(id.peerId, operations: operations, processedOperationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: &timestampBasedMessageAttributesOperations)
}
func updateMessageTimestamp(_ id: MessageId, timestamp: Int32, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation]) {
func updateMessageTimestamp(_ id: MessageId, timestamp: Int32, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation]) {
var operations: [MessageHistoryIndexOperation] = []
self.messageHistoryIndexTable.updateTimestamp(id, timestamp: timestamp, operations: &operations)
self.processIndexOperations(id.peerId, operations: operations, processedOperationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations)
self.processIndexOperations(id.peerId, operations: operations, processedOperationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: &timestampBasedMessageAttributesOperations)
}
func updateMedia(_ id: MediaId, media: Media?, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], updatedMessageIndices: inout Set<MessageIndex>) {
@ -812,7 +819,7 @@ final class MessageHistoryTable: Table {
})
}
func offsetPendingMessagesTimestamps(lowerBound: MessageId, excludeIds: Set<MessageId>, timestamp: Int32, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation]) {
func offsetPendingMessagesTimestamps(lowerBound: MessageId, excludeIds: Set<MessageId>, timestamp: Int32, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation]) {
var peerMessageIds: [MessageId] = []
for messageId in self.unsentTable.get() {
if messageId.peerId == lowerBound.peerId && messageId.namespace == lowerBound.namespace && messageId.id > lowerBound.id {
@ -825,7 +832,7 @@ final class MessageHistoryTable: Table {
peerMessageIds.sort()
for messageId in peerMessageIds.reversed() {
self.updateMessageTimestamp(messageId, timestamp: timestamp, operationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations)
self.updateMessageTimestamp(messageId, timestamp: timestamp, operationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: &timestampBasedMessageAttributesOperations)
}
}
@ -1247,7 +1254,7 @@ final class MessageHistoryTable: Table {
return result
}
private func justRemove(_ index: MessageIndex, unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation]) -> (MessageTags, GlobalMessageTags)? {
private func justRemove(_ index: MessageIndex, unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation]) -> (MessageTags, GlobalMessageTags)? {
let key = self.key(index)
if let value = self.valueBox.get(self.table, key: key) {
let resultTags: MessageTags
@ -1292,6 +1299,11 @@ final class MessageHistoryTable: Table {
if !message.localTags.isEmpty {
self.localTagsTable.set(id: index.id, tags: [], previousTags: message.localTags, operations: &localTagsOperations)
}
for attribute in MessageHistoryTable.renderMessageAttributes(message) {
if let (tag, _) = attribute.automaticTimestampBasedAttribute {
self.timeBasedAttributesTable.remove(tag: tag, id: message.id, operations: &timestampBasedMessageAttributesOperations)
}
}
for mediaId in message.referencedMedia {
let _ = self.messageMediaTable.removeReference(mediaId)
@ -1409,7 +1421,7 @@ final class MessageHistoryTable: Table {
})
}
private func justUpdate(_ index: MessageIndex, message: InternalStoreMessage, keepLocalTags: Bool, sharedKey: ValueBoxKey, sharedBuffer: WriteBuffer, sharedEncoder: PostboxEncoder, unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], updatedGroupInfos: inout [MessageId: MessageGroupInfo], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], updatedMedia: inout [MediaId: Media?]) -> (IntermediateMessage, MessageTags)? {
private func justUpdate(_ index: MessageIndex, message: InternalStoreMessage, keepLocalTags: Bool, sharedKey: ValueBoxKey, sharedBuffer: WriteBuffer, sharedEncoder: PostboxEncoder, unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], updatedGroupInfos: inout [MessageId: MessageGroupInfo], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation], updatedMedia: inout [MediaId: Media?]) -> (IntermediateMessage, MessageTags)? {
if let previousMessage = self.getMessage(index) {
var mediaToUpdate: [Media] = []
@ -1503,6 +1515,41 @@ final class MessageHistoryTable: Table {
}
}
var previousTimestampBasedAttibutes: [UInt16: Int32] = [:]
for attribute in MessageHistoryTable.renderMessageAttributes(previousMessage) {
if let (tag, timestamp) = attribute.automaticTimestampBasedAttribute {
previousTimestampBasedAttibutes[tag] = timestamp
}
}
if previousMessage.id != message.id {
for tag in previousTimestampBasedAttibutes.keys {
self.timeBasedAttributesTable.remove(tag: tag, id: previousMessage.id, operations: &timestampBasedMessageAttributesOperations)
}
for attribute in message.attributes {
if let (tag, timestamp) = attribute.automaticTimestampBasedAttribute {
self.timeBasedAttributesTable.set(tag: tag, id: message.id, timestamp: timestamp, operations: &timestampBasedMessageAttributesOperations)
}
}
} else {
var updatedTimestampBasedAttibuteTags: [UInt16] = []
for attribute in message.attributes {
if let (tag, timestamp) = attribute.automaticTimestampBasedAttribute {
updatedTimestampBasedAttibuteTags.append(tag)
if previousTimestampBasedAttibutes[tag] != timestamp {
self.timeBasedAttributesTable.remove(tag: tag, id: previousMessage.id, operations: &timestampBasedMessageAttributesOperations)
self.timeBasedAttributesTable.set(tag: tag, id: message.id, timestamp: timestamp, operations: &timestampBasedMessageAttributesOperations)
}
}
}
for tag in previousTimestampBasedAttibutes.keys {
if !updatedTimestampBasedAttibuteTags.contains(tag) {
self.timeBasedAttributesTable.remove(tag: tag, id: previousMessage.id, operations: &timestampBasedMessageAttributesOperations)
}
}
}
//self.timeBasedAttributesTable.remove(tag: tag, id: message.id, operations: &timestampBasedMessageAttributesOperations)
if message.globallyUniqueId != previousMessage.globallyUniqueId {
if let globallyUniqueId = previousMessage.globallyUniqueId {
self.globallyUniqueMessageIdsTable.remove(peerId: message.id.peerId, globallyUniqueId: globallyUniqueId)
@ -1809,7 +1856,7 @@ final class MessageHistoryTable: Table {
}
}
private func justUpdateTimestamp(_ index: MessageIndex, timestamp: Int32, unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], updatedGroupInfos: inout [MessageId: MessageGroupInfo], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], updatedMedia: inout [MediaId: Media?]) -> (MessageTags, GlobalMessageTags)? {
private func justUpdateTimestamp(_ index: MessageIndex, timestamp: Int32, unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], updatedGroupInfos: inout [MessageId: MessageGroupInfo], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation], updatedMedia: inout [MediaId: Media?]) -> (MessageTags, GlobalMessageTags)? {
if let previousMessage = self.getMessage(index) {
var storeForwardInfo: StoreMessageForwardInfo?
if let forwardInfo = previousMessage.forwardInfo {
@ -1857,7 +1904,7 @@ final class MessageHistoryTable: Table {
let updatedIndex = MessageIndex(id: index.id, timestamp: timestamp)
let _ = self.justUpdate(index, message: InternalStoreMessage(id: previousMessage.id, timestamp: timestamp, globallyUniqueId: previousMessage.globallyUniqueId, groupingKey: previousMessage.groupingKey, threadId: previousMessage.threadId, flags: StoreMessageFlags(previousMessage.flags), tags: previousMessage.tags, globalTags: previousMessage.globalTags, localTags: previousMessage.localTags, forwardInfo: storeForwardInfo, authorId: previousMessage.authorId, text: previousMessage.text, attributes: parsedAttributes, media: parsedMedia), keepLocalTags: false, sharedKey: self.key(updatedIndex), sharedBuffer: WriteBuffer(), sharedEncoder: PostboxEncoder(), unsentMessageOperations: &unsentMessageOperations, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, updatedGroupInfos: &updatedGroupInfos, localTagsOperations: &localTagsOperations, updatedMedia: &updatedMedia)
let _ = self.justUpdate(index, message: InternalStoreMessage(id: previousMessage.id, timestamp: timestamp, globallyUniqueId: previousMessage.globallyUniqueId, groupingKey: previousMessage.groupingKey, threadId: previousMessage.threadId, flags: StoreMessageFlags(previousMessage.flags), tags: previousMessage.tags, globalTags: previousMessage.globalTags, localTags: previousMessage.localTags, forwardInfo: storeForwardInfo, authorId: previousMessage.authorId, text: previousMessage.text, attributes: parsedAttributes, media: parsedMedia), keepLocalTags: false, sharedKey: self.key(updatedIndex), sharedBuffer: WriteBuffer(), sharedEncoder: PostboxEncoder(), unsentMessageOperations: &unsentMessageOperations, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, updatedGroupInfos: &updatedGroupInfos, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: &timestampBasedMessageAttributesOperations, updatedMedia: &updatedMedia)
return (previousMessage.tags, previousMessage.globalTags)
} else {
return nil

@ -11,7 +11,7 @@ final class MutableMessageView {
self.stableId = message?.stableId
}
func replay(_ operations: [MessageHistoryOperation], updatedMedia: [MediaId: Media?], renderIntermediateMessage: (IntermediateMessage) -> Message) -> Bool {
func replay(postbox: Postbox, operations: [MessageHistoryOperation], updatedMedia: [MediaId: Media?]) -> Bool {
var updated = false
for operation in operations {
switch operation {
@ -28,7 +28,7 @@ final class MutableMessageView {
}
case let .InsertMessage(message):
if message.id == self.messageId || message.stableId == self.stableId {
self.message = renderIntermediateMessage(message)
self.message = postbox.renderIntermediateMessage(message)
self.stableId = message.stableId
updated = true
}

@ -6,14 +6,14 @@ final class MutablePeerMergedOperationLogView {
var tailIndex: Int32?
let limit: Int
init(tag: PeerOperationLogTag, limit: Int, getOperations: (PeerOperationLogTag, Int32, Int) -> [PeerMergedOperationLogEntry], getTailIndex: (PeerOperationLogTag) -> Int32?) {
init(postbox: Postbox, tag: PeerOperationLogTag, limit: Int) {
self.tag = tag
self.entries = getOperations(tag, 0, limit)
self.tailIndex = getTailIndex(tag)
self.entries = postbox.peerOperationLogTable.getMergedEntries(tag: tag, fromIndex: 0, limit: limit)
self.tailIndex = postbox.peerMergedOperationLogIndexTable.tailIndex(tag: tag)
self.limit = limit
}
func replay(operations: [PeerMergedOperationLogOperation], getOperations: (PeerOperationLogTag, Int32, Int) -> [PeerMergedOperationLogEntry], getTailIndex: (PeerOperationLogTag) -> Int32?) -> Bool {
func replay(postbox: Postbox, operations: [PeerMergedOperationLogOperation]) -> Bool {
var updated = false
var invalidatedTail = false
@ -65,7 +65,7 @@ final class MutablePeerMergedOperationLogView {
if updated {
if invalidatedTail {
self.tailIndex = getTailIndex(self.tag)
self.tailIndex = postbox.peerMergedOperationLogIndexTable.tailIndex(tag: self.tag)
}
if self.entries.count < self.limit {
if let tailIndex = self.tailIndex {
@ -74,7 +74,7 @@ final class MutablePeerMergedOperationLogView {
if !self.entries.isEmpty {
fromIndex = self.entries.last!.mergedIndex + 1
}
for entry in getOperations(self.tag, fromIndex, self.limit - self.entries.count) {
for entry in postbox.peerOperationLogTable.getMergedEntries(tag: self.tag, fromIndex: fromIndex, limit: self.limit - self.entries.count) {
self.entries.append(entry)
}
for i in 0 ..< self.entries.count {

@ -765,16 +765,6 @@ public final class Transaction {
self.postbox?.operationLogEnumerateEntries(peerId: peerId, tag: tag, f)
}
public func addTimestampBasedMessageAttribute(tag: UInt16, timestamp: Int32, messageId: MessageId) {
assert(!self.disposed)
self.postbox?.addTimestampBasedMessageAttribute(tag: tag, timestamp: timestamp, messageId: messageId)
}
public func removeTimestampBasedMessageAttribute(tag: UInt16, messageId: MessageId) {
assert(!self.disposed)
self.postbox?.removeTimestampBasedMessageAttribute(tag: tag, messageId: messageId)
}
public func enumeratePreferencesEntries(_ f: (PreferencesEntry) -> Bool) {
assert(!self.disposed)
self.postbox?.enumeratePreferencesEntries(f)
@ -1068,6 +1058,25 @@ public final class Transaction {
return postbox.chatListTable.getNamespaceEntries(groupId: groupId, namespace: namespace, summaryTag: summaryTag, messageIndexTable: postbox.messageHistoryIndexTable, messageHistoryTable: postbox.messageHistoryTable, peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable, readStateTable: postbox.readStateTable, summaryTable: postbox.messageHistoryTagsSummaryTable)
}
public func getTopChatListEntries(groupId: PeerGroupId, count: Int) -> [RenderedPeer] {
assert(!self.disposed)
guard let postbox = self.postbox else {
return []
}
return postbox.chatListTable.earlierEntryInfos(groupId: groupId, index: nil, messageHistoryTable: postbox.messageHistoryTable, peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable, count: count).compactMap { entry -> RenderedPeer? in
switch entry {
case let .message(index, _):
if let peer = self.getPeer(index.messageIndex.id.peerId) {
return RenderedPeer(peer: peer)
} else {
return nil
}
case .hole:
return nil
}
}
}
public func addHolesEverywhere(peerNamespaces: [PeerId.Namespace], holeNamespace: MessageId.Namespace) {
assert(!self.disposed)
self.postbox?.addHolesEverywhere(peerNamespaces: peerNamespaces, holeNamespace: holeNamespace)
@ -1077,6 +1086,11 @@ public final class Transaction {
assert(!self.disposed)
self.postbox?.reindexUnreadCounters()
}
public func searchPeers(query: String) -> [RenderedPeer] {
assert(!self.disposed)
return self.postbox?.searchPeers(query: query) ?? []
}
}
public enum PostboxResult {
@ -1394,7 +1408,7 @@ public final class Postbox {
self.timestampBasedMessageAttributesTable = TimestampBasedMessageAttributesTable(valueBox: self.valueBox, table: TimestampBasedMessageAttributesTable.tableSpec(34), indexTable: self.timestampBasedMessageAttributesIndexTable)
self.textIndexTable = MessageHistoryTextIndexTable(valueBox: self.valueBox, table: MessageHistoryTextIndexTable.tableSpec(41))
self.additionalChatListItemsTable = AdditionalChatListItemsTable(valueBox: self.valueBox, table: AdditionalChatListItemsTable.tableSpec(55))
self.messageHistoryTable = MessageHistoryTable(valueBox: self.valueBox, table: MessageHistoryTable.tableSpec(7), seedConfiguration: seedConfiguration, messageHistoryIndexTable: self.messageHistoryIndexTable, messageHistoryHoleIndexTable: self.messageHistoryHoleIndexTable, messageMediaTable: self.mediaTable, historyMetadataTable: self.messageHistoryMetadataTable, globallyUniqueMessageIdsTable: self.globallyUniqueMessageIdsTable, unsentTable: self.messageHistoryUnsentTable, failedTable: self.messageHistoryFailedTable, tagsTable: self.messageHistoryTagsTable, threadsTable: self.messageHistoryThreadsTable, globalTagsTable: self.globalMessageHistoryTagsTable, localTagsTable: self.localMessageHistoryTagsTable, readStateTable: self.readStateTable, synchronizeReadStateTable: self.synchronizeReadStateTable, textIndexTable: self.textIndexTable, summaryTable: self.messageHistoryTagsSummaryTable, pendingActionsTable: self.pendingMessageActionsTable)
self.messageHistoryTable = MessageHistoryTable(valueBox: self.valueBox, table: MessageHistoryTable.tableSpec(7), seedConfiguration: seedConfiguration, messageHistoryIndexTable: self.messageHistoryIndexTable, messageHistoryHoleIndexTable: self.messageHistoryHoleIndexTable, messageMediaTable: self.mediaTable, historyMetadataTable: self.messageHistoryMetadataTable, globallyUniqueMessageIdsTable: self.globallyUniqueMessageIdsTable, unsentTable: self.messageHistoryUnsentTable, failedTable: self.messageHistoryFailedTable, tagsTable: self.messageHistoryTagsTable, threadsTable: self.messageHistoryThreadsTable, globalTagsTable: self.globalMessageHistoryTagsTable, localTagsTable: self.localMessageHistoryTagsTable, timeBasedAttributesTable: self.timestampBasedMessageAttributesTable, readStateTable: self.readStateTable, synchronizeReadStateTable: self.synchronizeReadStateTable, textIndexTable: self.textIndexTable, summaryTable: self.messageHistoryTagsSummaryTable, pendingActionsTable: self.pendingMessageActionsTable)
self.peerChatStateTable = PeerChatStateTable(valueBox: self.valueBox, table: PeerChatStateTable.tableSpec(13))
self.peerNameTokenIndexTable = ReverseIndexReferenceTable<PeerIdReverseIndexReference>(valueBox: self.valueBox, table: ReverseIndexReferenceTable<PeerIdReverseIndexReference>.tableSpec(26))
self.peerNameIndexTable = PeerNameIndexTable(valueBox: self.valueBox, table: PeerNameIndexTable.tableSpec(27), peerTable: self.peerTable, peerNameTokenIndexTable: self.peerNameTokenIndexTable)
@ -1491,27 +1505,13 @@ public final class Postbox {
self.transactionStateVersion = self.metadataTable.transactionStateVersion()
self.viewTracker = ViewTracker(queue: self.queue, renderMessage: self.renderIntermediateMessage, getPeer: { peerId in
return self.peerTable.get(peerId)
}, getPeerNotificationSettings: { peerId in
return self.peerNotificationSettingsTable.getEffective(peerId)
}, getCachedPeerData: { peerId in
return self.cachedPeerDataTable.get(peerId)
}, getPeerPresence: { peerId in
return self.peerPresenceTable.get(peerId)
}, getPeerReadState: { peerId in
return self.readStateTable.getCombinedState(peerId)
}, operationLogGetOperations: { tag, fromIndex, limit in
return self.peerOperationLogTable.getMergedEntries(tag: tag, fromIndex: fromIndex, limit: limit)
}, operationLogGetTailIndex: { tag in
return self.peerMergedOperationLogIndexTable.tailIndex(tag: tag)
}, getTimestampBasedMessageAttributesHead: { tag in
return self.timestampBasedMessageAttributesTable.head(tag: tag)
}, getPreferencesEntry: { key in
return self.preferencesTable.get(key: key)
}, unsentMessageIds: self.messageHistoryUnsentTable.get(), synchronizePeerReadStateOperations: self.synchronizeReadStateTable.get(getCombinedPeerReadState: { peerId in
return self.readStateTable.getCombinedState(peerId)
}))
self.viewTracker = ViewTracker(
queue: self.queue,
unsentMessageIds: self.messageHistoryUnsentTable.get(),
synchronizePeerReadStateOperations: self.synchronizeReadStateTable.get(getCombinedPeerReadState: { peerId in
return self.readStateTable.getCombinedState(peerId)
})
)
print("(Postbox initialization took \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms")
@ -1640,7 +1640,7 @@ public final class Postbox {
fileprivate func addMessages(transaction: Transaction, messages: [StoreMessage], location: AddMessagesLocation) -> [Int64: MessageId] {
var addedMessagesByPeerId: [PeerId: [StoreMessage]] = [:]
let addResult = self.messageHistoryTable.addMessages(messages: messages, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, processMessages: { messagesByPeerId in
let addResult = self.messageHistoryTable.addMessages(messages: messages, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, timestampBasedMessageAttributesOperations: &self.currentTimestampBasedMessageAttributesOperations, processMessages: { messagesByPeerId in
addedMessagesByPeerId = messagesByPeerId
})
@ -1713,11 +1713,11 @@ public final class Postbox {
}
fileprivate func deleteMessages(_ messageIds: [MessageId], forEachMedia: (Media) -> Void) {
self.messageHistoryTable.removeMessages(messageIds, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, forEachMedia: forEachMedia)
self.messageHistoryTable.removeMessages(messageIds, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, timestampBasedMessageAttributesOperations: &self.currentTimestampBasedMessageAttributesOperations, forEachMedia: forEachMedia)
}
fileprivate func deleteMessagesInRange(peerId: PeerId, namespace: MessageId.Namespace, minId: MessageId.Id, maxId: MessageId.Id, forEachMedia: (Media) -> Void) {
self.messageHistoryTable.removeMessagesInRange(peerId: peerId, namespace: namespace, minId: minId, maxId: maxId, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, forEachMedia: forEachMedia)
self.messageHistoryTable.removeMessagesInRange(peerId: peerId, namespace: namespace, minId: minId, maxId: maxId, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, timestampBasedMessageAttributesOperations: &self.currentTimestampBasedMessageAttributesOperations, forEachMedia: forEachMedia)
}
fileprivate func withAllMessages(peerId: PeerId, namespace: MessageId.Namespace?, _ f: (Message) -> Bool) {
@ -1733,22 +1733,22 @@ public final class Postbox {
}
fileprivate func clearHistory(_ peerId: PeerId, namespaces: MessageIdNamespaces, forEachMedia: (Media) -> Void) {
self.messageHistoryTable.clearHistory(peerId: peerId, namespaces: namespaces, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, forEachMedia: forEachMedia)
self.messageHistoryTable.clearHistory(peerId: peerId, namespaces: namespaces, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, timestampBasedMessageAttributesOperations: &self.currentTimestampBasedMessageAttributesOperations, forEachMedia: forEachMedia)
for namespace in self.messageHistoryHoleIndexTable.existingNamespaces(peerId: peerId, holeSpace: .everywhere) where namespaces.contains(namespace) {
self.messageHistoryHoleIndexTable.remove(peerId: peerId, namespace: namespace, space: .everywhere, range: 1 ... Int32.max - 1, operations: &self.currentPeerHoleOperations)
}
}
fileprivate func removeAllMessagesWithAuthor(_ peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace, forEachMedia: (Media) -> Void) {
self.messageHistoryTable.removeAllMessagesWithAuthor(peerId: peerId, authorId: authorId, namespace: namespace, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, forEachMedia: forEachMedia)
self.messageHistoryTable.removeAllMessagesWithAuthor(peerId: peerId, authorId: authorId, namespace: namespace, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, timestampBasedMessageAttributesOperations: &self.currentTimestampBasedMessageAttributesOperations, forEachMedia: forEachMedia)
}
fileprivate func removeAllMessagesWithGlobalTag(tag: GlobalMessageTags) {
self.messageHistoryTable.removeAllMessagesWithGlobalTag(tag: tag, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, forEachMedia: { _ in })
self.messageHistoryTable.removeAllMessagesWithGlobalTag(tag: tag, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, timestampBasedMessageAttributesOperations: &self.currentTimestampBasedMessageAttributesOperations, forEachMedia: { _ in })
}
fileprivate func removeAllMessagesWithForwardAuthor(_ peerId: PeerId, forwardAuthorId: PeerId, namespace: MessageId.Namespace, forEachMedia: (Media) -> Void) {
self.messageHistoryTable.removeAllMessagesWithForwardAuthor(peerId: peerId, forwardAuthorId: forwardAuthorId, namespace: namespace, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, forEachMedia: forEachMedia)
self.messageHistoryTable.removeAllMessagesWithForwardAuthor(peerId: peerId, forwardAuthorId: forwardAuthorId, namespace: namespace, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, timestampBasedMessageAttributesOperations: &self.currentTimestampBasedMessageAttributesOperations, forEachMedia: forEachMedia)
}
fileprivate func resetIncomingReadStates(_ states: [PeerId: [MessageId.Namespace: PeerReadState]]) {
@ -2176,13 +2176,13 @@ public final class Postbox {
if let index = self.messageHistoryIndexTable.getIndex(id), let intermediateMessage = self.messageHistoryTable.getMessage(index) {
let message = self.renderIntermediateMessage(intermediateMessage)
if case let .update(updatedMessage) = update(message) {
self.messageHistoryTable.updateMessage(id, message: updatedMessage, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &self.currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations)
self.messageHistoryTable.updateMessage(id, message: updatedMessage, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &self.currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, timestampBasedMessageAttributesOperations: &self.currentTimestampBasedMessageAttributesOperations)
}
}
}
fileprivate func offsetPendingMessagesTimestamps(lowerBound: MessageId, excludeIds: Set<MessageId>, timestamp: Int32) {
self.messageHistoryTable.offsetPendingMessagesTimestamps(lowerBound: lowerBound, excludeIds: excludeIds, timestamp: timestamp, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &self.currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations)
self.messageHistoryTable.offsetPendingMessagesTimestamps(lowerBound: lowerBound, excludeIds: excludeIds, timestamp: timestamp, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &self.currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, timestampBasedMessageAttributesOperations: &self.currentTimestampBasedMessageAttributesOperations)
}
fileprivate func updateMessageGroupingKeysAtomically(_ ids: [MessageId], groupingKey: Int64) {
@ -2794,13 +2794,7 @@ public final class Postbox {
public func aroundChatListView(groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate? = nil, index: ChatListIndex, count: Int, summaryComponents: ChatListEntrySummaryComponents, userInteractive: Bool = false) -> Signal<(ChatListView, ViewUpdateType), NoError> {
return self.transactionSignal(userInteractive: userInteractive, { subscriber, transaction in
let mutableView = MutableChatListView(postbox: self, groupId: groupId, filterPredicate: filterPredicate, aroundIndex: index, count: count, summaryComponents: summaryComponents)
mutableView.render(postbox: self, renderMessage: self.renderIntermediateMessage, getPeer: { id in
return self.peerTable.get(id)
}, getPeerNotificationSettings: {
self.peerNotificationSettingsTable.getEffective($0)
}, getPeerPresence: {
self.peerPresenceTable.get($0)
})
mutableView.render(postbox: self)
let (index, signal) = self.viewTracker.addChatListView(mutableView)
@ -2900,52 +2894,56 @@ public final class Postbox {
public func searchPeers(query: String) -> Signal<[RenderedPeer], NoError> {
return self.transaction { transaction -> Signal<[RenderedPeer], NoError> in
var peerIds = Set<PeerId>()
var chatPeers: [RenderedPeer] = []
var (chatPeerIds, contactPeerIds) = self.peerNameIndexTable.matchingPeerIds(tokens: (regular: stringIndexTokens(query, transliteration: .none), transliterated: stringIndexTokens(query, transliteration: .transliterated)), categories: [.chats, .contacts], chatListIndexTable: self.chatListIndexTable, contactTable: self.contactsTable)
var additionalChatPeerIds: [PeerId] = []
for peerId in chatPeerIds {
for associatedId in self.reverseAssociatedPeerTable.get(peerId: peerId) {
let inclusionIndex = self.chatListIndexTable.get(peerId: associatedId)
if inclusionIndex.includedIndex(peerId: associatedId) != nil {
additionalChatPeerIds.append(associatedId)
}
return .single(transaction.searchPeers(query: query))
} |> switchToLatest
}
fileprivate func searchPeers(query: String) -> [RenderedPeer] {
var peerIds = Set<PeerId>()
var chatPeers: [RenderedPeer] = []
var (chatPeerIds, contactPeerIds) = self.peerNameIndexTable.matchingPeerIds(tokens: (regular: stringIndexTokens(query, transliteration: .none), transliterated: stringIndexTokens(query, transliteration: .transliterated)), categories: [.chats, .contacts], chatListIndexTable: self.chatListIndexTable, contactTable: self.contactsTable)
var additionalChatPeerIds: [PeerId] = []
for peerId in chatPeerIds {
for associatedId in self.reverseAssociatedPeerTable.get(peerId: peerId) {
let inclusionIndex = self.chatListIndexTable.get(peerId: associatedId)
if inclusionIndex.includedIndex(peerId: associatedId) != nil {
additionalChatPeerIds.append(associatedId)
}
}
chatPeerIds.append(contentsOf: additionalChatPeerIds)
for peerId in chatPeerIds {
}
chatPeerIds.append(contentsOf: additionalChatPeerIds)
for peerId in chatPeerIds {
if let peer = self.peerTable.get(peerId) {
var peers = SimpleDictionary<PeerId, Peer>()
peers[peer.id] = peer
if let associatedPeerId = peer.associatedPeerId {
if let associatedPeer = self.peerTable.get(associatedPeerId) {
peers[associatedPeer.id] = associatedPeer
}
}
chatPeers.append(RenderedPeer(peerId: peer.id, peers: peers))
peerIds.insert(peerId)
}
}
var contactPeers: [RenderedPeer] = []
for peerId in contactPeerIds {
if !peerIds.contains(peerId) {
if let peer = self.peerTable.get(peerId) {
var peers = SimpleDictionary<PeerId, Peer>()
peers[peer.id] = peer
if let associatedPeerId = peer.associatedPeerId {
if let associatedPeer = self.peerTable.get(associatedPeerId) {
peers[associatedPeer.id] = associatedPeer
}
}
chatPeers.append(RenderedPeer(peerId: peer.id, peers: peers))
peerIds.insert(peerId)
contactPeers.append(RenderedPeer(peerId: peer.id, peers: peers))
}
}
var contactPeers: [RenderedPeer] = []
for peerId in contactPeerIds {
if !peerIds.contains(peerId) {
if let peer = self.peerTable.get(peerId) {
var peers = SimpleDictionary<PeerId, Peer>()
peers[peer.id] = peer
contactPeers.append(RenderedPeer(peerId: peer.id, peers: peers))
}
}
}
contactPeers.sort(by: { lhs, rhs in
lhs.peers[lhs.peerId]!.indexName.indexName(.lastNameFirst) < rhs.peers[rhs.peerId]!.indexName.indexName(.lastNameFirst)
})
return .single(chatPeers + contactPeers)
} |> switchToLatest
}
contactPeers.sort(by: { lhs, rhs in
lhs.peers[lhs.peerId]!.indexName.indexName(.lastNameFirst) < rhs.peers[rhs.peerId]!.indexName.indexName(.lastNameFirst)
})
return chatPeers + contactPeers
}
public func peerView(id: PeerId) -> Signal<PeerView, NoError> {
@ -3138,11 +3136,7 @@ public final class Postbox {
public func mergedOperationLogView(tag: PeerOperationLogTag, limit: Int) -> Signal<PeerMergedOperationLogView, NoError> {
return self.transactionSignal { subscriber, transaction in
let view = MutablePeerMergedOperationLogView(tag: tag, limit: limit, getOperations: { tag, fromIndex, limit in
return self.peerOperationLogTable.getMergedEntries(tag: tag, fromIndex: fromIndex, limit: limit)
}, getTailIndex: { tag in
return self.peerMergedOperationLogIndexTable.tailIndex(tag: tag)
})
let view = MutablePeerMergedOperationLogView(postbox: self, tag: tag, limit: limit)
subscriber.putNext(PeerMergedOperationLogView(view))
@ -3165,9 +3159,7 @@ public final class Postbox {
public func timestampBasedMessageAttributesView(tag: UInt16) -> Signal<TimestampBasedMessageAttributesView, NoError> {
return self.transactionSignal { subscriber, transaction in
let view = MutableTimestampBasedMessageAttributesView(tag: tag, getHead: { tag in
return self.timestampBasedMessageAttributesTable.head(tag: tag)
})
let view = MutableTimestampBasedMessageAttributesView(postbox: self, tag: tag)
let (index, signal) = self.viewTracker.addTimestampBasedMessageAttributesView(view)
subscriber.putNext(TimestampBasedMessageAttributesView(view))
@ -3215,14 +3207,6 @@ public final class Postbox {
self.peerOperationLogTable.enumerateEntries(peerId: peerId, tag: tag, f)
}
fileprivate func addTimestampBasedMessageAttribute(tag: UInt16, timestamp: Int32, messageId: MessageId) {
self.timestampBasedMessageAttributesTable.set(tag: tag, id: messageId, timestamp: timestamp, operations: &self.currentTimestampBasedMessageAttributesOperations)
}
fileprivate func removeTimestampBasedMessageAttribute(tag: UInt16, messageId: MessageId) {
self.timestampBasedMessageAttributesTable.remove(tag: tag, id: messageId, operations: &self.currentTimestampBasedMessageAttributesOperations)
}
public func messageView(_ messageId: MessageId) -> Signal<MessageView, NoError> {
return self.transactionSignal { subscriber, transaction in
let view = MutableMessageView(messageId: messageId, message: transaction.getMessage(messageId))

@ -4,12 +4,12 @@ final class MutableTimestampBasedMessageAttributesView {
let tag: UInt16
var head: TimestampBasedMessageAttributesEntry?
init(tag: UInt16, getHead: (UInt16) -> TimestampBasedMessageAttributesEntry?) {
init(postbox: Postbox, tag: UInt16) {
self.tag = tag
self.head = getHead(tag)
self.head = postbox.timestampBasedMessageAttributesTable.head(tag: tag)
}
func replay(operations: [TimestampBasedMessageAttributesOperation], getHead: (UInt16) -> TimestampBasedMessageAttributesEntry?) -> Bool {
func replay(postbox: Postbox, operations: [TimestampBasedMessageAttributesOperation]) -> Bool {
var updated = false
var invalidatedHead = false
for operation in operations {
@ -37,7 +37,7 @@ final class MutableTimestampBasedMessageAttributesView {
}
}
if invalidatedHead {
self.head = getHead(self.tag)
self.head = postbox.timestampBasedMessageAttributesTable.head(tag: self.tag)
}
return updated
}

@ -11,15 +11,6 @@ public enum ViewUpdateType {
final class ViewTracker {
private let queue: Queue
private let renderMessage: (IntermediateMessage) -> Message
private let getPeer: (PeerId) -> Peer?
private let getPeerNotificationSettings: (PeerId) -> PeerNotificationSettings?
private let getCachedPeerData: (PeerId) -> CachedPeerData?
private let getPeerPresence: (PeerId) -> PeerPresence?
private let getPeerReadState: (PeerId) -> CombinedPeerReadState?
private let operationLogGetOperations: (PeerOperationLogTag, Int32, Int) -> [PeerMergedOperationLogEntry]
private let operationLogGetTailIndex: (PeerOperationLogTag) -> Int32?
private let getPreferencesEntry: (ValueBoxKey) -> PreferencesEntry?
private var chatListViews = Bag<(MutableChatListView, ValuePipe<(ChatListView, ViewUpdateType)>)>()
private var messageHistoryViews = Bag<(MutableMessageHistoryView, ValuePipe<(MessageHistoryView, ViewUpdateType)>)>()
@ -46,7 +37,6 @@ final class ViewTracker {
private var unreadMessageCountsViews = Bag<(MutableUnreadMessageCountsView, ValuePipe<UnreadMessageCountsView>)>()
private var peerMergedOperationLogViews = Bag<(MutablePeerMergedOperationLogView, ValuePipe<PeerMergedOperationLogView>)>()
private let getTimestampBasedMessageAttributesHead: (UInt16) -> TimestampBasedMessageAttributesEntry?
private var timestampBasedMessageAttributesViews = Bag<(MutableTimestampBasedMessageAttributesView, ValuePipe<TimestampBasedMessageAttributesView>)>()
private var combinedViews = Bag<(CombinedMutableView, ValuePipe<CombinedView>)>()
@ -56,23 +46,14 @@ final class ViewTracker {
private var preferencesViews = Bag<(MutablePreferencesView, ValuePipe<PreferencesView>)>()
private var multiplePeersViews = Bag<(MutableMultiplePeersView, ValuePipe<MultiplePeersView>)>()
private var itemCollectionsViews = Bag<(MutableItemCollectionsView, ValuePipe<ItemCollectionsView>)>()
private var failedMessageIdsViews = Bag<(MutableFailedMessageIdsView, ValuePipe<FailedMessageIdsView>)>()
init(queue: Queue, renderMessage: @escaping (IntermediateMessage) -> Message, getPeer: @escaping (PeerId) -> Peer?, getPeerNotificationSettings: @escaping (PeerId) -> PeerNotificationSettings?, getCachedPeerData: @escaping (PeerId) -> CachedPeerData?, getPeerPresence: @escaping (PeerId) -> PeerPresence?, getPeerReadState: @escaping (PeerId) -> CombinedPeerReadState?, operationLogGetOperations: @escaping (PeerOperationLogTag, Int32, Int) -> [PeerMergedOperationLogEntry], operationLogGetTailIndex: @escaping (PeerOperationLogTag) -> Int32?, getTimestampBasedMessageAttributesHead: @escaping (UInt16) -> TimestampBasedMessageAttributesEntry?, getPreferencesEntry: @escaping (ValueBoxKey) -> PreferencesEntry?, unsentMessageIds: [MessageId], synchronizePeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation]) {
init(
queue: Queue,
unsentMessageIds: [MessageId],
synchronizePeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation]
) {
self.queue = queue
self.renderMessage = renderMessage
self.getPeer = getPeer
self.getPeerNotificationSettings = getPeerNotificationSettings
self.getCachedPeerData = getCachedPeerData
self.getPeerPresence = getPeerPresence
self.getPeerReadState = getPeerReadState
self.operationLogGetOperations = operationLogGetOperations
self.operationLogGetTailIndex = operationLogGetTailIndex
self.getTimestampBasedMessageAttributesHead = getTimestampBasedMessageAttributesHead
self.getPreferencesEntry = getPreferencesEntry
self.unsentMessageView = UnsentMessageHistoryView(ids: unsentMessageIds)
self.synchronizeReadStatesView = MutableSynchronizePeerReadStatesView(operations: synchronizePeerReadStateOperations)
@ -254,9 +235,7 @@ final class ViewTracker {
for (mutableView, pipe) in self.chatListViews.copyItems() {
if mutableView.refreshDueToExternalTransaction(postbox: postbox) {
mutableView.render(postbox: postbox, renderMessage: self.renderMessage, getPeer: { id in
return self.getPeer(id)
}, getPeerNotificationSettings: self.getPeerNotificationSettings, getPeerPresence: self.getPeerPresence)
mutableView.render(postbox: postbox)
pipe.putNext((ChatListView(mutableView), .Generic))
}
}
@ -350,7 +329,7 @@ final class ViewTracker {
for (mutableView, pipe) in self.messageViews.copyItems() {
let operations = transaction.currentOperationsByPeerId[mutableView.messageId.peerId]
if operations != nil || !transaction.updatedMedia.isEmpty || !transaction.currentUpdatedCachedPeerData.isEmpty {
if mutableView.replay(operations ?? [], updatedMedia: transaction.updatedMedia, renderIntermediateMessage: self.renderMessage) {
if mutableView.replay(postbox: postbox, operations: operations ?? [], updatedMedia: transaction.updatedMedia) {
pipe.putNext(MessageView(mutableView))
}
}
@ -360,9 +339,7 @@ final class ViewTracker {
let context = MutableChatListViewReplayContext()
if mutableView.replay(postbox: postbox, operations: transaction.chatListOperations, updatedPeerNotificationSettings: transaction.currentUpdatedPeerNotificationSettings, updatedPeers: transaction.currentUpdatedPeers, updatedPeerPresences: transaction.currentUpdatedPeerPresences, transaction: transaction, context: context) {
mutableView.complete(postbox: postbox, context: context)
mutableView.render(postbox: postbox, renderMessage: self.renderMessage, getPeer: { id in
return self.getPeer(id)
}, getPeerNotificationSettings: self.getPeerNotificationSettings, getPeerPresence: self.getPeerPresence)
mutableView.render(postbox: postbox)
pipe.putNext((ChatListView(mutableView), .Generic))
}
}
@ -396,7 +373,7 @@ final class ViewTracker {
}
for (mutableView, pipe) in self.contactPeersViews.copyItems() {
if mutableView.replay(replacePeerIds: transaction.replaceContactPeerIds, updatedPeerPresences: transaction.currentUpdatedPeerPresences, getPeer: self.getPeer, getPeerPresence: self.getPeerPresence) {
if mutableView.replay(postbox: postbox, replacePeerIds: transaction.replaceContactPeerIds, updatedPeerPresences: transaction.currentUpdatedPeerPresences) {
pipe.putNext(ContactPeersView(mutableView))
}
}
@ -408,13 +385,13 @@ final class ViewTracker {
}
for (mutableView, pipe) in self.peerMergedOperationLogViews.copyItems() {
if mutableView.replay(operations: transaction.currentPeerMergedOperationLogOperations, getOperations: self.operationLogGetOperations, getTailIndex: self.operationLogGetTailIndex) {
if mutableView.replay(postbox: postbox, operations: transaction.currentPeerMergedOperationLogOperations) {
pipe.putNext(PeerMergedOperationLogView(mutableView))
}
}
for (mutableView, pipe) in self.timestampBasedMessageAttributesViews.copyItems() {
if mutableView.replay(operations: transaction.currentTimestampBasedMessageAttributesOperations, getHead: self.getTimestampBasedMessageAttributesHead) {
if mutableView.replay(postbox: postbox, operations: transaction.currentTimestampBasedMessageAttributesOperations) {
pipe.putNext(TimestampBasedMessageAttributesView(mutableView))
}
}

@ -0,0 +1,19 @@
//
// SQueueLocalObject.h
// SSignalKit
//
// Created by Mikhail Filimonov on 13.01.2021.
// Copyright © 2021 Telegram. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <SSignalKit/SQueue.h>
NS_ASSUME_NONNULL_BEGIN
@interface SQueueLocalObject : NSObject
-(id)initWithQueue:(SQueue *)queue generate:(id (^)(void))next;
-(void)with:(void (^)(id object))f;
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,40 @@
//
// SQueueLocalObject.m
// SSignalKit
//
// Created by Mikhail Filimonov on 13.01.2021.
// Copyright © 2021 Telegram. All rights reserved.
//
#import "SQueueLocalObject.h"
@implementation SQueueLocalObject {
SQueue *queue;
id valueRef;
}
-(id)initWithQueue:(SQueue *)queue generate:(id _Nonnull (^)(void))next {
if (self = [super init]) {
self->queue = queue;
[queue dispatch:^{
self->valueRef = next();
}];
}
return self;
}
-(void)with:(void (^)(id object))f {
[self->queue dispatch:^{
f(self->valueRef);
}];
}
-(void)dealloc {
__block id value = self->valueRef;
self->valueRef = nil;
[queue dispatch:^{
value = nil;
}];
}
@end

@ -35,3 +35,4 @@ FOUNDATION_EXPORT const unsigned char SSignalKitVersionString[];
#import <SSignalKit/SMulticastSignalManager.h>
#import <SSignalKit/STimer.h>
#import <SSignalKit/SVariable.h>
#import <SSignalKit/SQueueLocalObject.h>

@ -9,7 +9,7 @@ private let selectedTextFont = Font.bold(13.0)
public enum SegmentedControlLayout {
case stretchToFill(width: CGFloat)
case sizeToFit(maximumWidth: CGFloat, minimumWidth: CGFloat)
case sizeToFit(maximumWidth: CGFloat, minimumWidth: CGFloat, height: CGFloat)
}
public final class SegmentedControlTheme: Equatable {
@ -294,11 +294,14 @@ public final class SegmentedControlNode: ASDisplayNode, UIGestureRecognizerDeleg
let calculatedWidth: CGFloat = 0.0
let width: CGFloat
let height: CGFloat
switch layout {
case let .stretchToFill(targetWidth):
width = targetWidth
case let .sizeToFit(maximumWidth, minimumWidth):
height = 32.0
case let .sizeToFit(maximumWidth, minimumWidth, targetHeight):
width = max(minimumWidth, min(maximumWidth, calculatedWidth))
height = targetHeight
}
let selectedIndex: Int
@ -308,7 +311,7 @@ public final class SegmentedControlNode: ASDisplayNode, UIGestureRecognizerDeleg
selectedIndex = self.selectedIndex
}
let size = CGSize(width: width, height: 32.0)
let size = CGSize(width: width, height: height)
if !self.itemNodes.isEmpty {
let itemSize = CGSize(width: floorToScreenPixels(size.width / CGFloat(self.itemNodes.count)), height: size.height)

@ -748,6 +748,9 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
case .voiceConference:
return ItemListDisclosureItem(presentationData: presentationData, title: "Voice Conference (Test)", label: "", sectionId: self.section, style: .blocks, action: {
guard let _ = arguments.context else {
return
}
})
case let .preferredVideoCodec(_, title, value, isSelected):
return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .right, checked: isSelected, zeroSeparatorInsets: false, sectionId: self.section, action: {

@ -2,19 +2,43 @@ import Foundation
import Postbox
public class AutoremoveTimeoutMessageAttribute: MessageAttribute {
public enum Action: Int32 {
case remove = 0
case clear = 1
}
public let timeout: Int32
public let countdownBeginTime: Int32?
public let action: Action
public var associatedMessageIds: [MessageId] = []
public init(timeout: Int32, countdownBeginTime: Int32?) {
public let automaticTimestampBasedAttribute: (UInt16, Int32)?
public init(timeout: Int32, countdownBeginTime: Int32?, action: Action = .remove) {
self.timeout = timeout
self.countdownBeginTime = countdownBeginTime
if let countdownBeginTime = countdownBeginTime {
self.automaticTimestampBasedAttribute = (0, countdownBeginTime + timeout)
} else {
self.automaticTimestampBasedAttribute = nil
}
self.action = action
}
required public init(decoder: PostboxDecoder) {
self.timeout = decoder.decodeInt32ForKey("t", orElse: 0)
self.countdownBeginTime = decoder.decodeOptionalInt32ForKey("c")
if let countdownBeginTime = self.countdownBeginTime {
self.automaticTimestampBasedAttribute = (0, countdownBeginTime + self.timeout)
} else {
self.automaticTimestampBasedAttribute = nil
}
self.action = Action(rawValue: decoder.decodeInt32ForKey("a", orElse: 0)) ?? .remove
}
public func encode(_ encoder: PostboxEncoder) {
@ -24,6 +48,7 @@ public class AutoremoveTimeoutMessageAttribute: MessageAttribute {
} else {
encoder.encodeNil(forKey: "c")
}
encoder.encodeInt32(self.action.rawValue, forKey: "a")
}
}
@ -59,4 +84,13 @@ public extension Message {
return false
}
var isSelfExpiring: Bool {
for attribute in self.attributes {
if let _ = attribute as? AutoremoveTimeoutMessageAttribute {
return true
}
}
return false
}
}

@ -194,6 +194,7 @@ public final class CachedChannelData: CachedPeerData {
public let slowModeTimeout: Int32?
public let slowModeValidUntilTimestamp: Int32?
public let hasScheduledMessages: Bool
public let autoremoveTimeout: CachedPeerAutoremoveTimeout
public let statsDatacenterId: Int32
public let invitedBy: PeerId?
public let photo: TelegramMediaImage?
@ -224,13 +225,14 @@ public final class CachedChannelData: CachedPeerData {
self.slowModeTimeout = nil
self.slowModeValidUntilTimestamp = nil
self.hasScheduledMessages = false
self.autoremoveTimeout = .unknown
self.statsDatacenterId = 0
self.invitedBy = nil
self.photo = nil
self.activeCall = nil
}
public init(isNotAccessible: Bool, flags: CachedChannelFlags, about: String?, participantsSummary: CachedChannelParticipantsSummary, exportedInvitation: ExportedInvitation?, botInfos: [CachedPeerBotInfo], peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, stickerPack: StickerPackCollectionInfo?, minAvailableMessageId: MessageId?, migrationReference: ChannelMigrationReference?, linkedDiscussionPeerId: LinkedDiscussionPeerId, peerGeoLocation: PeerGeoLocation?, slowModeTimeout: Int32?, slowModeValidUntilTimestamp: Int32?, hasScheduledMessages: Bool, statsDatacenterId: Int32, invitedBy: PeerId?, photo: TelegramMediaImage?, activeCall: ActiveCall?) {
public init(isNotAccessible: Bool, flags: CachedChannelFlags, about: String?, participantsSummary: CachedChannelParticipantsSummary, exportedInvitation: ExportedInvitation?, botInfos: [CachedPeerBotInfo], peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, stickerPack: StickerPackCollectionInfo?, minAvailableMessageId: MessageId?, migrationReference: ChannelMigrationReference?, linkedDiscussionPeerId: LinkedDiscussionPeerId, peerGeoLocation: PeerGeoLocation?, slowModeTimeout: Int32?, slowModeValidUntilTimestamp: Int32?, hasScheduledMessages: Bool, statsDatacenterId: Int32, invitedBy: PeerId?, photo: TelegramMediaImage?, activeCall: ActiveCall?, autoremoveTimeout: CachedPeerAutoremoveTimeout) {
self.isNotAccessible = isNotAccessible
self.flags = flags
self.about = about
@ -251,6 +253,7 @@ public final class CachedChannelData: CachedPeerData {
self.invitedBy = invitedBy
self.photo = photo
self.activeCall = activeCall
self.autoremoveTimeout = autoremoveTimeout
var peerIds = Set<PeerId>()
for botInfo in botInfos {
@ -278,83 +281,87 @@ public final class CachedChannelData: CachedPeerData {
}
public func withUpdatedIsNotAccessible(_ isNotAccessible: Bool) -> CachedChannelData {
return CachedChannelData(isNotAccessible: isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
return CachedChannelData(isNotAccessible: isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedFlags(_ flags: CachedChannelFlags) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedAbout(_ about: String?) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedParticipantsSummary(_ participantsSummary: CachedChannelParticipantsSummary) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedExportedInvitation(_ exportedInvitation: ExportedInvitation?) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedBotInfos(_ botInfos: [CachedPeerBotInfo]) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedPeerStatusSettings(_ peerStatusSettings: PeerStatusSettings?) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedStickerPack(_ stickerPack: StickerPackCollectionInfo?) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedMinAvailableMessageId(_ minAvailableMessageId: MessageId?) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedMigrationReference(_ migrationReference: ChannelMigrationReference?) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedLinkedDiscussionPeerId(_ linkedDiscussionPeerId: LinkedDiscussionPeerId) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedPeerGeoLocation(_ peerGeoLocation: PeerGeoLocation?) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedSlowModeTimeout(_ slowModeTimeout: Int32?) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedSlowModeValidUntilTimestamp(_ slowModeValidUntilTimestamp: Int32?) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedHasScheduledMessages(_ hasScheduledMessages: Bool) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedStatsDatacenterId(_ statsDatacenterId: Int32) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedInvitedBy(_ invitedBy: PeerId?) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: invitedBy, photo: self.photo, activeCall: self.activeCall)
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedPhoto(_ photo: TelegramMediaImage?) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: photo, activeCall: self.activeCall)
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedActiveCall(_ activeCall: ActiveCall?) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: activeCall)
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: activeCall, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedAutoremoveTimeout(_ autoremoveTimeout: CachedPeerAutoremoveTimeout) -> CachedChannelData {
return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: autoremoveTimeout)
}
public init(decoder: PostboxDecoder) {
@ -422,6 +429,7 @@ public final class CachedChannelData: CachedPeerData {
self.slowModeTimeout = decoder.decodeOptionalInt32ForKey("smt")
self.slowModeValidUntilTimestamp = decoder.decodeOptionalInt32ForKey("smv")
self.hasScheduledMessages = decoder.decodeBoolForKey("hsm", orElse: false)
self.autoremoveTimeout = decoder.decodeObjectForKey("artv", decoder: CachedPeerAutoremoveTimeout.init(decoder:)) as? CachedPeerAutoremoveTimeout ?? .unknown
self.statsDatacenterId = decoder.decodeInt32ForKey("sdi", orElse: 0)
self.invitedBy = decoder.decodeOptionalInt64ForKey("invBy").flatMap(PeerId.init)
@ -529,6 +537,7 @@ public final class CachedChannelData: CachedPeerData {
encoder.encodeNil(forKey: "smv")
}
encoder.encodeBool(self.hasScheduledMessages, forKey: "hsm")
encoder.encodeObject(self.autoremoveTimeout, forKey: "artv")
encoder.encodeInt32(self.statsDatacenterId, forKey: "sdi")
if let invitedBy = self.invitedBy {
@ -613,6 +622,10 @@ public final class CachedChannelData: CachedPeerData {
return false
}
if other.autoremoveTimeout != self.autoremoveTimeout {
return false
}
if other.statsDatacenterId != self.statsDatacenterId {
return false
}

@ -50,6 +50,7 @@ public final class CachedGroupData: CachedPeerData {
public let hasScheduledMessages: Bool
public let invitedBy: PeerId?
public let photo: TelegramMediaImage?
public let autoremoveTimeout: CachedPeerAutoremoveTimeout
public let peerIds: Set<PeerId>
public let messageIds: Set<MessageId>
@ -70,11 +71,12 @@ public final class CachedGroupData: CachedPeerData {
self.hasScheduledMessages = false
self.invitedBy = nil
self.photo = nil
self.autoremoveTimeout = .unknown
self.activeCall = nil
}
public init(participants: CachedGroupParticipants?, exportedInvitation: ExportedInvitation?, botInfos: [CachedPeerBotInfo], peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, about: String?, flags: CachedGroupFlags, hasScheduledMessages: Bool, invitedBy: PeerId?, photo: TelegramMediaImage?, activeCall: CachedChannelData.ActiveCall?) {
public init(participants: CachedGroupParticipants?, exportedInvitation: ExportedInvitation?, botInfos: [CachedPeerBotInfo], peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, about: String?, flags: CachedGroupFlags, hasScheduledMessages: Bool, invitedBy: PeerId?, photo: TelegramMediaImage?, activeCall: CachedChannelData.ActiveCall?, autoremoveTimeout: CachedPeerAutoremoveTimeout) {
self.participants = participants
self.exportedInvitation = exportedInvitation
self.botInfos = botInfos
@ -86,6 +88,7 @@ public final class CachedGroupData: CachedPeerData {
self.invitedBy = invitedBy
self.photo = photo
self.activeCall = activeCall
self.autoremoveTimeout = autoremoveTimeout
var messageIds = Set<MessageId>()
if let pinnedMessageId = self.pinnedMessageId {
@ -128,6 +131,7 @@ public final class CachedGroupData: CachedPeerData {
self.about = decoder.decodeOptionalStringForKey("ab")
self.flags = CachedGroupFlags(rawValue: decoder.decodeInt32ForKey("fl", orElse: 0))
self.hasScheduledMessages = decoder.decodeBoolForKey("hsm", orElse: false)
self.autoremoveTimeout = decoder.decodeObjectForKey("artv", decoder: CachedPeerAutoremoveTimeout.init(decoder:)) as? CachedPeerAutoremoveTimeout ?? .unknown
self.invitedBy = decoder.decodeOptionalInt64ForKey("invBy").flatMap(PeerId.init)
@ -195,6 +199,7 @@ public final class CachedGroupData: CachedPeerData {
}
encoder.encodeInt32(self.flags.rawValue, forKey: "fl")
encoder.encodeBool(self.hasScheduledMessages, forKey: "hsm")
encoder.encodeObject(self.autoremoveTimeout, forKey: "artv")
if let invitedBy = self.invitedBy {
encoder.encodeInt64(invitedBy.toInt64(), forKey: "invBy")
@ -224,50 +229,54 @@ public final class CachedGroupData: CachedPeerData {
return false
}
return self.participants == other.participants && self.exportedInvitation == other.exportedInvitation && self.botInfos == other.botInfos && self.peerStatusSettings == other.peerStatusSettings && self.pinnedMessageId == other.pinnedMessageId && self.about == other.about && self.flags == other.flags && self.hasScheduledMessages == other.hasScheduledMessages && self.invitedBy == other.invitedBy
return self.participants == other.participants && self.exportedInvitation == other.exportedInvitation && self.botInfos == other.botInfos && self.peerStatusSettings == other.peerStatusSettings && self.pinnedMessageId == other.pinnedMessageId && self.about == other.about && self.flags == other.flags && self.hasScheduledMessages == other.hasScheduledMessages && self.autoremoveTimeout == other.autoremoveTimeout && self.invitedBy == other.invitedBy
}
public func withUpdatedParticipants(_ participants: CachedGroupParticipants?) -> CachedGroupData {
return CachedGroupData(participants: participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
return CachedGroupData(participants: participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedExportedInvitation(_ exportedInvitation: ExportedInvitation?) -> CachedGroupData {
return CachedGroupData(participants: self.participants, exportedInvitation: exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
return CachedGroupData(participants: self.participants, exportedInvitation: exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedBotInfos(_ botInfos: [CachedPeerBotInfo]) -> CachedGroupData {
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedPeerStatusSettings(_ peerStatusSettings: PeerStatusSettings?) -> CachedGroupData {
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> CachedGroupData {
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedAbout(_ about: String?) -> CachedGroupData {
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedFlags(_ flags: CachedGroupFlags) -> CachedGroupData {
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedHasScheduledMessages(_ hasScheduledMessages: Bool) -> CachedGroupData {
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall)
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedInvitedBy(_ invitedBy: PeerId?) -> CachedGroupData {
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: invitedBy, photo: self.photo, activeCall: self.activeCall)
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedPhoto(_ photo: TelegramMediaImage?) -> CachedGroupData {
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: photo, activeCall: self.activeCall)
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: photo, activeCall: self.activeCall, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedActiveCall(_ activeCall: CachedChannelData.ActiveCall?) -> CachedGroupData {
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: activeCall)
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: activeCall, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedAutoremoveTimeout(_ autoremoveTimeout: CachedPeerAutoremoveTimeout) -> CachedGroupData {
return CachedGroupData(participants: self.participants, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, about: self.about, flags: self.flags, hasScheduledMessages: self.hasScheduledMessages, invitedBy: self.invitedBy, photo: self.photo, activeCall: self.activeCall, autoremoveTimeout: autoremoveTimeout)
}
}

@ -1,6 +1,78 @@
import Foundation
import Postbox
public enum CachedPeerAutoremoveTimeout: Equatable, PostboxCoding {
public struct Value: Equatable, PostboxCoding {
public var myValue: Int32?
public var peerValue: Int32?
public var isGlobal: Bool
public init(myValue: Int32?, peerValue: Int32?, isGlobal: Bool) {
self.myValue = myValue
self.peerValue = peerValue
self.isGlobal = isGlobal
}
public init(decoder: PostboxDecoder) {
self.myValue = decoder.decodeOptionalInt32ForKey("myValue")
self.peerValue = decoder.decodeOptionalInt32ForKey("peerValue")
self.isGlobal = decoder.decodeInt32ForKey("isGlobal", orElse: 1) != 0
}
public func encode(_ encoder: PostboxEncoder) {
if let myValue = self.myValue {
encoder.encodeInt32(myValue, forKey: "myValue")
} else {
encoder.encodeNil(forKey: "myValue")
}
if let peerValue = self.peerValue {
encoder.encodeInt32(peerValue, forKey: "peerValue")
} else {
encoder.encodeNil(forKey: "peerValue")
}
encoder.encodeInt32(self.isGlobal ? 1 : 0, forKey: "isGlobal")
}
public var effectiveValue: Int32? {
if let myValue = self.myValue, let peerValue = self.peerValue {
return min(myValue, peerValue)
} else if let myValue = self.myValue {
return myValue
} else if let peerValue = self.peerValue {
return peerValue
} else {
return nil
}
}
}
case unknown
case known(Value?)
public init(decoder: PostboxDecoder) {
switch decoder.decodeInt32ForKey("_v", orElse: 0) {
case 1:
self = .known(decoder.decodeObjectForKey("v", decoder: Value.init(decoder:)) as? Value)
default:
self = .unknown
}
}
public func encode(_ encoder: PostboxEncoder) {
switch self {
case .unknown:
encoder.encodeInt32(0, forKey: "_v")
case let .known(value):
encoder.encodeInt32(1, forKey: "_v")
if let value = value {
encoder.encodeObject(value, forKey: "v")
} else {
encoder.encodeNil(forKey: "v")
}
}
}
}
public final class CachedUserData: CachedPeerData {
public let about: String?
public let botInfo: BotInfo?
@ -13,6 +85,7 @@ public final class CachedUserData: CachedPeerData {
public let callsPrivate: Bool
public let canPinMessages: Bool
public let hasScheduledMessages: Bool
public let autoremoveTimeout: CachedPeerAutoremoveTimeout
public let peerIds = Set<PeerId>()
public let messageIds: Set<MessageId>
@ -30,10 +103,11 @@ public final class CachedUserData: CachedPeerData {
self.callsPrivate = false
self.canPinMessages = false
self.hasScheduledMessages = false
self.autoremoveTimeout = .unknown
self.messageIds = Set()
}
public init(about: String?, botInfo: BotInfo?, peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, isBlocked: Bool, commonGroupCount: Int32, voiceCallsAvailable: Bool, videoCallsAvailable: Bool, callsPrivate: Bool, canPinMessages: Bool, hasScheduledMessages: Bool) {
public init(about: String?, botInfo: BotInfo?, peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, isBlocked: Bool, commonGroupCount: Int32, voiceCallsAvailable: Bool, videoCallsAvailable: Bool, callsPrivate: Bool, canPinMessages: Bool, hasScheduledMessages: Bool, autoremoveTimeout: CachedPeerAutoremoveTimeout) {
self.about = about
self.botInfo = botInfo
self.peerStatusSettings = peerStatusSettings
@ -45,6 +119,7 @@ public final class CachedUserData: CachedPeerData {
self.callsPrivate = callsPrivate
self.canPinMessages = canPinMessages
self.hasScheduledMessages = hasScheduledMessages
self.autoremoveTimeout = autoremoveTimeout
var messageIds = Set<MessageId>()
if let pinnedMessageId = self.pinnedMessageId {
messageIds.insert(pinnedMessageId)
@ -74,6 +149,7 @@ public final class CachedUserData: CachedPeerData {
self.callsPrivate = decoder.decodeInt32ForKey("cp", orElse: 0) != 0
self.canPinMessages = decoder.decodeInt32ForKey("cpm", orElse: 0) != 0
self.hasScheduledMessages = decoder.decodeBoolForKey("hsm", orElse: false)
self.autoremoveTimeout = decoder.decodeObjectForKey("artv", decoder: CachedPeerAutoremoveTimeout.init(decoder:)) as? CachedPeerAutoremoveTimeout ?? .unknown
var messageIds = Set<MessageId>()
if let pinnedMessageId = self.pinnedMessageId {
@ -114,6 +190,7 @@ public final class CachedUserData: CachedPeerData {
encoder.encodeInt32(self.callsPrivate ? 1 : 0, forKey: "cp")
encoder.encodeInt32(self.canPinMessages ? 1 : 0, forKey: "cpm")
encoder.encodeBool(self.hasScheduledMessages, forKey: "hsm")
encoder.encodeObject(self.autoremoveTimeout, forKey: "artv")
}
public func isEqual(to: CachedPeerData) -> Bool {
@ -128,50 +205,54 @@ public final class CachedUserData: CachedPeerData {
return false
}
return other.about == self.about && other.botInfo == self.botInfo && self.peerStatusSettings == other.peerStatusSettings && self.isBlocked == other.isBlocked && self.commonGroupCount == other.commonGroupCount && self.voiceCallsAvailable == other.voiceCallsAvailable && self.videoCallsAvailable == other.videoCallsAvailable && self.callsPrivate == other.callsPrivate && self.hasScheduledMessages == other.hasScheduledMessages
return other.about == self.about && other.botInfo == self.botInfo && self.peerStatusSettings == other.peerStatusSettings && self.isBlocked == other.isBlocked && self.commonGroupCount == other.commonGroupCount && self.voiceCallsAvailable == other.voiceCallsAvailable && self.videoCallsAvailable == other.videoCallsAvailable && self.callsPrivate == other.callsPrivate && self.hasScheduledMessages == other.hasScheduledMessages && self.autoremoveTimeout == other.autoremoveTimeout
}
public func withUpdatedAbout(_ about: String?) -> CachedUserData {
return CachedUserData(about: about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages)
return CachedUserData(about: about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedBotInfo(_ botInfo: BotInfo?) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages)
return CachedUserData(about: self.about, botInfo: botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedPeerStatusSettings(_ peerStatusSettings: PeerStatusSettings) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages)
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages)
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedIsBlocked(_ isBlocked: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages)
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedCommonGroupCount(_ commonGroupCount: Int32) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages)
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedVoiceCallsAvailable(_ voiceCallsAvailable: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages)
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedVideoCallsAvailable(_ videoCallsAvailable: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages)
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedCallsPrivate(_ callsPrivate: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages)
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedCanPinMessages(_ canPinMessages: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: canPinMessages, hasScheduledMessages: self.hasScheduledMessages)
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedHasScheduledMessages(_ hasScheduledMessages: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: hasScheduledMessages)
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout)
}
public func withUpdatedAutoremoveTimeout(_ autoremoveTimeout: CachedPeerAutoremoveTimeout) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: autoremoveTimeout)
}
}

@ -11,8 +11,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-457104426] = { return Api.InputGeoPoint.parse_inputGeoPointEmpty($0) }
dict[1210199983] = { return Api.InputGeoPoint.parse_inputGeoPoint($0) }
dict[-784000893] = { return Api.payments.ValidatedRequestedInfo.parse_validatedRequestedInfo($0) }
dict[-500874592] = { return Api.ChatFull.parse_chatFull($0) }
dict[-66811386] = { return Api.ChatFull.parse_channelFull($0) }
dict[-500874592] = { return Api.ChatFull.parse_chatFull($0) }
dict[-1159937629] = { return Api.PollResults.parse_pollResults($0) }
dict[-925415106] = { return Api.ChatParticipant.parse_chatParticipant($0) }
dict[-636267638] = { return Api.ChatParticipant.parse_chatParticipantCreator($0) }
@ -109,6 +109,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1522240089] = { return Api.UserFull.parse_userFull($0) }
dict[-292807034] = { return Api.InputChannel.parse_inputChannelEmpty($0) }
dict[-1343524562] = { return Api.InputChannel.parse_inputChannel($0) }
dict[707290417] = { return Api.InputChannel.parse_inputChannelFromMessage($0) }
dict[414687501] = { return Api.DcOption.parse_dcOption($0) }
dict[997055186] = { return Api.PollAnswerVoters.parse_pollAnswerVoters($0) }
dict[-1705233435] = { return Api.account.PasswordSettings.parse_passwordSettings($0) }
@ -117,7 +118,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1000708810] = { return Api.help.AppUpdate.parse_noAppUpdate($0) }
dict[497489295] = { return Api.help.AppUpdate.parse_appUpdate($0) }
dict[-209337866] = { return Api.LangPackDifference.parse_langPackDifference($0) }
dict[-794007333] = { return Api.PeerHistoryTTL.parse_peerHistoryTTL($0) }
dict[-815649386] = { return Api.PeerHistoryTTL.parse_peerHistoryTTLPM($0) }
dict[1041354473] = { return Api.PeerHistoryTTL.parse_peerHistoryTTL($0) }
dict[84438264] = { return Api.WallPaperSettings.parse_wallPaperSettings($0) }
dict[-1519029347] = { return Api.EmojiURL.parse_emojiURL($0) }
dict[1611985938] = { return Api.StatsGroupTopAdmin.parse_statsGroupTopAdmin($0) }
@ -140,7 +142,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1511503333] = { return Api.InputEncryptedFile.parse_inputEncryptedFile($0) }
dict[767652808] = { return Api.InputEncryptedFile.parse_inputEncryptedFileBigUploaded($0) }
dict[-1456996667] = { return Api.messages.InactiveChats.parse_inactiveChats($0) }
dict[1690708501] = { return Api.GroupCallParticipant.parse_groupCallParticipant($0) }
dict[-817921892] = { return Api.GroupCallParticipant.parse_groupCallParticipant($0) }
dict[1443858741] = { return Api.messages.SentEncryptedMessage.parse_sentEncryptedMessage($0) }
dict[-1802240206] = { return Api.messages.SentEncryptedMessage.parse_sentEncryptedFile($0) }
dict[1571494644] = { return Api.ExportedMessageLink.parse_exportedMessageLink($0) }
@ -274,6 +276,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-219423922] = { return Api.Update.parse_updateGroupCallParticipants($0) }
dict[-1537295973] = { return Api.Update.parse_updateGroupCall($0) }
dict[1059076315] = { return Api.Update.parse_updateBotInlineQuery($0) }
dict[19291112] = { return Api.Update.parse_updatePeerHistoryTTL($0) }
dict[136574537] = { return Api.messages.VotesList.parse_votesList($0) }
dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) }
dict[-373643672] = { return Api.FolderPeer.parse_folderPeer($0) }
@ -398,6 +401,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[396093539] = { return Api.InputPeer.parse_inputPeerChat($0) }
dict[2072935910] = { return Api.InputPeer.parse_inputPeerUser($0) }
dict[548253432] = { return Api.InputPeer.parse_inputPeerChannel($0) }
dict[398123750] = { return Api.InputPeer.parse_inputPeerUserFromMessage($0) }
dict[-1667893317] = { return Api.InputPeer.parse_inputPeerChannelFromMessage($0) }
dict[568808380] = { return Api.upload.WebFile.parse_webFile($0) }
dict[-116274796] = { return Api.Contact.parse_contact($0) }
dict[-1078332329] = { return Api.help.PassportConfig.parse_passportConfigNotModified($0) }
@ -816,7 +821,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1730095465] = { return Api.MessageAction.parse_messageActionGeoProximityReached($0) }
dict[2047704898] = { return Api.MessageAction.parse_messageActionGroupCall($0) }
dict[1991897370] = { return Api.MessageAction.parse_messageActionInviteToGroupCall($0) }
dict[-1441072131] = { return Api.MessageAction.parse_messageActionSetMessagesTTL($0) }
dict[1399245077] = { return Api.PhoneCall.parse_phoneCallEmpty($0) }
dict[462375633] = { return Api.PhoneCall.parse_phoneCallWaiting($0) }
dict[-2014659757] = { return Api.PhoneCall.parse_phoneCallRequested($0) }
@ -834,7 +838,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1290580579] = { return Api.contacts.Found.parse_found($0) }
dict[-368018716] = { return Api.ChannelAdminLogEventsFilter.parse_channelAdminLogEventsFilter($0) }
dict[-1676371894] = { return Api.ThemeSettings.parse_themeSettings($0) }
dict[1889961234] = { return Api.PeerNotifySettings.parse_peerNotifySettingsEmpty($0) }
dict[-1353671392] = { return Api.PeerNotifySettings.parse_peerNotifySettings($0) }
dict[-1995686519] = { return Api.InputBotInlineMessageID.parse_inputBotInlineMessageID($0) }
dict[-1282352120] = { return Api.PageRelatedArticle.parse_pageRelatedArticle($0) }

@ -142,32 +142,11 @@ public extension Api {
}
public enum ChatFull: TypeConstructorDescription {
case chatFull(flags: Int32, id: Int32, about: String, participants: Api.ChatParticipants, chatPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite?, botInfo: [Api.BotInfo]?, pinnedMsgId: Int32?, folderId: Int32?, call: Api.InputGroupCall?, ttl: Api.PeerHistoryTTL?)
case channelFull(flags: Int32, id: Int32, about: String, participantsCount: Int32?, adminsCount: Int32?, kickedCount: Int32?, bannedCount: Int32?, onlineCount: Int32?, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, chatPhoto: Api.Photo, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite?, botInfo: [Api.BotInfo], migratedFromChatId: Int32?, migratedFromMaxId: Int32?, pinnedMsgId: Int32?, stickerset: Api.StickerSet?, availableMinId: Int32?, folderId: Int32?, linkedChatId: Int32?, location: Api.ChannelLocation?, slowmodeSeconds: Int32?, slowmodeNextSendDate: Int32?, statsDc: Int32?, pts: Int32, call: Api.InputGroupCall?, ttl: Api.PeerHistoryTTL?)
case chatFull(flags: Int32, id: Int32, about: String, participants: Api.ChatParticipants, chatPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite?, botInfo: [Api.BotInfo]?, pinnedMsgId: Int32?, folderId: Int32?, call: Api.InputGroupCall?, ttl: Api.PeerHistoryTTL?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId, let call, let ttl):
if boxed {
buffer.appendInt32(-500874592)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(id, buffer: buffer, boxed: false)
serializeString(about, buffer: buffer, boxed: false)
participants.serialize(buffer, true)
if Int(flags) & Int(1 << 2) != 0 {chatPhoto!.serialize(buffer, true)}
notifySettings.serialize(buffer, true)
if Int(flags) & Int(1 << 13) != 0 {exportedInvite!.serialize(buffer, true)}
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(botInfo!.count))
for item in botInfo! {
item.serialize(buffer, true)
}}
if Int(flags) & Int(1 << 6) != 0 {serializeInt32(pinnedMsgId!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 11) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 12) != 0 {call!.serialize(buffer, true)}
if Int(flags) & Int(1 << 14) != 0 {ttl!.serialize(buffer, true)}
break
case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts, let call, let ttl):
if boxed {
buffer.appendInt32(-66811386)
@ -206,76 +185,39 @@ public extension Api {
if Int(flags) & Int(1 << 21) != 0 {call!.serialize(buffer, true)}
if Int(flags) & Int(1 << 24) != 0 {ttl!.serialize(buffer, true)}
break
case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId, let call, let ttl):
if boxed {
buffer.appendInt32(-500874592)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(id, buffer: buffer, boxed: false)
serializeString(about, buffer: buffer, boxed: false)
participants.serialize(buffer, true)
if Int(flags) & Int(1 << 2) != 0 {chatPhoto!.serialize(buffer, true)}
notifySettings.serialize(buffer, true)
if Int(flags) & Int(1 << 13) != 0 {exportedInvite!.serialize(buffer, true)}
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(botInfo!.count))
for item in botInfo! {
item.serialize(buffer, true)
}}
if Int(flags) & Int(1 << 6) != 0 {serializeInt32(pinnedMsgId!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 11) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 12) != 0 {call!.serialize(buffer, true)}
if Int(flags) & Int(1 << 14) != 0 {ttl!.serialize(buffer, true)}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId, let call, let ttl):
return ("chatFull", [("flags", flags), ("id", id), ("about", about), ("participants", participants), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("pinnedMsgId", pinnedMsgId), ("folderId", folderId), ("call", call), ("ttl", ttl)])
case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts, let call, let ttl):
return ("channelFull", [("flags", flags), ("id", id), ("about", about), ("participantsCount", participantsCount), ("adminsCount", adminsCount), ("kickedCount", kickedCount), ("bannedCount", bannedCount), ("onlineCount", onlineCount), ("readInboxMaxId", readInboxMaxId), ("readOutboxMaxId", readOutboxMaxId), ("unreadCount", unreadCount), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("migratedFromChatId", migratedFromChatId), ("migratedFromMaxId", migratedFromMaxId), ("pinnedMsgId", pinnedMsgId), ("stickerset", stickerset), ("availableMinId", availableMinId), ("folderId", folderId), ("linkedChatId", linkedChatId), ("location", location), ("slowmodeSeconds", slowmodeSeconds), ("slowmodeNextSendDate", slowmodeNextSendDate), ("statsDc", statsDc), ("pts", pts), ("call", call), ("ttl", ttl)])
case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId, let call, let ttl):
return ("chatFull", [("flags", flags), ("id", id), ("about", about), ("participants", participants), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("pinnedMsgId", pinnedMsgId), ("folderId", folderId), ("call", call), ("ttl", ttl)])
}
}
public static func parse_chatFull(_ reader: BufferReader) -> ChatFull? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: String?
_3 = parseString(reader)
var _4: Api.ChatParticipants?
if let signature = reader.readInt32() {
_4 = Api.parse(reader, signature: signature) as? Api.ChatParticipants
}
var _5: Api.Photo?
if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() {
_5 = Api.parse(reader, signature: signature) as? Api.Photo
} }
var _6: Api.PeerNotifySettings?
if let signature = reader.readInt32() {
_6 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings
}
var _7: Api.ExportedChatInvite?
if Int(_1!) & Int(1 << 13) != 0 {if let signature = reader.readInt32() {
_7 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite
} }
var _8: [Api.BotInfo]?
if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() {
_8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotInfo.self)
} }
var _9: Int32?
if Int(_1!) & Int(1 << 6) != 0 {_9 = reader.readInt32() }
var _10: Int32?
if Int(_1!) & Int(1 << 11) != 0 {_10 = reader.readInt32() }
var _11: Api.InputGroupCall?
if Int(_1!) & Int(1 << 12) != 0 {if let signature = reader.readInt32() {
_11 = Api.parse(reader, signature: signature) as? Api.InputGroupCall
} }
var _12: Api.PeerHistoryTTL?
if Int(_1!) & Int(1 << 14) != 0 {if let signature = reader.readInt32() {
_12 = Api.parse(reader, signature: signature) as? Api.PeerHistoryTTL
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil
let _c6 = _6 != nil
let _c7 = (Int(_1!) & Int(1 << 13) == 0) || _7 != nil
let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil
let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil
let _c10 = (Int(_1!) & Int(1 << 11) == 0) || _10 != nil
let _c11 = (Int(_1!) & Int(1 << 12) == 0) || _11 != nil
let _c12 = (Int(_1!) & Int(1 << 14) == 0) || _12 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 {
return Api.ChatFull.chatFull(flags: _1!, id: _2!, about: _3!, participants: _4!, chatPhoto: _5, notifySettings: _6!, exportedInvite: _7, botInfo: _8, pinnedMsgId: _9, folderId: _10, call: _11, ttl: _12)
}
else {
return nil
}
}
public static func parse_channelFull(_ reader: BufferReader) -> ChatFull? {
var _1: Int32?
_1 = reader.readInt32()
@ -387,6 +329,64 @@ public extension Api {
return nil
}
}
public static func parse_chatFull(_ reader: BufferReader) -> ChatFull? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: String?
_3 = parseString(reader)
var _4: Api.ChatParticipants?
if let signature = reader.readInt32() {
_4 = Api.parse(reader, signature: signature) as? Api.ChatParticipants
}
var _5: Api.Photo?
if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() {
_5 = Api.parse(reader, signature: signature) as? Api.Photo
} }
var _6: Api.PeerNotifySettings?
if let signature = reader.readInt32() {
_6 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings
}
var _7: Api.ExportedChatInvite?
if Int(_1!) & Int(1 << 13) != 0 {if let signature = reader.readInt32() {
_7 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite
} }
var _8: [Api.BotInfo]?
if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() {
_8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotInfo.self)
} }
var _9: Int32?
if Int(_1!) & Int(1 << 6) != 0 {_9 = reader.readInt32() }
var _10: Int32?
if Int(_1!) & Int(1 << 11) != 0 {_10 = reader.readInt32() }
var _11: Api.InputGroupCall?
if Int(_1!) & Int(1 << 12) != 0 {if let signature = reader.readInt32() {
_11 = Api.parse(reader, signature: signature) as? Api.InputGroupCall
} }
var _12: Api.PeerHistoryTTL?
if Int(_1!) & Int(1 << 14) != 0 {if let signature = reader.readInt32() {
_12 = Api.parse(reader, signature: signature) as? Api.PeerHistoryTTL
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil
let _c6 = _6 != nil
let _c7 = (Int(_1!) & Int(1 << 13) == 0) || _7 != nil
let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil
let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil
let _c10 = (Int(_1!) & Int(1 << 11) == 0) || _10 != nil
let _c11 = (Int(_1!) & Int(1 << 12) == 0) || _11 != nil
let _c12 = (Int(_1!) & Int(1 << 14) == 0) || _12 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 {
return Api.ChatFull.chatFull(flags: _1!, id: _2!, about: _3!, participants: _4!, chatPhoto: _5, notifySettings: _6!, exportedInvite: _7, botInfo: _8, pinnedMsgId: _9, folderId: _10, call: _11, ttl: _12)
}
else {
return nil
}
}
}
public enum PollResults: TypeConstructorDescription {
@ -2934,6 +2934,7 @@ public extension Api {
public enum InputChannel: TypeConstructorDescription {
case inputChannelEmpty
case inputChannel(channelId: Int32, accessHash: Int64)
case inputChannelFromMessage(peer: Api.InputPeer, msgId: Int32, channelId: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
@ -2950,6 +2951,14 @@ public extension Api {
serializeInt32(channelId, buffer: buffer, boxed: false)
serializeInt64(accessHash, buffer: buffer, boxed: false)
break
case .inputChannelFromMessage(let peer, let msgId, let channelId):
if boxed {
buffer.appendInt32(707290417)
}
peer.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false)
serializeInt32(channelId, buffer: buffer, boxed: false)
break
}
}
@ -2959,6 +2968,8 @@ public extension Api {
return ("inputChannelEmpty", [])
case .inputChannel(let channelId, let accessHash):
return ("inputChannel", [("channelId", channelId), ("accessHash", accessHash)])
case .inputChannelFromMessage(let peer, let msgId, let channelId):
return ("inputChannelFromMessage", [("peer", peer), ("msgId", msgId), ("channelId", channelId)])
}
}
@ -2979,6 +2990,25 @@ public extension Api {
return nil
}
}
public static func parse_inputChannelFromMessage(_ reader: BufferReader) -> InputChannel? {
var _1: Api.InputPeer?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.InputPeer
}
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
_3 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.InputChannel.inputChannelFromMessage(peer: _1!, msgId: _2!, channelId: _3!)
}
else {
return nil
}
}
}
public enum DcOption: TypeConstructorDescription {
@ -3252,40 +3282,60 @@ public extension Api {
}
public enum PeerHistoryTTL: TypeConstructorDescription {
case peerHistoryTTL(flags: Int32, ttlPeriod: Int32, ttlPeriodMy: Int32?)
case peerHistoryTTLPM(flags: Int32, myTtlPeriod: Int32?, peerTtlPeriod: Int32?)
case peerHistoryTTL(ttlPeriod: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .peerHistoryTTL(let flags, let ttlPeriod, let ttlPeriodMy):
case .peerHistoryTTLPM(let flags, let myTtlPeriod, let peerTtlPeriod):
if boxed {
buffer.appendInt32(-794007333)
buffer.appendInt32(-815649386)
}
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(myTtlPeriod!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 2) != 0 {serializeInt32(peerTtlPeriod!, buffer: buffer, boxed: false)}
break
case .peerHistoryTTL(let ttlPeriod):
if boxed {
buffer.appendInt32(1041354473)
}
serializeInt32(ttlPeriod, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(ttlPeriodMy!, buffer: buffer, boxed: false)}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .peerHistoryTTL(let flags, let ttlPeriod, let ttlPeriodMy):
return ("peerHistoryTTL", [("flags", flags), ("ttlPeriod", ttlPeriod), ("ttlPeriodMy", ttlPeriodMy)])
case .peerHistoryTTLPM(let flags, let myTtlPeriod, let peerTtlPeriod):
return ("peerHistoryTTLPM", [("flags", flags), ("myTtlPeriod", myTtlPeriod), ("peerTtlPeriod", peerTtlPeriod)])
case .peerHistoryTTL(let ttlPeriod):
return ("peerHistoryTTL", [("ttlPeriod", ttlPeriod)])
}
}
public static func parse_peerHistoryTTL(_ reader: BufferReader) -> PeerHistoryTTL? {
public static func parse_peerHistoryTTLPM(_ reader: BufferReader) -> PeerHistoryTTL? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
if Int(_1!) & Int(1 << 1) != 0 {_2 = reader.readInt32() }
var _3: Int32?
if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt32() }
if Int(_1!) & Int(1 << 2) != 0 {_3 = reader.readInt32() }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil
let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil
if _c1 && _c2 && _c3 {
return Api.PeerHistoryTTL.peerHistoryTTL(flags: _1!, ttlPeriod: _2!, ttlPeriodMy: _3)
return Api.PeerHistoryTTL.peerHistoryTTLPM(flags: _1!, myTtlPeriod: _2, peerTtlPeriod: _3)
}
else {
return nil
}
}
public static func parse_peerHistoryTTL(_ reader: BufferReader) -> PeerHistoryTTL? {
var _1: Int32?
_1 = reader.readInt32()
let _c1 = _1 != nil
if _c1 {
return Api.PeerHistoryTTL.peerHistoryTTL(ttlPeriod: _1!)
}
else {
return nil
@ -3588,13 +3638,13 @@ public extension Api {
}
public enum GroupCallParticipant: TypeConstructorDescription {
case groupCallParticipant(flags: Int32, userId: Int32, date: Int32, activeDate: Int32?, source: Int32, volume: Int32?)
case groupCallParticipant(flags: Int32, userId: Int32, date: Int32, activeDate: Int32?, source: Int32, volume: Int32?, params: Api.DataJSON?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .groupCallParticipant(let flags, let userId, let date, let activeDate, let source, let volume):
case .groupCallParticipant(let flags, let userId, let date, let activeDate, let source, let volume, let params):
if boxed {
buffer.appendInt32(1690708501)
buffer.appendInt32(-817921892)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(userId, buffer: buffer, boxed: false)
@ -3602,14 +3652,15 @@ public extension Api {
if Int(flags) & Int(1 << 3) != 0 {serializeInt32(activeDate!, buffer: buffer, boxed: false)}
serializeInt32(source, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 7) != 0 {serializeInt32(volume!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 10) != 0 {params!.serialize(buffer, true)}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .groupCallParticipant(let flags, let userId, let date, let activeDate, let source, let volume):
return ("groupCallParticipant", [("flags", flags), ("userId", userId), ("date", date), ("activeDate", activeDate), ("source", source), ("volume", volume)])
case .groupCallParticipant(let flags, let userId, let date, let activeDate, let source, let volume, let params):
return ("groupCallParticipant", [("flags", flags), ("userId", userId), ("date", date), ("activeDate", activeDate), ("source", source), ("volume", volume), ("params", params)])
}
}
@ -3626,14 +3677,19 @@ public extension Api {
_5 = reader.readInt32()
var _6: Int32?
if Int(_1!) & Int(1 << 7) != 0 {_6 = reader.readInt32() }
var _7: Api.DataJSON?
if Int(_1!) & Int(1 << 10) != 0 {if let signature = reader.readInt32() {
_7 = Api.parse(reader, signature: signature) as? Api.DataJSON
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil
let _c5 = _5 != nil
let _c6 = (Int(_1!) & Int(1 << 7) == 0) || _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.GroupCallParticipant.groupCallParticipant(flags: _1!, userId: _2!, date: _3!, activeDate: _4, source: _5!, volume: _6)
let _c7 = (Int(_1!) & Int(1 << 10) == 0) || _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.GroupCallParticipant.groupCallParticipant(flags: _1!, userId: _2!, date: _3!, activeDate: _4, source: _5!, volume: _6, params: _7)
}
else {
return nil
@ -4602,6 +4658,7 @@ public extension Api {
case updateGroupCallParticipants(call: Api.InputGroupCall, participants: [Api.GroupCallParticipant], version: Int32)
case updateGroupCall(chatId: Int32, call: Api.GroupCall)
case updateBotInlineQuery(flags: Int32, queryId: Int64, userId: Int32, query: String, geo: Api.GeoPoint?, peerType: Api.InlineQueryPeerType?, offset: String)
case updatePeerHistoryTTL(flags: Int32, peer: Api.Peer, ttl: Api.PeerHistoryTTL?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
@ -5363,6 +5420,14 @@ public extension Api {
if Int(flags) & Int(1 << 1) != 0 {peerType!.serialize(buffer, true)}
serializeString(offset, buffer: buffer, boxed: false)
break
case .updatePeerHistoryTTL(let flags, let peer, let ttl):
if boxed {
buffer.appendInt32(19291112)
}
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {ttl!.serialize(buffer, true)}
break
}
}
@ -5544,6 +5609,8 @@ public extension Api {
return ("updateGroupCall", [("chatId", chatId), ("call", call)])
case .updateBotInlineQuery(let flags, let queryId, let userId, let query, let geo, let peerType, let offset):
return ("updateBotInlineQuery", [("flags", flags), ("queryId", queryId), ("userId", userId), ("query", query), ("geo", geo), ("peerType", peerType), ("offset", offset)])
case .updatePeerHistoryTTL(let flags, let peer, let ttl):
return ("updatePeerHistoryTTL", [("flags", flags), ("peer", peer), ("ttl", ttl)])
}
}
@ -7069,6 +7136,27 @@ public extension Api {
return nil
}
}
public static func parse_updatePeerHistoryTTL(_ reader: BufferReader) -> Update? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Api.Peer?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.Peer
}
var _3: Api.PeerHistoryTTL?
if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.PeerHistoryTTL
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
if _c1 && _c2 && _c3 {
return Api.Update.updatePeerHistoryTTL(flags: _1!, peer: _2!, ttl: _3)
}
else {
return nil
}
}
}
public enum PopularContact: TypeConstructorDescription {
@ -10287,12 +10375,14 @@ public extension Api {
}
}
public enum InputPeer: TypeConstructorDescription {
indirect public enum InputPeer: TypeConstructorDescription {
case inputPeerEmpty
case inputPeerSelf
case inputPeerChat(chatId: Int32)
case inputPeerUser(userId: Int32, accessHash: Int64)
case inputPeerChannel(channelId: Int32, accessHash: Int64)
case inputPeerUserFromMessage(peer: Api.InputPeer, msgId: Int32, userId: Int32)
case inputPeerChannelFromMessage(peer: Api.InputPeer, msgId: Int32, channelId: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
@ -10328,6 +10418,22 @@ public extension Api {
serializeInt32(channelId, buffer: buffer, boxed: false)
serializeInt64(accessHash, buffer: buffer, boxed: false)
break
case .inputPeerUserFromMessage(let peer, let msgId, let userId):
if boxed {
buffer.appendInt32(398123750)
}
peer.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false)
serializeInt32(userId, buffer: buffer, boxed: false)
break
case .inputPeerChannelFromMessage(let peer, let msgId, let channelId):
if boxed {
buffer.appendInt32(-1667893317)
}
peer.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false)
serializeInt32(channelId, buffer: buffer, boxed: false)
break
}
}
@ -10343,6 +10449,10 @@ public extension Api {
return ("inputPeerUser", [("userId", userId), ("accessHash", accessHash)])
case .inputPeerChannel(let channelId, let accessHash):
return ("inputPeerChannel", [("channelId", channelId), ("accessHash", accessHash)])
case .inputPeerUserFromMessage(let peer, let msgId, let userId):
return ("inputPeerUserFromMessage", [("peer", peer), ("msgId", msgId), ("userId", userId)])
case .inputPeerChannelFromMessage(let peer, let msgId, let channelId):
return ("inputPeerChannelFromMessage", [("peer", peer), ("msgId", msgId), ("channelId", channelId)])
}
}
@ -10391,6 +10501,44 @@ public extension Api {
return nil
}
}
public static func parse_inputPeerUserFromMessage(_ reader: BufferReader) -> InputPeer? {
var _1: Api.InputPeer?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.InputPeer
}
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
_3 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.InputPeer.inputPeerUserFromMessage(peer: _1!, msgId: _2!, userId: _3!)
}
else {
return nil
}
}
public static func parse_inputPeerChannelFromMessage(_ reader: BufferReader) -> InputPeer? {
var _1: Api.InputPeer?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.InputPeer
}
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
_3 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.InputPeer.inputPeerChannelFromMessage(peer: _1!, msgId: _2!, channelId: _3!)
}
else {
return nil
}
}
}
public enum Contact: TypeConstructorDescription {
@ -20092,7 +20240,6 @@ public extension Api {
case messageActionGeoProximityReached(fromId: Api.Peer, toId: Api.Peer, distance: Int32)
case messageActionGroupCall(flags: Int32, call: Api.InputGroupCall, duration: Int32?)
case messageActionInviteToGroupCall(call: Api.InputGroupCall, users: [Int32])
case messageActionSetMessagesTTL(period: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
@ -20291,12 +20438,6 @@ public extension Api {
serializeInt32(item, buffer: buffer, boxed: false)
}
break
case .messageActionSetMessagesTTL(let period):
if boxed {
buffer.appendInt32(-1441072131)
}
serializeInt32(period, buffer: buffer, boxed: false)
break
}
}
@ -20354,8 +20495,6 @@ public extension Api {
return ("messageActionGroupCall", [("flags", flags), ("call", call), ("duration", duration)])
case .messageActionInviteToGroupCall(let call, let users):
return ("messageActionInviteToGroupCall", [("call", call), ("users", users)])
case .messageActionSetMessagesTTL(let period):
return ("messageActionSetMessagesTTL", [("period", period)])
}
}
@ -20682,17 +20821,6 @@ public extension Api {
return nil
}
}
public static func parse_messageActionSetMessagesTTL(_ reader: BufferReader) -> MessageAction? {
var _1: Int32?
_1 = reader.readInt32()
let _c1 = _1 != nil
if _c1 {
return Api.MessageAction.messageActionSetMessagesTTL(period: _1!)
}
else {
return nil
}
}
}
public enum PhoneCall: TypeConstructorDescription {
@ -21290,17 +21418,10 @@ public extension Api {
}
public enum PeerNotifySettings: TypeConstructorDescription {
case peerNotifySettingsEmpty
case peerNotifySettings(flags: Int32, showPreviews: Api.Bool?, silent: Api.Bool?, muteUntil: Int32?, sound: String?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .peerNotifySettingsEmpty:
if boxed {
buffer.appendInt32(1889961234)
}
break
case .peerNotifySettings(let flags, let showPreviews, let silent, let muteUntil, let sound):
if boxed {
buffer.appendInt32(-1353671392)
@ -21316,16 +21437,11 @@ public extension Api {
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .peerNotifySettingsEmpty:
return ("peerNotifySettingsEmpty", [])
case .peerNotifySettings(let flags, let showPreviews, let silent, let muteUntil, let sound):
return ("peerNotifySettings", [("flags", flags), ("showPreviews", showPreviews), ("silent", silent), ("muteUntil", muteUntil), ("sound", sound)])
}
}
public static func parse_peerNotifySettingsEmpty(_ reader: BufferReader) -> PeerNotifySettings? {
return Api.PeerNotifySettings.peerNotifySettingsEmpty
}
public static func parse_peerNotifySettings(_ reader: BufferReader) -> PeerNotifySettings? {
var _1: Int32?
_1 = reader.readInt32()

@ -4111,16 +4111,17 @@ public extension Api {
})
}
public static func setPeerMessagesTTL(peer: Api.InputPeer, period: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
public static func setHistoryTTL(flags: Int32, peer: Api.InputPeer, period: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(-56903344)
buffer.appendInt32(-859093215)
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
serializeInt32(period, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.setPeerMessagesTTL", parameters: [("peer", peer), ("period", period)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
return (FunctionDescription(name: "messages.setHistoryTTL", parameters: [("flags", flags), ("peer", peer), ("period", period)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Bool?
var result: Api.Updates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Bool
result = Api.parse(reader, signature: signature) as? Api.Updates
}
return result
})
@ -4908,24 +4909,6 @@ public extension Api {
})
}
public static func sendCode(flags: Int32, phoneNumber: String, currentNumber: Api.Bool?, apiId: Int32, apiHash: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.auth.SentCode>) {
let buffer = Buffer()
buffer.appendInt32(-2035355412)
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(phoneNumber, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {currentNumber!.serialize(buffer, true)}
serializeInt32(apiId, buffer: buffer, boxed: false)
serializeString(apiHash, buffer: buffer, boxed: false)
return (FunctionDescription(name: "auth.sendCode", parameters: [("flags", flags), ("phoneNumber", phoneNumber), ("currentNumber", currentNumber), ("apiId", apiId), ("apiHash", apiHash)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in
let reader = BufferReader(buffer)
var result: Api.auth.SentCode?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.auth.SentCode
}
return result
})
}
public static func signIn(phoneNumber: String, phoneCodeHash: String, phoneCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.auth.Authorization>) {
let buffer = Buffer()
buffer.appendInt32(-1126886015)
@ -5206,6 +5189,23 @@ public extension Api {
return result
})
}
public static func sendCode(phoneNumber: String, apiId: Int32, apiHash: String, settings: Api.CodeSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.auth.SentCode>) {
let buffer = Buffer()
buffer.appendInt32(-1502141361)
serializeString(phoneNumber, buffer: buffer, boxed: false)
serializeInt32(apiId, buffer: buffer, boxed: false)
serializeString(apiHash, buffer: buffer, boxed: false)
settings.serialize(buffer, true)
return (FunctionDescription(name: "auth.sendCode", parameters: [("phoneNumber", phoneNumber), ("apiId", apiId), ("apiHash", apiHash), ("settings", settings)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in
let reader = BufferReader(buffer)
var result: Api.auth.SentCode?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.auth.SentCode
}
return result
})
}
}
public struct bots {
public static func sendCustomRequest(customMethod: String, params: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.DataJSON>) {
@ -6322,22 +6322,6 @@ public extension Api {
})
}
public static func sendChangePhoneCode(flags: Int32, phoneNumber: String, currentNumber: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.auth.SentCode>) {
let buffer = Buffer()
buffer.appendInt32(149257707)
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(phoneNumber, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {currentNumber!.serialize(buffer, true)}
return (FunctionDescription(name: "account.sendChangePhoneCode", parameters: [("flags", flags), ("phoneNumber", phoneNumber), ("currentNumber", currentNumber)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in
let reader = BufferReader(buffer)
var result: Api.auth.SentCode?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.auth.SentCode
}
return result
})
}
public static func changePhone(phoneNumber: String, phoneCodeHash: String, phoneCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.User>) {
let buffer = Buffer()
buffer.appendInt32(1891839707)
@ -6410,22 +6394,6 @@ public extension Api {
})
}
public static func sendConfirmPhoneCode(flags: Int32, hash: String, currentNumber: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.auth.SentCode>) {
let buffer = Buffer()
buffer.appendInt32(353818557)
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(hash, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {currentNumber!.serialize(buffer, true)}
return (FunctionDescription(name: "account.sendConfirmPhoneCode", parameters: [("flags", flags), ("hash", hash), ("currentNumber", currentNumber)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in
let reader = BufferReader(buffer)
var result: Api.auth.SentCode?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.auth.SentCode
}
return result
})
}
public static func confirmPhone(phoneCodeHash: String, phoneCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(1596029123)
@ -6606,22 +6574,6 @@ public extension Api {
})
}
public static func sendVerifyPhoneCode(flags: Int32, phoneNumber: String, currentNumber: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.auth.SentCode>) {
let buffer = Buffer()
buffer.appendInt32(-2110553932)
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(phoneNumber, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {currentNumber!.serialize(buffer, true)}
return (FunctionDescription(name: "account.sendVerifyPhoneCode", parameters: [("flags", flags), ("phoneNumber", phoneNumber), ("currentNumber", currentNumber)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in
let reader = BufferReader(buffer)
var result: Api.auth.SentCode?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.auth.SentCode
}
return result
})
}
public static func verifyPhone(phoneNumber: String, phoneCodeHash: String, phoneCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(1305716726)
@ -7128,6 +7080,51 @@ public extension Api {
return result
})
}
public static func sendChangePhoneCode(phoneNumber: String, settings: Api.CodeSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.auth.SentCode>) {
let buffer = Buffer()
buffer.appendInt32(-2108208411)
serializeString(phoneNumber, buffer: buffer, boxed: false)
settings.serialize(buffer, true)
return (FunctionDescription(name: "account.sendChangePhoneCode", parameters: [("phoneNumber", phoneNumber), ("settings", settings)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in
let reader = BufferReader(buffer)
var result: Api.auth.SentCode?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.auth.SentCode
}
return result
})
}
public static func sendConfirmPhoneCode(hash: String, settings: Api.CodeSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.auth.SentCode>) {
let buffer = Buffer()
buffer.appendInt32(457157256)
serializeString(hash, buffer: buffer, boxed: false)
settings.serialize(buffer, true)
return (FunctionDescription(name: "account.sendConfirmPhoneCode", parameters: [("hash", hash), ("settings", settings)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in
let reader = BufferReader(buffer)
var result: Api.auth.SentCode?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.auth.SentCode
}
return result
})
}
public static func sendVerifyPhoneCode(phoneNumber: String, settings: Api.CodeSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.auth.SentCode>) {
let buffer = Buffer()
buffer.appendInt32(-1516022023)
serializeString(phoneNumber, buffer: buffer, boxed: false)
settings.serialize(buffer, true)
return (FunctionDescription(name: "account.sendVerifyPhoneCode", parameters: [("phoneNumber", phoneNumber), ("settings", settings)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in
let reader = BufferReader(buffer)
var result: Api.auth.SentCode?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.auth.SentCode
}
return result
})
}
}
public struct wallet {
public static func sendLiteRequest(body: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.wallet.LiteResponse>) {
@ -7208,21 +7205,6 @@ public extension Api {
})
}
public static func getDifference(langCode: String, fromVersion: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.LangPackDifference>) {
let buffer = Buffer()
buffer.appendInt32(-1655576556)
serializeString(langCode, buffer: buffer, boxed: false)
serializeInt32(fromVersion, buffer: buffer, boxed: false)
return (FunctionDescription(name: "langpack.getDifference", parameters: [("langCode", langCode), ("fromVersion", fromVersion)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.LangPackDifference? in
let reader = BufferReader(buffer)
var result: Api.LangPackDifference?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.LangPackDifference
}
return result
})
}
public static func getLanguage(langPack: String, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.LangPackLanguage>) {
let buffer = Buffer()
buffer.appendInt32(1784243458)
@ -7237,6 +7219,22 @@ public extension Api {
return result
})
}
public static func getDifference(langPack: String, langCode: String, fromVersion: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.LangPackDifference>) {
let buffer = Buffer()
buffer.appendInt32(-845657435)
serializeString(langPack, buffer: buffer, boxed: false)
serializeString(langCode, buffer: buffer, boxed: false)
serializeInt32(fromVersion, buffer: buffer, boxed: false)
return (FunctionDescription(name: "langpack.getDifference", parameters: [("langPack", langPack), ("langCode", langCode), ("fromVersion", fromVersion)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.LangPackDifference? in
let reader = BufferReader(buffer)
var result: Api.LangPackDifference?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.LangPackDifference
}
return result
})
}
}
public struct photos {
public static func deletePhotos(id: [Api.InputPhoto]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int64]>) {

@ -305,9 +305,9 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
var effectiveLevel: Float = 0.0
var audioLevels = audioLevels
if !strongSelf.currentIsMuted {
audioLevels.append((PeerId(0), myAudioLevel, true))
audioLevels.append((PeerId(0), 0, myAudioLevel, true))
}
effectiveLevel = audioLevels.map { $0.1 }.max() ?? 0.0
effectiveLevel = audioLevels.map { $0.2 }.max() ?? 0.0
strongSelf.backgroundNode.audioLevel = effectiveLevel
}))
}

@ -928,6 +928,7 @@ public final class PresentationCallImpl: PresentationCall {
let setOnOrientationUpdated = view.setOnOrientationUpdated
let setOnIsMirroredUpdated = view.setOnIsMirroredUpdated
completion(PresentationCallVideoView(
holder: view,
view: view.view,
setOnFirstFrameReceived: { f in
setOnFirstFrameReceived(f)
@ -997,6 +998,7 @@ public final class PresentationCallImpl: PresentationCall {
let setOnOrientationUpdated = view.setOnOrientationUpdated
let setOnIsMirroredUpdated = view.setOnIsMirroredUpdated
completion(PresentationCallVideoView(
holder: view,
view: view.view,
setOnFirstFrameReceived: { f in
setOnFirstFrameReceived(f)

@ -16,6 +16,27 @@ import UniversalMediaPlayer
import AccountContext
import DeviceProximity
private extension GroupCallParticipantsContext.Participant {
var allSsrcs: Set<UInt32> {
var participantSsrcs = Set<UInt32>()
participantSsrcs.insert(self.ssrc)
if let jsonParams = self.jsonParams, let jsonData = jsonParams.data(using: .utf8), let json = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] {
if let groups = json["ssrc-groups"] as? [Any] {
for group in groups {
if let group = group as? [String: Any] {
if let groupSources = group["sources"] as? [UInt32] {
for source in groupSources {
participantSsrcs.insert(source)
}
}
}
}
}
}
return participantSsrcs
}
}
public final class AccountGroupCallContextImpl: AccountGroupCallContext {
public final class Proxy {
public let context: AccountGroupCallContextImpl
@ -241,7 +262,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
}
}
private let audioLevelsPromise = Promise<[(PeerId, Float, Bool)]>()
private let audioLevelsPromise = Promise<[(PeerId, UInt32, Float, Bool)]>()
init() {
}
@ -278,10 +299,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
}
}
var audioLevels: [(PeerId, Float, Bool)] = []
for (peerId, _, level, hasVoice) in levels {
var audioLevels: [(PeerId, UInt32, Float, Bool)] = []
for (peerId, source, level, hasVoice) in levels {
if level > 0.001 {
audioLevels.append((peerId, level, hasVoice))
audioLevels.append((peerId, source, level, hasVoice))
}
}
@ -294,7 +315,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
return self.speakingParticipantsPromise.get()
}
func getAudioLevels() -> Signal<[(PeerId, Float, Bool)], NoError> {
func getAudioLevels() -> Signal<[(PeerId, UInt32, Float, Bool)], NoError> {
return self.audioLevelsPromise.get()
}
}
@ -314,6 +335,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
public let peerId: PeerId
public let peer: Peer?
public private(set) var isVideo: Bool
private let temporaryJoinTimestamp: Int32
private var internalState: InternalState = .requesting
@ -321,6 +344,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
private var callContext: OngoingGroupCallContext?
private var ssrcMapping: [UInt32: PeerId] = [:]
private var requestedSsrcs = Set<UInt32>()
private var summaryInfoState = Promise<SummaryInfoState?>(nil)
private var summaryParticipantsState = Promise<SummaryParticipantsState?>(nil)
@ -363,7 +388,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
private let speakingParticipantsContext = SpeakingParticipantsContext()
private var speakingParticipantsReportTimestamp: [PeerId: Double] = [:]
public var audioLevels: Signal<[(PeerId, Float, Bool)], NoError> {
public var audioLevels: Signal<[(PeerId, UInt32, Float, Bool)], NoError> {
return self.speakingParticipantsContext.getAudioLevels()
}
@ -435,6 +460,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
}
private let memberEventsPipeDisposable = MetaDisposable()
private let joinDisposable = MetaDisposable()
private let requestDisposable = MetaDisposable()
private var groupCallParticipantUpdatesDisposable: Disposable?
@ -456,6 +482,18 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
private var didConnectOnce: Bool = false
private var toneRenderer: PresentationCallToneRenderer?
private var videoCapturer: OngoingCallVideoCapturer?
private let incomingVideoSourcePromise = Promise<[PeerId: UInt32]>([:])
public var incomingVideoSources: Signal<[PeerId: UInt32], NoError> {
return self.incomingVideoSourcePromise.get()
}
private var missingSsrcs = Set<UInt32>()
private var processedMissingSsrcs = Set<UInt32>()
private let missingSsrcsDisposable = MetaDisposable()
private var isRequestingMissingSsrcs: Bool = false
init(
accountContext: AccountContext,
audioSession: ManagedAudioSession,
@ -479,6 +517,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.temporaryJoinTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
//self.videoCapturer = OngoingCallVideoCapturer(keepLandscape: true)
self.isVideo = self.videoCapturer != nil
var didReceiveAudioOutputs = false
if !audioSession.getIsHeadsetPluggedIn() {
@ -581,6 +622,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
return
}
if case let .estabilished(callInfo, _, _, _) = strongSelf.internalState {
var addedParticipants: [(UInt32, String?)] = []
var removedSsrc: [UInt32] = []
for (callId, update) in updates {
if callId == callInfo.id {
@ -600,6 +642,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
strongSelf._canBeRemoved.set(.single(true))
}
} else if case .joined = participantUpdate.participationStatusChange {
addedParticipants.append((participantUpdate.ssrc, participantUpdate.jsonParams))
} else if strongSelf.ssrcMapping[participantUpdate.ssrc] == nil {
addedParticipants.append((participantUpdate.ssrc, participantUpdate.jsonParams))
}
}
case let .call(isTerminated, _):
@ -612,6 +657,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
if !removedSsrc.isEmpty {
strongSelf.callContext?.removeSsrcs(ssrcs: removedSsrc)
}
//strongSelf.callContext?.addParticipants(participants: addedParticipants)
}
})
@ -669,6 +715,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
participants.append(GroupCallParticipantsContext.Participant(
peer: accountPeer,
ssrc: 0,
jsonParams: nil,
joinTimestamp: strongSelf.temporaryJoinTimestamp,
activityTimestamp: nil,
muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false),
@ -731,6 +778,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.audioSessionActiveDisposable?.dispose()
self.summaryStateDisposable?.dispose()
self.audioSessionDisposable?.dispose()
self.joinDisposable.dispose()
self.requestDisposable.dispose()
self.groupCallParticipantUpdatesDisposable?.dispose()
self.leaveDisposable.dispose()
@ -742,6 +790,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.participantsContextStateDisposable.dispose()
self.myAudioLevelDisposable.dispose()
self.memberEventsPipeDisposable.dispose()
self.missingSsrcsDisposable.dispose()
self.myAudioLevelTimer?.invalidate()
self.typingDisposable.dispose()
@ -788,10 +837,39 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
break
default:
if case let .active(callInfo) = internalState {
let callContext = OngoingGroupCallContext()
let callContext = OngoingGroupCallContext(video: self.videoCapturer, participantDescriptionsRequired: { [weak self] ssrcs in
Queue.mainQueue().async {
guard let strongSelf = self else {
return
}
strongSelf.maybeRequestParticipants(ssrcs: ssrcs)
}
})
self.incomingVideoSourcePromise.set(callContext.videoSources
|> deliverOnMainQueue
|> map { [weak self] sources -> [PeerId: UInt32] in
guard let strongSelf = self else {
return [:]
}
var result: [PeerId: UInt32] = [:]
for source in sources {
if let peerId = strongSelf.ssrcMapping[source] {
result[peerId] = source
}
}
return result
})
self.callContext = callContext
self.requestDisposable.set((callContext.joinPayload
|> take(1)
self.joinDisposable.set((callContext.joinPayload
|> distinctUntilChanged(isEqual: { lhs, rhs in
if lhs.0 != rhs.0 {
return false
}
if lhs.1 != rhs.1 {
return false
}
return true
})
|> deliverOnMainQueue).start(next: { [weak self] joinPayload, ssrc in
guard let strongSelf = self else {
return
@ -809,6 +887,14 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
return
}
if let clientParams = joinCallResult.callInfo.clientParams {
strongSelf.ssrcMapping.removeAll()
var addedParticipants: [(UInt32, String?)] = []
for participant in joinCallResult.state.participants {
strongSelf.ssrcMapping[participant.ssrc] = participant.peer.id
//addedParticipants.append((participant.ssrc, participant.jsonParams))
}
strongSelf.callContext?.setJoinResponse(payload: clientParams, participants: addedParticipants)
strongSelf.updateSessionState(internalState: .estabilished(info: joinCallResult.callInfo, clientParams: clientParams, localSsrc: ssrc, initialState: joinCallResult.state), audioSessionControl: strongSelf.audioSessionControl)
}
}, error: { error in
@ -941,14 +1027,6 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.stateValue.defaultParticipantMuteState = initialState.defaultParticipantsAreMuted.isMuted ? .muted : .unmuted
}
self.ssrcMapping.removeAll()
var ssrcs: [UInt32] = []
for participant in initialState.participants {
self.ssrcMapping[participant.ssrc] = participant.peer.id
ssrcs.append(participant.ssrc)
}
self.callContext?.setJoinResponse(payload: clientParams, ssrcs: ssrcs)
let accountContext = self.accountContext
let peerId = self.peerId
let rawAdminIds = Signal<Set<PeerId>, NoError> { subscriber in
@ -1118,6 +1196,73 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
}
}
private func maybeRequestParticipants(ssrcs: Set<UInt32>) {
var missingSsrcs = ssrcs
missingSsrcs.subtract(self.processedMissingSsrcs)
if missingSsrcs.isEmpty {
return
}
self.processedMissingSsrcs.formUnion(ssrcs)
var addedParticipants: [(UInt32, String?)] = []
if let membersValue = self.membersValue {
for participant in membersValue.participants {
let participantSsrcs = participant.allSsrcs
if !missingSsrcs.intersection(participantSsrcs).isEmpty {
missingSsrcs.subtract(participantSsrcs)
self.processedMissingSsrcs.formUnion(participantSsrcs)
addedParticipants.append((participant.ssrc, participant.jsonParams))
}
}
}
if !addedParticipants.isEmpty {
self.callContext?.addParticipants(participants: addedParticipants)
}
if !missingSsrcs.isEmpty {
self.missingSsrcs.formUnion(missingSsrcs)
self.maybeRequestMissingSsrcs()
}
}
private func maybeRequestMissingSsrcs() {
if self.isRequestingMissingSsrcs {
return
}
if self.missingSsrcs.isEmpty {
return
}
if case let .estabilished(callInfo, _, _, _) = self.internalState {
self.isRequestingMissingSsrcs = true
let requestedSsrcs = self.missingSsrcs
self.missingSsrcsDisposable.set((getGroupCallParticipants(account: self.account, callId: callInfo.id, accessHash: callInfo.accessHash, offset: "", ssrcs: Array(requestedSsrcs), limit: 100)
|> deliverOnMainQueue).start(next: { [weak self] state in
guard let strongSelf = self else {
return
}
strongSelf.isRequestingMissingSsrcs = false
strongSelf.missingSsrcs.subtract(requestedSsrcs)
var addedParticipants: [(UInt32, String?)] = []
for participant in state.participants {
addedParticipants.append((participant.ssrc, participant.jsonParams))
}
if !addedParticipants.isEmpty {
strongSelf.callContext?.addParticipants(participants: addedParticipants)
}
strongSelf.maybeRequestMissingSsrcs()
}))
}
}
private func startCheckingCallIfNeeded() {
if self.checkCallDisposable != nil {
return
@ -1253,7 +1398,26 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.stateValue.muteState = nil
}
}
public func requestVideo() {
if self.videoCapturer == nil {
let videoCapturer = OngoingCallVideoCapturer()
self.videoCapturer = videoCapturer
}
self.isVideo = true
if let videoCapturer = self.videoCapturer {
self.callContext?.requestVideo(videoCapturer)
}
}
public func disableVideo() {
self.isVideo = false
if let _ = self.videoCapturer {
self.videoCapturer = nil
self.callContext?.disableVideo()
}
}
public func setVolume(peerId: PeerId, volume: Int32, sync: Bool) {
for (ssrc, id) in self.ssrcMapping {
if id == peerId {
@ -1266,6 +1430,19 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
}
}
public func setFullSizeVideo(peerId: PeerId?) {
var resolvedSsrc: UInt32?
if let peerId = peerId {
for (ssrc, id) in self.ssrcMapping {
if id == peerId {
resolvedSsrc = ssrc
break
}
}
}
self.callContext?.setFullSizeVideoSsrc(ssrc: resolvedSsrc)
}
public func setCurrentAudioOutput(_ output: AudioSessionOutput) {
guard self.currentSelectedAudioOutputValue != output else {
return
@ -1376,6 +1553,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.callContext?.stop()
self.callContext = nil
self.missingSsrcsDisposable.set(nil)
self.missingSsrcs.removeAll()
self.processedMissingSsrcs.removeAll()
self.internalState = .requesting
self.isCurrentlyConnecting = nil
@ -1407,6 +1588,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
}
}
self.joinDisposable.set(nil)
self.requestDisposable.set((currentOrRequestedCall
|> deliverOnMainQueue).start(next: { [weak self] value in
guard let strongSelf = self else {
@ -1497,6 +1679,71 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.participantsContext?.updateDefaultParticipantsAreMuted(isMuted: isMuted)
}
public func makeIncomingVideoView(source: UInt32, completion: @escaping (PresentationCallVideoView?) -> Void) {
self.callContext?.makeIncomingVideoView(source: source, completion: { view in
if let view = view {
let setOnFirstFrameReceived = view.setOnFirstFrameReceived
let setOnOrientationUpdated = view.setOnOrientationUpdated
let setOnIsMirroredUpdated = view.setOnIsMirroredUpdated
completion(PresentationCallVideoView(
holder: view,
view: view.view,
setOnFirstFrameReceived: { f in
setOnFirstFrameReceived(f)
},
getOrientation: { [weak view] in
if let view = view {
let mappedValue: PresentationCallVideoView.Orientation
switch view.getOrientation() {
case .rotation0:
mappedValue = .rotation0
case .rotation90:
mappedValue = .rotation90
case .rotation180:
mappedValue = .rotation180
case .rotation270:
mappedValue = .rotation270
}
return mappedValue
} else {
return .rotation0
}
},
getAspect: { [weak view] in
if let view = view {
return view.getAspect()
} else {
return 0.0
}
},
setOnOrientationUpdated: { f in
setOnOrientationUpdated { value, aspect in
let mappedValue: PresentationCallVideoView.Orientation
switch value {
case .rotation0:
mappedValue = .rotation0
case .rotation90:
mappedValue = .rotation90
case .rotation180:
mappedValue = .rotation180
case .rotation270:
mappedValue = .rotation270
}
f?(mappedValue, aspect)
}
},
setOnIsMirroredUpdated: { f in
setOnIsMirroredUpdated { value in
f?(value)
}
}
))
} else {
completion(nil)
}
})
}
public func loadMoreMembers(token: String) {
self.participantsContext?.loadMore(token: token)
}

@ -118,6 +118,169 @@ private final class VoiceChatControllerTitleNode: ASDisplayNode {
}
}
final class GroupVideoNode: ASDisplayNode {
private let videoViewContainer: UIView
private let videoView: PresentationCallVideoView
private var validLayout: CGSize?
var tapped: (() -> Void)?
init(videoView: PresentationCallVideoView) {
self.videoViewContainer = UIView()
self.videoView = videoView
super.init()
self.videoViewContainer.addSubview(self.videoView.view)
self.view.addSubview(self.videoViewContainer)
videoView.setOnFirstFrameReceived({ [weak self] _ in
Queue.mainQueue().async {
guard let strongSelf = self else {
return
}
if let size = strongSelf.validLayout {
strongSelf.updateLayout(size: size, transition: .immediate)
}
}
})
videoView.setOnOrientationUpdated({ [weak self] _, _ in
Queue.mainQueue().async {
guard let strongSelf = self else {
return
}
if let size = strongSelf.validLayout {
strongSelf.updateLayout(size: size, transition: .immediate)
}
}
})
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
}
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
self.tapped?()
}
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
self.validLayout = size
self.videoViewContainer.frame = CGRect(origin: CGPoint(), size: size)
let orientation = self.videoView.getOrientation()
var aspect = self.videoView.getAspect()
if aspect <= 0.01 {
aspect = 3.0 / 4.0
}
let rotatedAspect: CGFloat
let angle: CGFloat
let switchOrientation: Bool
switch orientation {
case .rotation0:
angle = 0.0
rotatedAspect = 1 / aspect
switchOrientation = false
case .rotation90:
angle = CGFloat.pi / 2.0
rotatedAspect = aspect
switchOrientation = true
case .rotation180:
angle = CGFloat.pi
rotatedAspect = 1 / aspect
switchOrientation = false
case .rotation270:
angle = CGFloat.pi * 3.0 / 2.0
rotatedAspect = aspect
switchOrientation = true
}
var rotatedVideoSize = CGSize(width: 100.0, height: rotatedAspect * 100.0)
if size.width < 100.0 || true {
rotatedVideoSize = rotatedVideoSize.aspectFilled(size)
} else {
rotatedVideoSize = rotatedVideoSize.aspectFitted(size)
}
if switchOrientation {
rotatedVideoSize = CGSize(width: rotatedVideoSize.height, height: rotatedVideoSize.width)
}
var rotatedVideoFrame = CGRect(origin: CGPoint(x: floor((size.width - rotatedVideoSize.width) / 2.0), y: floor((size.height - rotatedVideoSize.height) / 2.0)), size: rotatedVideoSize)
rotatedVideoFrame.origin.x = floor(rotatedVideoFrame.origin.x)
rotatedVideoFrame.origin.y = floor(rotatedVideoFrame.origin.y)
rotatedVideoFrame.size.width = ceil(rotatedVideoFrame.size.width)
rotatedVideoFrame.size.height = ceil(rotatedVideoFrame.size.height)
self.videoView.view.center = rotatedVideoFrame.center
self.videoView.view.bounds = CGRect(origin: CGPoint(), size: rotatedVideoFrame.size)
let transition: ContainedViewLayoutTransition = .immediate
transition.updateTransformRotation(view: self.videoView.view, angle: angle)
}
}
private final class MainVideoContainerNode: ASDisplayNode {
private let context: AccountContext
private let call: PresentationGroupCall
private var currentVideoNode: GroupVideoNode?
private var currentPeer: (PeerId, UInt32)?
private var validLayout: CGSize?
init(context: AccountContext, call: PresentationGroupCall) {
self.context = context
self.call = call
super.init()
self.backgroundColor = .black
}
func updatePeer(peer: (peerId: PeerId, source: UInt32)?) {
if self.currentPeer?.0 == peer?.0 && self.currentPeer?.1 == peer?.1 {
return
}
self.currentPeer = peer
if let (peerId, source) = peer {
self.call.makeIncomingVideoView(source: source, completion: { [weak self] videoView in
Queue.mainQueue().async {
guard let strongSelf = self, let videoView = videoView else {
return
}
let videoNode = GroupVideoNode(videoView: videoView)
if let currentVideoNode = strongSelf.currentVideoNode {
currentVideoNode.removeFromSupernode()
strongSelf.currentVideoNode = nil
}
strongSelf.currentVideoNode = videoNode
strongSelf.addSubnode(videoNode)
if let size = strongSelf.validLayout {
strongSelf.update(size: size, transition: .immediate)
}
}
})
} else {
if let currentVideoNode = self.currentVideoNode {
currentVideoNode.removeFromSupernode()
self.currentVideoNode = nil
}
}
}
func update(size: CGSize, transition: ContainedViewLayoutTransition) {
self.validLayout = size
if let currentVideoNode = self.currentVideoNode {
transition.updateFrame(node: currentVideoNode, frame: CGRect(origin: CGPoint(), size: size))
currentVideoNode.updateLayout(size: size, transition: .immediate)
}
}
}
public final class VoiceChatController: ViewController {
private final class Node: ViewControllerTracingNode, UIGestureRecognizerDelegate {
private struct ListTransition {
@ -137,22 +300,28 @@ public final class VoiceChatController: ViewController {
private final class Interaction {
let updateIsMuted: (PeerId, Bool) -> Void
let openPeer: (PeerId) -> Void
let openInvite: () -> Void
let peerContextAction: (PeerEntry, ASDisplayNode, ContextGesture?) -> Void
let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
let getPeerVideo: (UInt32) -> GroupVideoNode?
private var audioLevels: [PeerId: ValuePipe<Float>] = [:]
init(
updateIsMuted: @escaping (PeerId, Bool) -> Void,
openPeer: @escaping (PeerId) -> Void,
openInvite: @escaping () -> Void,
peerContextAction: @escaping (PeerEntry, ASDisplayNode, ContextGesture?) -> Void,
setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void
setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void,
getPeerVideo: @escaping (UInt32) -> GroupVideoNode?
) {
self.updateIsMuted = updateIsMuted
self.openPeer = openPeer
self.openInvite = openInvite
self.peerContextAction = peerContextAction
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
self.getPeerVideo = getPeerVideo
}
func getAudioLevel(_ peerId: PeerId) -> Signal<Float, NoError> {
@ -170,9 +339,9 @@ public final class VoiceChatController: ViewController {
}
}
func updateAudioLevels(_ levels: [(PeerId, Float, Bool)], reset: Bool = false) {
func updateAudioLevels(_ levels: [(PeerId, UInt32, Float, Bool)], reset: Bool = false) {
var updated = Set<PeerId>()
for (peerId, level, _) in levels {
for (peerId, _, level, _) in levels {
if let pipe = self.audioLevels[peerId] {
if reset {
pipe.putNext(level)
@ -200,6 +369,7 @@ public final class VoiceChatController: ViewController {
}
var peer: Peer
var ssrc: UInt32
var presence: TelegramUserPresence?
var activityTimestamp: Int32
var state: State
@ -216,6 +386,9 @@ public final class VoiceChatController: ViewController {
if !lhs.peer.isEqual(rhs.peer) {
return false
}
if lhs.ssrc != rhs.ssrc {
return false
}
if lhs.presence != rhs.presence {
return false
}
@ -366,7 +539,9 @@ public final class VoiceChatController: ViewController {
let revealOptions: [VoiceChatParticipantItem.RevealOption] = []
return VoiceChatParticipantItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: context, peer: peer, presence: peerEntry.presence, text: text, icon: icon, enabled: true, selectable: peer.id != context.account.peerId || peerEntry.canManageCall, getAudioLevel: { return interaction.getAudioLevel(peer.id) }, revealOptions: revealOptions, revealed: peerEntry.revealed, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
return VoiceChatParticipantItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: context, peer: peer, ssrc: peerEntry.ssrc, presence: peerEntry.presence, text: text, icon: icon, enabled: true, selectable: peer.id != context.account.peerId || peerEntry.canManageCall, getAudioLevel: { return interaction.getAudioLevel(peer.id) }, getVideo: {
return interaction.getPeerVideo(peerEntry.ssrc)
}, revealOptions: revealOptions, revealed: peerEntry.revealed, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
interaction.setPeerIdWithRevealedOptions(peerId, fromPeerId)
}, action: { node in
interaction.peerContextAction(peerEntry, node, nil)
@ -396,6 +571,7 @@ public final class VoiceChatController: ViewController {
private let dimNode: ASDisplayNode
private let contentContainer: ASDisplayNode
private let backgroundNode: ASDisplayNode
private var mainVideoContainer: MainVideoContainerNode?
private let listNode: ListView
private let topPanelNode: ASDisplayNode
private let topPanelEdgeNode: ASDisplayNode
@ -408,6 +584,7 @@ public final class VoiceChatController: ViewController {
private let bottomPanelBackgroundNode: ASDisplayNode
private let bottomCornersNode: ASImageNode
fileprivate let audioOutputNode: CallControllerButtonItemNode
fileprivate let cameraButtonNode: CallControllerButtonItemNode
fileprivate let leaveNode: CallControllerButtonItemNode
fileprivate let actionButton: VoiceChatActionButton
private let leftBorderNode: ASDisplayNode
@ -468,6 +645,12 @@ public final class VoiceChatController: ViewController {
private let inviteDisposable = MetaDisposable()
private let memberEventsDisposable = MetaDisposable()
private let voiceSourcesDisposable = MetaDisposable()
private var requestedVideoSources = Set<UInt32>()
private var videoNodes: [(PeerId, UInt32, GroupVideoNode)] = []
private var currentDominantSpeakerWithVideo: (PeerId, UInt32)?
init(controller: VoiceChatController, sharedContext: SharedAccountContext, call: PresentationGroupCall) {
self.controller = controller
@ -489,6 +672,10 @@ public final class VoiceChatController: ViewController {
self.backgroundNode.backgroundColor = secondaryPanelBackgroundColor
self.backgroundNode.clipsToBounds = false
if false {
self.mainVideoContainer = MainVideoContainerNode(context: call.accountContext, call: call)
}
self.listNode = ListView()
self.listNode.verticalScrollIndicatorColor = UIColor(white: 1.0, alpha: 0.3)
self.listNode.clipsToBounds = true
@ -537,6 +724,7 @@ public final class VoiceChatController: ViewController {
self.bottomCornersNode.image = cornersImage(top: false, bottom: true, dark: false)
self.audioOutputNode = CallControllerButtonItemNode()
self.cameraButtonNode = CallControllerButtonItemNode()
self.leaveNode = CallControllerButtonItemNode()
self.actionButton = VoiceChatActionButton()
@ -561,6 +749,34 @@ public final class VoiceChatController: ViewController {
self.itemInteraction = Interaction(
updateIsMuted: { [weak self] peerId, isMuted in
let _ = self?.call.updateMuteState(peerId: peerId, isMuted: isMuted)
}, openPeer: { [weak self] peerId in
if let strongSelf = self, let navigationController = strongSelf.controller?.parentNavigationController {
/*let context = strongSelf.context
strongSelf.controller?.dismiss(completion: {
Queue.mainQueue().justDispatch {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId), keepStack: .always, purposefulAction: {}, peekData: nil))
}
})*/
for entry in strongSelf.currentEntries {
switch entry {
case let .peer(peer):
if peer.peer.id == peerId {
let source = peer.ssrc
if strongSelf.currentDominantSpeakerWithVideo?.0 != peerId || strongSelf.currentDominantSpeakerWithVideo?.1 != source {
strongSelf.currentDominantSpeakerWithVideo = (peerId, source)
strongSelf.call.setFullSizeVideo(peerId: peerId)
strongSelf.mainVideoContainer?.updatePeer(peer: (peerId: peerId, source: source))
} else {
strongSelf.currentDominantSpeakerWithVideo = nil
strongSelf.call.setFullSizeVideo(peerId: nil)
strongSelf.mainVideoContainer?.updatePeer(peer: nil)
}
}
default:
break
}
}
}
}, openInvite: { [weak self] in
guard let strongSelf = self else {
return
@ -815,6 +1031,17 @@ public final class VoiceChatController: ViewController {
}), true))
}
/*items.append(.action(ContextMenuActionItem(text: "Toggle Full Screen", icon: { theme in
return nil
}, action: { _, f in
guard let strongSelf = self else {
return
}
strongSelf.itemInteraction?.openPeer(peer.id)
f(.default)
})))*/
if peer.id != strongSelf.context.account.peerId {
if let callState = strongSelf.callState, (callState.canManageCall || callState.adminIds.contains(strongSelf.context.account.peerId)) {
if callState.adminIds.contains(peer.id) {
@ -955,6 +1182,16 @@ public final class VoiceChatController: ViewController {
updated.revealedPeerId = peerId
return updated
}
}, getPeerVideo: { [weak self] ssrc in
guard let strongSelf = self else {
return nil
}
for (_, listSsrc, videoNode) in strongSelf.videoNodes {
if listSsrc == ssrc {
return videoNode
}
}
return nil
})
self.topPanelNode.addSubnode(self.topPanelEdgeNode)
@ -968,6 +1205,7 @@ public final class VoiceChatController: ViewController {
self.bottomPanelNode.addSubnode(self.bottomCornersNode)
self.bottomPanelNode.addSubnode(self.bottomPanelBackgroundNode)
self.bottomPanelNode.addSubnode(self.audioOutputNode)
//self.bottomPanelNode.addSubnode(self.cameraButtonNode)
self.bottomPanelNode.addSubnode(self.leaveNode)
self.bottomPanelNode.addSubnode(self.actionButton)
@ -976,6 +1214,9 @@ public final class VoiceChatController: ViewController {
self.contentContainer.addSubnode(self.backgroundNode)
self.contentContainer.addSubnode(self.listNode)
if let mainVideoContainer = self.mainVideoContainer {
self.contentContainer.addSubnode(mainVideoContainer)
}
self.contentContainer.addSubnode(self.topPanelNode)
self.contentContainer.addSubnode(self.leftBorderNode)
self.contentContainer.addSubnode(self.rightBorderNode)
@ -1092,6 +1333,28 @@ public final class VoiceChatController: ViewController {
if strongSelf.effectiveMuteState != nil {
levels = levels.filter { $0.0 != strongSelf.context.account.peerId }
}
var maxLevelWithVideo: (PeerId, UInt32, Float)?
for (peerId, source, level, hasSpeech) in levels {
if hasSpeech && source != 0 {
if let (_, _, currentLevel) = maxLevelWithVideo {
if currentLevel < level {
maxLevelWithVideo = (peerId, source, level)
}
} else {
maxLevelWithVideo = (peerId, source, level)
}
}
}
if let (peerId, source, _) = maxLevelWithVideo {
if strongSelf.currentDominantSpeakerWithVideo?.0 != peerId || strongSelf.currentDominantSpeakerWithVideo?.1 != source {
strongSelf.currentDominantSpeakerWithVideo = (peerId, source)
strongSelf.call.setFullSizeVideo(peerId: peerId)
strongSelf.mainVideoContainer?.updatePeer(peer: (peerId: peerId, source: source))
}
}
strongSelf.itemInteraction?.updateAudioLevels(levels)
})
@ -1113,6 +1376,8 @@ public final class VoiceChatController: ViewController {
self.audioOutputNode.addTarget(self, action: #selector(self.audioOutputPressed), forControlEvents: .touchUpInside)
self.cameraButtonNode.addTarget(self, action: #selector(self.cameraPressed), forControlEvents: .touchUpInside)
self.optionsButton.contextAction = { [weak self] sourceNode, gesture in
guard let strongSelf = self, let controller = strongSelf.controller else {
return
@ -1246,6 +1511,90 @@ public final class VoiceChatController: ViewController {
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: event.peer, text: strongSelf.presentationData.strings.VoiceChat_PeerJoinedText(event.peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).0), action: { _ in return false })
}
}))
self.voiceSourcesDisposable.set((self.call.incomingVideoSources
|> deliverOnMainQueue).start(next: { [weak self] sources in
guard let strongSelf = self else {
return
}
var validSources = Set<UInt32>()
for (peerId, source) in sources {
validSources.insert(source)
if !strongSelf.requestedVideoSources.contains(source) {
strongSelf.requestedVideoSources.insert(source)
strongSelf.call.makeIncomingVideoView(source: source, completion: { videoView in
Queue.mainQueue().async {
guard let strongSelf = self, let videoView = videoView else {
return
}
let videoNode = GroupVideoNode(videoView: videoView)
strongSelf.videoNodes.append((peerId, source, videoNode))
//strongSelf.addSubnode(videoNode)
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .immediate)
loop: for i in 0 ..< strongSelf.currentEntries.count {
let entry = strongSelf.currentEntries[i]
switch entry {
case let .peer(peerEntry):
if peerEntry.ssrc == source {
let presentationData = strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme)
strongSelf.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [ListViewUpdateItem(index: i, previousIndex: i, item: entry.item(context: strongSelf.context, presentationData: presentationData, interaction: strongSelf.itemInteraction!), directionHint: nil)], options: [.Synchronous], updateOpaqueState: nil)
break loop
}
default:
break
}
}
}
}
})
}
}
var updated = false
for i in (0 ..< strongSelf.videoNodes.count).reversed() {
if !validSources.contains(strongSelf.videoNodes[i].1) {
let ssrc = strongSelf.videoNodes[i].1
strongSelf.videoNodes.remove(at: i)
loop: for j in 0 ..< strongSelf.currentEntries.count {
let entry = strongSelf.currentEntries[j]
switch entry {
case let .peer(peerEntry):
if peerEntry.ssrc == ssrc {
let presentationData = strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme)
strongSelf.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [ListViewUpdateItem(index: i, previousIndex: i, item: entry.item(context: strongSelf.context, presentationData: presentationData, interaction: strongSelf.itemInteraction!), directionHint: nil)], options: [.Synchronous], updateOpaqueState: nil)
break loop
}
default:
break
}
}
//strongSelf.videoNodes[i].2.removeFromSupernode()
updated = true
}
}
if let (_, source) = strongSelf.currentDominantSpeakerWithVideo {
if !validSources.contains(source) {
strongSelf.currentDominantSpeakerWithVideo = nil
strongSelf.call.setFullSizeVideo(peerId: nil)
strongSelf.mainVideoContainer?.updatePeer(peer: nil)
}
}
if updated {
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .immediate)
}
}
}))
//self.isFullscreen = true
//self.isExpanded = true
}
deinit {
@ -1259,6 +1608,8 @@ public final class VoiceChatController: ViewController {
self.audioLevelsDisposable?.dispose()
self.myAudioLevelDisposable?.dispose()
self.inviteDisposable.dispose()
self.memberEventsDisposable.dispose()
self.voiceSourcesDisposable.dispose()
}
override func didLoad() {
@ -1390,7 +1741,7 @@ public final class VoiceChatController: ViewController {
self.call.setIsMuted(action: .muted(isPushToTalkActive: false))
}
self.itemInteraction?.updateAudioLevels([(self.context.account.peerId, 0.0, false)], reset: true)
self.itemInteraction?.updateAudioLevels([(self.context.account.peerId, 0, 0.0, false)], reset: true)
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .spring))
@ -1468,6 +1819,14 @@ public final class VoiceChatController: ViewController {
}
}
@objc private func cameraPressed() {
if self.call.isVideo {
self.call.disableVideo()
} else {
self.call.requestVideo()
}
}
private func updateFloatingHeaderOffset(offset: CGFloat, transition: ContainedViewLayoutTransition, completion: (() -> Void)? = nil) {
guard let (layout, _) = self.validLayout else {
return
@ -1507,6 +1866,12 @@ public final class VoiceChatController: ViewController {
let panelOffset = max(layoutTopInset, rawPanelOffset)
let topPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: panelOffset), size: CGSize(width: size.width, height: topPanelHeight))
if let mainVideoContainer = self.mainVideoContainer {
let videoContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY), size: CGSize(width: layout.size.width, height: 200.0))
transition.updateFrameAdditive(node: mainVideoContainer, frame: videoContainerFrame)
mainVideoContainer.update(size: videoContainerFrame.size, transition: transition)
}
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY), size: CGSize(width: size.width, height: layout.size.height))
let sideInset: CGFloat = 16.0
let leftBorderFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY - 16.0), size: CGSize(width: sideInset, height: layout.size.height))
@ -1720,6 +2085,10 @@ public final class VoiceChatController: ViewController {
let sideButtonSize = CGSize(width: 60.0, height: 60.0)
self.audioOutputNode.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: soundAppearance, image: soundImage), text: soundTitle, transition: .animated(duration: 0.3, curve: .linear))
let cameraButtonSize = CGSize(width: 40.0, height: 40.0)
self.cameraButtonNode.update(size: cameraButtonSize, content: CallControllerButtonItemNode.Content(appearance: CallControllerButtonItemNode.Content.Appearance.blurred(isFilled: false), image: .camera), text: " ", transition: .animated(duration: 0.3, curve: .linear))
self.leaveNode.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: .color(.custom(0xff3b30, 0.3)), image: .end), text: self.presentationData.strings.VoiceChat_Leave, transition: .immediate)
}
@ -1769,7 +2138,10 @@ public final class VoiceChatController: ViewController {
}
let bottomPanelHeight = bottomAreaHeight + layout.intrinsicInsets.bottom
let listTopInset = layoutTopInset + topPanelHeight
var listTopInset = layoutTopInset + topPanelHeight
if self.mainVideoContainer != nil {
listTopInset += 200.0
}
let listSize = CGSize(width: size.width, height: layout.size.height - listTopInset - bottomPanelHeight)
let topInset: CGFloat
@ -1800,6 +2172,7 @@ public final class VoiceChatController: ViewController {
transition.updateFrame(node: self.bottomPanelNode, frame: bottomPanelFrame)
let sideButtonSize = CGSize(width: 60.0, height: 60.0)
let cameraButtonSize = CGSize(width: 40.0, height: 40.0)
let centralButtonSize = CGSize(width: 440.0, height: 440.0)
let actionButtonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - centralButtonSize.width) / 2.0), y: floorToScreenPixels((bottomAreaHeight - centralButtonSize.height) / 2.0)), size: centralButtonSize)
@ -1851,12 +2224,41 @@ public final class VoiceChatController: ViewController {
self.updateButtons(transition: transition)
/*var currentVideoOrigin = CGPoint(x: 4.0, y: (layout.statusBarHeight ?? 0.0) + 4.0)
for (_, _, videoNode) in self.videoNodes {
let videoSize = CGSize(width: 300.0, height: 500.0)
if currentVideoOrigin.x + videoSize.width > layout.size.width {
currentVideoOrigin.x = 0.0
currentVideoOrigin.y += videoSize.height
}
videoNode.frame = CGRect(origin: currentVideoOrigin, size: videoSize)
videoNode.updateLayout(size: videoSize, transition: .immediate)
if videoNode.supernode == nil {
self.contentContainer.addSubnode(videoNode)
}
currentVideoOrigin.x += videoSize.width + 4.0
}*/
let sideButtonMinimalInset: CGFloat = 16.0
let sideButtonOffset = min(36.0, floor((((size.width - 144.0) / 2.0) - sideButtonSize.width) / 2.0))
let sideButtonOrigin = max(sideButtonMinimalInset, floor((size.width - 144.0) / 2.0) - sideButtonOffset - sideButtonSize.width)
if self.audioOutputNode.supernode === self.bottomPanelNode {
transition.updateFrame(node: self.audioOutputNode, frame: CGRect(origin: CGPoint(x: sideButtonOrigin, y: floor((bottomAreaHeight - sideButtonSize.height) / 2.0)), size: sideButtonSize))
if true {
let audioOutputFrame = CGRect(origin: CGPoint(x: sideButtonOrigin, y: floor((bottomAreaHeight - sideButtonSize.height) / 2.0)), size: sideButtonSize)
transition.updateFrame(node: self.audioOutputNode, frame: audioOutputFrame)
} else {
let cameraButtonDistance: CGFloat = 4.0
let audioOutputFrame = CGRect(origin: CGPoint(x: sideButtonOrigin, y: floor((bottomAreaHeight - sideButtonSize.height - cameraButtonDistance - cameraButtonSize.height) / 2.0) + cameraButtonDistance + cameraButtonSize.height), size: sideButtonSize)
transition.updateFrame(node: self.audioOutputNode, frame: audioOutputFrame)
transition.updateFrame(node: self.cameraButtonNode, frame: CGRect(origin: CGPoint(x: floor(audioOutputFrame.midX - cameraButtonSize.width / 2.0), y: audioOutputFrame.minY - cameraButtonDistance - cameraButtonSize.height), size: cameraButtonSize))
}
transition.updateFrame(node: self.leaveNode, frame: CGRect(origin: CGPoint(x: size.width - sideButtonOrigin - sideButtonSize.width, y: floor((bottomAreaHeight - sideButtonSize.height) / 2.0)), size: sideButtonSize))
}
if isFirstTime {
@ -1883,10 +2285,13 @@ public final class VoiceChatController: ViewController {
if self.actionButton.supernode !== self.bottomPanelNode {
self.actionButton.ignoreHierarchyChanges = true
self.audioOutputNode.isHidden = false
self.cameraButtonNode.isHidden = false
self.leaveNode.isHidden = false
self.audioOutputNode.layer.removeAllAnimations()
self.cameraButtonNode.layer.removeAllAnimations()
self.leaveNode.layer.removeAllAnimations()
self.bottomPanelNode.addSubnode(self.audioOutputNode)
//self.bottomPanelNode.addSubnode(self.cameraButtonNode)
self.bottomPanelNode.addSubnode(self.leaveNode)
self.bottomPanelNode.addSubnode(self.actionButton)
self.containerLayoutUpdated(layout, navigationHeight :navigationHeight, transition: .immediate)
@ -2085,6 +2490,7 @@ public final class VoiceChatController: ViewController {
entries.append(.peer(PeerEntry(
peer: member.peer,
ssrc: member.ssrc,
presence: nil,
activityTimestamp: Int32.max - 1 - index,
state: memberState,
@ -2098,6 +2504,7 @@ public final class VoiceChatController: ViewController {
if let accountPeer = self.accountPeer, !processedPeerIds.contains(accountPeer.id) {
entries.insert(.peer(PeerEntry(
peer: accountPeer,
ssrc: 0,
presence: nil,
activityTimestamp: Int32.max - 1 - index,
state: .listening,
@ -2115,6 +2522,7 @@ public final class VoiceChatController: ViewController {
entries.append(.peer(PeerEntry(
peer: peer,
ssrc: 0,
presence: nil,
activityTimestamp: Int32.max - 1 - index,
state: .invited,
@ -2135,7 +2543,7 @@ public final class VoiceChatController: ViewController {
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer is DirectionalPanGestureRecognizer {
let location = gestureRecognizer.location(in: self.bottomPanelNode.view)
if self.audioOutputNode.frame.contains(location) || self.leaveNode.frame.contains(location) {
if self.audioOutputNode.frame.contains(location) || (!self.cameraButtonNode.isHidden && self.cameraButtonNode.frame.contains(location)) || self.leaveNode.frame.contains(location) {
return false
}
}
@ -2328,6 +2736,14 @@ public final class VoiceChatController: ViewController {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let result = super.hitTest(point, with: event)
if let result = result {
for (_, _, videoNode) in self.videoNodes {
if videoNode.view === result || result.isDescendant(of: videoNode.view) {
return result
}
}
}
if result === self.topPanelNode.view {
return self.view

@ -62,30 +62,34 @@ final class VoiceChatParticipantItem: ListViewItem {
let nameDisplayOrder: PresentationPersonNameOrder
let context: AccountContext
let peer: Peer
let ssrc: UInt32?
let presence: PeerPresence?
let text: ParticipantText
let icon: Icon
let enabled: Bool
public let selectable: Bool
let getAudioLevel: (() -> Signal<Float, NoError>)?
let getVideo: () -> GroupVideoNode?
let revealOptions: [RevealOption]
let revealed: Bool?
let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
let action: ((ASDisplayNode) -> Void)?
let contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: Peer, presence: PeerPresence?, text: ParticipantText, icon: Icon, enabled: Bool, selectable: Bool, getAudioLevel: (() -> Signal<Float, NoError>)?, revealOptions: [RevealOption], revealed: Bool?, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, action: ((ASDisplayNode) -> Void)?, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil) {
public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: Peer, ssrc: UInt32?, presence: PeerPresence?, text: ParticipantText, icon: Icon, enabled: Bool, selectable: Bool, getAudioLevel: (() -> Signal<Float, NoError>)?, getVideo: @escaping () -> GroupVideoNode?, revealOptions: [RevealOption], revealed: Bool?, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, action: ((ASDisplayNode) -> Void)?, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil) {
self.presentationData = presentationData
self.dateTimeFormat = dateTimeFormat
self.nameDisplayOrder = nameDisplayOrder
self.context = context
self.peer = peer
self.ssrc = ssrc
self.presence = presence
self.text = text
self.icon = icon
self.enabled = enabled
self.selectable = selectable
self.getAudioLevel = getAudioLevel
self.getVideo = getVideo
self.revealOptions = revealOptions
self.revealed = revealed
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
@ -171,6 +175,12 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
private var layoutParams: (VoiceChatParticipantItem, ListViewItemLayoutParams, Bool, Bool)?
private var wavesColor: UIColor?
private var videoNode: GroupVideoNode?
var item: VoiceChatParticipantItem? {
return self.layoutParams?.0
}
init() {
self.topStripeNode = ASDisplayNode()
self.topStripeNode.isLayerBacked = true
@ -188,7 +198,6 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
self.offsetContainerNode = ASDisplayNode()
self.avatarNode = AvatarNode(font: avatarFont)
self.avatarNode.isLayerBacked = !smartInvertColorsEnabled()
self.avatarNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 40.0, height: 40.0))
self.titleNode = TextNode()
@ -667,11 +676,31 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
})
}
let videoSize = CGSize(width: avatarSize, height: avatarSize)
let videoNode = item.getVideo()
if let current = strongSelf.videoNode, current !== videoNode {
current.removeFromSupernode()
}
let actionOffset: CGFloat = 0.0
strongSelf.videoNode = videoNode
if let videoNode = videoNode {
videoNode.updateLayout(size: videoSize, transition: .immediate)
if videoNode.supernode !== strongSelf.avatarNode {
videoNode.clipsToBounds = true
videoNode.cornerRadius = avatarSize / 2.0
strongSelf.avatarNode.addSubnode(videoNode)
}
videoNode.frame = CGRect(origin: CGPoint(), size: videoSize)
}
let animationSize = CGSize(width: 36.0, height: 36.0)
strongSelf.iconNode?.frame = CGRect(origin: CGPoint(), size: animationSize)
strongSelf.animationNode?.frame = CGRect(origin: CGPoint(), size: animationSize)
strongSelf.actionButtonNode.frame = CGRect(x: params.width - animationSize.width - 6.0 - params.rightInset, y: floor((layout.contentSize.height - animationSize.height) / 2.0) + 1.0, width: animationSize.width, height: animationSize.height)
strongSelf.actionButtonNode.frame = CGRect(x: params.width - animationSize.width - 6.0 - params.rightInset + actionOffset, y: floor((layout.contentSize.height - animationSize.height) / 2.0) + 1.0, width: animationSize.width, height: animationSize.height)
if let presence = item.presence as? TelegramUserPresence {
strongSelf.peerPresenceManager?.reset(presence: presence)

@ -112,6 +112,7 @@ enum AccountStateMutationOperation {
case UpdateReadThread(threadMessageId: MessageId, readMaxId: Int32, isIncoming: Bool, mainChannelMessage: MessageId?)
case UpdateGroupCallParticipants(id: Int64, accessHash: Int64, participants: [Api.GroupCallParticipant], version: Int32)
case UpdateGroupCall(peerId: PeerId, call: Api.GroupCall)
case UpdateAutoremoveTimeout(peer: Api.Peer, value: CachedPeerAutoremoveTimeout.Value?)
}
struct HoleFromPreviousState {
@ -286,6 +287,10 @@ struct AccountMutableState {
self.addOperation(.UpdateGroupCall(peerId: peerId, call: call))
}
mutating func updateAutoremoveTimeout(peer: Api.Peer, value: CachedPeerAutoremoveTimeout.Value?) {
self.addOperation(.UpdateAutoremoveTimeout(peer: peer, value: value))
}
mutating func readGroupFeedInbox(groupId: PeerGroupId, index: MessageIndex) {
self.addOperation(.ReadGroupFeedInbox(groupId, index))
}
@ -494,7 +499,7 @@ struct AccountMutableState {
mutating func addOperation(_ operation: AccountStateMutationOperation) {
switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll/*, .UpdateMessageReactions*/, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateMessagesPinned:
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll/*, .UpdateMessageReactions*/, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateMessagesPinned, .UpdateAutoremoveTimeout:
break
case let .AddMessages(messages, location):
for message in messages {

@ -1358,6 +1358,8 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
case let .updateGroupCall(channelId, call):
updatedState.updateGroupCall(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId), call: call)
updatedState.updateGroupCall(peerId: PeerId(namespace: Namespaces.Peer.CloudGroup, id: channelId), call: call)
case let .updatePeerHistoryTTL(_, peer, ttl):
updatedState.updateAutoremoveTimeout(peer: peer, value: CachedPeerAutoremoveTimeout.Value(ttl))
case let .updateLangPackTooLong(langCode):
updatedState.updateLangPack(langCode: langCode, difference: nil)
case let .updateLangPack(difference):
@ -2148,7 +2150,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation])
var currentAddScheduledMessages: OptimizeAddMessagesState?
for operation in operations {
switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll/*, .UpdateMessageReactions*/, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall:
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll/*, .UpdateMessageReactions*/, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateAutoremoveTimeout:
if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty {
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
}
@ -3021,6 +3023,18 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
}
})
}
case let .UpdateAutoremoveTimeout(peer, value):
transaction.updatePeerCachedData(peerIds: Set([peer.peerId]), update: { _, current in
if let current = current as? CachedUserData {
return current.withUpdatedAutoremoveTimeout(.known(value))
} else if let current = current as? CachedGroupData {
return current.withUpdatedAutoremoveTimeout(.known(value))
} else if let current = current as? CachedChannelData {
return current.withUpdatedAutoremoveTimeout(.known(value))
} else {
return current
}
})
case let .UpdateLangPack(langCode, difference):
if let difference = difference {
if langPackDifferences[langCode] == nil {

@ -37,6 +37,8 @@ func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? {
migrationReference = TelegramGroupToChannelMigrationReference(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId), accessHash: accessHash)
case .inputChannelEmpty:
break
case .inputChannelFromMessage:
break
}
}
var groupFlags = TelegramGroupFlags()

@ -34,7 +34,6 @@ func applyMaxReadIndexInteractively(transaction: Transaction, stateManager: Acco
})
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media))
})
transaction.addTimestampBasedMessageAttribute(tag: 0, timestamp: timestamp + attribute.timeout, messageId: id)
}
break
}
@ -103,7 +102,6 @@ func applySecretOutgoingMessageReadActions(transaction: Transaction, id: Message
})
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media))
})
transaction.addTimestampBasedMessageAttribute(tag: 0, timestamp: timestamp + attribute.timeout, messageId: id)
}
break
}

@ -117,7 +117,7 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes
attributes = updatedMessage.attributes
text = updatedMessage.text
forwardInfo = updatedMessage.forwardInfo
} else if case let .updateShortSentMessage(_, _, _, _, _, apiMedia, entities, _) = result {
} else if case let .updateShortSentMessage(_, _, _, _, _, apiMedia, entities, ttlPeriod) = result {
let (mediaValue, _) = textMediaAndExpirationTimerFromApiMedia(apiMedia, currentMessage.id.peerId)
if let mediaValue = mediaValue {
media = [mediaValue]
@ -136,6 +136,11 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes
updatedAttributes.append(TextEntitiesMessageAttribute(entities: messageTextEntitiesFromApiEntities(entities)))
}
updatedAttributes = updatedAttributes.filter({ !($0 is AutoremoveTimeoutMessageAttribute) })
if let ttlPeriod = ttlPeriod {
updatedAttributes.append(AutoremoveTimeoutMessageAttribute(timeout: ttlPeriod, countdownBeginTime: updatedTimestamp))
}
if Namespaces.Message.allScheduled.contains(message.id.namespace) && updatedId.namespace == Namespaces.Message.Cloud {
for i in 0 ..< updatedAttributes.count {
if updatedAttributes[i] is OutgoingScheduleInfoMessageAttribute {

@ -32,7 +32,7 @@ func switchToAuthorizedAccount(transaction: AccountManagerModifier, account: Una
}
public func sendAuthorizationCode(accountManager: AccountManager, account: UnauthorizedAccount, phoneNumber: String, apiId: Int32, apiHash: String, syncContacts: Bool) -> Signal<UnauthorizedAccount, AuthorizationCodeRequestError> {
let sendCode = Api.functions.auth.sendCode(flags: 0, phoneNumber: phoneNumber, currentNumber: nil, apiId: apiId, apiHash: apiHash)
let sendCode = Api.functions.auth.sendCode(phoneNumber: phoneNumber, apiId: apiId, apiHash: apiHash, settings: .codeSettings(flags: 0))
let codeAndAccount = account.network.request(sendCode, automaticFloodWait: false)
|> map { result in

@ -19,7 +19,7 @@ public enum RequestCancelAccountResetDataError {
}
public func requestCancelAccountResetData(network: Network, hash: String) -> Signal<CancelAccountResetData, RequestCancelAccountResetDataError> {
return network.request(Api.functions.account.sendConfirmPhoneCode(flags: 0, hash: hash, currentNumber: nil), automaticFloodWait: false)
return network.request(Api.functions.account.sendConfirmPhoneCode(hash: hash, settings: .codeSettings(flags: 0)), automaticFloodWait: false)
|> mapError { error -> RequestCancelAccountResetDataError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded

@ -37,7 +37,7 @@ public enum RequestChangeAccountPhoneNumberVerificationError {
}
public func requestChangeAccountPhoneNumberVerification(account: Account, phoneNumber: String) -> Signal<ChangeAccountPhoneNumberData, RequestChangeAccountPhoneNumberVerificationError> {
return account.network.request(Api.functions.account.sendChangePhoneCode(flags: 0, phoneNumber: phoneNumber, currentNumber: nil), automaticFloodWait: false)
return account.network.request(Api.functions.account.sendChangePhoneCode(phoneNumber: phoneNumber, settings: .codeSettings(flags: 0)), automaticFloodWait: false)
|> mapError { error -> RequestChangeAccountPhoneNumberVerificationError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded

@ -484,6 +484,10 @@ private func requestChatListFilters(accountPeerId: PeerId, postbox: Postbox, net
missingGroups.append(id)
case .inputPeerEmpty:
break
case .inputPeerUserFromMessage:
break
case .inputPeerChannelFromMessage:
break
}
}

@ -141,3 +141,77 @@ public func clearCallHistory(account: Account, forEveryone: Bool) -> Signal<Neve
|> ignoreValues
|> castError(ClearCallHistoryError.self)
}
public enum SetChatMessageAutoremoveTimeoutError {
case generic
}
public func setChatMessageAutoremoveTimeoutInteractively(account: Account, peerId: PeerId, timeout: Int32?, isGlobal: Bool) -> Signal<Never, SetChatMessageAutoremoveTimeoutError> {
return account.postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(peerId).flatMap(apiInputPeer)
}
|> castError(SetChatMessageAutoremoveTimeoutError.self)
|> mapToSignal { inputPeer -> Signal<Never, SetChatMessageAutoremoveTimeoutError> in
guard let inputPeer = inputPeer else {
return .fail(.generic)
}
var flags: Int32 = 0
if !isGlobal {
flags |= 1 << 0
}
return account.network.request(Api.functions.messages.setHistoryTTL(flags: flags, peer: inputPeer, period: timeout ?? 0))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil)
}
|> castError(SetChatMessageAutoremoveTimeoutError.self)
|> mapToSignal { result -> Signal<Never, SetChatMessageAutoremoveTimeoutError> in
if let result = result {
account.stateManager.addUpdates(result)
return account.postbox.transaction { transaction -> Void in
transaction.updatePeerCachedData(peerIds: [peerId], update: { _, current in
var currentPeerValue: Int32?
if let current = current as? CachedUserData {
if case let .known(value?) = current.autoremoveTimeout {
currentPeerValue = value.peerValue
}
} else if let current = current as? CachedGroupData {
if case let .known(value?) = current.autoremoveTimeout {
currentPeerValue = value.peerValue
}
} else if let current = current as? CachedChannelData {
if case let .known(value?) = current.autoremoveTimeout {
currentPeerValue = value.peerValue
}
}
let updatedTimeout: CachedPeerAutoremoveTimeout
if let timeout = timeout {
updatedTimeout = .known(CachedPeerAutoremoveTimeout.Value(myValue: timeout, peerValue: currentPeerValue ?? timeout, isGlobal: isGlobal))
} else {
updatedTimeout = .known(nil)
}
if let current = current as? CachedUserData {
return current.withUpdatedAutoremoveTimeout(updatedTimeout)
} else if let current = current as? CachedGroupData {
return current.withUpdatedAutoremoveTimeout(updatedTimeout)
} else if let current = current as? CachedChannelData {
return current.withUpdatedAutoremoveTimeout(updatedTimeout)
} else {
return current
}
})
}
|> castError(SetChatMessageAutoremoveTimeoutError.self)
|> ignoreValues
} else {
return .fail(.generic)
}
}
|> `catch` { _ -> Signal<Never, SetChatMessageAutoremoveTimeoutError> in
return .complete()
}
}
}

@ -332,6 +332,25 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
if !disableAutoremove, let messageAutoremoveTimeout = peer.messageAutoremoveTimeout, !isAction {
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: messageAutoremoveTimeout, countdownBeginTime: nil))
}
} else if let cachedData = transaction.getPeerCachedData(peerId: peer.id) {
var messageAutoremoveTimeout: Int32?
if let cachedData = cachedData as? CachedUserData {
if case let .known(value) = cachedData.autoremoveTimeout {
messageAutoremoveTimeout = value?.effectiveValue
}
} else if let cachedData = cachedData as? CachedGroupData {
if case let .known(value) = cachedData.autoremoveTimeout {
messageAutoremoveTimeout = value?.effectiveValue
}
} else if let cachedData = cachedData as? CachedChannelData {
if case let .known(value) = cachedData.autoremoveTimeout {
messageAutoremoveTimeout = value?.effectiveValue
}
}
if let messageAutoremoveTimeout = messageAutoremoveTimeout {
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: messageAutoremoveTimeout, countdownBeginTime: nil))
}
}
attributes.append(contentsOf: filterMessageAttributesForOutgoingMessage(requestedAttributes))

@ -7,8 +7,6 @@ import SyncCore
extension MessageNotificationSettings {
init(apiSettings: Api.PeerNotifySettings) {
switch apiSettings {
case .peerNotifySettingsEmpty:
self = .defaultSettings
case let .peerNotifySettings(_, showPreviews, _, muteUntil, sound):
let displayPreviews: Bool
if let showPreviews = showPreviews, case .boolFalse = showPreviews {

@ -88,7 +88,7 @@ public func getCurrentGroupCall(account: Account, callId: Int64, accessHash: Int
loop: for participant in participants {
switch participant {
case let .groupCallParticipant(flags, userId, date, activeDate, source, volume):
case let .groupCallParticipant(flags, userId, date, activeDate, source, volume, params):
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
let ssrc = UInt32(bitPattern: source)
guard let peer = transaction.getPeer(peerId) else {
@ -103,9 +103,17 @@ public func getCurrentGroupCall(account: Account, callId: Int64, accessHash: Int
} else if mutedByYou {
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: false, mutedByYou: mutedByYou)
}
var jsonParams: String?
if let params = params {
switch params {
case let .dataJSON(data):
jsonParams = data
}
}
parsedParticipants.append(GroupCallParticipantsContext.Participant(
peer: peer,
ssrc: ssrc,
jsonParams: jsonParams,
joinTimestamp: date,
activityTimestamp: activeDate.flatMap(Double.init),
muteState: muteState,
@ -228,7 +236,7 @@ public func getGroupCallParticipants(account: Account, callId: Int64, accessHash
loop: for participant in participants {
switch participant {
case let .groupCallParticipant(flags, userId, date, activeDate, source, volume):
case let .groupCallParticipant(flags, userId, date, activeDate, source, volume, params):
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
let ssrc = UInt32(bitPattern: source)
guard let peer = transaction.getPeer(peerId) else {
@ -243,9 +251,17 @@ public func getGroupCallParticipants(account: Account, callId: Int64, accessHash
} else if mutedByYou {
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: false, mutedByYou: mutedByYou)
}
var jsonParams: String?
if let params = params {
switch params {
case let .dataJSON(data):
jsonParams = data
}
}
parsedParticipants.append(GroupCallParticipantsContext.Participant(
peer: peer,
ssrc: ssrc,
jsonParams: jsonParams,
joinTimestamp: date,
activityTimestamp: activeDate.flatMap(Double.init),
muteState: muteState,
@ -564,6 +580,7 @@ public final class GroupCallParticipantsContext {
public var peer: Peer
public var ssrc: UInt32
public var jsonParams: String?
public var joinTimestamp: Int32
public var activityTimestamp: Double?
public var muteState: MuteState?
@ -572,6 +589,7 @@ public final class GroupCallParticipantsContext {
public init(
peer: Peer,
ssrc: UInt32,
jsonParams: String?,
joinTimestamp: Int32,
activityTimestamp: Double?,
muteState: MuteState?,
@ -579,6 +597,7 @@ public final class GroupCallParticipantsContext {
) {
self.peer = peer
self.ssrc = ssrc
self.jsonParams = jsonParams
self.joinTimestamp = joinTimestamp
self.activityTimestamp = activityTimestamp
self.muteState = muteState
@ -687,11 +706,32 @@ public final class GroupCallParticipantsContext {
public var peerId: PeerId
public var ssrc: UInt32
public var jsonParams: String?
public var joinTimestamp: Int32
public var activityTimestamp: Double?
public var muteState: Participant.MuteState?
public var participationStatusChange: ParticipationStatusChange
public var volume: Int32?
init(
peerId: PeerId,
ssrc: UInt32,
jsonParams: String?,
joinTimestamp: Int32,
activityTimestamp: Double?,
muteState: Participant.MuteState?,
participationStatusChange: ParticipationStatusChange,
volume: Int32?
) {
self.peerId = peerId
self.ssrc = ssrc
self.jsonParams = jsonParams
self.joinTimestamp = joinTimestamp
self.activityTimestamp = activityTimestamp
self.muteState = muteState
self.participationStatusChange = participationStatusChange
self.volume = volume
}
}
public var participantUpdates: [ParticipantUpdate]
@ -1107,6 +1147,7 @@ public final class GroupCallParticipantsContext {
let participant = Participant(
peer: peer,
ssrc: participantUpdate.ssrc,
jsonParams: participantUpdate.jsonParams,
joinTimestamp: participantUpdate.joinTimestamp,
activityTimestamp: activityTimestamp,
muteState: participantUpdate.muteState,
@ -1322,7 +1363,7 @@ public final class GroupCallParticipantsContext {
extension GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate {
init(_ apiParticipant: Api.GroupCallParticipant) {
switch apiParticipant {
case let .groupCallParticipant(flags, userId, date, activeDate, source, volume):
case let .groupCallParticipant(flags, userId, date, activeDate, source, volume, params):
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
let ssrc = UInt32(bitPattern: source)
let muted = (flags & (1 << 0)) != 0
@ -1346,9 +1387,18 @@ extension GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate {
participationStatusChange = .none
}
var jsonParams: String?
if let params = params {
switch params {
case let .dataJSON(data):
jsonParams = data
}
}
self.init(
peerId: peerId,
ssrc: ssrc,
jsonParams: jsonParams,
joinTimestamp: date,
activityTimestamp: activeDate.flatMap(Double.init),
muteState: muteState,
@ -1364,7 +1414,7 @@ extension GroupCallParticipantsContext.Update.StateUpdate {
var participantUpdates: [GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate] = []
for participant in participants {
switch participant {
case let .groupCallParticipant(flags, userId, date, activeDate, source, volume):
case let .groupCallParticipant(flags, userId, date, activeDate, source, volume, params):
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
let ssrc = UInt32(bitPattern: source)
let muted = (flags & (1 << 0)) != 0
@ -1388,9 +1438,18 @@ extension GroupCallParticipantsContext.Update.StateUpdate {
participationStatusChange = .none
}
var jsonParams: String?
if let params = params {
switch params {
case let .dataJSON(data):
jsonParams = data
}
}
participantUpdates.append(GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate(
peerId: peerId,
ssrc: ssrc,
jsonParams: jsonParams,
joinTimestamp: date,
activityTimestamp: activeDate.flatMap(Double.init),
muteState: muteState,

@ -78,35 +78,41 @@ public enum RevokePeerExportedInvitationError {
case generic
}
public func revokePeerExportedInvitation(account: Account, peerId: PeerId, link: String) -> Signal<ExportedInvitation?, RevokePeerExportedInvitationError> {
return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, RevokePeerExportedInvitationError> in
public enum RevokeExportedInvitationResult {
case update(ExportedInvitation)
case replace(ExportedInvitation, ExportedInvitation)
}
public func revokePeerExportedInvitation(account: Account, peerId: PeerId, link: String) -> Signal<RevokeExportedInvitationResult?, RevokePeerExportedInvitationError> {
return account.postbox.transaction { transaction -> Signal<RevokeExportedInvitationResult?, RevokePeerExportedInvitationError> in
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
let flags: Int32 = (1 << 2)
return account.network.request(Api.functions.messages.editExportedChatInvite(flags: flags, peer: inputPeer, link: link, expireDate: nil, usageLimit: nil))
|> mapError { _ in return RevokePeerExportedInvitationError.generic }
|> mapToSignal { result -> Signal<ExportedInvitation?, RevokePeerExportedInvitationError> in
|> mapToSignal { result -> Signal<RevokeExportedInvitationResult?, RevokePeerExportedInvitationError> in
return account.postbox.transaction { transaction in
switch result {
case let .exportedChatInvite(invite, users):
var peers: [Peer] = []
for user in users {
let telegramUser = TelegramUser(user: user)
peers.append(telegramUser)
}
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
return updated
})
return ExportedInvitation(apiExportedInvite: invite)
case let .exportedChatInviteReplaced(_, newInvite, users):
var peers: [Peer] = []
for user in users {
let telegramUser = TelegramUser(user: user)
peers.append(telegramUser)
}
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
return updated
})
return ExportedInvitation(apiExportedInvite: newInvite)
if case let .exportedChatInvite(invite, users) = result {
var peers: [Peer] = []
for user in users {
let telegramUser = TelegramUser(user: user)
peers.append(telegramUser)
}
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
return updated
})
return .update(ExportedInvitation(apiExportedInvite: invite))
} else if case let .exportedChatInviteReplaced(invite, newInvite, users) = result {
var peers: [Peer] = []
for user in users {
let telegramUser = TelegramUser(user: user)
peers.append(telegramUser)
}
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
return updated
})
return .replace(ExportedInvitation(apiExportedInvite: invite), ExportedInvitation(apiExportedInvite: newInvite))
} else {
return nil
}
} |> mapError { _ in .generic }
}
@ -123,9 +129,9 @@ public struct ExportedInvitations : Equatable {
public let totalCount: Int32
}
public func peerExportedInvitations(account: Account, peerId: PeerId, revoked: Bool, offsetLink: ExportedInvitation? = nil) -> Signal<ExportedInvitations?, NoError> {
public func peerExportedInvitations(account: Account, peerId: PeerId, revoked: Bool, adminId: PeerId? = nil, offsetLink: ExportedInvitation? = nil) -> Signal<ExportedInvitations?, NoError> {
return account.postbox.transaction { transaction -> Signal<ExportedInvitations?, NoError> in
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer), let accountPeer = transaction.getPeer(account.peerId), let adminId = apiInputUser(accountPeer) {
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer), let adminPeer = transaction.getPeer(adminId ?? account.peerId), let adminId = apiInputUser(adminPeer) {
var flags: Int32 = 0
if let _ = offsetLink {
flags |= (1 << 2)

@ -76,7 +76,14 @@ func managedAutoremoveMessageOperations(network: Network, postbox: Postbox) -> S
|> suspendAwareDelay(max(0.0, Double(entry.timestamp) - timestamp), queue: Queue.concurrentDefaultQueue())
|> then(postbox.transaction { transaction -> Void in
if let message = transaction.getMessage(entry.messageId) {
if message.id.peerId.namespace == Namespaces.Peer.SecretChat {
var action: AutoremoveTimeoutMessageAttribute.Action = .remove
for attribute in message.attributes {
if let attribute = attribute as? AutoremoveTimeoutMessageAttribute {
action = attribute.action
}
}
if message.id.peerId.namespace == Namespaces.Peer.SecretChat || action == .remove {
deleteMessages(transaction: transaction, mediaBox: postbox.mediaBox, ids: [entry.messageId])
} else {
transaction.updateMessage(message.id, update: { currentMessage in
@ -92,11 +99,17 @@ func managedAutoremoveMessageOperations(network: Network, postbox: Postbox) -> S
updatedMedia[i] = TelegramMediaExpiredContent(data: .file)
}
}
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: updatedMedia))
var updatedAttributes = currentMessage.attributes
for i in 0 ..< updatedAttributes.count {
if let _ = updatedAttributes[i] as? AutoremoveTimeoutMessageAttribute {
updatedAttributes.remove(at: i)
break
}
}
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: updatedMedia))
})
}
}
transaction.removeTimestampBasedMessageAttribute(tag: 0, messageId: entry.messageId)
})
disposable.set(signal.start())
}

@ -114,8 +114,6 @@ private func fetchedNotificationSettings(network: Network) -> Signal<GlobalNotif
|> map { chats, users, channels, contactsJoinedMuted in
let chatsSettings: MessageNotificationSettings
switch chats {
case .peerNotifySettingsEmpty:
chatsSettings = MessageNotificationSettings.defaultSettings
case let .peerNotifySettings(_, showPreviews, _, muteUntil, sound):
let enabled: Bool
if muteUntil != nil && muteUntil != 0 {
@ -134,8 +132,6 @@ private func fetchedNotificationSettings(network: Network) -> Signal<GlobalNotif
let userSettings: MessageNotificationSettings
switch users {
case .peerNotifySettingsEmpty:
userSettings = MessageNotificationSettings.defaultSettings
case let .peerNotifySettings(_, showPreviews, _, muteUntil, sound):
let enabled: Bool
if muteUntil != nil && muteUntil != 0 {
@ -154,8 +150,6 @@ private func fetchedNotificationSettings(network: Network) -> Signal<GlobalNotif
let channelSettings: MessageNotificationSettings
switch channels {
case .peerNotifySettingsEmpty:
channelSettings = MessageNotificationSettings.defaultSettings
case let .peerNotifySettings(_, showPreviews, _, muteUntil, sound):
let enabled: Bool
if muteUntil != nil && muteUntil != 0 {

@ -159,9 +159,9 @@ private func synchronizeLocalizationUpdates(accountManager: AccountManager, post
|> castError(SynchronizeLocalizationUpdatesError.self)
|> mapToSignal { (primary, secondary) -> Signal<Void, SynchronizeLocalizationUpdatesError> in
var differences: [Signal<Api.LangPackDifference, MTRpcError>] = []
differences.append(network.request(Api.functions.langpack.getDifference(langCode: primary.code, fromVersion: primary.version)))
differences.append(network.request(Api.functions.langpack.getDifference(langPack: "", langCode: primary.code, fromVersion: primary.version)))
if let secondary = secondary {
differences.append(network.request(Api.functions.langpack.getDifference(langCode: secondary.code, fromVersion: secondary.version)))
differences.append(network.request(Api.functions.langpack.getDifference(langPack: "", langCode: secondary.code, fromVersion: secondary.version)))
}
return combineLatest(differences)

@ -60,8 +60,6 @@ public func markMessageContentAsConsumedInteractively(postbox: Postbox, messageI
updatedAttributes[i] = AutoremoveTimeoutMessageAttribute(timeout: timeout, countdownBeginTime: timestamp)
updateMessage = true
transaction.addTimestampBasedMessageAttribute(tag: 0, timestamp: timestamp + timeout, messageId: messageId)
if messageId.peerId.namespace == Namespaces.Peer.SecretChat {
var layer: SecretChatLayer?
let state = transaction.getPeerChatState(message.id.peerId) as? SecretChatState
@ -132,7 +130,6 @@ func markMessageContentAsConsumedRemotely(transaction: Transaction, messageId: M
updateMessage = true
if message.id.peerId.namespace == Namespaces.Peer.SecretChat {
transaction.addTimestampBasedMessageAttribute(tag: 0, timestamp: timestamp + attribute.timeout, messageId: messageId)
} else {
for i in 0 ..< updatedMedia.count {
if let _ = updatedMedia[i] as? TelegramMediaImage {

@ -289,7 +289,7 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q
return (state?.main.nextRate ?? 0, lowerBound, inputPeer)
} else {
return (0, lowerBound, .inputPeerEmpty)
}
}
}
|> mapToSignal { (nextRate, lowerBound, inputPeer) in
return account.network.request(Api.functions.messages.searchGlobal(flags: flags, folderId: folderId, q: query, filter: filter, minDate: minDate ?? 0, maxDate: maxDate ?? (Int32.max - 1), offsetRate: nextRate, offsetPeer: inputPeer, offsetId: lowerBound?.id.id ?? 0, limit: limit), automaticFloodWait: false)

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

@ -192,7 +192,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
}
switch action {
case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL:
case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall:
break
case let .messageActionChannelMigrateFrom(_, chatId):
result.append(PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId))
@ -371,7 +371,7 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes
extension StoreMessage {
convenience init?(apiMessage: Api.Message, namespace: MessageId.Namespace = Namespaces.Message.Cloud) {
switch apiMessage {
case let .message(flags, id, fromId, chatPeerId, fwdFrom, viaBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, restrictionReason, _):
case let .message(flags, id, fromId, chatPeerId, fwdFrom, viaBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, restrictionReason, ttlPeriod):
let resolvedFromId = fromId?.peerId ?? chatPeerId.peerId
let peerId: PeerId
@ -468,19 +468,29 @@ extension StoreMessage {
var consumableContent: (Bool, Bool)? = nil
var resolvedTtlPeriod: Int32? = ttlPeriod
if let media = media {
let (mediaValue, expirationTimer) = textMediaAndExpirationTimerFromApiMedia(media, peerId)
if let mediaValue = mediaValue {
medias.append(mediaValue)
if let expirationTimer = expirationTimer, expirationTimer > 0 {
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: expirationTimer, countdownBeginTime: nil))
if let resolvedTtlPeriodValue = resolvedTtlPeriod {
resolvedTtlPeriod = min(resolvedTtlPeriodValue, expirationTimer)
} else {
resolvedTtlPeriod = expirationTimer
}
consumableContent = (true, false)
}
}
}
if let resolvedTtlPeriod = resolvedTtlPeriod {
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: resolvedTtlPeriod, countdownBeginTime: ttlPeriod == nil ? nil : date))
}
if let postAuthor = postAuthor {
attributes.append(AuthorSignatureMessageAttribute(signature: postAuthor))
}

@ -71,8 +71,6 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
}))
}
case .messageActionSetMessagesTTL(period: let period):
return nil
}
}

@ -7,8 +7,6 @@ import SyncCore
extension TelegramPeerNotificationSettings {
convenience init(apiSettings: Api.PeerNotifySettings) {
switch apiSettings {
case .peerNotifySettingsEmpty:
self.init(muteState: .unmuted, messageSound: .bundledModern(id: 0), displayPreviews: .default)
case let .peerNotifySettings(_, showPreviews, _, muteUntil, sound):
let muteState: PeerMuteState
if let muteUntil = muteUntil {

@ -206,8 +206,11 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
let peerStatusSettings = PeerStatusSettings(apiSettings: userFull.settings)
let hasScheduledMessages = (userFull.flags & 1 << 12) != 0
let autoremoveTimeout: CachedPeerAutoremoveTimeout = .known(CachedPeerAutoremoveTimeout.Value(userFull.ttl))
return previous.withUpdatedAbout(userFull.about).withUpdatedBotInfo(botInfo).withUpdatedCommonGroupCount(userFull.commonChatsCount).withUpdatedIsBlocked(isBlocked).withUpdatedVoiceCallsAvailable(voiceCallsAvailable).withUpdatedVideoCallsAvailable(videoCallsAvailable).withUpdatedCallsPrivate(callsPrivate).withUpdatedCanPinMessages(canPinMessages).withUpdatedPeerStatusSettings(peerStatusSettings).withUpdatedPinnedMessageId(pinnedMessageId).withUpdatedHasScheduledMessages(hasScheduledMessages)
.withUpdatedAutoremoveTimeout(autoremoveTimeout)
}
})
return true
@ -352,7 +355,7 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
}
switch fullChat {
case let .channelFull(flags, _, about, participantsCount, adminsCount, kickedCount, bannedCount, _, _, _, _, chatPhoto, _, apiExportedInvite, apiBotInfos, migratedFromChatId, migratedFromMaxId, pinnedMsgId, stickerSet, minAvailableMsgId, folderId, linkedChatId, location, slowmodeSeconds, slowmodeNextSendDate, statsDc, pts, inputCall, _):
case let .channelFull(flags, _, about, participantsCount, adminsCount, kickedCount, bannedCount, _, _, _, _, chatPhoto, _, apiExportedInvite, apiBotInfos, migratedFromChatId, migratedFromMaxId, pinnedMsgId, stickerSet, minAvailableMsgId, folderId, linkedChatId, location, slowmodeSeconds, slowmodeNextSendDate, statsDc, pts, inputCall, ttl):
var channelFlags = CachedChannelFlags()
if (flags & (1 << 3)) != 0 {
channelFlags.insert(.canDisplayParticipants)
@ -379,7 +382,9 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
} else {
linkedDiscussionPeerId = nil
}
let autoremoveTimeout: CachedPeerAutoremoveTimeout = .known(CachedPeerAutoremoveTimeout.Value(ttl))
let peerGeoLocation: PeerGeoLocation?
if let location = location {
peerGeoLocation = PeerGeoLocation(apiLocation: location)
@ -525,6 +530,7 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
.withUpdatedInvitedBy(invitedBy)
.withUpdatedPhoto(photo)
.withUpdatedActiveCall(updatedActiveCall)
.withUpdatedAutoremoveTimeout(autoremoveTimeout)
})
if let minAvailableMessageId = minAvailableMessageId, minAvailableMessageIdUpdated {
@ -556,3 +562,19 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI
}
}
}
extension CachedPeerAutoremoveTimeout.Value {
init?(_ apiValue: Api.PeerHistoryTTL?) {
if let apiValue = apiValue {
switch apiValue {
case let .peerHistoryTTLPM(flags, ttlPeriodMy, ttlPeriodPeer):
let pmOneSide = flags & (1 << 0) != 0
self.init(myValue: ttlPeriodMy, peerValue: ttlPeriodPeer, isGlobal: !pmOneSide)
case let .peerHistoryTTL(ttlPeriodPeer):
self.init(myValue: ttlPeriodPeer, peerValue: ttlPeriodPeer, isGlobal: true)
}
} else {
return nil
}
}
}

@ -57,14 +57,14 @@ class UpdateMessageService: NSObject, MTMessageService {
if groups.count != 0 {
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: .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, ttlPeriod: nil)
case let .updateShortChatMessage(flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyHeader, entities, ttlPeriod):
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, ttlPeriod: ttlPeriod)
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 {
self.putNext(groups)
}
case let .updateShortMessage(flags, id, userId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyHeader, entities, _):
case let .updateShortMessage(flags, id, userId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyHeader, entities, ttlPeriod):
let generatedFromId: Api.Peer
if (Int(flags) & 1 << 1) != 0 {
generatedFromId = Api.Peer.peerUser(userId: self.peerId.id)
@ -74,7 +74,7 @@ class UpdateMessageService: NSObject, MTMessageService {
let generatedPeerId = Api.Peer.peerUser(userId: userId)
let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: generatedFromId, peerId: generatedPeerId, 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, ttlPeriod: nil)
let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: generatedFromId, peerId: generatedPeerId, 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, ttlPeriod: ttlPeriod)
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 {

@ -20,7 +20,7 @@ public struct SecureIdPreparePhoneVerificationPayload {
}
public func secureIdPreparePhoneVerification(network: Network, value: SecureIdPhoneValue) -> Signal<SecureIdPreparePhoneVerificationPayload, SecureIdPreparePhoneVerificationError> {
return network.request(Api.functions.account.sendVerifyPhoneCode(flags: 0, phoneNumber: value.phone, currentNumber: nil), automaticFloodWait: false)
return network.request(Api.functions.account.sendVerifyPhoneCode(phoneNumber: value.phone, settings: .codeSettings(flags: 0)), automaticFloodWait: false)
|> mapError { error -> SecureIdPreparePhoneVerificationError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .flood

@ -1,39 +0,0 @@
import Foundation
import Postbox
import SwiftSignalKit
import MtProtoKit
import TelegramApi
public enum GetServerWalletSaltError {
case generic
}
public func getServerWalletSalt(network: Network) -> Signal<Data, GetServerWalletSaltError> {
return network.request(Api.functions.wallet.getKeySecretSalt(revoke: .boolFalse))
|> mapError { _ -> GetServerWalletSaltError in
return .generic
}
|> map { result -> Data in
switch result {
case let .secretSalt(salt):
return salt.makeData()
}
}
}
public enum WalletProxyRequestError {
case generic(Int32, String)
}
public func walletProxyRequest(network: Network, data: Data) -> Signal<Data, WalletProxyRequestError> {
return network.request(Api.functions.wallet.sendLiteRequest(body: Buffer(data: data)))
|> mapError { error -> WalletProxyRequestError in
return .generic(error.errorCode, error.errorDescription)
}
|> map { result -> Data in
switch result {
case let .liteResponse(response):
return response.makeData()
}
}
}

@ -167,6 +167,7 @@ private struct ApplicationSpecificNoticeKeys {
private static let peerReportNamespace: Int32 = 4
private static let inlineBotLocationRequestNamespace: Int32 = 5
private static let psaAcknowledgementNamespace: Int32 = 6
private static let botGameNoticeNamespace: Int32 = 7
static func inlineBotLocationRequestNotice(peerId: PeerId) -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: inlineBotLocationRequestNamespace), key: noticeKey(peerId: peerId, key: 0))
@ -176,6 +177,10 @@ private struct ApplicationSpecificNoticeKeys {
return NoticeEntryKey(namespace: noticeNamespace(namespace: botPaymentLiabilityNamespace), key: noticeKey(peerId: peerId, key: 0))
}
static func botGameNotice(peerId: PeerId) -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: botGameNoticeNamespace), key: noticeKey(peerId: peerId, key: 0))
}
static func irrelevantPeerGeoNotice(peerId: PeerId) -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: peerReportNamespace), key: noticeKey(peerId: peerId, key: 0))
}
@ -292,6 +297,22 @@ public struct ApplicationSpecificNotice {
}
}
public static func getBotGameNotice(accountManager: AccountManager, peerId: PeerId) -> Signal<Bool, NoError> {
return accountManager.transaction { transaction -> Bool in
if let _ = transaction.getNotice(ApplicationSpecificNoticeKeys.botGameNotice(peerId: peerId)) as? ApplicationSpecificBoolNotice {
return true
} else {
return false
}
}
}
public static func setBotGameNotice(accountManager: AccountManager, peerId: PeerId) -> Signal<Void, NoError> {
return accountManager.transaction { transaction -> Void in
transaction.setNotice(ApplicationSpecificNoticeKeys.botGameNotice(peerId: peerId), ApplicationSpecificBoolNotice())
}
}
public static func getInlineBotLocationRequest(accountManager: AccountManager, peerId: PeerId) -> Signal<Int32?, NoError> {
return accountManager.transaction { transaction -> Int32? in
if let notice = transaction.getNotice(ApplicationSpecificNoticeKeys.inlineBotLocationRequestNotice(peerId: peerId)) as? ApplicationSpecificTimestampNotice {

File diff suppressed because it is too large Load Diff

@ -176,6 +176,10 @@ public final class PrincipalThemeEssentialGraphics {
public let outgoingDateAndStatusPinnedIcon: UIImage
public let mediaPinnedIcon: UIImage
public let freePinnedIcon: UIImage
public let incomingDateAndStatusSelfExpiringIcon: UIImage
public let outgoingDateAndStatusSelfExpiringIcon: UIImage
public let mediaSelfExpiringIcon: UIImage
public let freeSelfExpiringIcon: UIImage
public let dateStaticBackground: UIImage
public let dateFloatingBackground: UIImage
@ -350,6 +354,12 @@ public final class PrincipalThemeEssentialGraphics {
self.mediaPinnedIcon = generateTintedImage(image: pinnedImage, color: .white)!
self.freePinnedIcon = generateTintedImage(image: pinnedImage, color: serviceColor.primaryText)!
let selfExpiringImage = UIImage(bundleImageName: "Chat/Message/SelfExpiring")!
self.incomingDateAndStatusSelfExpiringIcon = generateTintedImage(image: selfExpiringImage, color: theme.message.incoming.secondaryTextColor)!
self.outgoingDateAndStatusSelfExpiringIcon = generateTintedImage(image: selfExpiringImage, color: theme.message.outgoing.secondaryTextColor)!
self.mediaSelfExpiringIcon = generateTintedImage(image: selfExpiringImage, color: .white)!
self.freeSelfExpiringIcon = generateTintedImage(image: selfExpiringImage, color: serviceColor.primaryText)!
self.radialIndicatorFileIconIncoming = emptyImage
self.radialIndicatorFileIconOutgoing = emptyImage
} else {
@ -463,6 +473,12 @@ public final class PrincipalThemeEssentialGraphics {
self.mediaPinnedIcon = generateTintedImage(image: pinnedImage, color: .white)!
self.freePinnedIcon = generateTintedImage(image: pinnedImage, color: serviceColor.primaryText)!
let selfExpiringImage = UIImage(bundleImageName: "Chat/Message/SelfExpiring")!
self.incomingDateAndStatusSelfExpiringIcon = generateTintedImage(image: selfExpiringImage, color: theme.message.incoming.secondaryTextColor)!
self.outgoingDateAndStatusSelfExpiringIcon = generateTintedImage(image: selfExpiringImage, color: theme.message.outgoing.secondaryTextColor)!
self.mediaSelfExpiringIcon = generateTintedImage(image: selfExpiringImage, color: .white)!
self.freeSelfExpiringIcon = generateTintedImage(image: selfExpiringImage, color: serviceColor.primaryText)!
self.radialIndicatorFileIconIncoming = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/RadialProgressIconDocument"), color: .black)!
self.radialIndicatorFileIconOutgoing = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/RadialProgressIconDocument"), color: .black)!
}

@ -16,6 +16,3 @@ public func stringForDuration(_ duration: Int32, position: Int32? = nil) -> Stri
}
return durationString
}

@ -212,7 +212,7 @@ swift_library(
"//submodules/SlotMachineAnimationNode:SlotMachineAnimationNode",
"//submodules/AnimatedNavigationStripeNode:AnimatedNavigationStripeNode",
"//submodules/AudioBlob:AudioBlob",
"//Telegram:GeneratedSources",
"//:GeneratedSources",
"//third-party/ZipArchive:ZipArchive",
"//submodules/ChatImportUI:ChatImportUI",
"//submodules/ChatHistoryImportTasks:ChatHistoryImportTasks",

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_autodeleteindicator.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

@ -1040,8 +1040,35 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.botCallbackAlertMessage.set(message |> then(delayedNoMessage))
case let .url(url):
if isGame {
strongSelf.chatDisplayNode.dismissInput()
strongSelf.effectiveNavigationController?.pushViewController(GameController(context: strongSelf.context, url: url, message: message))
let openBot: () -> Void = {
guard let strongSelf = self else {
return
}
strongSelf.chatDisplayNode.dismissInput()
strongSelf.effectiveNavigationController?.pushViewController(GameController(context: strongSelf.context, url: url, message: message))
}
if let botPeer = message.author as? TelegramUser, botPeer.flags.contains(.isVerified) {
openBot()
} else {
let _ = (ApplicationSpecificNotice.getBotGameNotice(accountManager: strongSelf.context.sharedContext.accountManager, peerId: message.id.peerId)
|> deliverOnMainQueue).start(next: { value in
guard let strongSelf = self else {
return
}
if value {
openBot()
} else if let botPeer = message.author as? TelegramUser {
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Conversation_BotInteractiveUrlAlert(botPeer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).0, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { }), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
if let strongSelf = self {
let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: strongSelf.context.sharedContext.accountManager, peerId: botPeer.id).start()
openBot()
}
})]), in: .window(.root), with: nil)
}
})
}
} else {
strongSelf.openUrl(url, concealed: false)
}
@ -2888,14 +2915,27 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
var hasBots: Bool = false
var autoremoveTimeout: Int32?
if let peer = peerView.peers[peerView.peerId] {
if let cachedGroupData = peerView.cachedData as? CachedGroupData {
if !cachedGroupData.botInfos.isEmpty {
hasBots = true
}
} else if let cachedChannelData = peerView.cachedData as? CachedChannelData, let channel = peer as? TelegramChannel, case .group = channel.info {
if !cachedChannelData.botInfos.isEmpty {
hasBots = true
if case let .known(value) = cachedGroupData.autoremoveTimeout {
autoremoveTimeout = value?.effectiveValue
}
} else if let cachedChannelData = peerView.cachedData as? CachedChannelData {
if let channel = peer as? TelegramChannel, case .group = channel.info {
if !cachedChannelData.botInfos.isEmpty {
hasBots = true
}
}
if case let .known(value) = cachedChannelData.autoremoveTimeout {
autoremoveTimeout = value?.effectiveValue
}
} else if let cachedUserData = peerView.cachedData as? CachedUserData {
if case let .known(value) = cachedUserData.autoremoveTimeout {
autoremoveTimeout = value?.effectiveValue
}
}
}
@ -2973,6 +3013,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return $0.updatedPeer { _ in
return renderedPeer
}.updatedIsNotAccessible(isNotAccessible).updatedContactStatus(contactStatus).updatedHasBots(hasBots).updatedIsArchived(isArchived).updatedPeerIsMuted(peerIsMuted).updatedPeerDiscussionId(peerDiscussionId).updatedPeerGeoLocation(peerGeoLocation).updatedExplicitelyCanPinMessages(explicitelyCanPinMessages).updatedHasScheduledMessages(hasScheduledMessages)
.updatedAutoremoveTimeout(autoremoveTimeout)
})
if !strongSelf.didSetChatLocationInfoReady {
strongSelf.didSetChatLocationInfoReady = true
@ -5471,10 +5512,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.displayMediaRecordingTooltip()
}
}, setupMessageAutoremoveTimeout: { [weak self] in
if let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation, peerId.namespace == Namespaces.Peer.SecretChat {
guard let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation else {
return
}
guard let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else {
return
}
if peerId.namespace == Namespaces.Peer.SecretChat {
strongSelf.chatDisplayNode.dismissInput()
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat {
if let peer = peer as? TelegramSecretChat {
let controller = ChatSecretAutoremoveTimerActionSheetController(context: strongSelf.context, currentValue: peer.messageAutoremoveTimeout == nil ? 0 : peer.messageAutoremoveTimeout!, applyValue: { value in
if let strongSelf = self {
let _ = setSecretChatMessageAutoremoveTimeoutInteractively(account: strongSelf.context.account, peerId: peer.id, timeout: value == 0 ? nil : value).start()
@ -5482,6 +5529,59 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})
strongSelf.present(controller, in: .window(.root))
}
} else {
var currentAutoremoveTimeout: Int32? = strongSelf.presentationInterfaceState.autoremoveTimeout
var canSetupAutoremoveTimeout = false
if let secretChat = peer as? TelegramSecretChat {
currentAutoremoveTimeout = secretChat.messageAutoremoveTimeout
canSetupAutoremoveTimeout = true
} else if let group = peer as? TelegramGroup {
if case .creator = group.role {
canSetupAutoremoveTimeout = true
} else if case let .admin(rights, _) = group.role {
if rights.flags.contains(.canChangeInfo) {
canSetupAutoremoveTimeout = true
}
} else if let defaultBannedRights = group.defaultBannedRights {
if !defaultBannedRights.flags.contains(.banChangeInfo) {
canSetupAutoremoveTimeout = true
}
}
} else if let _ = peer as? TelegramUser {
canSetupAutoremoveTimeout = true
} else if let channel = peer as? TelegramChannel {
if channel.hasPermission(.changeInfo) {
canSetupAutoremoveTimeout = true
}
}
if canSetupAutoremoveTimeout {
strongSelf.presentAutoremoveSetup()
} else if let currentAutoremoveTimeout = currentAutoremoveTimeout, let rect = strongSelf.chatDisplayNode.frameForInputPanelAccessoryButton(.messageAutoremoveTimeout(currentAutoremoveTimeout)) {
//TODO:localize
let intervalText = timeIntervalString(strings: strongSelf.presentationData.strings, value: currentAutoremoveTimeout)
let text: String = "Messages in this chat are automatically\ndeleted \(intervalText) after they have been sent."
if let tooltipController = strongSelf.silentPostTooltipController {
tooltipController.updateContent(.text(text), animated: true, extendTimer: true)
} else {
let tooltipController = TooltipController(content: .text(text), baseFontSize: strongSelf.presentationData.listsFontSize.baseDisplaySize)
strongSelf.silentPostTooltipController = tooltipController
tooltipController.dismissed = { [weak tooltipController] _ in
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.silentPostTooltipController === tooltipController {
strongSelf.silentPostTooltipController = nil
}
}
strongSelf.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: {
if let strongSelf = self {
return (strongSelf.chatDisplayNode, rect)
}
return nil
}))
}
}
}
}, sendSticker: { [weak self] file, sourceNode, sourceRect in
if let strongSelf = self, canSendMessagesToChat(strongSelf.presentationInterfaceState) {
@ -7490,13 +7590,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
var isClearCache = false
let text: String
if peerId == self.context.account.peerId {
text = self.presentationData.strings.Conversation_ClearSelfHistory
} else if peerId.namespace == Namespaces.Peer.SecretChat {
text = self.presentationData.strings.Conversation_ClearSecretHistory
} else if peerId.namespace == Namespaces.Peer.CloudGroup || peerId.namespace == Namespaces.Peer.CloudChannel {
text = self.presentationData.strings.Conversation_ClearGroupHistory
if let channel = peer.peer as? TelegramChannel, case .broadcast = channel.info {
isClearCache = true
text = self.presentationData.strings.Conversation_ClearCache
} else {
text = self.presentationData.strings.Conversation_ClearGroupHistory
}
} else {
text = self.presentationData.strings.Conversation_ClearPrivateHistory
}
@ -7565,6 +7671,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}))
} else if canRemoveGlobally {
items.append(DeleteChatPeerActionSheetItem(context: self.context, peer: mainPeer, chatPeer: chatPeer, action: .clearHistory, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder))
items.append(ActionSheetButtonItem(title: self.presentationData.strings.ChatList_DeleteForCurrentUser, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
beginClear(.forLocalPeer)
}))
items.append(ActionSheetButtonItem(title: self.presentationData.strings.ChatList_DeleteForEveryone(mainPeer.compactDisplayTitle).0, color: .destructive, action: { [weak self, weak actionSheet] in
actionSheet?.dismissAnimated()
@ -7580,28 +7690,75 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})
], parseMarkdown: true), in: .window(.root))
}))
items.append(ActionSheetButtonItem(title: self.presentationData.strings.ChatList_DeleteForCurrentUser, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
beginClear(.forLocalPeer)
}))
} else {
items.append(ActionSheetTextItem(title: text))
items.append(ActionSheetButtonItem(title: self.presentationData.strings.Conversation_ClearAll, color: .destructive, action: { [weak self, weak actionSheet] in
items.append(ActionSheetButtonItem(title: isClearCache ? self.presentationData.strings.Conversation_ClearCache : self.presentationData.strings.Conversation_ClearAll, color: isClearCache ? .accent : .destructive, action: { [weak self, weak actionSheet] in
actionSheet?.dismissAnimated()
guard let strongSelf = self else {
return
}
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationTitle, text: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationText, actions: [
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {
}),
TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationAction, action: {
beginClear(.forLocalPeer)
})
], parseMarkdown: true), in: .window(.root))
if isClearCache {
strongSelf.navigationButtonAction(.clearCache)
} else {
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationTitle, text: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationText, actions: [
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {
}),
TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationAction, action: {
beginClear(.forLocalPeer)
})
], parseMarkdown: true), in: .window(.root))
}
}))
}
if let peer = self.presentationInterfaceState.renderedPeer?.peer {
var currentAutoremoveTimeout: Int32? = self.presentationInterfaceState.autoremoveTimeout
var canSetupAutoremoveTimeout = false
if let secretChat = peer as? TelegramSecretChat {
currentAutoremoveTimeout = secretChat.messageAutoremoveTimeout
canSetupAutoremoveTimeout = true
} else if let group = peer as? TelegramGroup {
if case .creator = group.role {
canSetupAutoremoveTimeout = true
} else if case let .admin(rights, _) = group.role {
if rights.flags.contains(.canChangeInfo) {
canSetupAutoremoveTimeout = true
}
} else if let defaultBannedRights = group.defaultBannedRights {
if !defaultBannedRights.flags.contains(.banChangeInfo) {
canSetupAutoremoveTimeout = true
}
}
} else if let _ = self.presentationInterfaceState.renderedPeer?.peer as? TelegramUser {
canSetupAutoremoveTimeout = true
} else if let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel {
if channel.hasPermission(.changeInfo) {
canSetupAutoremoveTimeout = true
}
}
if canSetupAutoremoveTimeout {
//TODO:localize
items.append(ActionSheetButtonItem(title: currentAutoremoveTimeout == nil ? "Enable Auto-Delete" : "Edit Auto-Delete Settings", color: .accent, action: { [weak self, weak actionSheet] in
guard let actionSheet = actionSheet else {
return
}
guard let strongSelf = self else {
return
}
Queue.mainQueue().after(0.8, {
self?.updateChatPresentationInterfaceState(animated: false, interactive: false, { $0.updatedInterfaceState({ $0.withoutSelectionState() }) })
})
actionSheet.dismissAnimated()
strongSelf.presentAutoremoveSetup()
}))
}
}
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
@ -11750,6 +11907,36 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return false
}
private func presentAutoremoveSetup() {
guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
return
}
let controller = peerAutoremoveSetupScreen(context: self.context, peerId: peer.id, completion: { [weak self] updatedValue in
if case let .updated(value) = updatedValue {
guard let strongSelf = self else {
return
}
var text: String?
if let myValue = value.myValue {
if let limitedByValue = value.limitedByValue, limitedByValue < myValue {
text = "\(peer.compactDisplayTitle) has set messages to auto-delete in \(timeIntervalString(strings: strongSelf.presentationData.strings, value: limitedByValue)). You can't cancel it or make this interval longer."
} else {
text = strongSelf.presentationData.strings.Conversation_AutoremoveChanged("\(timeIntervalString(strings: strongSelf.presentationData.strings, value: myValue))").0
}
} else if let limitedByValue = value.limitedByValue {
text = "\(peer.compactDisplayTitle) has set messages to auto-delete in \(timeIntervalString(strings: strongSelf.presentationData.strings, value: limitedByValue)). You can't cancel it or make this interval longer."
}
if let text = text {
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .succeed(text: text), elevatedLayout: false, action: { _ in return false }), in: .current)
}
}
})
self.chatDisplayNode.dismissInput()
self.push(controller)
}
private var effectiveNavigationController: NavigationController? {
if let navigationController = self.navigationController as? NavigationController {
return navigationController

@ -221,6 +221,9 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte
}
}
var currentAutoremoveTimeout: Int32? = chatPresentationInterfaceState.autoremoveTimeout
var canSetupAutoremoveTimeout = false
var accessoryItems: [ChatTextInputAccessoryItem] = []
if let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat {
var extendedSearchLayout = false
@ -232,7 +235,32 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte
}
if !extendedSearchLayout {
accessoryItems.append(.messageAutoremoveTimeout(peer.messageAutoremoveTimeout))
currentAutoremoveTimeout = peer.messageAutoremoveTimeout
canSetupAutoremoveTimeout = true
}
} else if let group = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramGroup {
if case .creator = group.role {
canSetupAutoremoveTimeout = true
} else if case let .admin(rights, _) = group.role {
if rights.flags.contains(.canChangeInfo) {
canSetupAutoremoveTimeout = true
}
} else if let defaultBannedRights = group.defaultBannedRights {
if !defaultBannedRights.flags.contains(.banChangeInfo) {
canSetupAutoremoveTimeout = true
}
}
} else if let _ = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser {
canSetupAutoremoveTimeout = true
} else if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel {
if channel.hasPermission(.changeInfo) {
canSetupAutoremoveTimeout = true
}
}
if canSetupAutoremoveTimeout {
if currentAutoremoveTimeout != nil || chatPresentationInterfaceState.renderedPeer?.peer is TelegramSecretChat {
accessoryItems.append(.messageAutoremoveTimeout(currentAutoremoveTimeout))
}
}
@ -247,17 +275,18 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte
return ChatTextInputPanelState(accessoryItems: [], contextPlaceholder: contextPlaceholder, mediaRecordingState: chatPresentationInterfaceState.inputTextPanelState.mediaRecordingState)
} else {
var accessoryItems: [ChatTextInputAccessoryItem] = []
if let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat {
var extendedSearchLayout = false
loop: for (_, result) in chatPresentationInterfaceState.inputQueryResults {
if case let .contextRequestResult(peer, _) = result, peer != nil {
extendedSearchLayout = true
break loop
}
var extendedSearchLayout = false
loop: for (_, result) in chatPresentationInterfaceState.inputQueryResults {
if case let .contextRequestResult(peer, _) = result, peer != nil {
extendedSearchLayout = true
break loop
}
if !extendedSearchLayout {
}
if !extendedSearchLayout {
if let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat {
accessoryItems.append(.messageAutoremoveTimeout(peer.messageAutoremoveTimeout))
} else if currentAutoremoveTimeout != nil {
accessoryItems.append(.messageAutoremoveTimeout(currentAutoremoveTimeout))
}
}

@ -3,6 +3,7 @@ import UIKit
import Postbox
import TelegramCore
import SyncCore
import AsyncDisplayKit
import Display
import UIKit
import SwiftSignalKit
@ -15,6 +16,8 @@ import LegacyUI
import AppBundle
import SaveToCameraRoll
import PresentationDataUtils
import TelegramPresentationData
import TelegramStringFormatting
private struct MessageContextMenuData {
let starStatus: Bool?
@ -890,6 +893,16 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
}
if !isReplyThreadHead, (!data.messageActions.options.intersection([.deleteLocally, .deleteGlobally]).isEmpty || clearCacheAsDelete) && !isAction {
var autoremoveDeadline: Int32?
for attribute in message.attributes {
if let attribute = attribute as? AutoremoveTimeoutMessageAttribute {
if let countdownBeginTime = attribute.countdownBeginTime {
autoremoveDeadline = countdownBeginTime + attribute.timeout
}
break
}
}
let title: String
var isSending = false
var isEditing = false
@ -903,16 +916,27 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
} else {
title = chatPresentationInterfaceState.strings.Conversation_ContextMenuDelete
}
actions.append(.action(ContextMenuActionItem(text: title, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: isSending ? "Chat/Context Menu/Clear" : "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
}, action: { controller, f in
if isEditing {
context.account.pendingUpdateMessageManager.cancel(messageId: message.id)
f(.default)
} else {
interfaceInteraction.deleteMessages(selectAll ? messages : [message], controller, f)
}
})))
if let autoremoveDeadline = autoremoveDeadline, !isEditing, !isSending {
actions.append(.custom(ChatDeleteMessageContextItem(timestamp: Double(autoremoveDeadline), action: { controller, f in
if isEditing {
context.account.pendingUpdateMessageManager.cancel(messageId: message.id)
f(.default)
} else {
interfaceInteraction.deleteMessages(selectAll ? messages : [message], controller, f)
}
}), false))
} else {
actions.append(.action(ContextMenuActionItem(text: title, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: isSending ? "Chat/Context Menu/Clear" : "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
}, action: { controller, f in
if isEditing {
context.account.pendingUpdateMessageManager.cancel(messageId: message.id)
f(.default)
} else {
interfaceInteraction.deleteMessages(selectAll ? messages : [message], controller, f)
}
})))
}
}
if !isPinnedMessages, !isReplyThreadHead, data.canSelect {
@ -1204,3 +1228,229 @@ func chatAvailableMessageActionsImpl(postbox: Postbox, accountPeerId: PeerId, me
}
}
}
final class ChatDeleteMessageContextItem: ContextMenuCustomItem {
fileprivate let timestamp: Double
fileprivate let action: (ContextController, @escaping (ContextMenuActionResult) -> Void) -> Void
init(timestamp: Double, action: @escaping (ContextController, @escaping (ContextMenuActionResult) -> Void) -> Void) {
self.timestamp = timestamp
self.action = action
}
func node(presentationData: PresentationData, getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
return ChatDeleteMessageContextItemNode(presentationData: presentationData, item: self, getController: getController, actionSelected: actionSelected)
}
}
private let textFont = Font.regular(17.0)
private final class ChatDeleteMessageContextItemNode: ASDisplayNode, ContextMenuCustomNode {
private let item: ChatDeleteMessageContextItem
private let presentationData: PresentationData
private let getController: () -> ContextController?
private let actionSelected: (ContextMenuActionResult) -> Void
private let backgroundNode: ASDisplayNode
private let highlightedBackgroundNode: ASDisplayNode
private let textNode: ImmediateTextNode
private let statusNode: ImmediateTextNode
private let iconNode: ASImageNode
private let textIconNode: ASImageNode
private let buttonNode: HighlightTrackingButtonNode
private var timer: SwiftSignalKit.Timer?
private var pointerInteraction: PointerInteraction?
init(presentationData: PresentationData, item: ChatDeleteMessageContextItem, getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void) {
self.item = item
self.presentationData = presentationData
self.getController = getController
self.actionSelected = actionSelected
let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize)
let subtextFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0)
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isAccessibilityElement = false
self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.itemBackgroundColor
self.highlightedBackgroundNode = ASDisplayNode()
self.highlightedBackgroundNode.isAccessibilityElement = false
self.highlightedBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
self.highlightedBackgroundNode.alpha = 0.0
self.textNode = ImmediateTextNode()
self.textNode.isAccessibilityElement = false
self.textNode.isUserInteractionEnabled = false
self.textNode.displaysAsynchronously = false
self.textNode.attributedText = NSAttributedString(string: presentationData.strings.Conversation_ContextMenuDelete, font: textFont, textColor: presentationData.theme.contextMenu.destructiveColor)
self.textNode.maximumNumberOfLines = 1
let statusNode = ImmediateTextNode()
statusNode.isAccessibilityElement = false
statusNode.isUserInteractionEnabled = false
statusNode.displaysAsynchronously = false
statusNode.attributedText = NSAttributedString(string: stringForRemainingTime(Int32(max(0.0, self.item.timestamp - Date().timeIntervalSince1970)), strings: presentationData.strings), font: subtextFont, textColor: presentationData.theme.contextMenu.destructiveColor)
statusNode.maximumNumberOfLines = 1
self.statusNode = statusNode
self.buttonNode = HighlightTrackingButtonNode()
self.buttonNode.isAccessibilityElement = true
self.buttonNode.accessibilityLabel = presentationData.strings.VoiceChat_StopRecording
self.iconNode = ASImageNode()
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: presentationData.theme.actionSheet.destructiveActionTextColor)
self.textIconNode = ASImageNode()
self.textIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/SelfExpiring"), color: presentationData.theme.actionSheet.destructiveActionTextColor)
super.init()
self.addSubnode(self.backgroundNode)
self.addSubnode(self.highlightedBackgroundNode)
self.addSubnode(self.textNode)
self.addSubnode(self.statusNode)
self.addSubnode(self.iconNode)
self.addSubnode(self.textIconNode)
self.addSubnode(self.buttonNode)
self.buttonNode.highligthedChanged = { [weak self] highligted in
guard let strongSelf = self else {
return
}
if highligted {
strongSelf.highlightedBackgroundNode.alpha = 1.0
} else {
strongSelf.highlightedBackgroundNode.alpha = 0.0
strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
}
}
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
}
deinit {
self.timer?.invalidate()
}
override func didLoad() {
super.didLoad()
self.pointerInteraction = PointerInteraction(node: self.buttonNode, style: .hover, willEnter: { [weak self] in
if let strongSelf = self {
strongSelf.highlightedBackgroundNode.alpha = 0.75
}
}, willExit: { [weak self] in
if let strongSelf = self {
strongSelf.highlightedBackgroundNode.alpha = 0.0
}
})
let timer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: { [weak self] in
self?.updateTime(transition: .immediate)
}, queue: Queue.mainQueue())
self.timer = timer
timer.start()
}
private var validLayout: CGSize?
func updateTime(transition: ContainedViewLayoutTransition) {
guard let size = self.validLayout else {
return
}
let subtextFont = Font.regular(self.presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0)
self.statusNode.attributedText = NSAttributedString(string: stringForRemainingTime(Int32(max(0.0, self.item.timestamp - Date().timeIntervalSince1970)), strings: presentationData.strings), font: subtextFont, textColor: presentationData.theme.contextMenu.destructiveColor)
let sideInset: CGFloat = 16.0
let statusSize = self.statusNode.updateLayout(CGSize(width: size.width - sideInset - 32.0, height: .greatestFiniteMagnitude))
transition.updateFrameAdditive(node: self.statusNode, frame: CGRect(origin: CGPoint(x: self.statusNode.frame.minX, y: self.statusNode.frame.minY), size: statusSize))
}
func updateLayout(constrainedWidth: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
let sideInset: CGFloat = 16.0
let iconSideInset: CGFloat = 12.0
let verticalInset: CGFloat = 12.0
let iconSize: CGSize = self.iconNode.image?.size ?? CGSize(width: 10.0, height: 10.0)
let textIconSize: CGSize = self.textIconNode.image?.size ?? CGSize(width: 2.0, height: 2.0)
let standardIconWidth: CGFloat = 32.0
var rightTextInset: CGFloat = sideInset
if !iconSize.width.isZero {
rightTextInset = max(iconSize.width, standardIconWidth) + iconSideInset + sideInset
}
let textSize = self.textNode.updateLayout(CGSize(width: constrainedWidth - sideInset - rightTextInset, height: .greatestFiniteMagnitude))
let statusSize = self.statusNode.updateLayout(CGSize(width: constrainedWidth - sideInset - rightTextInset - textIconSize.width - 2.0, height: .greatestFiniteMagnitude))
let verticalSpacing: CGFloat = 2.0
let combinedTextHeight = textSize.height + verticalSpacing + statusSize.height
return (CGSize(width: max(textSize.width, statusSize.width) + sideInset + rightTextInset, height: verticalInset * 2.0 + combinedTextHeight), { size, transition in
self.validLayout = size
let verticalOrigin = floor((size.height - combinedTextHeight) / 2.0)
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: verticalOrigin), size: textSize)
transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
transition.updateFrame(node: self.textIconNode, frame: CGRect(origin: CGPoint(x: sideInset, y: verticalOrigin + verticalSpacing + textSize.height + floorToScreenPixels((statusSize.height - textIconSize.height) / 2.0) + 1.0), size: textIconSize))
transition.updateFrameAdditive(node: self.statusNode, frame: CGRect(origin: CGPoint(x: sideInset + textIconSize.width + 2.0, y: verticalOrigin + verticalSpacing + textSize.height), size: statusSize))
if !iconSize.width.isZero {
transition.updateFrameAdditive(node: self.iconNode, frame: CGRect(origin: CGPoint(x: size.width - standardIconWidth - iconSideInset + floor((standardIconWidth - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize))
}
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
transition.updateFrame(node: self.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
})
}
func updateTheme(presentationData: PresentationData) {
self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.itemBackgroundColor
self.highlightedBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize)
let subtextFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0)
self.textNode.attributedText = NSAttributedString(string: self.textNode.attributedText?.string ?? "", font: textFont, textColor: presentationData.theme.contextMenu.primaryColor)
self.statusNode.attributedText = NSAttributedString(string: self.statusNode.attributedText?.string ?? "", font: subtextFont, textColor: presentationData.theme.contextMenu.secondaryColor)
}
@objc private func buttonPressed() {
self.performAction()
}
func performAction() {
guard let controller = self.getController() else {
return
}
self.item.action(controller, { [weak self] result in
self?.actionSelected(result)
})
}
func setIsHighlighted(_ value: Bool) {
if value {
self.highlightedBackgroundNode.alpha = 1.0
} else {
self.highlightedBackgroundNode.alpha = 0.0
}
}
}
private func stringForRemainingTime(_ duration: Int32, strings: PresentationStrings) -> String {
let days = duration / (3600 * 24)
let hours = duration / 3600
let minutes = duration / 60 % 60
let seconds = duration % 60
let durationString: String
if days > 0 {
let roundDays = round(Double(duration) / (3600.0 * 24.0))
return strings.Conversation_AutoremoveRemainingDays(Int32(roundDays))
} else if hours > 0 {
durationString = String(format: "%d:%02d:%02d", hours, minutes, seconds)
} else {
durationString = String(format: "%d:%02d", minutes, seconds)
}
return strings.Conversation_AutoremoveRemainingTime(durationString).0
}

@ -48,6 +48,16 @@ func leftNavigationButtonForChatInterfaceState(_ presentationInterfaceState: Cha
canClear = true
} else if let peer = peer as? TelegramChannel, case .group = peer.info, peer.addressName == nil && presentationInterfaceState.peerGeoLocation == nil {
canClear = true
} else if let peer = peer as? TelegramChannel {
if case .broadcast = peer.info {
//TODO:localize
title = "Clear Channel"
}
if peer.hasPermission(.changeInfo) {
canClear = true
} else {
canClear = false
}
} else {
canClear = false
}

@ -773,7 +773,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
isReplyThread = true
}
let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies, item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread)
let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies, item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, item.message.isSelfExpiring)
var viaBotApply: (TextNodeLayout, () -> TextNode)?
var replyInfoApply: (CGSize, () -> ChatMessageReplyInfoNode)?

Some files were not shown because too many files have changed in this diff Show More