mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
[WIP] Message auto-delete and widget
This commit is contained in:
parent
d7538bf131
commit
3880af10ae
10
BUILD
Normal file
10
BUILD
Normal file
@ -0,0 +1,10 @@
|
||||
|
||||
load("@build_bazel_rules_apple//apple:resources.bzl",
|
||||
"swift_intent_library",
|
||||
)
|
||||
|
||||
swift_intent_library(
|
||||
name = "GeneratedSources",
|
||||
src = "Intents.intentdefinition",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
@ -75,66 +75,6 @@
|
||||
<string>SelectFriends</string>
|
||||
<key>INIntentParameters</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>INIntentParameterConfigurable</key>
|
||||
<true/>
|
||||
<key>INIntentParameterDisplayName</key>
|
||||
<string>Contents</string>
|
||||
<key>INIntentParameterDisplayNameID</key>
|
||||
<string>WAiyZm</string>
|
||||
<key>INIntentParameterDisplayPriority</key>
|
||||
<integer>1</integer>
|
||||
<key>INIntentParameterEnumType</key>
|
||||
<string>Contents</string>
|
||||
<key>INIntentParameterEnumTypeNamespace</key>
|
||||
<string>p74MWb</string>
|
||||
<key>INIntentParameterMetadata</key>
|
||||
<dict>
|
||||
<key>INIntentParameterMetadataDefaultValue</key>
|
||||
<string>recent</string>
|
||||
</dict>
|
||||
<key>INIntentParameterName</key>
|
||||
<string>contents</string>
|
||||
<key>INIntentParameterPromptDialogs</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>INIntentParameterPromptDialogCustom</key>
|
||||
<true/>
|
||||
<key>INIntentParameterPromptDialogType</key>
|
||||
<string>Configuration</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>INIntentParameterPromptDialogCustom</key>
|
||||
<true/>
|
||||
<key>INIntentParameterPromptDialogType</key>
|
||||
<string>Primary</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>INIntentParameterPromptDialogCustom</key>
|
||||
<true/>
|
||||
<key>INIntentParameterPromptDialogFormatString</key>
|
||||
<string>There are ${count} options matching ‘${contents}’.</string>
|
||||
<key>INIntentParameterPromptDialogFormatStringID</key>
|
||||
<string>oSRWBb</string>
|
||||
<key>INIntentParameterPromptDialogType</key>
|
||||
<string>DisambiguationIntroduction</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>INIntentParameterPromptDialogCustom</key>
|
||||
<true/>
|
||||
<key>INIntentParameterPromptDialogFormatString</key>
|
||||
<string>Just to confirm, you wanted ‘${contents}’?</string>
|
||||
<key>INIntentParameterPromptDialogFormatStringID</key>
|
||||
<string>jvYJCG</string>
|
||||
<key>INIntentParameterPromptDialogType</key>
|
||||
<string>Confirmation</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>INIntentParameterTag</key>
|
||||
<integer>5</integer>
|
||||
<key>INIntentParameterType</key>
|
||||
<string>Integer</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>INIntentParameterArraySizes</key>
|
||||
<array>
|
||||
@ -160,11 +100,11 @@
|
||||
<key>INIntentParameterConfigurable</key>
|
||||
<true/>
|
||||
<key>INIntentParameterDisplayName</key>
|
||||
<string>Chats</string>
|
||||
<string> </string>
|
||||
<key>INIntentParameterDisplayNameID</key>
|
||||
<string>WIf4LD</string>
|
||||
<key>INIntentParameterDisplayPriority</key>
|
||||
<integer>2</integer>
|
||||
<integer>1</integer>
|
||||
<key>INIntentParameterFixedSizeArray</key>
|
||||
<integer>1</integer>
|
||||
<key>INIntentParameterName</key>
|
||||
@ -178,6 +118,10 @@
|
||||
<dict>
|
||||
<key>INIntentParameterPromptDialogCustom</key>
|
||||
<true/>
|
||||
<key>INIntentParameterPromptDialogFormatString</key>
|
||||
<string>Search</string>
|
||||
<key>INIntentParameterPromptDialogFormatStringID</key>
|
||||
<string>ORCbLf</string>
|
||||
<key>INIntentParameterPromptDialogType</key>
|
||||
<string>Configuration</string>
|
||||
</dict>
|
||||
@ -190,8 +134,6 @@
|
||||
</array>
|
||||
<key>INIntentParameterRelationship</key>
|
||||
<dict>
|
||||
<key>INIntentParameterRelationshipParentName</key>
|
||||
<string>contents</string>
|
||||
<key>INIntentParameterRelationshipPredicateName</key>
|
||||
<string>EnumHasExactValue</string>
|
||||
<key>INIntentParameterRelationshipPredicateValue</key>
|
||||
@ -201,6 +143,8 @@
|
||||
<true/>
|
||||
<key>INIntentParameterSupportsMultipleValues</key>
|
||||
<true/>
|
||||
<key>INIntentParameterSupportsSearch</key>
|
||||
<true/>
|
||||
<key>INIntentParameterTag</key>
|
||||
<integer>19</integer>
|
||||
<key>INIntentParameterType</key>
|
@ -8,6 +8,10 @@ load("@build_bazel_rules_apple//apple:ios.bzl",
|
||||
"ios_framework",
|
||||
)
|
||||
|
||||
load("@build_bazel_rules_apple//apple:resources.bzl",
|
||||
"swift_intent_library",
|
||||
)
|
||||
|
||||
load("@build_bazel_rules_apple//apple:watchos.bzl",
|
||||
"watchos_application",
|
||||
"watchos_extension",
|
||||
@ -1158,7 +1162,7 @@ plist_fragment(
|
||||
)
|
||||
|
||||
swift_library(
|
||||
name = "GeneratedSources",
|
||||
name = "GeneratedSources1",
|
||||
module_name = "GeneratedSources",
|
||||
srcs = glob([
|
||||
"Generated/**/*.swift",
|
||||
@ -1166,6 +1170,12 @@ swift_library(
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
'''swift_intent_library(
|
||||
name = "GeneratedSources",
|
||||
src = "SiriIntents/Intents.intentdefinition",
|
||||
visibility = ["//visibility:public"],
|
||||
)'''
|
||||
|
||||
swift_library(
|
||||
name = "WidgetExtensionLib",
|
||||
module_name = "WidgetExtensionLib",
|
||||
@ -1173,7 +1183,7 @@ swift_library(
|
||||
"WidgetKitWidget/**/*.swift",
|
||||
]),
|
||||
data = [
|
||||
"SiriIntents/Intents.intentdefinition",
|
||||
#"SiriIntents/Intents.intentdefinition",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/BuildConfig:BuildConfig",
|
||||
@ -1185,7 +1195,7 @@ swift_library(
|
||||
"//submodules/TelegramCore:TelegramCore",
|
||||
"//submodules/SyncCore:SyncCore",
|
||||
"//submodules/OpenSSLEncryptionProvider:OpenSSLEncryptionProvider",
|
||||
":GeneratedSources",
|
||||
"//:GeneratedSources",
|
||||
],
|
||||
)
|
||||
|
||||
@ -1267,7 +1277,7 @@ swift_library(
|
||||
"SiriIntents/**/*.swift",
|
||||
]),
|
||||
data = [
|
||||
"SiriIntents/Intents.intentdefinition",
|
||||
#"SiriIntents/Intents.intentdefinition",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
@ -1278,7 +1288,7 @@ swift_library(
|
||||
"//submodules/BuildConfig:BuildConfig",
|
||||
"//submodules/OpenSSLEncryptionProvider:OpenSSLEncryptionProvider",
|
||||
"//submodules/AppLockState:AppLockState",
|
||||
":GeneratedSources",
|
||||
"//:GeneratedSources",
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -1,36 +0,0 @@
|
||||
//
|
||||
// Contents.swift
|
||||
//
|
||||
// This file was automatically generated and should not be edited.
|
||||
//
|
||||
|
||||
#if canImport(Intents)
|
||||
|
||||
import Intents
|
||||
|
||||
@available(iOS 12.0, macOS 10.16, watchOS 5.0, *) @available(tvOS, unavailable)
|
||||
@objc public enum Contents: Int {
|
||||
case `unknown` = 0
|
||||
case `recent` = 1
|
||||
case `custom` = 2
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.16, watchOS 6.0, *) @available(tvOS, unavailable)
|
||||
@objc(ContentsResolutionResult)
|
||||
public class ContentsResolutionResult: INEnumResolutionResult {
|
||||
|
||||
// This resolution result is for when the app extension wants to tell Siri to proceed, with a given Contents. The resolvedValue can be different than the original Contents. This allows app extensions to apply business logic constraints.
|
||||
// Use notRequired() to continue with a 'nil' value.
|
||||
@objc(successWithResolvedContents:)
|
||||
public class func success(with resolvedValue: Contents) -> Self {
|
||||
return __success(withResolvedValue: resolvedValue.rawValue)
|
||||
}
|
||||
|
||||
// This resolution result is to ask Siri to confirm if this is the value with which the user wants to continue.
|
||||
@objc(confirmationRequiredWithContentsToConfirm:)
|
||||
public class func confirmationRequired(with valueToConfirm: Contents) -> Self {
|
||||
return __confirmationRequiredWithValue(toConfirm: valueToConfirm.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@ -1,60 +0,0 @@
|
||||
//
|
||||
// Friend.swift
|
||||
//
|
||||
// This file was automatically generated and should not be edited.
|
||||
//
|
||||
|
||||
#if canImport(Intents)
|
||||
|
||||
import Intents
|
||||
|
||||
@available(iOS 12.0, macOS 10.16, watchOS 5.0, *) @available(tvOS, unavailable)
|
||||
@objc(Friend)
|
||||
public class Friend: INObject {
|
||||
|
||||
@available(iOS 13.0, macOS 10.16, watchOS 6.0, *)
|
||||
@NSManaged public var subtitle: String?
|
||||
|
||||
}
|
||||
|
||||
@available(iOS 13.0, macOS 10.16, watchOS 6.0, *) @available(tvOS, unavailable)
|
||||
@objc(FriendResolutionResult)
|
||||
public class FriendResolutionResult: INObjectResolutionResult {
|
||||
|
||||
// This resolution result is for when the app extension wants to tell Siri to proceed, with a given Friend. The resolvedValue can be different than the original Friend. This allows app extensions to apply business logic constraints.
|
||||
// Use notRequired() to continue with a 'nil' value.
|
||||
@objc(successWithResolvedFriend:)
|
||||
public class func success(with resolvedObject: Friend) -> Self {
|
||||
return super.success(with: resolvedObject) as! Self
|
||||
}
|
||||
|
||||
// This resolution result is to ask Siri to disambiguate between the provided Friend.
|
||||
@objc(disambiguationWithFriendsToDisambiguate:)
|
||||
public class func disambiguation(with objectsToDisambiguate: [Friend]) -> Self {
|
||||
return super.disambiguation(with: objectsToDisambiguate) as! Self
|
||||
}
|
||||
|
||||
// This resolution result is to ask Siri to confirm if this is the value with which the user wants to continue.
|
||||
@objc(confirmationRequiredWithFriendToConfirm:)
|
||||
public class func confirmationRequired(with objectToConfirm: Friend?) -> Self {
|
||||
return super.confirmationRequired(with: objectToConfirm) as! Self
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
override public class func success(with resolvedObject: INObject) -> Self {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
override public class func disambiguation(with objectsToDisambiguate: [INObject]) -> Self {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
override public class func confirmationRequired(with objectToConfirm: INObject?) -> Self {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -1,120 +0,0 @@
|
||||
//
|
||||
// SelectFriendsIntent.swift
|
||||
//
|
||||
// This file was automatically generated and should not be edited.
|
||||
//
|
||||
|
||||
#if canImport(Intents)
|
||||
|
||||
import Intents
|
||||
|
||||
@available(iOS 12.0, macOS 10.16, watchOS 5.0, *) @available(tvOS, unavailable)
|
||||
@objc(SelectFriendsIntent)
|
||||
public class SelectFriendsIntent: INIntent {
|
||||
|
||||
@NSManaged public var contents: Contents
|
||||
@NSManaged public var friends: [Friend]?
|
||||
|
||||
}
|
||||
|
||||
/*!
|
||||
@abstract Protocol to declare support for handling a SelectFriendsIntent. By implementing this protocol, a class can provide logic for resolving, confirming and handling the intent.
|
||||
@discussion The minimum requirement for an implementing class is that it should be able to handle the intent. The confirmation method is optional. The handling method is always called last, after confirming the intent.
|
||||
*/
|
||||
@available(iOS 12.0, macOS 10.16, watchOS 5.0, *) @available(tvOS, unavailable)
|
||||
@objc(SelectFriendsIntentHandling)
|
||||
public protocol SelectFriendsIntentHandling: NSObjectProtocol {
|
||||
|
||||
/*!
|
||||
@abstract Dynamic options methods - provide options for the parameter at runtime
|
||||
@discussion Called to query dynamic options for the parameter and this intent in its current form.
|
||||
|
||||
@param intent The input intent
|
||||
@param completion The response block contains options for the parameter
|
||||
*/
|
||||
@available(iOS 14.0, macOS 10.16, watchOS 7.0, *)
|
||||
@objc(provideFriendsOptionsCollectionForSelectFriends:withCompletion:)
|
||||
func provideFriendsOptionsCollection(for intent: SelectFriendsIntent, with completion: @escaping (INObjectCollection<Friend>?, Error?) -> Swift.Void)
|
||||
|
||||
/*!
|
||||
@abstract Confirmation method - Validate that this intent is ready for the next step (i.e. handling)
|
||||
@discussion Called prior to asking the app to handle the intent. The app should return a response object that contains additional information about the intent, which may be relevant for the system to show the user prior to handling. If unimplemented, the system will assume the intent is valid, and will assume there is no additional information relevant to this intent.
|
||||
|
||||
@param intent The input intent
|
||||
@param completion The response block contains a SelectFriendsIntentResponse containing additional details about the intent that may be relevant for the system to show the user prior to handling.
|
||||
|
||||
@see SelectFriendsIntentResponse
|
||||
*/
|
||||
@objc(confirmSelectFriends:completion:)
|
||||
optional func confirm(intent: SelectFriendsIntent, completion: @escaping (SelectFriendsIntentResponse) -> Swift.Void)
|
||||
|
||||
/*!
|
||||
@abstract Handling method - Execute the task represented by the SelectFriendsIntent that's passed in
|
||||
@discussion Called to actually execute the intent. The app must return a response for this intent.
|
||||
|
||||
@param intent The input intent
|
||||
@param completion The response handling block takes a SelectFriendsIntentResponse containing the details of the result of having executed the intent
|
||||
|
||||
@see SelectFriendsIntentResponse
|
||||
*/
|
||||
@objc(handleSelectFriends:completion:)
|
||||
optional func handle(intent: SelectFriendsIntent, completion: @escaping (SelectFriendsIntentResponse) -> Swift.Void)
|
||||
|
||||
/*!
|
||||
@abstract Default values for parameters with dynamic options
|
||||
@discussion Called to query the parameter default value.
|
||||
*/
|
||||
@available(iOS 14.0, macOS 10.16, watchOS 7.0, *)
|
||||
@objc(defaultFriendsForSelectFriends:)
|
||||
optional func defaultFriends(for intent: SelectFriendsIntent) -> [Friend]?
|
||||
|
||||
/*!
|
||||
@abstract Deprecated dynamic options methods.
|
||||
*/
|
||||
@available(iOS, introduced: 13.0, deprecated: 14.0, message: "")
|
||||
@available(watchOS, introduced: 6.0, deprecated: 7.0, message: "")
|
||||
@objc(provideFriendsOptionsForSelectFriends:withCompletion:)
|
||||
optional func provideFriendsOptions(for intent: SelectFriendsIntent, with completion: @escaping ([Friend]?, Error?) -> Swift.Void)
|
||||
|
||||
}
|
||||
|
||||
/*!
|
||||
@abstract Constants indicating the state of the response.
|
||||
*/
|
||||
@available(iOS 12.0, macOS 10.16, watchOS 5.0, *) @available(tvOS, unavailable)
|
||||
@objc public enum SelectFriendsIntentResponseCode: Int {
|
||||
case unspecified = 0
|
||||
case ready
|
||||
case continueInApp
|
||||
case inProgress
|
||||
case success
|
||||
case failure
|
||||
case failureRequiringAppLaunch
|
||||
}
|
||||
|
||||
@available(iOS 12.0, macOS 10.16, watchOS 5.0, *) @available(tvOS, unavailable)
|
||||
@objc(SelectFriendsIntentResponse)
|
||||
public class SelectFriendsIntentResponse: INIntentResponse {
|
||||
|
||||
/*!
|
||||
@abstract The response code indicating your success or failure in confirming or handling the intent.
|
||||
*/
|
||||
@objc public fileprivate(set) var code: SelectFriendsIntentResponseCode = .unspecified
|
||||
|
||||
/*!
|
||||
@abstract Initializes the response object with the specified code and user activity object.
|
||||
@discussion The app extension has the option of capturing its private state as an NSUserActivity and returning it as the 'currentActivity'. If the app is launched, an NSUserActivity will be passed in with the private state. The NSUserActivity may also be used to query the app's UI extension (if provided) for a view controller representing the current intent handling state. In the case of app launch, the NSUserActivity will have its activityType set to the name of the intent. This intent object will also be available in the NSUserActivity.interaction property.
|
||||
|
||||
@param code The response code indicating your success or failure in confirming or handling the intent.
|
||||
@param userActivity The user activity object to use when launching your app. Provide an object if you want to add information that is specific to your app. If you specify nil, the system automatically creates a user activity object for you, sets its type to the class name of the intent being handled, and fills it with an INInteraction object containing the intent and your response.
|
||||
*/
|
||||
@objc(initWithCode:userActivity:)
|
||||
public convenience init(code: SelectFriendsIntentResponseCode, userActivity: NSUserActivity?) {
|
||||
self.init()
|
||||
self.code = code
|
||||
self.userActivity = userActivity
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -56,10 +56,15 @@ enum IntentHandlingError {
|
||||
@objc(IntentHandler)
|
||||
class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessagesIntentHandling, INSetMessageAttributeIntentHandling, INStartAudioCallIntentHandling, INSearchCallHistoryIntentHandling, SelectFriendsIntentHandling {
|
||||
private let accountPromise = Promise<Account?>()
|
||||
private let allAccounts = Promise<[(AccountRecordId, PeerId)]>()
|
||||
|
||||
private let resolvePersonsDisposable = MetaDisposable()
|
||||
private let actionDisposable = MetaDisposable()
|
||||
private let searchDisposable = MetaDisposable()
|
||||
|
||||
private var rootPath: String?
|
||||
private var accountManager: AccountManager?
|
||||
private var encryptionParameters: ValueBoxEncryptionParameters?
|
||||
private var appGroupUrl: URL?
|
||||
|
||||
override init() {
|
||||
@ -88,6 +93,8 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
||||
let rootPath = rootPathForBasePath(appGroupUrl.path)
|
||||
performAppGroupUpgrades(appGroupPath: appGroupUrl.path, rootPath: rootPath)
|
||||
|
||||
self.rootPath = rootPath
|
||||
|
||||
TempBox.initializeShared(basePath: rootPath, processType: "siri", launchSpecificId: arc4random64())
|
||||
|
||||
let logsPath = rootPath + "/siri-logs"
|
||||
@ -97,16 +104,61 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
||||
|
||||
let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown"
|
||||
|
||||
initializeAccountManagement()
|
||||
let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata")
|
||||
self.accountManager = accountManager
|
||||
|
||||
let deviceSpecificEncryptionParameters = BuildConfig.deviceSpecificEncryptionParameters(rootPath, baseAppBundleId: baseAppBundleId)
|
||||
let encryptionParameters = ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: deviceSpecificEncryptionParameters.key)!, salt: ValueBoxEncryptionParameters.Salt(data: deviceSpecificEncryptionParameters.salt)!)
|
||||
self.encryptionParameters = encryptionParameters
|
||||
|
||||
self.allAccounts.set(accountManager.accountRecords()
|
||||
|> take(1)
|
||||
|> map { view -> [(AccountRecordId, PeerId)] in
|
||||
var result: [(AccountRecordId, Int, PeerId)] = []
|
||||
for record in view.records {
|
||||
let isLoggedOut = record.attributes.contains(where: { attribute in
|
||||
return attribute is LoggedOutAccountAttribute
|
||||
})
|
||||
if isLoggedOut {
|
||||
continue
|
||||
}
|
||||
/*let isTestingEnvironment = record.attributes.contains(where: { attribute in
|
||||
if let attribute = attribute as? AccountEnvironmentAttribute, case .test = attribute.environment {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})*/
|
||||
var backupData: AccountBackupData?
|
||||
var sortIndex: Int32 = 0
|
||||
for attribute in record.attributes {
|
||||
if let attribute = attribute as? AccountSortOrderAttribute {
|
||||
sortIndex = attribute.order
|
||||
} else if let attribute = attribute as? AccountBackupDataAttribute {
|
||||
backupData = attribute.data
|
||||
}
|
||||
}
|
||||
if let backupData = backupData {
|
||||
result.append((record.id, Int(sortIndex), PeerId(backupData.peerId)))
|
||||
}
|
||||
}
|
||||
result.sort(by: { lhs, rhs in
|
||||
if lhs.1 != rhs.1 {
|
||||
return lhs.1 < rhs.1
|
||||
} else {
|
||||
return lhs.0 < rhs.0
|
||||
}
|
||||
})
|
||||
return result.map { record -> (AccountRecordId, PeerId) in
|
||||
return (record.0, record.2)
|
||||
}
|
||||
})
|
||||
|
||||
let account: Signal<Account?, NoError>
|
||||
if let accountCache = accountCache {
|
||||
account = .single(accountCache)
|
||||
} else {
|
||||
initializeAccountManagement()
|
||||
let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata")
|
||||
|
||||
let deviceSpecificEncryptionParameters = BuildConfig.deviceSpecificEncryptionParameters(rootPath, baseAppBundleId: baseAppBundleId)
|
||||
let encryptionParameters = ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: deviceSpecificEncryptionParameters.key)!, salt: ValueBoxEncryptionParameters.Salt(data: deviceSpecificEncryptionParameters.salt)!)
|
||||
|
||||
account = currentAccount(allocateIfNotExists: false, networkArguments: NetworkInitializationArguments(apiId: apiId, apiHash: apiHash, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider()), supplementary: true, manager: accountManager, rootPath: rootPath, auxiliaryMethods: accountAuxiliaryMethods, encryptionParameters: encryptionParameters)
|
||||
|> mapToSignal { account -> Signal<Account?, NoError> in
|
||||
if let account = account {
|
||||
@ -139,6 +191,7 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
||||
deinit {
|
||||
self.resolvePersonsDisposable.dispose()
|
||||
self.actionDisposable.dispose()
|
||||
self.searchDisposable.dispose()
|
||||
}
|
||||
|
||||
override public func handler(for intent: INIntent) -> Any {
|
||||
@ -701,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)
|
||||
})
|
||||
})*/
|
||||
}
|
||||
}
|
||||
|
@ -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";
|
||||
|
||||
|
@ -94,11 +94,11 @@ private func avatarViewLettersImage(size: CGSize, peerId: Int64, accountPeerId:
|
||||
|
||||
private let avatarSize = CGSize(width: 50.0, height: 50.0)
|
||||
|
||||
func avatarImage(accountPeerId: Int64, peer: WidgetDataPeer, size: CGSize) -> UIImage {
|
||||
if let path = peer.avatarPath, let image = UIImage(contentsOfFile: path), let roundImage = avatarRoundImage(size: size, source: image) {
|
||||
func avatarImage(accountPeerId: Int64?, peer: WidgetDataPeer?, size: CGSize) -> UIImage {
|
||||
if let path = peer?.avatarPath, let image = UIImage(contentsOfFile: path), let roundImage = avatarRoundImage(size: size, source: image) {
|
||||
return roundImage
|
||||
} else {
|
||||
return avatarViewLettersImage(size: size, peerId: peer.id, accountPeerId: accountPeerId, letters: peer.letters)!
|
||||
return avatarViewLettersImage(size: size, peerId: peer?.id ?? 1, accountPeerId: accountPeerId ?? 1, letters: peer?.letters ?? [" "])!
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,29 @@ import WidgetItemsUtils
|
||||
|
||||
import GeneratedSources
|
||||
|
||||
struct ParsedPeer {
|
||||
var accountId: Int64
|
||||
var accountPeerId: Int64
|
||||
var peer: WidgetDataPeer
|
||||
}
|
||||
|
||||
struct ParsedPeers {
|
||||
var peers: [ParsedPeer]
|
||||
var updateTimestamp: Int32
|
||||
}
|
||||
|
||||
private extension ParsedPeers {
|
||||
init(accountId: Int64, peers: WidgetDataPeers) {
|
||||
self.init(peers: peers.peers.map { peer -> ParsedPeer in
|
||||
return ParsedPeer(
|
||||
accountId: accountId,
|
||||
accountPeerId: peers.accountPeerId,
|
||||
peer: peer
|
||||
)
|
||||
}, updateTimestamp: peers.updateTimestamp)
|
||||
}
|
||||
}
|
||||
|
||||
private var installedSharedLogger = false
|
||||
|
||||
private func setupSharedLogger(rootPath: String, path: String) {
|
||||
@ -64,14 +87,7 @@ struct Provider: IntentTimelineProvider {
|
||||
}
|
||||
|
||||
func getSnapshot(for configuration: SelectFriendsIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
|
||||
let contents: SimpleEntry.Contents
|
||||
switch configuration.contents {
|
||||
case .unknown, .recent:
|
||||
contents = .recent
|
||||
case .custom:
|
||||
contents = .recent
|
||||
}
|
||||
let entry = SimpleEntry(date: Date(), contents: contents)
|
||||
let entry = SimpleEntry(date: Date(), contents: .peers(ParsedPeers(accountId: 0, peers: WidgetDataPeers(accountPeerId: 0, peers: [], updateTimestamp: 0))))
|
||||
completion(entry)
|
||||
}
|
||||
|
||||
@ -79,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
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -79,6 +79,17 @@ public enum PostboxAccessChallengeData: PostboxCoding, Equatable, Codable {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public var lockId: String? {
|
||||
switch self {
|
||||
case .none:
|
||||
return nil
|
||||
case let .numericalPassword(value):
|
||||
return "numericalPassword:\(value)"
|
||||
case let .plaintextPassword(value):
|
||||
return "plaintextPassword:\(value)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct AuthAccountRecord: PostboxCoding, Codable {
|
||||
|
@ -559,31 +559,31 @@ final class MutableChatListView {
|
||||
return self.sampledState.hole
|
||||
}
|
||||
|
||||
private func renderEntry(_ entry: MutableChatListEntry, postbox: Postbox, renderMessage: (IntermediateMessage) -> Message, getPeer: (PeerId) -> Peer?, getPeerNotificationSettings: (PeerId) -> PeerNotificationSettings?, getPeerPresence: (PeerId) -> PeerPresence?) -> MutableChatListEntry? {
|
||||
private func renderEntry(_ entry: MutableChatListEntry, postbox: Postbox) -> MutableChatListEntry? {
|
||||
switch entry {
|
||||
case let .IntermediateMessageEntry(index, messageIndex):
|
||||
var renderedMessages: [Message] = []
|
||||
if let messageIndex = messageIndex {
|
||||
if let messageGroup = postbox.messageHistoryTable.getMessageGroup(at: messageIndex, limit: 10) {
|
||||
renderedMessages.append(contentsOf: messageGroup.compactMap(renderMessage))
|
||||
renderedMessages.append(contentsOf: messageGroup.compactMap(postbox.renderIntermediateMessage))
|
||||
}
|
||||
}
|
||||
var peers = SimpleDictionary<PeerId, Peer>()
|
||||
var notificationSettings: PeerNotificationSettings?
|
||||
var presence: PeerPresence?
|
||||
var isContact: Bool = false
|
||||
if let peer = getPeer(index.messageIndex.id.peerId) {
|
||||
if let peer = postbox.peerTable.get(index.messageIndex.id.peerId) {
|
||||
peers[peer.id] = peer
|
||||
if let associatedPeerId = peer.associatedPeerId {
|
||||
if let associatedPeer = getPeer(associatedPeerId) {
|
||||
if let associatedPeer = postbox.peerTable.get(associatedPeerId) {
|
||||
peers[associatedPeer.id] = associatedPeer
|
||||
}
|
||||
notificationSettings = getPeerNotificationSettings(associatedPeerId)
|
||||
presence = getPeerPresence(associatedPeerId)
|
||||
notificationSettings = postbox.peerNotificationSettingsTable.getEffective(associatedPeerId)
|
||||
presence = postbox.peerPresenceTable.get(associatedPeerId)
|
||||
isContact = postbox.contactsTable.isContact(peerId: associatedPeerId)
|
||||
} else {
|
||||
notificationSettings = getPeerNotificationSettings(index.messageIndex.id.peerId)
|
||||
presence = getPeerPresence(index.messageIndex.id.peerId)
|
||||
notificationSettings = postbox.peerNotificationSettingsTable.getEffective(index.messageIndex.id.peerId)
|
||||
presence = postbox.peerPresenceTable.get(index.messageIndex.id.peerId)
|
||||
isContact = postbox.contactsTable.isContact(peerId: peer.id)
|
||||
}
|
||||
}
|
||||
@ -597,9 +597,9 @@ final class MutableChatListView {
|
||||
}
|
||||
}
|
||||
|
||||
func render(postbox: Postbox, renderMessage: (IntermediateMessage) -> Message, getPeer: (PeerId) -> Peer?, getPeerNotificationSettings: (PeerId) -> PeerNotificationSettings?, getPeerPresence: (PeerId) -> PeerPresence?) {
|
||||
func render(postbox: Postbox) {
|
||||
for i in 0 ..< self.additionalItemEntries.count {
|
||||
if let updatedEntry = self.renderEntry(self.additionalItemEntries[i].entry, postbox: postbox, renderMessage: renderMessage, getPeer: getPeer, getPeerNotificationSettings: getPeerNotificationSettings, getPeerPresence: getPeerPresence) {
|
||||
if let updatedEntry = self.renderEntry(self.additionalItemEntries[i].entry, postbox: postbox) {
|
||||
self.additionalItemEntries[i].entry = updatedEntry
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ final class MutableContactPeersView {
|
||||
self.includePresences = includePresences
|
||||
}
|
||||
|
||||
func replay(replacePeerIds: Set<PeerId>?, updatedPeerPresences: [PeerId: PeerPresence], getPeer: (PeerId) -> Peer?, getPeerPresence: (PeerId) -> PeerPresence?) -> Bool {
|
||||
func replay(postbox: Postbox, replacePeerIds: Set<PeerId>?, updatedPeerPresences: [PeerId: PeerPresence]) -> Bool {
|
||||
var updated = false
|
||||
if let replacePeerIds = replacePeerIds {
|
||||
let removedPeerIds = self.peerIds.subtracting(replacePeerIds)
|
||||
@ -29,11 +29,11 @@ final class MutableContactPeersView {
|
||||
}
|
||||
|
||||
for peerId in addedPeerIds {
|
||||
if let peer = getPeer(peerId) {
|
||||
if let peer = postbox.peerTable.get(peerId) {
|
||||
self.peers[peerId] = peer
|
||||
}
|
||||
if self.includePresences {
|
||||
if let presence = getPeerPresence(peerId) {
|
||||
if let presence = postbox.peerPresenceTable.get(peerId) {
|
||||
self.peerPresences[peerId] = presence
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ final class MutableMessageView {
|
||||
self.stableId = message?.stableId
|
||||
}
|
||||
|
||||
func replay(_ operations: [MessageHistoryOperation], updatedMedia: [MediaId: Media?], renderIntermediateMessage: (IntermediateMessage) -> Message) -> Bool {
|
||||
func replay(postbox: Postbox, operations: [MessageHistoryOperation], updatedMedia: [MediaId: Media?]) -> Bool {
|
||||
var updated = false
|
||||
for operation in operations {
|
||||
switch operation {
|
||||
@ -28,7 +28,7 @@ final class MutableMessageView {
|
||||
}
|
||||
case let .InsertMessage(message):
|
||||
if message.id == self.messageId || message.stableId == self.stableId {
|
||||
self.message = renderIntermediateMessage(message)
|
||||
self.message = postbox.renderIntermediateMessage(message)
|
||||
self.stableId = message.stableId
|
||||
updated = true
|
||||
}
|
||||
|
@ -6,14 +6,14 @@ final class MutablePeerMergedOperationLogView {
|
||||
var tailIndex: Int32?
|
||||
let limit: Int
|
||||
|
||||
init(tag: PeerOperationLogTag, limit: Int, getOperations: (PeerOperationLogTag, Int32, Int) -> [PeerMergedOperationLogEntry], getTailIndex: (PeerOperationLogTag) -> Int32?) {
|
||||
init(postbox: Postbox, tag: PeerOperationLogTag, limit: Int) {
|
||||
self.tag = tag
|
||||
self.entries = getOperations(tag, 0, limit)
|
||||
self.tailIndex = getTailIndex(tag)
|
||||
self.entries = postbox.peerOperationLogTable.getMergedEntries(tag: tag, fromIndex: 0, limit: limit)
|
||||
self.tailIndex = postbox.peerMergedOperationLogIndexTable.tailIndex(tag: tag)
|
||||
self.limit = limit
|
||||
}
|
||||
|
||||
func replay(operations: [PeerMergedOperationLogOperation], getOperations: (PeerOperationLogTag, Int32, Int) -> [PeerMergedOperationLogEntry], getTailIndex: (PeerOperationLogTag) -> Int32?) -> Bool {
|
||||
func replay(postbox: Postbox, operations: [PeerMergedOperationLogOperation]) -> Bool {
|
||||
var updated = false
|
||||
var invalidatedTail = false
|
||||
|
||||
@ -65,7 +65,7 @@ final class MutablePeerMergedOperationLogView {
|
||||
|
||||
if updated {
|
||||
if invalidatedTail {
|
||||
self.tailIndex = getTailIndex(self.tag)
|
||||
self.tailIndex = postbox.peerMergedOperationLogIndexTable.tailIndex(tag: self.tag)
|
||||
}
|
||||
if self.entries.count < self.limit {
|
||||
if let tailIndex = self.tailIndex {
|
||||
@ -74,7 +74,7 @@ final class MutablePeerMergedOperationLogView {
|
||||
if !self.entries.isEmpty {
|
||||
fromIndex = self.entries.last!.mergedIndex + 1
|
||||
}
|
||||
for entry in getOperations(self.tag, fromIndex, self.limit - self.entries.count) {
|
||||
for entry in postbox.peerOperationLogTable.getMergedEntries(tag: self.tag, fromIndex: fromIndex, limit: self.limit - self.entries.count) {
|
||||
self.entries.append(entry)
|
||||
}
|
||||
for i in 0 ..< self.entries.count {
|
||||
|
@ -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))
|
||||
|
@ -4,12 +4,12 @@ final class MutableTimestampBasedMessageAttributesView {
|
||||
let tag: UInt16
|
||||
var head: TimestampBasedMessageAttributesEntry?
|
||||
|
||||
init(tag: UInt16, getHead: (UInt16) -> TimestampBasedMessageAttributesEntry?) {
|
||||
init(postbox: Postbox, tag: UInt16) {
|
||||
self.tag = tag
|
||||
self.head = getHead(tag)
|
||||
self.head = postbox.timestampBasedMessageAttributesTable.head(tag: tag)
|
||||
}
|
||||
|
||||
func replay(operations: [TimestampBasedMessageAttributesOperation], getHead: (UInt16) -> TimestampBasedMessageAttributesEntry?) -> Bool {
|
||||
func replay(postbox: Postbox, operations: [TimestampBasedMessageAttributesOperation]) -> Bool {
|
||||
var updated = false
|
||||
var invalidatedHead = false
|
||||
for operation in operations {
|
||||
@ -37,7 +37,7 @@ final class MutableTimestampBasedMessageAttributesView {
|
||||
}
|
||||
}
|
||||
if invalidatedHead {
|
||||
self.head = getHead(self.tag)
|
||||
self.head = postbox.timestampBasedMessageAttributesTable.head(tag: self.tag)
|
||||
}
|
||||
return updated
|
||||
}
|
||||
|
@ -11,15 +11,6 @@ public enum ViewUpdateType {
|
||||
|
||||
final class ViewTracker {
|
||||
private let queue: Queue
|
||||
private let renderMessage: (IntermediateMessage) -> Message
|
||||
private let getPeer: (PeerId) -> Peer?
|
||||
private let getPeerNotificationSettings: (PeerId) -> PeerNotificationSettings?
|
||||
private let getCachedPeerData: (PeerId) -> CachedPeerData?
|
||||
private let getPeerPresence: (PeerId) -> PeerPresence?
|
||||
private let getPeerReadState: (PeerId) -> CombinedPeerReadState?
|
||||
private let operationLogGetOperations: (PeerOperationLogTag, Int32, Int) -> [PeerMergedOperationLogEntry]
|
||||
private let operationLogGetTailIndex: (PeerOperationLogTag) -> Int32?
|
||||
private let getPreferencesEntry: (ValueBoxKey) -> PreferencesEntry?
|
||||
|
||||
private var chatListViews = Bag<(MutableChatListView, ValuePipe<(ChatListView, ViewUpdateType)>)>()
|
||||
private var messageHistoryViews = Bag<(MutableMessageHistoryView, ValuePipe<(MessageHistoryView, ViewUpdateType)>)>()
|
||||
@ -46,7 +37,6 @@ final class ViewTracker {
|
||||
private var unreadMessageCountsViews = Bag<(MutableUnreadMessageCountsView, ValuePipe<UnreadMessageCountsView>)>()
|
||||
private var peerMergedOperationLogViews = Bag<(MutablePeerMergedOperationLogView, ValuePipe<PeerMergedOperationLogView>)>()
|
||||
|
||||
private let getTimestampBasedMessageAttributesHead: (UInt16) -> TimestampBasedMessageAttributesEntry?
|
||||
private var timestampBasedMessageAttributesViews = Bag<(MutableTimestampBasedMessageAttributesView, ValuePipe<TimestampBasedMessageAttributesView>)>()
|
||||
|
||||
private var combinedViews = Bag<(CombinedMutableView, ValuePipe<CombinedView>)>()
|
||||
@ -56,23 +46,14 @@ final class ViewTracker {
|
||||
private var preferencesViews = Bag<(MutablePreferencesView, ValuePipe<PreferencesView>)>()
|
||||
private var multiplePeersViews = Bag<(MutableMultiplePeersView, ValuePipe<MultiplePeersView>)>()
|
||||
private var itemCollectionsViews = Bag<(MutableItemCollectionsView, ValuePipe<ItemCollectionsView>)>()
|
||||
|
||||
|
||||
private var failedMessageIdsViews = Bag<(MutableFailedMessageIdsView, ValuePipe<FailedMessageIdsView>)>()
|
||||
|
||||
|
||||
init(queue: Queue, renderMessage: @escaping (IntermediateMessage) -> Message, getPeer: @escaping (PeerId) -> Peer?, getPeerNotificationSettings: @escaping (PeerId) -> PeerNotificationSettings?, getCachedPeerData: @escaping (PeerId) -> CachedPeerData?, getPeerPresence: @escaping (PeerId) -> PeerPresence?, getPeerReadState: @escaping (PeerId) -> CombinedPeerReadState?, operationLogGetOperations: @escaping (PeerOperationLogTag, Int32, Int) -> [PeerMergedOperationLogEntry], operationLogGetTailIndex: @escaping (PeerOperationLogTag) -> Int32?, getTimestampBasedMessageAttributesHead: @escaping (UInt16) -> TimestampBasedMessageAttributesEntry?, getPreferencesEntry: @escaping (ValueBoxKey) -> PreferencesEntry?, unsentMessageIds: [MessageId], synchronizePeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation]) {
|
||||
init(
|
||||
queue: Queue,
|
||||
unsentMessageIds: [MessageId],
|
||||
synchronizePeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation]
|
||||
) {
|
||||
self.queue = queue
|
||||
self.renderMessage = renderMessage
|
||||
self.getPeer = getPeer
|
||||
self.getPeerNotificationSettings = getPeerNotificationSettings
|
||||
self.getCachedPeerData = getCachedPeerData
|
||||
self.getPeerPresence = getPeerPresence
|
||||
self.getPeerReadState = getPeerReadState
|
||||
self.operationLogGetOperations = operationLogGetOperations
|
||||
self.operationLogGetTailIndex = operationLogGetTailIndex
|
||||
self.getTimestampBasedMessageAttributesHead = getTimestampBasedMessageAttributesHead
|
||||
self.getPreferencesEntry = getPreferencesEntry
|
||||
|
||||
self.unsentMessageView = UnsentMessageHistoryView(ids: unsentMessageIds)
|
||||
self.synchronizeReadStatesView = MutableSynchronizePeerReadStatesView(operations: synchronizePeerReadStateOperations)
|
||||
@ -254,9 +235,7 @@ final class ViewTracker {
|
||||
|
||||
for (mutableView, pipe) in self.chatListViews.copyItems() {
|
||||
if mutableView.refreshDueToExternalTransaction(postbox: postbox) {
|
||||
mutableView.render(postbox: postbox, renderMessage: self.renderMessage, getPeer: { id in
|
||||
return self.getPeer(id)
|
||||
}, getPeerNotificationSettings: self.getPeerNotificationSettings, getPeerPresence: self.getPeerPresence)
|
||||
mutableView.render(postbox: postbox)
|
||||
pipe.putNext((ChatListView(mutableView), .Generic))
|
||||
}
|
||||
}
|
||||
@ -350,7 +329,7 @@ final class ViewTracker {
|
||||
for (mutableView, pipe) in self.messageViews.copyItems() {
|
||||
let operations = transaction.currentOperationsByPeerId[mutableView.messageId.peerId]
|
||||
if operations != nil || !transaction.updatedMedia.isEmpty || !transaction.currentUpdatedCachedPeerData.isEmpty {
|
||||
if mutableView.replay(operations ?? [], updatedMedia: transaction.updatedMedia, renderIntermediateMessage: self.renderMessage) {
|
||||
if mutableView.replay(postbox: postbox, operations: operations ?? [], updatedMedia: transaction.updatedMedia) {
|
||||
pipe.putNext(MessageView(mutableView))
|
||||
}
|
||||
}
|
||||
@ -360,9 +339,7 @@ final class ViewTracker {
|
||||
let context = MutableChatListViewReplayContext()
|
||||
if mutableView.replay(postbox: postbox, operations: transaction.chatListOperations, updatedPeerNotificationSettings: transaction.currentUpdatedPeerNotificationSettings, updatedPeers: transaction.currentUpdatedPeers, updatedPeerPresences: transaction.currentUpdatedPeerPresences, transaction: transaction, context: context) {
|
||||
mutableView.complete(postbox: postbox, context: context)
|
||||
mutableView.render(postbox: postbox, renderMessage: self.renderMessage, getPeer: { id in
|
||||
return self.getPeer(id)
|
||||
}, getPeerNotificationSettings: self.getPeerNotificationSettings, getPeerPresence: self.getPeerPresence)
|
||||
mutableView.render(postbox: postbox)
|
||||
pipe.putNext((ChatListView(mutableView), .Generic))
|
||||
}
|
||||
}
|
||||
@ -396,7 +373,7 @@ final class ViewTracker {
|
||||
}
|
||||
|
||||
for (mutableView, pipe) in self.contactPeersViews.copyItems() {
|
||||
if mutableView.replay(replacePeerIds: transaction.replaceContactPeerIds, updatedPeerPresences: transaction.currentUpdatedPeerPresences, getPeer: self.getPeer, getPeerPresence: self.getPeerPresence) {
|
||||
if mutableView.replay(postbox: postbox, replacePeerIds: transaction.replaceContactPeerIds, updatedPeerPresences: transaction.currentUpdatedPeerPresences) {
|
||||
pipe.putNext(ContactPeersView(mutableView))
|
||||
}
|
||||
}
|
||||
@ -408,13 +385,13 @@ final class ViewTracker {
|
||||
}
|
||||
|
||||
for (mutableView, pipe) in self.peerMergedOperationLogViews.copyItems() {
|
||||
if mutableView.replay(operations: transaction.currentPeerMergedOperationLogOperations, getOperations: self.operationLogGetOperations, getTailIndex: self.operationLogGetTailIndex) {
|
||||
if mutableView.replay(postbox: postbox, operations: transaction.currentPeerMergedOperationLogOperations) {
|
||||
pipe.putNext(PeerMergedOperationLogView(mutableView))
|
||||
}
|
||||
}
|
||||
|
||||
for (mutableView, pipe) in self.timestampBasedMessageAttributesViews.copyItems() {
|
||||
if mutableView.replay(operations: transaction.currentTimestampBasedMessageAttributesOperations, getHead: self.getTimestampBasedMessageAttributesHead) {
|
||||
if mutableView.replay(postbox: postbox, operations: transaction.currentTimestampBasedMessageAttributesOperations) {
|
||||
pipe.putNext(TimestampBasedMessageAttributesView(mutableView))
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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):
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
2
third-party/webrtc/webrtc
vendored
2
third-party/webrtc/webrtc
vendored
@ -1 +1 @@
|
||||
Subproject commit 5746d4b64798196743aaf083b0c07a3cc58a713f
|
||||
Subproject commit e42b4630117498504692fbac80ec6dbb970b2314
|
Loading…
x
Reference in New Issue
Block a user