Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2021-02-14 19:08:14 +04:00
commit 0456336a34
10 changed files with 459 additions and 155 deletions

View File

@ -136,13 +136,13 @@
<key>INIntentCategory</key>
<string>information</string>
<key>INIntentDescriptionID</key>
<string>zzS0gJ</string>
<string>DwL4WQ</string>
<key>INIntentEligibleForWidgets</key>
<true/>
<key>INIntentIneligibleForSuggestions</key>
<true/>
<key>INIntentLastParameterTag</key>
<integer>3</integer>
<integer>19</integer>
<key>INIntentName</key>
<string>SelectAvatarFriends</string>
<key>INIntentParameters</key>
@ -152,25 +152,29 @@
<array>
<dict>
<key>INIntentParameterArraySizeSize</key>
<integer>1</integer>
<integer>4</integer>
<key>INIntentParameterArraySizeSizeClass</key>
<string>Small</string>
</dict>
<dict>
<key>INIntentParameterArraySizeSize</key>
<integer>4</integer>
<integer>8</integer>
<key>INIntentParameterArraySizeSizeClass</key>
<string>Medium</string>
</dict>
<dict>
<key>INIntentParameterArraySizeSize</key>
<integer>8</integer>
<integer>16</integer>
<key>INIntentParameterArraySizeSizeClass</key>
<string>Large</string>
</dict>
</array>
<key>INIntentParameterConfigurable</key>
<true/>
<key>INIntentParameterDisplayName</key>
<string> </string>
<key>INIntentParameterDisplayNameID</key>
<string>Jg5dYF</string>
<key>INIntentParameterDisplayPriority</key>
<integer>1</integer>
<key>INIntentParameterFixedSizeArray</key>
@ -189,7 +193,7 @@
<key>INIntentParameterPromptDialogFormatString</key>
<string>Search</string>
<key>INIntentParameterPromptDialogFormatStringID</key>
<string>xeb2pd</string>
<string>ORCbLf</string>
<key>INIntentParameterPromptDialogType</key>
<string>Configuration</string>
</dict>
@ -200,6 +204,13 @@
<string>Primary</string>
</dict>
</array>
<key>INIntentParameterRelationship</key>
<dict>
<key>INIntentParameterRelationshipPredicateName</key>
<string>EnumHasExactValue</string>
<key>INIntentParameterRelationshipPredicateValue</key>
<string>custom</string>
</dict>
<key>INIntentParameterSupportsDynamicEnumeration</key>
<true/>
<key>INIntentParameterSupportsMultipleValues</key>
@ -207,7 +218,7 @@
<key>INIntentParameterSupportsSearch</key>
<true/>
<key>INIntentParameterTag</key>
<integer>3</integer>
<integer>19</integer>
<key>INIntentParameterType</key>
<string>Object</string>
</dict>
@ -229,9 +240,9 @@
</array>
</dict>
<key>INIntentTitle</key>
<string>Select</string>
<string>Select Chats</string>
<key>INIntentTitleID</key>
<string>kiqCaL</string>
<string>3Sbb7H</string>
<key>INIntentType</key>
<string>Custom</string>
<key>INIntentVerb</key>

View File

@ -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(
<string>INSetMessageAttributeIntent</string>
<string>INSearchCallHistoryIntent</string>
<string>SelectFriendsIntent</string>
<string>SelectAvatarFriendsIntent</string>
</array>
</dict>
<key>NSExtensionPointIdentifier</key>

View File

@ -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<Account?>()
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<Friend>(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<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, *)
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] = []
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<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)!
}
}

View File

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

@ -1 +1 @@
Subproject commit e80b9795db5e35f86d643e0fba7776f1b1f71066
Subproject commit 81aa39ed9f58c416e7255adc0c8a6ba50c081030

@ -1 +1 @@
Subproject commit 734518e85d769de070b5a78b234080d9580ae625
Subproject commit 0bddcf7522cd4f3bcad4e1502e5189156a2eb615

View File

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

View File

