Swiftgram/submodules/TelegramUI/Sources/OpenChatMessage.swift
2020-09-07 15:22:35 +03:00

366 lines
22 KiB
Swift

import Foundation
import Display
import AsyncDisplayKit
import Postbox
import TelegramCore
import SyncCore
import SwiftSignalKit
import PassKit
import Lottie
import TelegramUIPreferences
import TelegramPresentationData
import AccountContext
import GalleryUI
import InstantPageUI
import LocationUI
import StickerPackPreviewUI
import PeerAvatarGalleryUI
import PeerInfoUI
import SettingsUI
import AlertUI
import PresentationDataUtils
import ShareController
import UndoUI
import WebsiteType
import GalleryData
func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
if let mediaData = chatMessageGalleryControllerData(context: params.context, chatLocation: params.chatLocation, chatLocationContextHolder: params.chatLocationContextHolder, message: params.message, navigationController: params.navigationController, standalone: params.standalone, reverseMessageGalleryOrder: params.reverseMessageGalleryOrder, mode: params.mode, source: params.gallerySource, synchronousLoad: false, actionInteraction: params.actionInteraction) {
switch mediaData {
case let .url(url):
params.openUrl(url)
return true
case let .pass(file):
let _ = (params.context.account.postbox.mediaBox.resourceData(file.resource, option: .complete(waitUntilFetchStatus: true))
|> take(1)
|> deliverOnMainQueue).start(next: { data in
guard let navigationController = params.navigationController else {
return
}
if data.complete, let content = try? Data(contentsOf: URL(fileURLWithPath: data.path)) {
if let pass = try? PKPass(data: content), let controller = PKAddPassesViewController(pass: pass) {
if let window = navigationController.view.window {
controller.popoverPresentationController?.sourceView = window
controller.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0))
window.rootViewController?.present(controller, animated: true)
}
}
}
})
return true
case let .instantPage(gallery, centralIndex, galleryMedia):
params.setupTemporaryHiddenMedia(gallery.hiddenMedia |> map { a -> Any? in a }, centralIndex, galleryMedia)
params.dismissInput()
params.present(gallery, InstantPageGalleryControllerPresentationArguments(transitionArguments: { entry in
var selectedTransitionNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?
if entry.index == centralIndex {
selectedTransitionNode = params.transitionNode(params.message.id, galleryMedia)
}
if let selectedTransitionNode = selectedTransitionNode {
return GalleryTransitionArguments(transitionNode: selectedTransitionNode, addToTransitionSurface: params.addToTransitionSurface)
}
return nil
}))
return true
case let .map(mapMedia):
params.dismissInput()
// let controllerParams = LocationViewParams(sendLiveLocation: { location in
// let outMessage: EnqueueMessage = .message(text: "", attributes: [], mediaReference: .standalone(media: location), replyToMessageId: nil, localGroupingKey: nil)
// params.enqueueMessage(outMessage)
// }, stopLiveLocation: {
// params.context.liveLocationManager?.cancelLiveLocation(peerId: params.message.id.peerId)
// }, openUrl: params.openUrl, openPeer: { peer in
// params.openPeer(peer, .info)
// })
// let controller = LocationViewController(context: params.context, mapMedia: mapMedia, params: controllerParams)
let controller = legacyLocationController(message: params.message, mapMedia: mapMedia, context: params.context, openPeer: { peer in
params.openPeer(peer, .info)
}, sendLiveLocation: { coordinate, period in
let outMessage: EnqueueMessage = .message(text: "", attributes: [], mediaReference: .standalone(media: TelegramMediaMap(latitude: coordinate.latitude, longitude: coordinate.longitude, geoPlace: nil, venue: nil, liveBroadcastingTimeout: period)), replyToMessageId: nil, localGroupingKey: nil)
params.enqueueMessage(outMessage)
}, stopLiveLocation: {
params.context.liveLocationManager?.cancelLiveLocation(peerId: params.message.id.peerId)
}, openUrl: params.openUrl)
controller.navigationPresentation = .modal
params.navigationController?.pushViewController(controller)
return true
case let .stickerPack(reference):
let controller = StickerPackScreen(context: params.context, mainStickerPack: reference, stickerPacks: [reference], parentNavigationController: params.navigationController, sendSticker: params.sendSticker, actionPerformed: { info, items, action in
let presentationData = params.context.sharedContext.currentPresentationData.with { $0 }
var animateInAsReplacement = false
if let navigationController = params.navigationController {
for controller in navigationController.overlayControllers {
if let controller = controller as? UndoOverlayController {
controller.dismissWithCommitActionAndReplacementAnimation()
animateInAsReplacement = true
}
}
}
switch action {
case .add:
params.navigationController?.presentOverlay(controller: UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_AddedTitle, text: presentationData.strings.StickerPackActionInfo_AddedText(info.title).0, undo: false, info: info, topItem: items.first, account: params.context.account), elevatedLayout: true, animateInAsReplacement: animateInAsReplacement, action: { _ in
return true
}))
case let .remove(positionInList):
params.navigationController?.presentOverlay(controller: UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_RemovedTitle, text: presentationData.strings.StickerPackActionInfo_RemovedText(info.title).0, undo: true, info: info, topItem: items.first, account: params.context.account), elevatedLayout: true, animateInAsReplacement: animateInAsReplacement, action: { action in
if case .undo = action {
let _ = addStickerPackInteractively(postbox: params.context.account.postbox, info: info, items: items, positionInList: positionInList).start()
}
return true
}))
}
})
params.dismissInput()
params.present(controller, nil)
return true
case let .document(file, immediateShare):
params.dismissInput()
let presentationData = params.context.sharedContext.currentPresentationData.with { $0 }
if immediateShare {
let controller = ShareController(context: params.context, subject: .media(.standalone(media: file)), immediateExternalShare: true)
params.present(controller, nil)
} else if let rootController = params.navigationController?.view.window?.rootViewController {
presentDocumentPreviewController(rootController: rootController, theme: presentationData.theme, strings: presentationData.strings, postbox: params.context.account.postbox, file: file)
}
return true
case let .audio(file):
let location: PeerMessagesPlaylistLocation
let playerType: MediaManagerPlayerType
var control = SharedMediaPlayerControlAction.playback(.play)
if case let .timecode(time) = params.mode {
control = .seek(time)
}
if (file.isVoice || file.isInstantVideo) && params.message.tags.contains(.voiceOrInstantVideo) {
if params.standalone {
location = .recentActions(params.message)
} else {
location = .messages(peerId: params.message.id.peerId, tagMask: .voiceOrInstantVideo, at: params.message.id)
}
playerType = .voice
} else if file.isMusic && params.message.tags.contains(.music) {
if let playlistLocation = params.playlistLocation {
location = playlistLocation
} else if params.standalone {
location = .recentActions(params.message)
} else {
location = .messages(peerId: params.message.id.peerId, tagMask: .music, at: params.message.id)
}
playerType = .music
} else {
if params.standalone {
location = .recentActions(params.message)
} else {
location = .singleMessage(params.message.id)
}
playerType = (file.isVoice || file.isInstantVideo) ? .voice : .music
}
params.context.sharedContext.mediaManager.setPlaylist((params.context.account, PeerMessagesMediaPlaylist(postbox: params.context.account.postbox, network: params.context.account.network, location: location)), type: playerType, control: control)
return true
case let .gallery(gallery):
params.dismissInput()
let _ = (gallery
|> deliverOnMainQueue).start(next: { gallery in
params.present(gallery, GalleryControllerPresentationArguments(transitionArguments: { messageId, media in
let selectedTransitionNode = params.transitionNode(messageId, media)
if let selectedTransitionNode = selectedTransitionNode {
return GalleryTransitionArguments(transitionNode: selectedTransitionNode, addToTransitionSurface: params.addToTransitionSurface)
}
return nil
}))
})
return true
case let .secretGallery(gallery):
params.dismissInput()
params.present(gallery, GalleryControllerPresentationArguments(transitionArguments: { messageId, media in
let selectedTransitionNode = params.transitionNode(messageId, media)
if let selectedTransitionNode = selectedTransitionNode {
return GalleryTransitionArguments(transitionNode: selectedTransitionNode, addToTransitionSurface: params.addToTransitionSurface)
}
return nil
}))
return true
case let .other(otherMedia):
params.dismissInput()
if let contact = otherMedia as? TelegramMediaContact {
let _ = (params.context.account.postbox.transaction { transaction -> (Peer?, Bool?) in
if let peerId = contact.peerId {
return (transaction.getPeer(peerId), transaction.isPeerContact(peerId: peerId))
} else {
return (nil, nil)
}
} |> deliverOnMainQueue).start(next: { peer, isContact in
let contactData: DeviceContactExtendedData
if let vCard = contact.vCardData, let vCardData = vCard.data(using: .utf8), let parsed = DeviceContactExtendedData(vcard: vCardData) {
contactData = parsed
} else {
contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName, lastName: contact.lastName, phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!<Mobile>!$_", value: contact.phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "")
}
let controller = deviceContactInfoController(context: params.context, subject: .vcard(peer, nil, contactData), completed: nil, cancelled: nil)
params.navigationController?.pushViewController(controller)
})
return true
}
case let .chatAvatars(controller, media):
params.dismissInput()
params.chatAvatarHiddenMedia(controller.hiddenMedia |> map { value -> MessageId? in
if value != nil {
return params.message.id
} else {
return nil
}
}, media)
params.present(controller, AvatarGalleryControllerPresentationArguments(transitionArguments: { entry in
if let selectedTransitionNode = params.transitionNode(params.message.id, media) {
return GalleryTransitionArguments(transitionNode: selectedTransitionNode, addToTransitionSurface: params.addToTransitionSurface)
}
return nil
}))
case let .theme(media):
params.dismissInput()
let path = params.context.account.postbox.mediaBox.completedResourcePath(media.resource)
var previewTheme: PresentationTheme?
if let path = path, let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe) {
let startTime = CACurrentMediaTime()
previewTheme = makePresentationTheme(data: data)
print("time \(CACurrentMediaTime() - startTime)")
}
guard let theme = previewTheme else {
return false
}
let controller = ThemePreviewController(context: params.context, previewTheme: theme, source: .media(.message(message: MessageReference(params.message), media: media)))
params.navigationController?.pushViewController(controller)
}
}
return false
}
func openChatInstantPage(context: AccountContext, message: Message, sourcePeerType: MediaAutoDownloadPeerType?, navigationController: NavigationController) {
for media in message.media {
if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
if let _ = content.instantPage {
var textUrl: String?
if let pageUrl = URL(string: content.url) {
inner: for attribute in message.attributes {
if let attribute = attribute as? TextEntitiesMessageAttribute {
for entity in attribute.entities {
switch entity.type {
case let .TextUrl(url):
if let parsedUrl = URL(string: url) {
if pageUrl.scheme == parsedUrl.scheme && pageUrl.host == parsedUrl.host && pageUrl.path == parsedUrl.path {
textUrl = url
}
}
case .Url:
let nsText = message.text as NSString
var entityRange = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound)
if entityRange.location + entityRange.length > nsText.length {
entityRange.location = max(0, nsText.length - entityRange.length)
entityRange.length = nsText.length - entityRange.location
}
let url = nsText.substring(with: entityRange)
if let parsedUrl = URL(string: url) {
if pageUrl.scheme == parsedUrl.scheme && pageUrl.host == parsedUrl.host && pageUrl.path == parsedUrl.path {
textUrl = url
}
}
default:
break
}
}
break inner
}
}
}
var anchor: String?
if let textUrl = textUrl, let anchorRange = textUrl.range(of: "#") {
anchor = String(textUrl[anchorRange.upperBound...])
}
let pageController = InstantPageController(context: context, webPage: webpage, sourcePeerType: sourcePeerType ?? .channel, anchor: anchor)
navigationController.pushViewController(pageController)
}
break
}
}
}
func openChatWallpaper(context: AccountContext, message: Message, present: @escaping (ViewController, Any?) -> Void) {
for media in message.media {
if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
let _ = (context.sharedContext.resolveUrl(account: context.account, url: content.url)
|> deliverOnMainQueue).start(next: { resolvedUrl in
if case let .wallpaper(parameter) = resolvedUrl {
let source: WallpaperListSource
switch parameter {
case let .slug(slug, options, firstColor, secondColor, intensity, rotation):
source = .slug(slug, content.file, options, firstColor, secondColor, intensity, rotation, message)
case let .color(color):
source = .wallpaper(.color(color.argb), nil, nil, nil, nil, nil, message)
case let .gradient(topColor, bottomColor, rotation):
source = .wallpaper(.gradient(topColor.argb, bottomColor.argb, WallpaperSettings(rotation: rotation)), nil, nil, nil, nil, rotation, message)
}
let controller = WallpaperGalleryController(context: context, source: source)
present(controller, nil)
}
})
}
}
}
func openChatTheme(context: AccountContext, message: Message, pushController: @escaping (ViewController) -> Void, present: @escaping (ViewController, Any?) -> Void) {
for media in message.media {
if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
let _ = (context.sharedContext.resolveUrl(account: context.account, url: content.url)
|> deliverOnMainQueue).start(next: { resolvedUrl in
var file: TelegramMediaFile?
var settings: TelegramThemeSettings?
let themeMimeType = "application/x-tgtheme-ios"
for attribute in content.attributes {
if case let .theme(attribute) = attribute {
if let attributeSettings = attribute.settings {
settings = attributeSettings
} else if let filteredFile = attribute.files.filter({ $0.mimeType == themeMimeType }).first {
file = filteredFile
}
}
}
if file == nil && settings == nil, let contentFile = content.file, contentFile.mimeType == themeMimeType {
file = contentFile
}
let displayUnsupportedAlert: () -> Void = {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
present(textAlertController(context: context, title: nil, text: presentationData.strings.Theme_Unsupported, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}
if case let .theme(slug) = resolvedUrl {
if let file = file {
if let path = context.sharedContext.accountManager.mediaBox.completedResourcePath(file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) {
if let theme = makePresentationTheme(data: data) {
let controller = ThemePreviewController(context: context, previewTheme: theme, source: .slug(slug, file))
pushController(controller)
} else {
displayUnsupportedAlert()
}
}
} else if let settings = settings {
if let theme = makePresentationTheme(settings: settings, title: content.title) {
let controller = ThemePreviewController(context: context, previewTheme: theme, source: .themeSettings(slug, settings))
pushController(controller)
} else {
displayUnsupportedAlert()
}
}
} else {
displayUnsupportedAlert()
}
})
}
}
}