[WIP] Message auto-delete and widget

This commit is contained in:
Ali 2021-02-09 22:55:41 +04:00
parent d7538bf131
commit 3880af10ae
38 changed files with 1009 additions and 766 deletions

10
BUILD Normal file
View 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"],
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,17 +754,148 @@ 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([])
}
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([])
}
} else {
return account.postbox.transaction { transaction -> [Friend] in
var peers: [Peer] = []
outer: for peerId in transaction.getContactPeerIds() {
for peerId in transaction.getContactPeerIds() {
if let peer = transaction.getPeer(peerId) as? TelegramUser {
peers.append(peer)
}
@ -721,6 +905,10 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
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
@ -737,9 +925,10 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
return result
}
}
}
|> deliverOnMainQueue).start(next: { result in
let collection = INObjectCollection(items: result)
completion(collection, nil)
})
})*/
}
}

View File

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

View File

@ -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 ?? [" "])!
}
}

View File

@ -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,10 +95,6 @@ struct Provider: IntentTimelineProvider {
let currentDate = Date()
let entryDate = Calendar.current.date(byAdding: .hour, value: 0, to: currentDate)!
switch configuration.contents {
case .unknown, .recent:
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
@ -119,8 +131,111 @@ struct Provider: IntentTimelineProvider {
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] = []
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))
}
}
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
}
} 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)
}
}
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
}
}
}
}
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 {
@ -171,19 +286,18 @@ struct Provider: IntentTimelineProvider {
}, badge: badge, message: mappedMessage))
}
}
return WidgetDataPeers(accountPeerId: widgetPeers.accountPeerId, peers: peers, updateTimestamp: Int32(Date().timeIntervalSince1970))
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 {
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 = "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,55 +598,86 @@ 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, size: CGSize) -> AnyView {
let peers: WidgetDataPeers
let peers: ParsedPeers?
switch data {
case let .peers(peersValue):
if peersValue.peers.count <= index {
peers = nil
} else {
peers = peersValue
if peers.peers.count <= index {
return AnyView(Spacer())
}
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: 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: 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))
})
}).position(x: size.width / 2.0, y: (itemHeight * 2.0) / 4.0 + CGFloat(index) * itemHeight).frame(width: size.width, height: itemHeight, alignment: .leading)
})
.frame(width: size.width, height: itemHeight, alignment: .leading)
)
}
func chatSeparatorView(size: CGSize) -> some View {
let separatorWidth = size.width - 54.0 - 20.0
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: separatorWidth, height: 0.33, alignment: .leading)
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 - 22.0 - 11.0).frame(width: size.width, height: 22.0, alignment: .leading)
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
@ -494,15 +692,20 @@ struct WidgetView: View {
formatter.timeStyle = .short
text = "updated at \(formatter.string(from: date))"
}
}
default:
text = ""
}
return HStack(alignment: .center, spacing: 0.0, content: {
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 - 22.0 - 11.0).frame(width: size.width, height: 22.0, alignment: .leading)
}).position(x: size.width / 2.0, y: size.height - 11.0).frame(width: size.width, height: 22.0, alignment: .leading)*/
}
func getSeparatorColor() -> Color {
@ -540,13 +743,12 @@ struct WidgetView: View {
var body: some View {
GeometryReader(content: { geometry in
return ZStack {
return VStack(alignment: .center, spacing: 0.0, content: {
chatContentView(0, size: geometry.size)
chatContentView(1, size: geometry.size)
chatSeparatorView(size: geometry.size)
chatsUpdateBackgroundView(size: geometry.size)
chatsUpdateTimestampView(size: geometry.size)
}
chatContentView(1, size: geometry.size)
chatUpdateView(size: geometry.size)
})
})
.padding(0.0)
}
@ -600,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])
@ -608,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 49ef5bc098f68e40f730518f1c795d642e5c11a2

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

View File

