From bf4013781ccb5ee8eedb45fec460e7029e8bb54f Mon Sep 17 00:00:00 2001
From: Ali <>
Date: Sun, 14 Feb 2021 18:22:29 +0400
Subject: [PATCH] New widget type
---
Intents.intentdefinition | 29 +-
Telegram/BUILD | 16 +-
Telegram/SiriIntents/IntentHandler.swift | 284 ++++++++++++++++--
.../WidgetKitWidget/TodayViewController.swift | 150 +++++----
build-system/bazel-rules/rules_swift | 2 +-
build-system/tulsi | 2 +-
.../Sources/PhotoResources.swift | 11 +-
.../Sources/UndoOverlayControllerNode.swift | 14 +-
8 files changed, 390 insertions(+), 118 deletions(-)
diff --git a/Intents.intentdefinition b/Intents.intentdefinition
index 6b23905c2f..f83b915d39 100644
--- a/Intents.intentdefinition
+++ b/Intents.intentdefinition
@@ -136,13 +136,13 @@
INIntentCategory
information
INIntentDescriptionID
- zzS0gJ
+ DwL4WQ
INIntentEligibleForWidgets
INIntentIneligibleForSuggestions
INIntentLastParameterTag
- 3
+ 19
INIntentName
SelectAvatarFriends
INIntentParameters
@@ -152,25 +152,29 @@
INIntentParameterArraySizeSize
- 1
+ 4
INIntentParameterArraySizeSizeClass
Small
INIntentParameterArraySizeSize
- 4
+ 8
INIntentParameterArraySizeSizeClass
Medium
INIntentParameterArraySizeSize
- 8
+ 16
INIntentParameterArraySizeSizeClass
Large
INIntentParameterConfigurable
+ INIntentParameterDisplayName
+
+ INIntentParameterDisplayNameID
+ Jg5dYF
INIntentParameterDisplayPriority
1
INIntentParameterFixedSizeArray
@@ -189,7 +193,7 @@
INIntentParameterPromptDialogFormatString
Search
INIntentParameterPromptDialogFormatStringID
- xeb2pd
+ ORCbLf
INIntentParameterPromptDialogType
Configuration
@@ -200,6 +204,13 @@
Primary
+ INIntentParameterRelationship
+
+ INIntentParameterRelationshipPredicateName
+ EnumHasExactValue
+ INIntentParameterRelationshipPredicateValue
+ custom
+
INIntentParameterSupportsDynamicEnumeration
INIntentParameterSupportsMultipleValues
@@ -207,7 +218,7 @@
INIntentParameterSupportsSearch
INIntentParameterTag
- 3
+ 19
INIntentParameterType
Object
@@ -229,9 +240,9 @@
INIntentTitle
- Select
+ Select Chats
INIntentTitleID
- kiqCaL
+ 3Sbb7H
INIntentType
Custom
INIntentVerb
diff --git a/Telegram/BUILD b/Telegram/BUILD
index 4ff16a70ec..ee94e58836 100644
--- a/Telegram/BUILD
+++ b/Telegram/BUILD
@@ -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(
name = "WidgetExtensionLib",
module_name = "WidgetExtensionLib",
@@ -1258,6 +1243,7 @@ plist_fragment(
INSetMessageAttributeIntent
INSearchCallHistoryIntent
SelectFriendsIntent
+ SelectAvatarFriendsIntent
NSExtensionPointIdentifier
diff --git a/Telegram/SiriIntents/IntentHandler.swift b/Telegram/SiriIntents/IntentHandler.swift
index 13edd74c6c..73097a632e 100644
--- a/Telegram/SiriIntents/IntentHandler.swift
+++ b/Telegram/SiriIntents/IntentHandler.swift
@@ -54,7 +54,7 @@ enum IntentHandlingError {
@available(iOSApplicationExtension 10.0, iOS 10.0, *)
@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()
private let allAccounts = Promise<[(AccountRecordId, PeerId)]>()
@@ -195,7 +195,15 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
}
override public func handler(for intent: INIntent) -> Any {
- return self
+ if #available(iOSApplicationExtension 12.0, iOS 12.0, *) {
+ if intent is SelectAvatarFriendsIntent {
+ return AvatarsIntentHandler()
+ } else {
+ return self
+ }
+ } else {
+ return self
+ }
}
enum ResolveResult {
@@ -812,16 +820,18 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
var items: [Friend] = []
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)
- }.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))
+
+ 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 {
+ profileImage = nil
+ }
+ items.append(Friend(identifier: "\(accountId.int64):\(peer.id.toInt64())", display: peer.debugDisplayTitle, subtitle: nil, image: profileImage))
}
return INObjectSection(title: accountTitle, items: items)
@@ -846,6 +856,149 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
completion(nil, error)
}))
}
+}
+
+@available(iOSApplicationExtension 10.0, iOS 10.0, *)
+@objc(AvatarsIntentHandler)
+class AvatarsIntentHandler: NSObject, SelectAvatarFriendsIntentHandling {
+ private let accountPromise = Promise()
+ 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[.. 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
+ 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 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, *)
func provideFriendsOptionsCollection(for intent: SelectAvatarFriendsIntent, searchTerm: String?, with completion: @escaping (INObjectCollection?, Error?) -> Void) {
@@ -906,16 +1059,18 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
var items: [Friend] = []
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)
- }.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))
+
+ 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 {
+ profileImage = nil
+ }
+ items.append(Friend(identifier: "\(accountId.int64):\(peer.id.toInt64())", display: peer.debugDisplayTitle, subtitle: nil, image: profileImage))
}
return INObjectSection(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)!
+ }
+}
diff --git a/Telegram/WidgetKitWidget/TodayViewController.swift b/Telegram/WidgetKitWidget/TodayViewController.swift
index 1b4f666292..0b7638b82b 100644
--- a/Telegram/WidgetKitWidget/TodayViewController.swift
+++ b/Telegram/WidgetKitWidget/TodayViewController.swift
@@ -288,7 +288,7 @@ struct AvatarsProvider: 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)!)
- var itemsByAccount: [Int64: [(Int64, Friend)]] = [:]
+ var itemsByAccount: [Int64: [(Int64, Any)]] = [:]
var itemOrder: [(Int64, Int64)] = []
if let friends = configuration.friends {
for item in friends {
@@ -416,7 +416,6 @@ struct AvatarItemView: View {
var peer: ParsedPeer?
var itemSize: CGFloat
var placeholderColor: Color
- var displayBadge: Bool = true
var body: some View {
return ZStack {
@@ -427,23 +426,7 @@ struct AvatarItemView: View {
Circle()
.fill(placeholderColor)
.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)
.lineLimit(2)
.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 {
@@ -805,7 +757,7 @@ struct WidgetView: View {
return AnyView(
Link(destination: url, label: {
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))
})
})
@@ -821,11 +773,6 @@ struct WidgetView: View {
.frame(width: size.width - 54.0 - 20.0, height: 0.5, alignment: .leading)
})
.frame(width: size.width, height: 1.0, alignment: .leading)
- /*let separatorWidth = size.width - 54.0 - 20.0
- let itemHeight = (size.height - 22.0) / 2.0
- return Rectangle().foregroundColor(getSeparatorColor())
- //.position(x: (54.0 + 20.0 + separatorWidth) / 2.0, y: itemHeight / 2.0)
- .frame(width: size.width, height: 1.0, alignment: .leading)*/
}
func chatsUpdateBackgroundView(size: CGSize) -> some View {
@@ -871,12 +818,6 @@ struct WidgetView: View {
return Text(text)
.font(Font.system(size: 12.0))
.foregroundColor(getUpdatedTextColor())
-
- /*return HStack(alignment: .center, spacing: 0.0, content: {
- Text(text)
- .font(Font.system(size: 12.0))
- .foregroundColor(getUpdatedTextColor())
- }).position(x: size.width / 2.0, y: size.height - 11.0).frame(width: size.width, height: 22.0, alignment: .leading)*/
}
func getSeparatorColor() -> Color {
@@ -933,6 +874,89 @@ struct WidgetView: View {
})
})
.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 {
return IntentConfiguration(kind: kind, intent: SelectAvatarFriendsIntent.self, provider: AvatarsProvider(), content: { entry in
- Spacer()
+ AvatarsWidgetView(data: getWidgetData(contents: entry.contents))
})
.supportedFamilies([.systemMedium])
.configurationDisplayName(presentationData.widgetGalleryTitle)
@@ -1050,6 +1074,6 @@ struct Static_AvatarsWidget: Widget {
struct AllWidgets: WidgetBundle {
var body: some Widget {
Static_Widget()
- //Static_AvatarsWidget()
+ Static_AvatarsWidget()
}
}
diff --git a/build-system/bazel-rules/rules_swift b/build-system/bazel-rules/rules_swift
index e80b9795db..81aa39ed9f 160000
--- a/build-system/bazel-rules/rules_swift
+++ b/build-system/bazel-rules/rules_swift
@@ -1 +1 @@
-Subproject commit e80b9795db5e35f86d643e0fba7776f1b1f71066
+Subproject commit 81aa39ed9f58c416e7255adc0c8a6ba50c081030
diff --git a/build-system/tulsi b/build-system/tulsi
index 734518e85d..0bddcf7522 160000
--- a/build-system/tulsi
+++ b/build-system/tulsi
@@ -1 +1 @@
-Subproject commit 734518e85d769de070b5a78b234080d9580ae625
+Subproject commit 0bddcf7522cd4f3bcad4e1502e5189156a2eb615
diff --git a/submodules/PhotoResources/Sources/PhotoResources.swift b/submodules/PhotoResources/Sources/PhotoResources.swift
index 2827eff05a..9e195e9130 100644
--- a/submodules/PhotoResources/Sources/PhotoResources.swift
+++ b/submodules/PhotoResources/Sources/PhotoResources.swift
@@ -41,7 +41,7 @@ public func representationFetchRangeForDisplayAtSize(representation: TelegramMed
if representation.progressiveSizes.count > 1, let dimension = dimension {
var largestByteSize = Int(representation.progressiveSizes[0])
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 {
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, 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 {
case miniThumbnail(data: Data)
case image(size: Int)
@@ -68,10 +68,13 @@ public func chatMessagePhotoDatas(postbox: Postbox, photoReference: ImageMediaRe
if Int(fullRepresentationSize.width) > 100 && maxDimension <= 100 {
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]))
})
- largestByteSize = Int(progressiveRepresentation.progressiveSizes[byteSizes.last!])
+ largestByteSize = Int(progressiveRepresentation.progressiveSizes[min(progressiveRepresentation.progressiveSizes.count - 1, byteSizes.last!)])
if maxDimension >= Int(fullRepresentationSize.width) {
break
}
diff --git a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift
index 8510995889..5fa56f0291 100644
--- a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift
+++ b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift
@@ -47,8 +47,8 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
private let animationBackgroundColor: UIColor
- private var originalRemainingSeconds: Int
- private var remainingSeconds: Int
+ private var originalRemainingSeconds: Double
+ private var remainingSeconds: Double
private var timer: SwiftSignalKit.Timer?
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)
displayUndo = false
- self.originalRemainingSeconds = 5
+ self.originalRemainingSeconds = 3.5
case let .succeed(text):
self.avatarNode = nil
self.iconNode = nil
@@ -175,7 +175,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.textNode.attributedText = attributedText
self.textNode.maximumNumberOfLines = 2
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):
self.avatarNode = nil
self.iconNode = nil
@@ -617,9 +617,9 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
private func checkTimer() {
if self.timer != nil {
- self.remainingSeconds -= 1
+ self.remainingSeconds -= 0.5
}
- if self.remainingSeconds == 0 {
+ if self.remainingSeconds <= 0.0 {
let _ = self.action(.commit)
self.dismiss()
} else {
@@ -637,7 +637,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
if let validLayout = self.validLayout {
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()
}, queue: .mainQueue())
self.timer = timer