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