@ -40,6 +40,7 @@ const CGFloat TGPhotoEditorSliderViewInternalMargin = 7.0f;
_startValue = 0.0f;
_value = _startValue;
_dotSize = 10.5f;
_minimumUndottedValue = -1;
_lineSize = TGPhotoEditorSliderViewLineSize;
_knobPadding = TGPhotoEditorSliderViewInternalMargin;
@ -174,7 +175,7 @@ const CGFloat TGPhotoEditorSliderViewInternalMargin = 7.0f;
CGContextSetBlendMode(context, kCGBlendModeCopy);
}
if (_minimumUndottedValue > 0 && self.positionsCount > 1) {
if (_minimumUndottedValue > -1 && self.positionsCount > 1) {
CGContextSetLineWidth(context, backFrame.size.height);
CGContextSetLineCap(context, kCGLineCapRound);
@ -222,7 +223,7 @@ const CGFloat TGPhotoEditorSliderViewInternalMargin = 7.0f;
CGContextSetBlendMode(context, kCGBlendModeNormal);
if (_minimumUndottedValue > 0) {
if (_minimumUndottedValue > -1) {
} else {
CGContextSetFillColorWithColor(context, _trackColor.CGColor);
[self drawRectangle:trackFrame cornerRadius:self.trackCornerRadius context:context];
@ -359,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

View File

@ -147,9 +147,13 @@ private func peerAutoremoveSetupEntries(peer: Peer?, presentationData: Presentat
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))
}
@ -158,8 +162,13 @@ private func peerAutoremoveSetupEntries(peer: Peer?, presentationData: Presentat
}
public enum PeerAutoremoveSetupScreenResult {
public struct Updated {
public var myValue: Int32?
public var limitedByValue: Int32?
}
case unchanged
case updated(Int32?)
case updated(Updated)
}
public func peerAutoremoveSetupScreen(context: AccountContext, peerId: PeerId, completion: @escaping (PeerAutoremoveSetupScreenResult) -> Void = { _ in }) -> ViewController {
@ -257,6 +266,13 @@ public func peerAutoremoveSetupScreen(context: AccountContext, peerId: PeerId, c
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)
@ -267,7 +283,10 @@ public func peerAutoremoveSetupScreen(context: AccountContext, peerId: PeerId, c
}, completed: {
dismissImpl?()
if resolvedValue != resolvedDefaultValue {
completion(.updated(changedValue))
completion(.updated(PeerAutoremoveSetupScreenResult.Updated(
myValue: resolvedValue,
limitedByValue: resolvedMaxValue == Int32.max ? nil : resolvedMaxValue
)))
} else {
completion(.unchanged)
}

View File

@ -90,7 +90,7 @@ class PeerRemoveTimeoutItem: ListViewItem, ItemListItem {
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: -1.0), blur: 3.5, color: UIColor(white: 0.0, alpha: 0.25).cgColor)
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)))
})
@ -160,7 +160,7 @@ class PeerRemoveTimeoutItemNode: ListViewItemNode, ItemListItemNode {
sliderView.value = mapTimeoutToSliderValue(item.value)
sliderView.minimumUndottedValue = 2 - Int32(mapTimeoutToSliderValue(item.maxValue))
sliderView.minimumUndottedValue = Int32(mapTimeoutToSliderValue(item.maxValue))
sliderView.backgroundColor = item.theme.list.itemBlocksBackgroundColor
sliderView.backColor = item.theme.list.disclosureArrowColor
@ -287,6 +287,7 @@ class PeerRemoveTimeoutItemNode: ListViewItemNode, ItemListItemNode {
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1058,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)
@ -1067,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 {
@ -1481,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
self.viewTracker = ViewTracker(
queue: self.queue,
unsentMessageIds: self.messageHistoryUnsentTable.get(),
synchronizePeerReadStateOperations: self.synchronizeReadStateTable.get(getCombinedPeerReadState: { 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)
}))
})
)
print("(Postbox initialization took \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms")
@ -2784,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)
@ -2890,6 +2894,11 @@ public final class Postbox {
public func searchPeers(query: String) -> Signal<[RenderedPeer], NoError> {
return self.transaction { transaction -> Signal<[RenderedPeer], NoError> in
return .single(transaction.searchPeers(query: query))
} |> switchToLatest
}
fileprivate func searchPeers(query: String) -> [RenderedPeer] {
var peerIds = Set<PeerId>()
var chatPeers: [RenderedPeer] = []
@ -2934,8 +2943,7 @@ public final class Postbox {
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
return chatPeers + contactPeers
}
public func peerView(id: PeerId) -> Signal<PeerView, NoError> {
@ -3128,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))
@ -3155,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))