@ -172,10 +172,18 @@ static CGSize TGFitSize(CGSize size, CGSize maxSize) {
{
return [[MTSignal alloc] initWithGenerator:^id<MTDisposable>(MTSubscriber *subscriber)
{
bool preferAsFile = false;
#if DEBUG
preferAsFile = true;
#endif
CGSize maxSize = CGSizeMake(1280.0, 1280.0);
NSDictionary *imageOptions = @{
NSItemProviderPreferredImageSizeKey: [NSValue valueWithCGSize:maxSize]
};
if (preferAsFile) {
imageOptions = nil;
}
if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage]) {
[itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeImage options:imageOptions completionHandler:^(id<NSSecureCoding> _Nullable item, NSError * _Null_unspecified error) {
if (error != nil && ![(NSObject *)item respondsToSelector:@selector(CGImage)] && ![(NSObject *)item respondsToSelector:@selector(absoluteString)]) {
@ -193,44 +201,63 @@ static CGSize TGFitSize(CGSize size, CGSize maxSize) {
if ([(NSObject *)item respondsToSelector:@selector(absoluteString)]) {
NSURL *url = (NSURL *)item;
CGImageSourceRef src = CGImageSourceCreateWithURL((__bridge CFURLRef) url, NULL);
CFDictionaryRef options = (__bridge CFDictionaryRef) @{
(id) kCGImageSourceCreateThumbnailWithTransform : @YES,
(id) kCGImageSourceCreateThumbnailFromImageAlways : @YES,
(id) kCGImageSourceThumbnailMaxPixelSize : @(maxSize.width)
};
CGImageRef image = CGImageSourceCreateThumbnailAtIndex(src, 0, options);
CFRelease(src);
if (image == nil) {
[subscriber putError:nil];
return;
}
NSString *tempPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSString alloc] initWithFormat:@"img%d", (int)arc4random()]];
CFURLRef tempUrl = (__bridge CFURLRef)[NSURL fileURLWithPath:tempPath];
CGImageDestinationRef destination = CGImageDestinationCreateWithURL(tempUrl, kUTTypeJPEG, 1, NULL);
NSDictionary *properties = @{ (__bridge NSString *)kCGImageDestinationLossyCompressionQuality: @(0.52)};
CGImageDestinationSetProperties(destination, (__bridge CFDictionaryRef)properties);
CGImageDestinationAddImage(destination, image, nil);
if (!CGImageDestinationFinalize(destination)) {
CFRelease(destination);
[subscriber putError:nil];
return;
}
CFRelease(destination);
NSData *resultData = [[NSData alloc] initWithContentsOfFile:tempPath options:NSDataReadingMappedIfSafe error:nil];
if (resultData != nil) {
[subscriber putNext:@{@"scaledImageData": resultData, @"scaledImageDimensions": [NSValue valueWithCGSize:CGSizeMake(CGImageGetWidth(image), CGImageGetHeight(image))]}];
if (preferAsFile) {
NSData *data = [[NSData alloc] initWithContentsOfURL:url options:NSDataReadingMappedIfSafe error:nil];
if (data == nil) {
[subscriber putError:nil];
return;
}
NSString *fileName = [[url pathComponents] lastObject];
if (fileName.length == 0) {
fileName = @"file.bin";
}
NSString *extension = [fileName pathExtension];
NSString *mimeType = [TGMimeTypeMap mimeTypeForExtension:[extension lowercaseString]];
if (mimeType == nil) {
mimeType = @"application/octet-stream";
}
[subscriber putNext:@{@"data": data, @"fileName": fileName, @"mimeType": mimeType, @"treatAsFile": @true}];
[subscriber putCompletion];
} else {
[subscriber putError:nil];
CGImageSourceRef src = CGImageSourceCreateWithURL((__bridge CFURLRef) url, NULL);
CFDictionaryRef options = (__bridge CFDictionaryRef) @{
(id) kCGImageSourceCreateThumbnailWithTransform : @YES,
(id) kCGImageSourceCreateThumbnailFromImageAlways : @YES,
(id) kCGImageSourceThumbnailMaxPixelSize : @(maxSize.width)
};
CGImageRef image = CGImageSourceCreateThumbnailAtIndex(src, 0, options);
CFRelease(src);
if (image == nil) {
[subscriber putError:nil];
return;
}
NSString *tempPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSString alloc] initWithFormat:@"img%d", (int)arc4random()]];
CFURLRef tempUrl = (__bridge CFURLRef)[NSURL fileURLWithPath:tempPath];
CGImageDestinationRef destination = CGImageDestinationCreateWithURL(tempUrl, kUTTypeJPEG, 1, NULL);
NSDictionary *properties = @{ (__bridge NSString *)kCGImageDestinationLossyCompressionQuality: @(0.52)};
CGImageDestinationSetProperties(destination, (__bridge CFDictionaryRef)properties);
CGImageDestinationAddImage(destination, image, nil);
if (!CGImageDestinationFinalize(destination)) {
CFRelease(destination);
[subscriber putError:nil];
return;
}
CFRelease(destination);
NSData *resultData = [[NSData alloc] initWithContentsOfFile:tempPath options:NSDataReadingMappedIfSafe error:nil];
if (resultData != nil) {
[subscriber putNext:@{@"scaledImageData": resultData, @"scaledImageDimensions": [NSValue valueWithCGSize:CGSizeMake(CGImageGetWidth(image), CGImageGetHeight(image))]}];
[subscriber putCompletion];
} else {
[subscriber putError:nil];
}
}
} else {
[subscriber putNext:@{@"image": item}];

View File

@ -144,7 +144,12 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
let fileName = value["fileName"] as? String
let mimeType = (value["mimeType"] as? String) ?? "application/octet-stream"
if let image = UIImage(data: data) {
var treatAsFile = false
if let boolValue = value["treatAsFile"] as? Bool, boolValue {
treatAsFile = true
}
if !treatAsFile, let image = UIImage(data: data) {
var isGif = false
if data.count > 4 {
data.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in

View File

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