import Foundation import UIKit import Intents import Display import Postbox import TelegramCore import SwiftSignalKit import TelegramUIPreferences import TelegramPresentationData import AvatarNode import AccountContext private let savedMessagesAvatar: UIImage = { return generateImage(CGSize(width: 60.0, height: 60.0), rotatedContext: { size, context in var locations: [CGFloat] = [1.0, 0.0] let colorSpace = CGColorSpaceCreateDeviceRGB() let gradient = CGGradient(colorsSpace: colorSpace, colors: [UIColor(rgb: 0x2a9ef1).cgColor, UIColor(rgb: 0x72d5fd).cgColor] as CFArray, locations: &locations)! context.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions()) let factor = size.width / 60.0 context.translateBy(x: size.width / 2.0, y: size.height / 2.0) context.scaleBy(x: factor, y: -factor) context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) if let savedMessagesIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/SavedMessagesIcon"), color: .white) { context.draw(savedMessagesIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((size.width - savedMessagesIcon.size.width) / 2.0), y: floor((size.height - savedMessagesIcon.size.height) / 2.0)), size: savedMessagesIcon.size)) } })! }() public enum SendMessageIntentContext { case chat case share } public enum SendMessageIntentSubject: CaseIterable { case contact case savedMessages case privateChat case group func toString() -> String { switch self { case .contact: return "contact" case .savedMessages: return "savedMessages" case .privateChat: return "privateChat" case .group: return "group" } } } public func donateSendMessageIntent(account: Account, sharedContext: SharedAccountContext, intentContext: SendMessageIntentContext, peerIds: [PeerId]) { if #available(iOSApplicationExtension 13.2, iOS 13.2, *) { let _ = (sharedContext.accountManager.transaction { transaction -> Bool in if case .none = transaction.getAccessChallengeData() { return true } else { return false } } |> mapToSignal { unlocked -> Signal<[(Peer, SendMessageIntentSubject, UIImage?)], NoError> in if unlocked { return sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.intentsSettings]) |> take(1) |> mapToSignal { sharedData -> Signal<[(Peer, SendMessageIntentSubject)], NoError> in let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.intentsSettings]?.get(IntentsSettings.self) ?? IntentsSettings.defaultSettings if let accountId = settings.account, accountId != account.peerId { return .single([]) } if case .chat = intentContext, settings.onlyShared { return .single([]) } return TelegramEngine(account: account).data.get( EngineDataMap(peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init)), EngineDataMap(peerIds.map(TelegramEngine.EngineData.Item.Peer.IsContact.init)), EngineDataMap(peerIds.map(TelegramEngine.EngineData.Item.Messages.ChatListGroup.init)) ) |> map { peerMap, isContactMap, chatListGroupMap -> [(Peer, SendMessageIntentSubject)] in var peers: [(Peer, SendMessageIntentSubject)] = [] for peerId in peerIds { if peerId.namespace != Namespaces.Peer.SecretChat, let maybePeer = peerMap[peerId], let peer = maybePeer { var subject: SendMessageIntentSubject? let chatListGroup = chatListGroupMap[peerId] if chatListGroup == .archive { continue } if peerId.namespace == Namespaces.Peer.CloudUser { if peerId == account.peerId { if !settings.savedMessages { continue } subject = .savedMessages } else if let isContact = isContactMap[peerId], isContact { if !settings.contacts { continue } subject = .contact } else { if !settings.privateChats { continue } subject = .privateChat } } else if peerId.namespace == Namespaces.Peer.CloudGroup { if !settings.groups { continue } subject = .group } else if case let .channel(peer) = peer { if case .group = peer.info { if !settings.groups { continue } subject = .group } else { continue } } else { continue } if let subject = subject { peers.append((peer._asPeer(), subject)) } } } return peers } } |> mapToSignal { peers -> Signal<[(Peer, SendMessageIntentSubject, UIImage?)], NoError> in var signals: [Signal<(Peer, SendMessageIntentSubject, UIImage?), NoError>] = [] for (peer, subject) in peers { if peer.id == account.peerId { signals.append(.single((peer, subject, savedMessagesAvatar))) } else { let peerAndAvatar = (peerAvatarImage(account: account, peerReference: PeerReference(peer), authorOfMessage: nil, representation: peer.smallProfileImage, clipStyle: .none) ?? .single(nil)) |> map { imageVersions -> (Peer, SendMessageIntentSubject, UIImage?) in var avatarImage: UIImage? if let image = imageVersions?.0 { avatarImage = image } else { let avatarFont = avatarPlaceholderFont(size: 26.0) let size = CGSize(width: 60.0, height: 60.0) if let image = generateImage(size, rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) drawPeerAvatarLetters(context: context, size: CGSize(width: size.width, height: size.height), font: avatarFont, letters: peer.displayLetters, peerId: peer.id, nameColor: peer.nameColor) })?.withRenderingMode(.alwaysOriginal) { avatarImage = image } } return (peer, subject, avatarImage) } signals.append(peerAndAvatar) } } return combineLatest(signals) } } else { return .single([]) } } |> deliverOnMainQueue).start(next: { peers in let presentationData = sharedContext.currentPresentationData.with { $0 } for (peer, _, avatarImage) in peers { let recipientHandle = INPersonHandle(value: "tg\(peer.id.toInt64())", type: .unknown) let displayTitle: String var nameComponents = PersonNameComponents() if let peer = peer as? TelegramUser { if peer.botInfo != nil || peer.flags.contains(.isSupport) { continue } if peer.id == account.peerId { displayTitle = presentationData.strings.DialogList_SavedMessages nameComponents.givenName = displayTitle } else { displayTitle = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) nameComponents.givenName = peer.firstName nameComponents.familyName = peer.lastName } } else { displayTitle = EnginePeer(peer).compactDisplayTitle nameComponents.givenName = displayTitle } var personImage: INImage? if let avatarImage = avatarImage, let avatarImageData = avatarImage.jpegData(compressionQuality: 0.8) { personImage = INImage(imageData: avatarImageData) } let recipient = INPerson(personHandle: recipientHandle, nameComponents: nameComponents, displayName: displayTitle, image: personImage, contactIdentifier: nil, customIdentifier: "tg\(peer.id.toInt64())") let intent = INSendMessageIntent(recipients: [recipient], content: nil, speakableGroupName: INSpeakableString(spokenPhrase: displayTitle), conversationIdentifier: "tg\(peer.id.toInt64())", serviceName: nil, sender: nil) let interaction = INInteraction(intent: intent, response: nil) interaction.direction = .outgoing interaction.groupIdentifier = "sendMessage_\(peer.id.toInt64())" interaction.donate { error in if let error = error { print(error) } } } }) } } public func deleteSendMessageIntents(peerId: PeerId) { if #available(iOS 10.0, *) { INInteraction.delete(with: "sendMessage_\(peerId.toInt64())") } } public func deleteAllSendMessageIntents() { if #available(iOS 10.0, *) { INInteraction.deleteAll() } }