mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
New widget type
This commit is contained in:
parent
792dcac27b
commit
bf4013781c
@ -136,13 +136,13 @@
|
|||||||
<key>INIntentCategory</key>
|
<key>INIntentCategory</key>
|
||||||
<string>information</string>
|
<string>information</string>
|
||||||
<key>INIntentDescriptionID</key>
|
<key>INIntentDescriptionID</key>
|
||||||
<string>zzS0gJ</string>
|
<string>DwL4WQ</string>
|
||||||
<key>INIntentEligibleForWidgets</key>
|
<key>INIntentEligibleForWidgets</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>INIntentIneligibleForSuggestions</key>
|
<key>INIntentIneligibleForSuggestions</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>INIntentLastParameterTag</key>
|
<key>INIntentLastParameterTag</key>
|
||||||
<integer>3</integer>
|
<integer>19</integer>
|
||||||
<key>INIntentName</key>
|
<key>INIntentName</key>
|
||||||
<string>SelectAvatarFriends</string>
|
<string>SelectAvatarFriends</string>
|
||||||
<key>INIntentParameters</key>
|
<key>INIntentParameters</key>
|
||||||
@ -152,25 +152,29 @@
|
|||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
<key>INIntentParameterArraySizeSize</key>
|
<key>INIntentParameterArraySizeSize</key>
|
||||||
<integer>1</integer>
|
<integer>4</integer>
|
||||||
<key>INIntentParameterArraySizeSizeClass</key>
|
<key>INIntentParameterArraySizeSizeClass</key>
|
||||||
<string>Small</string>
|
<string>Small</string>
|
||||||
</dict>
|
</dict>
|
||||||
<dict>
|
<dict>
|
||||||
<key>INIntentParameterArraySizeSize</key>
|
<key>INIntentParameterArraySizeSize</key>
|
||||||
<integer>4</integer>
|
<integer>8</integer>
|
||||||
<key>INIntentParameterArraySizeSizeClass</key>
|
<key>INIntentParameterArraySizeSizeClass</key>
|
||||||
<string>Medium</string>
|
<string>Medium</string>
|
||||||
</dict>
|
</dict>
|
||||||
<dict>
|
<dict>
|
||||||
<key>INIntentParameterArraySizeSize</key>
|
<key>INIntentParameterArraySizeSize</key>
|
||||||
<integer>8</integer>
|
<integer>16</integer>
|
||||||
<key>INIntentParameterArraySizeSizeClass</key>
|
<key>INIntentParameterArraySizeSizeClass</key>
|
||||||
<string>Large</string>
|
<string>Large</string>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>INIntentParameterConfigurable</key>
|
<key>INIntentParameterConfigurable</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>INIntentParameterDisplayName</key>
|
||||||
|
<string> </string>
|
||||||
|
<key>INIntentParameterDisplayNameID</key>
|
||||||
|
<string>Jg5dYF</string>
|
||||||
<key>INIntentParameterDisplayPriority</key>
|
<key>INIntentParameterDisplayPriority</key>
|
||||||
<integer>1</integer>
|
<integer>1</integer>
|
||||||
<key>INIntentParameterFixedSizeArray</key>
|
<key>INIntentParameterFixedSizeArray</key>
|
||||||
@ -189,7 +193,7 @@
|
|||||||
<key>INIntentParameterPromptDialogFormatString</key>
|
<key>INIntentParameterPromptDialogFormatString</key>
|
||||||
<string>Search</string>
|
<string>Search</string>
|
||||||
<key>INIntentParameterPromptDialogFormatStringID</key>
|
<key>INIntentParameterPromptDialogFormatStringID</key>
|
||||||
<string>xeb2pd</string>
|
<string>ORCbLf</string>
|
||||||
<key>INIntentParameterPromptDialogType</key>
|
<key>INIntentParameterPromptDialogType</key>
|
||||||
<string>Configuration</string>
|
<string>Configuration</string>
|
||||||
</dict>
|
</dict>
|
||||||
@ -200,6 +204,13 @@
|
|||||||
<string>Primary</string>
|
<string>Primary</string>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
|
<key>INIntentParameterRelationship</key>
|
||||||
|
<dict>
|
||||||
|
<key>INIntentParameterRelationshipPredicateName</key>
|
||||||
|
<string>EnumHasExactValue</string>
|
||||||
|
<key>INIntentParameterRelationshipPredicateValue</key>
|
||||||
|
<string>custom</string>
|
||||||
|
</dict>
|
||||||
<key>INIntentParameterSupportsDynamicEnumeration</key>
|
<key>INIntentParameterSupportsDynamicEnumeration</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>INIntentParameterSupportsMultipleValues</key>
|
<key>INIntentParameterSupportsMultipleValues</key>
|
||||||
@ -207,7 +218,7 @@
|
|||||||
<key>INIntentParameterSupportsSearch</key>
|
<key>INIntentParameterSupportsSearch</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>INIntentParameterTag</key>
|
<key>INIntentParameterTag</key>
|
||||||
<integer>3</integer>
|
<integer>19</integer>
|
||||||
<key>INIntentParameterType</key>
|
<key>INIntentParameterType</key>
|
||||||
<string>Object</string>
|
<string>Object</string>
|
||||||
</dict>
|
</dict>
|
||||||
@ -229,9 +240,9 @@
|
|||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
<key>INIntentTitle</key>
|
<key>INIntentTitle</key>
|
||||||
<string>Select</string>
|
<string>Select Chats</string>
|
||||||
<key>INIntentTitleID</key>
|
<key>INIntentTitleID</key>
|
||||||
<string>kiqCaL</string>
|
<string>3Sbb7H</string>
|
||||||
<key>INIntentType</key>
|
<key>INIntentType</key>
|
||||||
<string>Custom</string>
|
<string>Custom</string>
|
||||||
<key>INIntentVerb</key>
|
<key>INIntentVerb</key>
|
||||||
|
@ -1161,21 +1161,6 @@ plist_fragment(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
swift_library(
|
|
||||||
name = "GeneratedSources1",
|
|
||||||
module_name = "GeneratedSources",
|
|
||||||
srcs = glob([
|
|
||||||
"Generated/**/*.swift",
|
|
||||||
]),
|
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
)
|
|
||||||
|
|
||||||
'''swift_intent_library(
|
|
||||||
name = "GeneratedSources",
|
|
||||||
src = "SiriIntents/Intents.intentdefinition",
|
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
)'''
|
|
||||||
|
|
||||||
swift_library(
|
swift_library(
|
||||||
name = "WidgetExtensionLib",
|
name = "WidgetExtensionLib",
|
||||||
module_name = "WidgetExtensionLib",
|
module_name = "WidgetExtensionLib",
|
||||||
@ -1258,6 +1243,7 @@ plist_fragment(
|
|||||||
<string>INSetMessageAttributeIntent</string>
|
<string>INSetMessageAttributeIntent</string>
|
||||||
<string>INSearchCallHistoryIntent</string>
|
<string>INSearchCallHistoryIntent</string>
|
||||||
<string>SelectFriendsIntent</string>
|
<string>SelectFriendsIntent</string>
|
||||||
|
<string>SelectAvatarFriendsIntent</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
<key>NSExtensionPointIdentifier</key>
|
<key>NSExtensionPointIdentifier</key>
|
||||||
|
@ -54,7 +54,7 @@ enum IntentHandlingError {
|
|||||||
|
|
||||||
@available(iOSApplicationExtension 10.0, iOS 10.0, *)
|
@available(iOSApplicationExtension 10.0, iOS 10.0, *)
|
||||||
@objc(IntentHandler)
|
@objc(IntentHandler)
|
||||||
class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessagesIntentHandling, INSetMessageAttributeIntentHandling, INStartAudioCallIntentHandling, INSearchCallHistoryIntentHandling, SelectFriendsIntentHandling, SelectAvatarFriendsIntentHandling {
|
class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessagesIntentHandling, INSetMessageAttributeIntentHandling, INStartAudioCallIntentHandling, INSearchCallHistoryIntentHandling, SelectFriendsIntentHandling {
|
||||||
private let accountPromise = Promise<Account?>()
|
private let accountPromise = Promise<Account?>()
|
||||||
private let allAccounts = Promise<[(AccountRecordId, PeerId)]>()
|
private let allAccounts = Promise<[(AccountRecordId, PeerId)]>()
|
||||||
|
|
||||||
@ -195,8 +195,16 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
|||||||
}
|
}
|
||||||
|
|
||||||
override public func handler(for intent: INIntent) -> Any {
|
override public func handler(for intent: INIntent) -> Any {
|
||||||
|
if #available(iOSApplicationExtension 12.0, iOS 12.0, *) {
|
||||||
|
if intent is SelectAvatarFriendsIntent {
|
||||||
|
return AvatarsIntentHandler()
|
||||||
|
} else {
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum ResolveResult {
|
enum ResolveResult {
|
||||||
case success(INPerson)
|
case success(INPerson)
|
||||||
@ -812,16 +820,18 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
|||||||
|
|
||||||
var items: [Friend] = []
|
var items: [Friend] = []
|
||||||
for peer in peers {
|
for peer in peers {
|
||||||
let profileImage = smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in
|
let path = smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in
|
||||||
return postbox.mediaBox.resourcePath(representation.resource)
|
return postbox.mediaBox.resourcePath(representation.resource)
|
||||||
}.flatMap { path -> INImage? in
|
}
|
||||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: path)) {
|
|
||||||
return INImage(imageData: data)
|
let profileImage: INImage?
|
||||||
|
let image = avatarImage(path: path, peerId: peer.id.toInt64(), accountPeerId: accountPeerId.toInt64(), letters: peer.displayLetters, size: CGSize(width: 50.0, height: 50.0))
|
||||||
|
if let data = image.pngData() {
|
||||||
|
profileImage = INImage(imageData: data)
|
||||||
} else {
|
} else {
|
||||||
return nil
|
profileImage = nil
|
||||||
}
|
}
|
||||||
}
|
items.append(Friend(identifier: "\(accountId.int64):\(peer.id.toInt64())", display: peer.debugDisplayTitle, subtitle: nil, image: profileImage))
|
||||||
items.append(Friend(identifier: "\(accountId.int64):\(peer.id.toInt64())", display: peer.debugDisplayTitle, subtitle: nil, image: nil))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return INObjectSection<Friend>(title: accountTitle, items: items)
|
return INObjectSection<Friend>(title: accountTitle, items: items)
|
||||||
@ -846,6 +856,149 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
|||||||
completion(nil, error)
|
completion(nil, error)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension 10.0, iOS 10.0, *)
|
||||||
|
@objc(AvatarsIntentHandler)
|
||||||
|
class AvatarsIntentHandler: NSObject, SelectAvatarFriendsIntentHandling {
|
||||||
|
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() {
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
guard let appBundleIdentifier = Bundle.main.bundleIdentifier, let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let baseAppBundleId = String(appBundleIdentifier[..<lastDotRange.lowerBound])
|
||||||
|
let buildConfig = BuildConfig(baseAppBundleId: baseAppBundleId)
|
||||||
|
|
||||||
|
let apiId: Int32 = buildConfig.apiId
|
||||||
|
let apiHash: String = buildConfig.apiHash
|
||||||
|
let languagesCategory = "ios"
|
||||||
|
|
||||||
|
let appGroupName = "group.\(baseAppBundleId)"
|
||||||
|
let maybeAppGroupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName)
|
||||||
|
|
||||||
|
guard let appGroupUrl = maybeAppGroupUrl else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.appGroupUrl = appGroupUrl
|
||||||
|
|
||||||
|
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"
|
||||||
|
let _ = try? FileManager.default.createDirectory(atPath: logsPath, withIntermediateDirectories: true, attributes: nil)
|
||||||
|
|
||||||
|
setupSharedLogger(rootPath: rootPath, path: logsPath)
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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 {
|
||||||
|
switch account {
|
||||||
|
case .upgrading:
|
||||||
|
return .complete()
|
||||||
|
case let .authorized(account):
|
||||||
|
return applicationSettings(accountManager: accountManager)
|
||||||
|
|> deliverOnMainQueue
|
||||||
|
|> map { settings -> Account in
|
||||||
|
accountCache = account
|
||||||
|
Logger.shared.logToFile = settings.logging.logToFile
|
||||||
|
Logger.shared.logToConsole = settings.logging.logToConsole
|
||||||
|
|
||||||
|
Logger.shared.redactSensitiveData = settings.logging.redactSensitiveData
|
||||||
|
return account
|
||||||
|
}
|
||||||
|
case .unauthorized:
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> take(1)
|
||||||
|
}
|
||||||
|
self.accountPromise.set(account)
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.resolvePersonsDisposable.dispose()
|
||||||
|
self.actionDisposable.dispose()
|
||||||
|
self.searchDisposable.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
@available(iOSApplicationExtension 14.0, iOS 14.0, *)
|
@available(iOSApplicationExtension 14.0, iOS 14.0, *)
|
||||||
func provideFriendsOptionsCollection(for intent: SelectAvatarFriendsIntent, searchTerm: String?, with completion: @escaping (INObjectCollection<Friend>?, Error?) -> Void) {
|
func provideFriendsOptionsCollection(for intent: SelectAvatarFriendsIntent, searchTerm: String?, with completion: @escaping (INObjectCollection<Friend>?, Error?) -> Void) {
|
||||||
@ -906,16 +1059,18 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
|||||||
|
|
||||||
var items: [Friend] = []
|
var items: [Friend] = []
|
||||||
for peer in peers {
|
for peer in peers {
|
||||||
let profileImage = smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in
|
let path = smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in
|
||||||
return postbox.mediaBox.resourcePath(representation.resource)
|
return postbox.mediaBox.resourcePath(representation.resource)
|
||||||
}.flatMap { path -> INImage? in
|
}
|
||||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: path)) {
|
|
||||||
return INImage(imageData: data)
|
let profileImage: INImage?
|
||||||
|
let image = avatarImage(path: path, peerId: peer.id.toInt64(), accountPeerId: accountPeerId.toInt64(), letters: peer.displayLetters, size: CGSize(width: 50.0, height: 50.0))
|
||||||
|
if let data = image.pngData() {
|
||||||
|
profileImage = INImage(imageData: data)
|
||||||
} else {
|
} else {
|
||||||
return nil
|
profileImage = nil
|
||||||
}
|
}
|
||||||
}
|
items.append(Friend(identifier: "\(accountId.int64):\(peer.id.toInt64())", display: peer.debugDisplayTitle, subtitle: nil, image: profileImage))
|
||||||
items.append(Friend(identifier: "\(accountId.int64):\(peer.id.toInt64())", display: peer.debugDisplayTitle, subtitle: nil, image: nil))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return INObjectSection<Friend>(title: accountTitle, items: items)
|
return INObjectSection<Friend>(title: accountTitle, items: items)
|
||||||
@ -941,3 +1096,96 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func avatarRoundImage(size: CGSize, source: UIImage) -> UIImage? {
|
||||||
|
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
|
||||||
|
let context = UIGraphicsGetCurrentContext()
|
||||||
|
|
||||||
|
context?.beginPath()
|
||||||
|
context?.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
|
||||||
|
context?.clip()
|
||||||
|
|
||||||
|
source.draw(in: CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
|
let image = UIGraphicsGetImageFromCurrentImageContext()
|
||||||
|
UIGraphicsEndImageContext()
|
||||||
|
return image
|
||||||
|
}
|
||||||
|
|
||||||
|
private let deviceColorSpace: CGColorSpace = {
|
||||||
|
if #available(iOSApplicationExtension 9.3, iOS 9.3, *) {
|
||||||
|
if let colorSpace = CGColorSpace(name: CGColorSpace.displayP3) {
|
||||||
|
return colorSpace
|
||||||
|
} else {
|
||||||
|
return CGColorSpaceCreateDeviceRGB()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return CGColorSpaceCreateDeviceRGB()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
private extension UIColor {
|
||||||
|
convenience init(rgb: UInt32) {
|
||||||
|
self.init(red: CGFloat((rgb >> 16) & 0xff) / 255.0, green: CGFloat((rgb >> 8) & 0xff) / 255.0, blue: CGFloat(rgb & 0xff) / 255.0, alpha: 1.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let gradientColors: [NSArray] = [
|
||||||
|
[UIColor(rgb: 0xff516a).cgColor, UIColor(rgb: 0xff885e).cgColor],
|
||||||
|
[UIColor(rgb: 0xffa85c).cgColor, UIColor(rgb: 0xffcd6a).cgColor],
|
||||||
|
[UIColor(rgb: 0x665fff).cgColor, UIColor(rgb: 0x82b1ff).cgColor],
|
||||||
|
[UIColor(rgb: 0x54cb68).cgColor, UIColor(rgb: 0xa0de7e).cgColor],
|
||||||
|
[UIColor(rgb: 0x4acccd).cgColor, UIColor(rgb: 0x00fcfd).cgColor],
|
||||||
|
[UIColor(rgb: 0x2a9ef1).cgColor, UIColor(rgb: 0x72d5fd).cgColor],
|
||||||
|
[UIColor(rgb: 0xd669ed).cgColor, UIColor(rgb: 0xe0a2f3).cgColor],
|
||||||
|
]
|
||||||
|
|
||||||
|
private func avatarViewLettersImage(size: CGSize, peerId: Int64, accountPeerId: Int64, letters: [String]) -> UIImage? {
|
||||||
|
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
|
||||||
|
let context = UIGraphicsGetCurrentContext()
|
||||||
|
|
||||||
|
context?.beginPath()
|
||||||
|
context?.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
|
||||||
|
context?.clip()
|
||||||
|
|
||||||
|
let colorIndex = abs(Int(accountPeerId + peerId))
|
||||||
|
|
||||||
|
let colorsArray = gradientColors[colorIndex % gradientColors.count]
|
||||||
|
var locations: [CGFloat] = [1.0, 0.0]
|
||||||
|
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray, locations: &locations)!
|
||||||
|
|
||||||
|
context?.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
||||||
|
|
||||||
|
context?.setBlendMode(.normal)
|
||||||
|
|
||||||
|
let string = letters.count == 0 ? "" : (letters[0] + (letters.count == 1 ? "" : letters[1]))
|
||||||
|
let attributedString = NSAttributedString(string: string, attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 20.0), NSAttributedString.Key.foregroundColor: UIColor.white])
|
||||||
|
|
||||||
|
let line = CTLineCreateWithAttributedString(attributedString)
|
||||||
|
let lineBounds = CTLineGetBoundsWithOptions(line, .useGlyphPathBounds)
|
||||||
|
|
||||||
|
let lineOffset = CGPoint(x: string == "B" ? 1.0 : 0.0, y: 0.0)
|
||||||
|
let lineOrigin = CGPoint(x: floor(-lineBounds.origin.x + (size.width - lineBounds.size.width) / 2.0) + lineOffset.x, y: floor(-lineBounds.origin.y + (size.height - lineBounds.size.height) / 2.0))
|
||||||
|
|
||||||
|
context?.translateBy(x: size.width / 2.0, y: size.height / 2.0)
|
||||||
|
context?.scaleBy(x: 1.0, y: -1.0)
|
||||||
|
context?.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
|
||||||
|
|
||||||
|
context?.translateBy(x: lineOrigin.x, y: lineOrigin.y)
|
||||||
|
if let context = context {
|
||||||
|
CTLineDraw(line, context)
|
||||||
|
}
|
||||||
|
context?.translateBy(x: -lineOrigin.x, y: -lineOrigin.y)
|
||||||
|
|
||||||
|
let image = UIGraphicsGetImageFromCurrentImageContext()
|
||||||
|
UIGraphicsEndImageContext()
|
||||||
|
return image
|
||||||
|
}
|
||||||
|
|
||||||
|
private func avatarImage(path: String?, peerId: Int64, accountPeerId: Int64, letters: [String], size: CGSize) -> UIImage {
|
||||||
|
if let path = path, let image = UIImage(contentsOfFile: path), let roundImage = avatarRoundImage(size: size, source: image) {
|
||||||
|
return roundImage
|
||||||
|
} else {
|
||||||
|
return avatarViewLettersImage(size: size, peerId: peerId, accountPeerId: accountPeerId, letters: letters)!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -288,7 +288,7 @@ struct AvatarsProvider: IntentTimelineProvider {
|
|||||||
let deviceSpecificEncryptionParameters = BuildConfig.deviceSpecificEncryptionParameters(rootPath, baseAppBundleId: baseAppBundleId)
|
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 encryptionParameters = ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: deviceSpecificEncryptionParameters.key)!, salt: ValueBoxEncryptionParameters.Salt(data: deviceSpecificEncryptionParameters.salt)!)
|
||||||
|
|
||||||
var itemsByAccount: [Int64: [(Int64, Friend)]] = [:]
|
var itemsByAccount: [Int64: [(Int64, Any)]] = [:]
|
||||||
var itemOrder: [(Int64, Int64)] = []
|
var itemOrder: [(Int64, Int64)] = []
|
||||||
if let friends = configuration.friends {
|
if let friends = configuration.friends {
|
||||||
for item in friends {
|
for item in friends {
|
||||||
@ -416,7 +416,6 @@ struct AvatarItemView: View {
|
|||||||
var peer: ParsedPeer?
|
var peer: ParsedPeer?
|
||||||
var itemSize: CGFloat
|
var itemSize: CGFloat
|
||||||
var placeholderColor: Color
|
var placeholderColor: Color
|
||||||
var displayBadge: Bool = true
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
return ZStack {
|
return ZStack {
|
||||||
@ -427,23 +426,7 @@ struct AvatarItemView: View {
|
|||||||
Circle()
|
Circle()
|
||||||
.fill(placeholderColor)
|
.fill(placeholderColor)
|
||||||
.frame(width: itemSize, height: itemSize)
|
.frame(width: itemSize, height: itemSize)
|
||||||
//Image(uiImage: avatarImage(accountPeerId: nil, peer: nil, size: CGSize(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)
|
|
||||||
.foregroundColor(.white)
|
|
||||||
.padding(.horizontal, 4.0)
|
|
||||||
.background(
|
|
||||||
RoundedRectangle(cornerRadius: 10)
|
|
||||||
.fill(badge.isMuted ? Color.gray : Color.red)
|
|
||||||
.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))
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -689,11 +672,6 @@ struct WidgetView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasBadge = false
|
|
||||||
if let peer = peer, let badge = peer.peer.badge, badge.count > 0 {
|
|
||||||
hasBadge = true
|
|
||||||
}
|
|
||||||
|
|
||||||
let textView = Text(text)
|
let textView = Text(text)
|
||||||
.lineLimit(2)
|
.lineLimit(2)
|
||||||
.font(Font.system(size: 15.0, weight: .regular, design: .default))
|
.font(Font.system(size: 15.0, weight: .regular, design: .default))
|
||||||
@ -735,34 +713,8 @@ struct WidgetView: View {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
//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)")
|
|
||||||
.font(Font.system(size: 14.0))
|
|
||||||
.multilineTextAlignment(.center)
|
|
||||||
.foregroundColor(.white)
|
|
||||||
.padding(.horizontal, 4.0)
|
|
||||||
.background(
|
|
||||||
RoundedRectangle(cornerRadius: 10)
|
|
||||||
.fill(badge.isMuted ? Color.gray : Color.blue)
|
|
||||||
.frame(minWidth: 20, idealWidth: 20, maxWidth: .infinity, minHeight: 20, idealHeight: 20, maxHeight: 20.0, alignment: .center)
|
|
||||||
)
|
|
||||||
.padding(EdgeInsets(top: 0.0, leading: 0.0, bottom: 6.0, trailing: 3.0))
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
})*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func chatContent(_ peer: ParsedPeer?) -> some View {
|
func chatContent(_ peer: ParsedPeer?) -> some View {
|
||||||
@ -805,7 +757,7 @@ struct WidgetView: View {
|
|||||||
return AnyView(
|
return AnyView(
|
||||||
Link(destination: url, label: {
|
Link(destination: url, label: {
|
||||||
HStack(alignment: .center, spacing: 0.0, content: {
|
HStack(alignment: .center, spacing: 0.0, content: {
|
||||||
AvatarItemView(peer: peers?.peers[index], itemSize: 54.0, placeholderColor: getPlaceholderColor(), 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))
|
AvatarItemView(peer: peers?.peers[index], itemSize: 54.0, placeholderColor: getPlaceholderColor()).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))
|
chatContent(peers?.peers[index]).frame(maxWidth: .infinity).padding(EdgeInsets(top: 0.0, leading: 0.0, bottom: 0.0, trailing: 10.0))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -821,11 +773,6 @@ struct WidgetView: View {
|
|||||||
.frame(width: size.width - 54.0 - 20.0, height: 0.5, alignment: .leading)
|
.frame(width: size.width - 54.0 - 20.0, height: 0.5, alignment: .leading)
|
||||||
})
|
})
|
||||||
.frame(width: size.width, height: 1.0, alignment: .leading)
|
.frame(width: size.width, height: 1.0, alignment: .leading)
|
||||||
/*let separatorWidth = size.width - 54.0 - 20.0
|
|
||||||
let itemHeight = (size.height - 22.0) / 2.0
|
|
||||||
return Rectangle().foregroundColor(getSeparatorColor())
|
|
||||||
//.position(x: (54.0 + 20.0 + separatorWidth) / 2.0, y: itemHeight / 2.0)
|
|
||||||
.frame(width: size.width, height: 1.0, alignment: .leading)*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func chatsUpdateBackgroundView(size: CGSize) -> some View {
|
func chatsUpdateBackgroundView(size: CGSize) -> some View {
|
||||||
@ -871,12 +818,6 @@ struct WidgetView: View {
|
|||||||
return Text(text)
|
return Text(text)
|
||||||
.font(Font.system(size: 12.0))
|
.font(Font.system(size: 12.0))
|
||||||
.foregroundColor(getUpdatedTextColor())
|
.foregroundColor(getUpdatedTextColor())
|
||||||
|
|
||||||
/*return HStack(alignment: .center, spacing: 0.0, content: {
|
|
||||||
Text(text)
|
|
||||||
.font(Font.system(size: 12.0))
|
|
||||||
.foregroundColor(getUpdatedTextColor())
|
|
||||||
}).position(x: size.width / 2.0, y: size.height - 11.0).frame(width: size.width, height: 22.0, alignment: .leading)*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSeparatorColor() -> Color {
|
func getSeparatorColor() -> Color {
|
||||||
@ -933,6 +874,89 @@ struct WidgetView: View {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.padding(0.0)
|
.padding(0.0)
|
||||||
|
.unredacted()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AvatarsWidgetView: View {
|
||||||
|
@Environment(\.widgetFamily) private var widgetFamily
|
||||||
|
@Environment(\.colorScheme) private var colorScheme
|
||||||
|
let data: PeersWidgetData
|
||||||
|
|
||||||
|
func placeholder(geometry: GeometryProxy) -> some View {
|
||||||
|
return Spacer()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func linkForPeer(accountId: Int64, id: Int64) -> String {
|
||||||
|
switch self.widgetFamily {
|
||||||
|
case .systemSmall:
|
||||||
|
return "\(buildConfig.appSpecificUrlScheme)://"
|
||||||
|
default:
|
||||||
|
return "\(buildConfig.appSpecificUrlScheme)://localpeer?id=\(id)&accountId=\(accountId)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPlaceholderColor() -> Color {
|
||||||
|
switch colorScheme {
|
||||||
|
case .light:
|
||||||
|
return Color(.sRGB, red: 235.0 / 255.0, green: 235.0 / 255.0, blue: 241.0 / 255.0, opacity: 1.0)
|
||||||
|
case .dark:
|
||||||
|
return Color(.sRGB, red: 38.0 / 255.0, green: 38.0 / 255.0, blue: 41.0 / 255.0, opacity: 1.0)
|
||||||
|
@unknown default:
|
||||||
|
return .secondary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func itemView(index: Int) -> some View {
|
||||||
|
let peers: ParsedPeers?
|
||||||
|
var isPlaceholder = false
|
||||||
|
switch data {
|
||||||
|
case let .peers(peersValue):
|
||||||
|
if peersValue.peers.count <= index {
|
||||||
|
isPlaceholder = peersValue.peers.count != 0
|
||||||
|
peers = nil
|
||||||
|
} else {
|
||||||
|
peers = peersValue
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
peers = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if let peers = peers {
|
||||||
|
return AnyView(Link(destination: URL(string: linkForPeer(accountId: peers.peers[index].accountId, id: peers.peers[index].peer.id))!, label: {
|
||||||
|
GeometryReader(content: { geometry in
|
||||||
|
AvatarItemView(peer: peers.peers[index], itemSize: geometry.size.height, placeholderColor: getPlaceholderColor())
|
||||||
|
})
|
||||||
|
}).aspectRatio(1.0, contentMode: .fit))
|
||||||
|
} else if isPlaceholder {
|
||||||
|
return AnyView(Circle().aspectRatio(1.0, contentMode: .fit).foregroundColor(.clear))
|
||||||
|
//return AnyView(Circle().aspectRatio(1.0, contentMode: .fit).foregroundColor(getPlaceholderColor()))
|
||||||
|
} else {
|
||||||
|
return AnyView(Circle().aspectRatio(1.0, contentMode: .fit).foregroundColor(getPlaceholderColor()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
return VStack(alignment: .center, spacing: 18.0, content: {
|
||||||
|
HStack(alignment: .center, spacing: nil, content: {
|
||||||
|
ForEach(0 ..< 4, id: \.self) { index in
|
||||||
|
itemView(index: index)
|
||||||
|
if index != 3 {
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
HStack(alignment: .center, spacing: nil, content: {
|
||||||
|
ForEach(0 ..< 4, id: \.self) { index in
|
||||||
|
itemView(index: 4 + index)
|
||||||
|
if index != 3 {
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.padding(EdgeInsets(top: 10.0, leading: 10.0, bottom: 10.0, trailing: 10.0))
|
||||||
|
.unredacted()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1038,7 +1062,7 @@ struct Static_AvatarsWidget: Widget {
|
|||||||
|
|
||||||
public var body: some WidgetConfiguration {
|
public var body: some WidgetConfiguration {
|
||||||
return IntentConfiguration(kind: kind, intent: SelectAvatarFriendsIntent.self, provider: AvatarsProvider(), content: { entry in
|
return IntentConfiguration(kind: kind, intent: SelectAvatarFriendsIntent.self, provider: AvatarsProvider(), content: { entry in
|
||||||
Spacer()
|
AvatarsWidgetView(data: getWidgetData(contents: entry.contents))
|
||||||
})
|
})
|
||||||
.supportedFamilies([.systemMedium])
|
.supportedFamilies([.systemMedium])
|
||||||
.configurationDisplayName(presentationData.widgetGalleryTitle)
|
.configurationDisplayName(presentationData.widgetGalleryTitle)
|
||||||
@ -1050,6 +1074,6 @@ struct Static_AvatarsWidget: Widget {
|
|||||||
struct AllWidgets: WidgetBundle {
|
struct AllWidgets: WidgetBundle {
|
||||||
var body: some Widget {
|
var body: some Widget {
|
||||||
Static_Widget()
|
Static_Widget()
|
||||||
//Static_AvatarsWidget()
|
Static_AvatarsWidget()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit e80b9795db5e35f86d643e0fba7776f1b1f71066
|
Subproject commit 81aa39ed9f58c416e7255adc0c8a6ba50c081030
|
@ -1 +1 @@
|
|||||||
Subproject commit 734518e85d769de070b5a78b234080d9580ae625
|
Subproject commit 0bddcf7522cd4f3bcad4e1502e5189156a2eb615
|
@ -41,7 +41,7 @@ public func representationFetchRangeForDisplayAtSize(representation: TelegramMed
|
|||||||
if representation.progressiveSizes.count > 1, let dimension = dimension {
|
if representation.progressiveSizes.count > 1, let dimension = dimension {
|
||||||
var largestByteSize = Int(representation.progressiveSizes[0])
|
var largestByteSize = Int(representation.progressiveSizes[0])
|
||||||
for (maxDimension, byteSizes) in progressiveRangeMap {
|
for (maxDimension, byteSizes) in progressiveRangeMap {
|
||||||
largestByteSize = Int(representation.progressiveSizes[byteSizes.last!])
|
largestByteSize = Int(representation.progressiveSizes[min(representation.progressiveSizes.count - 1, byteSizes.last!)])
|
||||||
if maxDimension >= dimension {
|
if maxDimension >= dimension {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -52,7 +52,7 @@ public func representationFetchRangeForDisplayAtSize(representation: TelegramMed
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func chatMessagePhotoDatas(postbox: Postbox, photoReference: ImageMediaReference, fullRepresentationSize: CGSize = CGSize(width: 1280.0, height: 1280.0), autoFetchFullSize: Bool = false, tryAdditionalRepresentations: Bool = false, synchronousLoad: Bool = false, useMiniThumbnailIfAvailable: Bool = false) -> Signal<Tuple4<Data?, Data?, ChatMessagePhotoQuality, Bool>, NoError> {
|
public func chatMessagePhotoDatas(postbox: Postbox, photoReference: ImageMediaReference, fullRepresentationSize: CGSize = CGSize(width: 1280.0, height: 1280.0), autoFetchFullSize: Bool = false, tryAdditionalRepresentations: Bool = false, synchronousLoad: Bool = false, useMiniThumbnailIfAvailable: Bool = false) -> Signal<Tuple4<Data?, Data?, ChatMessagePhotoQuality, Bool>, NoError> {
|
||||||
if let progressiveRepresentation = progressiveImageRepresentation(photoReference.media.representations), progressiveRepresentation.progressiveSizes.count == 5 {
|
if let progressiveRepresentation = progressiveImageRepresentation(photoReference.media.representations), progressiveRepresentation.progressiveSizes.count > 1 {
|
||||||
enum SizeSource {
|
enum SizeSource {
|
||||||
case miniThumbnail(data: Data)
|
case miniThumbnail(data: Data)
|
||||||
case image(size: Int)
|
case image(size: Int)
|
||||||
@ -68,10 +68,13 @@ public func chatMessagePhotoDatas(postbox: Postbox, photoReference: ImageMediaRe
|
|||||||
if Int(fullRepresentationSize.width) > 100 && maxDimension <= 100 {
|
if Int(fullRepresentationSize.width) > 100 && maxDimension <= 100 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
sources.append(contentsOf: byteSizes.map { sizeIndex -> SizeSource in
|
sources.append(contentsOf: byteSizes.compactMap { sizeIndex -> SizeSource? in
|
||||||
|
if progressiveRepresentation.progressiveSizes.count - 1 < sizeIndex {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return .image(size: Int(progressiveRepresentation.progressiveSizes[sizeIndex]))
|
return .image(size: Int(progressiveRepresentation.progressiveSizes[sizeIndex]))
|
||||||
})
|
})
|
||||||
largestByteSize = Int(progressiveRepresentation.progressiveSizes[byteSizes.last!])
|
largestByteSize = Int(progressiveRepresentation.progressiveSizes[min(progressiveRepresentation.progressiveSizes.count - 1, byteSizes.last!)])
|
||||||
if maxDimension >= Int(fullRepresentationSize.width) {
|
if maxDimension >= Int(fullRepresentationSize.width) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -47,8 +47,8 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
|
|
||||||
private let animationBackgroundColor: UIColor
|
private let animationBackgroundColor: UIColor
|
||||||
|
|
||||||
private var originalRemainingSeconds: Int
|
private var originalRemainingSeconds: Double
|
||||||
private var remainingSeconds: Int
|
private var remainingSeconds: Double
|
||||||
private var timer: SwiftSignalKit.Timer?
|
private var timer: SwiftSignalKit.Timer?
|
||||||
|
|
||||||
private var validLayout: ContainerViewLayout?
|
private var validLayout: ContainerViewLayout?
|
||||||
@ -147,7 +147,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
}
|
}
|
||||||
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
|
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
|
||||||
displayUndo = false
|
displayUndo = false
|
||||||
self.originalRemainingSeconds = 5
|
self.originalRemainingSeconds = 3.5
|
||||||
case let .succeed(text):
|
case let .succeed(text):
|
||||||
self.avatarNode = nil
|
self.avatarNode = nil
|
||||||
self.iconNode = nil
|
self.iconNode = nil
|
||||||
@ -175,7 +175,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
self.textNode.attributedText = attributedText
|
self.textNode.attributedText = attributedText
|
||||||
self.textNode.maximumNumberOfLines = 2
|
self.textNode.maximumNumberOfLines = 2
|
||||||
displayUndo = false
|
displayUndo = false
|
||||||
self.originalRemainingSeconds = max(5, min(8, text.count / 14))
|
self.originalRemainingSeconds = Double(max(5, min(8, text.count / 14)))
|
||||||
case let .actionSucceeded(title, text, cancel):
|
case let .actionSucceeded(title, text, cancel):
|
||||||
self.avatarNode = nil
|
self.avatarNode = nil
|
||||||
self.iconNode = nil
|
self.iconNode = nil
|
||||||
@ -617,9 +617,9 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
|
|
||||||
private func checkTimer() {
|
private func checkTimer() {
|
||||||
if self.timer != nil {
|
if self.timer != nil {
|
||||||
self.remainingSeconds -= 1
|
self.remainingSeconds -= 0.5
|
||||||
}
|
}
|
||||||
if self.remainingSeconds == 0 {
|
if self.remainingSeconds <= 0.0 {
|
||||||
let _ = self.action(.commit)
|
let _ = self.action(.commit)
|
||||||
self.dismiss()
|
self.dismiss()
|
||||||
} else {
|
} else {
|
||||||
@ -637,7 +637,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
if let validLayout = self.validLayout {
|
if let validLayout = self.validLayout {
|
||||||
self.containerLayoutUpdated(layout: validLayout, transition: .immediate)
|
self.containerLayoutUpdated(layout: validLayout, transition: .immediate)
|
||||||
}
|
}
|
||||||
let timer = SwiftSignalKit.Timer(timeout: 1.0, repeat: false, completion: { [weak self] in
|
let timer = SwiftSignalKit.Timer(timeout: 0.5, repeat: false, completion: { [weak self] in
|
||||||
self?.checkTimer()
|
self?.checkTimer()
|
||||||
}, queue: .mainQueue())
|
}, queue: .mainQueue())
|
||||||
self.timer = timer
|
self.timer = timer
|
||||||
|
Loading…
x
Reference in New Issue
Block a user