View File

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

View File

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

View File

@ -571,7 +571,7 @@ public final class VoiceChatController: ViewController {
private let dimNode: ASDisplayNode
private let contentContainer: ASDisplayNode
private let backgroundNode: ASDisplayNode
private let mainVideoContainer: MainVideoContainerNode
private var mainVideoContainer: MainVideoContainerNode?
private let listNode: ListView
private let topPanelNode: ASDisplayNode
private let topPanelEdgeNode: ASDisplayNode
@ -672,7 +672,9 @@ 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)
@ -763,11 +765,11 @@ public final class VoiceChatController: ViewController {
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.mainVideoContainer?.updatePeer(peer: (peerId: peerId, source: source))
} else {
strongSelf.currentDominantSpeakerWithVideo = nil
strongSelf.call.setFullSizeVideo(peerId: nil)
strongSelf.mainVideoContainer.updatePeer(peer: nil)
strongSelf.mainVideoContainer?.updatePeer(peer: nil)
}
}
default:
@ -1029,7 +1031,7 @@ public final class VoiceChatController: ViewController {
}), true))
}
items.append(.action(ContextMenuActionItem(text: "Toggle Full Screen", icon: { theme in
/*items.append(.action(ContextMenuActionItem(text: "Toggle Full Screen", icon: { theme in
return nil
}, action: { _, f in
guard let strongSelf = self else {
@ -1038,7 +1040,7 @@ public final class VoiceChatController: ViewController {
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)) {
@ -1203,7 +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.cameraButtonNode)
self.bottomPanelNode.addSubnode(self.leaveNode)
self.bottomPanelNode.addSubnode(self.actionButton)
@ -1212,7 +1214,9 @@ public final class VoiceChatController: ViewController {
self.contentContainer.addSubnode(self.backgroundNode)
self.contentContainer.addSubnode(self.listNode)
self.contentContainer.addSubnode(self.mainVideoContainer)
if let mainVideoContainer = self.mainVideoContainer {
self.contentContainer.addSubnode(mainVideoContainer)
}
self.contentContainer.addSubnode(self.topPanelNode)
self.contentContainer.addSubnode(self.leftBorderNode)
self.contentContainer.addSubnode(self.rightBorderNode)
@ -1347,7 +1351,7 @@ public final class VoiceChatController: ViewController {
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.mainVideoContainer?.updatePeer(peer: (peerId: peerId, source: source))
}
}
@ -1578,7 +1582,7 @@ public final class VoiceChatController: ViewController {
if !validSources.contains(source) {
strongSelf.currentDominantSpeakerWithVideo = nil
strongSelf.call.setFullSizeVideo(peerId: nil)
strongSelf.mainVideoContainer.updatePeer(peer: nil)
strongSelf.mainVideoContainer?.updatePeer(peer: nil)
}
}
@ -1589,8 +1593,8 @@ public final class VoiceChatController: ViewController {
}
}))
self.isFullscreen = true
self.isExpanded = true
//self.isFullscreen = true
//self.isExpanded = true
}
deinit {
@ -1862,9 +1866,11 @@ 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: self.mainVideoContainer, frame: videoContainerFrame)
self.mainVideoContainer.update(size: videoContainerFrame.size, transition: transition)
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
@ -2132,7 +2138,10 @@ public final class VoiceChatController: ViewController {
}
let bottomPanelHeight = bottomAreaHeight + layout.intrinsicInsets.bottom
let listTopInset = layoutTopInset + topPanelHeight + 200.0
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
@ -2237,15 +2246,21 @@ public final class VoiceChatController: ViewController {
let sideButtonOrigin = max(sideButtonMinimalInset, floor((size.width - 144.0) / 2.0) - sideButtonOffset - sideButtonSize.width)
if self.audioOutputNode.supernode === self.bottomPanelNode {
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.leaveNode, frame: CGRect(origin: CGPoint(x: size.width - sideButtonOrigin - sideButtonSize.width, y: floor((bottomAreaHeight - sideButtonSize.height) / 2.0)), size: sideButtonSize))
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 {
while !self.enqueuedTransitions.isEmpty {
self.dequeueTransition()
@ -2276,7 +2291,7 @@ public final class VoiceChatController: ViewController {
self.cameraButtonNode.layer.removeAllAnimations()
self.leaveNode.layer.removeAllAnimations()
self.bottomPanelNode.addSubnode(self.audioOutputNode)
self.bottomPanelNode.addSubnode(self.cameraButtonNode)
//self.bottomPanelNode.addSubnode(self.cameraButtonNode)
self.bottomPanelNode.addSubnode(self.leaveNode)
self.bottomPanelNode.addSubnode(self.actionButton)
self.containerLayoutUpdated(layout, navigationHeight :navigationHeight, transition: .immediate)

View File

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

View File

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

View File

@ -5505,10 +5505,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
if canSetupAutoremoveTimeout {
strongSelf.chatDisplayNode.dismissInput()
let controller = peerAutoremoveSetupScreen(context: strongSelf.context, peerId: peerId)
strongSelf.push(controller)
strongSelf.presentAutoremoveSetup()
} else if let currentAutoremoveTimeout = currentAutoremoveTimeout, let rect = strongSelf.chatDisplayNode.frameForInputPanelAccessoryButton(.messageAutoremoveTimeout(currentAutoremoveTimeout)) {
//TODO:localize
@ -7535,13 +7532,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 {
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
}
@ -7631,13 +7634,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}))
} 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
}
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: {
}),
@ -7645,6 +7650,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
beginClear(.forLocalPeer)
})
], parseMarkdown: true), in: .window(.root))
}
}))
}
@ -7691,19 +7697,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
actionSheet.dismissAnimated()
let controller = peerAutoremoveSetupScreen(context: strongSelf.context, peerId: peer.id, completion: { updatedValue in
if case let .updated(value) = updatedValue {
guard let strongSelf = self else {
return
}
if let value = value {
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .succeed(text: strongSelf.presentationData.strings.Conversation_AutoremoveChanged("\(timeIntervalString(strings: strongSelf.presentationData.strings, value: value))").0), elevatedLayout: false, action: { _ in return false }), in: .current)
}
}
})
strongSelf.chatDisplayNode.dismissInput()
strongSelf.push(controller)
strongSelf.presentAutoremoveSetup()
}))
}
}
@ -11856,6 +11850,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

View File

@ -1436,7 +1436,8 @@ private func stringForRemainingTime(_ duration: Int32, strings: PresentationStri
let seconds = duration % 60
let durationString: String
if days > 0 {
return strings.Conversation_AutoremoveRemainingDays(days)
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 {

View File

@ -44,6 +44,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
}

View File

@ -263,7 +263,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
repliesImage = graphics.incomingDateAndStatusPinnedIcon
}
if hasAutoremove {
selfExpiringImage = graphics.incomingDateAndStatusSelfExpiringIcon
//selfExpiringImage = graphics.incomingDateAndStatusSelfExpiringIcon
}
case let .BubbleOutgoing(status):
dateColor = presentationData.theme.theme.chat.message.outgoing.secondaryTextColor
@ -282,7 +282,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
repliesImage = graphics.outgoingDateAndStatusPinnedIcon
}
if hasAutoremove {
selfExpiringImage = graphics.outgoingDateAndStatusSelfExpiringIcon
//selfExpiringImage = graphics.outgoingDateAndStatusSelfExpiringIcon
}
case .ImageIncoming:
dateColor = presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor
@ -301,7 +301,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
repliesImage = graphics.mediaPinnedIcon
}
if hasAutoremove {
selfExpiringImage = graphics.mediaSelfExpiringIcon
//selfExpiringImage = graphics.mediaSelfExpiringIcon
}
case let .ImageOutgoing(status):
dateColor = presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor
@ -321,7 +321,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
repliesImage = graphics.mediaPinnedIcon
}
if hasAutoremove {
selfExpiringImage = graphics.mediaSelfExpiringIcon
//selfExpiringImage = graphics.mediaSelfExpiringIcon
}
case .FreeIncoming:
let serviceColor = serviceMessageColorComponents(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
@ -341,7 +341,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
repliesImage = graphics.freePinnedIcon
}
if hasAutoremove {
selfExpiringImage = graphics.freeSelfExpiringIcon
//selfExpiringImage = graphics.freeSelfExpiringIcon
}
case let .FreeOutgoing(status):
let serviceColor = serviceMessageColorComponents(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
@ -362,7 +362,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
repliesImage = graphics.freePinnedIcon
}
if hasAutoremove {
selfExpiringImage = graphics.freeSelfExpiringIcon
//selfExpiringImage = graphics.freeSelfExpiringIcon
}
}

View File

@ -23,6 +23,7 @@ import OverlayStatusController
import AlertUI
import PresentationDataUtils
import LocationUI
import AppLock
private final class AccountUserInterfaceInUseContext {
let subscribers = Bag<(Bool) -> Void>()
@ -742,7 +743,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
self.widgetDataContext = WidgetDataContext(basePath: self.basePath, activeAccount: self.activeAccounts
|> map { primary, _, _ in
return primary
}, presentationData: self.presentationData)
}, presentationData: self.presentationData, appLockContext: self.appLockContext as! AppLockContextImpl)
let enableSpotlight = accountManager.sharedData(keys: Set([ApplicationSpecificSharedDataKeys.intentsSettings]))
|> map { sharedData -> Bool in

View File

@ -9,23 +9,40 @@ import NotificationsPresentationData
import WidgetKit
import TelegramUIPreferences
import WidgetItemsUtils
import AccountContext
import AppLock
import GeneratedSources
@available(iOSApplicationExtension 14.0, iOS 14.0, *)
private extension SelectFriendsIntent {
var configurationHash: String {
var result = "widget"
if let items = self.friends {
for item in items {
if let identifier = item.identifier {
result.append("+\(identifier)")
}
}
}
return result
}
}
final class WidgetDataContext {
private var currentAccount: Account?
private var currentAccountDisposable: Disposable?
private var widgetPresentationDataDisposable: Disposable?
private var notificationPresentationDataDisposable: Disposable?
init(basePath: String, activeAccount: Signal<Account?, NoError>, presentationData: Signal<PresentationData, NoError>) {
init(basePath: String, activeAccount: Signal<Account?, NoError>, presentationData: Signal<PresentationData, NoError>, appLockContext: AppLockContextImpl) {
self.currentAccountDisposable = (activeAccount
|> distinctUntilChanged(isEqual: { lhs, rhs in
return lhs === rhs
})
|> mapToSignal { account -> Signal<WidgetData, NoError> in
guard let account = account else {
return .single(WidgetData(accountId: 0, content: .notAuthorized))
return .single(WidgetData(accountId: 0, content: .empty, unlockedForLockId: nil))
}
enum CombinedRecentPeers {
@ -38,11 +55,12 @@ final class WidgetDataContext {
case peers(peers: [Peer], unread: [PeerId: Unread], messages: [PeerId: WidgetDataPeer.Message])
}
let updatedAdditionalPeerIds: Signal<Set<PeerId>, NoError> = Signal { subscriber in
let updatedAdditionalPeerIds: Signal<(Set<PeerId>, Set<String>), NoError> = Signal { subscriber in
if #available(iOSApplicationExtension 14.0, iOS 14.0, *) {
#if arch(arm64) || arch(i386) || arch(x86_64)
WidgetCenter.shared.getCurrentConfigurations({ result in
var peerIds = Set<PeerId>()
var configurationHashes = Set<String>()
if case let .success(infos) = result {
for info in infos {
if let configuration = info.configuration as? SelectFriendsIntent {
@ -54,19 +72,20 @@ final class WidgetDataContext {
peerIds.insert(PeerId(peerIdValue))
}
}
configurationHashes.insert(configuration.configurationHash)
}
}
}
subscriber.putNext(peerIds)
subscriber.putNext((peerIds, configurationHashes))
subscriber.putCompletion()
})
#else
subscriber.putNext(Set())
subscriber.putNext((Set(), Set()))
subscriber.putCompletion()
#endif
} else {
subscriber.putNext(Set())
subscriber.putNext((Set(), Set()))
subscriber.putCompletion()
}
@ -74,33 +93,9 @@ final class WidgetDataContext {
}
|> runOn(.mainQueue())
let preferencesKey: PostboxViewKey = .preferences(keys: Set([
ApplicationSpecificPreferencesKeys.widgetSettings
]))
let sourcePeers: Signal<RecentPeers, NoError> = account.postbox.combinedView(keys: [
preferencesKey
])
|> mapToSignal { views -> Signal<RecentPeers, NoError> in
let widgetSettings: WidgetSettings
if let view = views.views[preferencesKey] as? PreferencesView, let value = view.values[ApplicationSpecificPreferencesKeys.widgetSettings] as? WidgetSettings {
widgetSettings = value
} else {
widgetSettings = .default
}
let unlockedForLockId: Signal<String?, NoError> = .single(nil)
if widgetSettings.useHints {
return recentPeers(account: account)
} else {
return account.postbox.transaction { transaction -> RecentPeers in
return .peers(widgetSettings.peers.compactMap { peerId -> Peer? in
guard let peer = transaction.getPeer(peerId) else {
return nil
}
return peer
})
}
}
}
let sourcePeers: Signal<RecentPeers, NoError> = recentPeers(account: account)
let recent: Signal<CombinedRecentPeers, NoError> = sourcePeers
|> mapToSignal { recent -> Signal<CombinedRecentPeers, NoError> in
@ -160,7 +155,7 @@ final class WidgetDataContext {
|> map { result -> WidgetData in
switch result {
case .disabled:
return WidgetData(accountId: account.id.int64, content: .disabled)
return WidgetData(accountId: account.id.int64, content: .empty, unlockedForLockId: nil)
case let .peers(peers, unread, messages):
return WidgetData(accountId: account.id.int64, content: .peers(WidgetDataPeers(accountPeerId: account.peerId.toInt64(), peers: peers.compactMap { peer -> WidgetDataPeer? in
var name: String = ""
@ -192,15 +187,23 @@ final class WidgetDataContext {
return WidgetDataPeer(id: peer.id.toInt64(), name: name, lastName: lastName, letters: peer.displayLetters, avatarPath: smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in
return account.postbox.mediaBox.resourcePath(representation.resource)
}, badge: badge, message: message)
}, updateTimestamp: Int32(Date().timeIntervalSince1970))))
}, updateTimestamp: Int32(Date().timeIntervalSince1970))), unlockedForLockId: nil)
}
}
|> distinctUntilChanged
let additionalPeerIds = Signal<Set<PeerId>, NoError>.single(Set()) |> then(updatedAdditionalPeerIds)
let additionalPeerIds = Signal<(Set<PeerId>, Set<String>), NoError>.complete() |> then(updatedAdditionalPeerIds)
let processedCustom: Signal<WidgetData, NoError> = additionalPeerIds
|> distinctUntilChanged
|> mapToSignal { additionalPeerIds -> Signal<CombinedRecentPeers, NoError> in
|> distinctUntilChanged(isEqual: { lhs, rhs in
if lhs.0 != rhs.0 {
return false
}
if lhs.1 != rhs.1 {
return false
}
return true
})
|> mapToSignal { additionalPeerIds, _ -> Signal<CombinedRecentPeers, NoError> in
return combineLatest(queue: .mainQueue(), additionalPeerIds.map { account.postbox.peerView(id: $0) })
|> mapToSignal { peerViews -> Signal<CombinedRecentPeers, NoError> in
let topMessagesKey: PostboxViewKey = .topChatMessage(peerIds: peerViews.map {
@ -250,7 +253,7 @@ final class WidgetDataContext {
|> map { result -> WidgetData in
switch result {
case .disabled:
return WidgetData(accountId: account.id.int64, content: .disabled)
return WidgetData(accountId: account.id.int64, content: .empty, unlockedForLockId: nil)
case let .peers(peers, unread, messages):
return WidgetData(accountId: account.id.int64, content: .peers(WidgetDataPeers(accountPeerId: account.peerId.toInt64(), peers: peers.compactMap { peer -> WidgetDataPeer? in
var name: String = ""
@ -282,13 +285,15 @@ final class WidgetDataContext {
return WidgetDataPeer(id: peer.id.toInt64(), name: name, lastName: lastName, letters: peer.displayLetters, avatarPath: smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in
return account.postbox.mediaBox.resourcePath(representation.resource)
}, badge: badge, message: message)
}, updateTimestamp: Int32(Date().timeIntervalSince1970))))
}, updateTimestamp: Int32(Date().timeIntervalSince1970))), unlockedForLockId: nil)
}
}
|> distinctUntilChanged
return combineLatest(processedRecent, processedCustom)
|> map { processedRecent, _ -> WidgetData in
return combineLatest(processedRecent, processedCustom, unlockedForLockId)
|> map { processedRecent, _, unlockedForLockId -> WidgetData in
var processedRecent = processedRecent
processedRecent.unlockedForLockId = unlockedForLockId
return processedRecent
}
}).start(next: { widgetData in

@ -1 +1 @@
Subproject commit ef3baff23ea69ff11673266b62996b88397b260b
Subproject commit 28aa4cb94cd150f825fefbf1400bf2eb6500a9f7

View File

@ -147,7 +147,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
self.textNode.attributedText = attributedText
self.textNode.maximumNumberOfLines = 2
self.textNode.maximumNumberOfLines = 5
displayUndo = false
self.originalRemainingSeconds = 3
case let .info(text):

View File

@ -16,6 +16,16 @@ public struct WidgetDataPeer: Codable, Equatable {
}
public struct Message: Codable, Equatable {
public struct Author: Codable, Equatable {
public var isMe: Bool
public var title: String
public init(isMe: Bool, title: String) {
self.isMe = isMe
self.title = title
}
}
public enum Content: Codable, Equatable {
public enum DecodingError: Error {
case generic
@ -205,11 +215,13 @@ public struct WidgetDataPeer: Codable, Equatable {
}
}
public var author: Author?
public var text: String
public var content: Content
public var timestamp: Int32
public init(text: String, content: Content, timestamp: Int32) {
public init(author: Author?, text: String, content: Content, timestamp: Int32) {
self.author = author
self.text = text
self.content = content
self.timestamp = timestamp
@ -273,23 +285,19 @@ public struct WidgetData: Codable, Equatable {
}
private enum Cases: Int32, Codable {
case notAuthorized
case disabled
case empty
case peers
}
case notAuthorized
case disabled
case empty
case peers(WidgetDataPeers)
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let discriminator = try container.decode(Cases.self, forKey: .discriminator)
switch discriminator {
case .notAuthorized:
self = .notAuthorized
case .disabled:
self = .disabled
case .empty:
self = .empty
case .peers:
self = .peers(try container.decode(WidgetDataPeers.self, forKey: .peers))
}
@ -298,10 +306,8 @@ public struct WidgetData: Codable, Equatable {
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .notAuthorized:
try container.encode(Cases.notAuthorized, forKey: .discriminator)
case .disabled:
try container.encode(Cases.disabled, forKey: .discriminator)
case .empty:
try container.encode(Cases.empty, forKey: .discriminator)
case let .peers(peers):
try container.encode(Cases.peers, forKey: .discriminator)
try container.encode(peers, forKey: .peers)
@ -311,9 +317,11 @@ public struct WidgetData: Codable, Equatable {
public var accountId: Int64
public var content: Content
public var unlockedForLockId: String?
public init(accountId: Int64, content: Content) {
public init(accountId: Int64, content: Content, unlockedForLockId: String?) {
self.accountId = accountId
self.content = content
self.unlockedForLockId = unlockedForLockId
}
}

View File

@ -54,6 +54,18 @@ public extension WidgetDataPeer.Message {
break
}
}
self.init(text: message.text, content: content, timestamp: message.timestamp)
var author: Author?
if let _ = message.peers[message.id.peerId] as? TelegramGroup {
if let authorPeer = message.author {
author = Author(isMe: false, title: authorPeer.debugDisplayTitle)
}
} else if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info {
if let authorPeer = message.author {
author = Author(isMe: false, title: authorPeer.debugDisplayTitle)
}
}
self.init(author: author, text: message.text, content: content, timestamp: message.timestamp)
}
}

@ -1 +1 @@
Subproject commit 5746d4b64798196743aaf083b0c07a3cc58a713f
Subproject commit e42b4630117498504692fbac80ec6dbb970b2314