import Foundation import UIKit import Display import LegacyComponents import TelegramCore import Postbox import TelegramPresentationData import AccountContext import ShareController import LegacyUI import OpenInExternalAppUI import AppBundle private func generateClearIcon(color: UIColor) -> UIImage? { return generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: color) } func makeLegacyPeer(_ peer: Peer) -> AnyObject? { if let user = peer as? TelegramUser { let legacyUser = TGUser() legacyUser.uid = user.id.id legacyUser.firstName = user.firstName legacyUser.lastName = user.lastName if let representation = smallestImageRepresentation(user.photo) { legacyUser.photoUrlSmall = legacyImageLocationUri(resource: representation.resource) } return legacyUser } else if let channel = peer as? TelegramChannel { let legacyConversation = TGConversation() legacyConversation.conversationId = Int64(channel.id.id) legacyConversation.chatTitle = channel.title if let representation = smallestImageRepresentation(channel.photo) { legacyConversation.chatPhotoSmall = legacyImageLocationUri(resource: representation.resource) } return legacyConversation } else { return nil } } private func makeLegacyMessage(_ message: Message) -> TGMessage { let result = TGMessage() result.mid = message.id.id result.date = Double(message.timestamp) if message.flags.contains(.Failed) { result.deliveryState = TGMessageDeliveryStateFailed } else if message.flags.contains(.Sending) { result.deliveryState = TGMessageDeliveryStatePending } else { result.deliveryState = TGMessageDeliveryStateDelivered } for attribute in message.attributes { if let attribute = attribute as? EditedMessageAttribute { result.editDate = Double(attribute.date) } } var media: [Any] = [] for m in message.media { if let mapMedia = m as? TelegramMediaMap { let legacyLocation = TGLocationMediaAttachment() legacyLocation.latitude = mapMedia.latitude legacyLocation.longitude = mapMedia.longitude if let venue = mapMedia.venue { legacyLocation.venue = TGVenueAttachment(title: venue.title, address: venue.address, provider: venue.provider, venueId: venue.id, type: venue.type) } if let liveBroadcastingTimeout = mapMedia.liveBroadcastingTimeout { legacyLocation.period = liveBroadcastingTimeout } media.append(legacyLocation) } } if !media.isEmpty { result.mediaAttachments = media } return result } private func legacyRemainingTime(message: TGMessage) -> SSignal { var liveBroadcastingTimeoutValue: Int32? if let mediaAttachments = message.mediaAttachments { for media in mediaAttachments { if let m = media as? TGLocationMediaAttachment, m.period != 0 { liveBroadcastingTimeoutValue = m.period } } } guard let liveBroadcastingTimeout = liveBroadcastingTimeoutValue else { return SSignal.fail(nil) } if message.deliveryState != TGMessageDeliveryStateDelivered { return SSignal.single(liveBroadcastingTimeout as NSNumber) } let remainingTime = SSignal.`defer`({ let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) let remainingTime = max(0, Int32(message.date) + liveBroadcastingTimeout - currentTime) var signal = SSignal.single(remainingTime as NSNumber) if remainingTime == 0 { signal = signal?.then(SSignal.fail(nil)) } return signal })! return (remainingTime.then(SSignal.complete().delay(5.0, on: SQueue.main()))).restart().`catch`({ _ in return SSignal.complete() }) } private func telegramMap(for location: TGLocationMediaAttachment) -> TelegramMediaMap { let mapVenue: MapVenue? if let venue = location.venue { mapVenue = MapVenue(title: venue.title ?? "", address: venue.address ?? "", provider: venue.provider ?? "", id: venue.venueId ?? "", type: venue.type ?? "") } else { mapVenue = nil } return TelegramMediaMap(latitude: location.latitude, longitude: location.longitude, geoPlace: nil, venue: mapVenue, liveBroadcastingTimeout: nil) } func legacyLocationPalette(from theme: PresentationTheme) -> TGLocationPallete { let listTheme = theme.list let searchTheme = theme.rootController.navigationSearchBar return TGLocationPallete(backgroundColor: listTheme.plainBackgroundColor, selectionColor: listTheme.itemHighlightedBackgroundColor, separatorColor: listTheme.itemPlainSeparatorColor, textColor: listTheme.itemPrimaryTextColor, secondaryTextColor: listTheme.itemSecondaryTextColor, accentColor: listTheme.itemAccentColor, destructiveColor: listTheme.itemDestructiveColor, locationColor: UIColor(rgb: 0x008df2), liveLocationColor: UIColor(rgb: 0xff6464), iconColor: searchTheme.backgroundColor, sectionHeaderBackgroundColor: theme.chatList.sectionHeaderFillColor, sectionHeaderTextColor: theme.chatList.sectionHeaderTextColor, searchBarPallete: TGSearchBarPallete(dark: theme.overallDarkAppearance, backgroundColor: searchTheme.backgroundColor, highContrastBackgroundColor: searchTheme.backgroundColor, textColor: searchTheme.inputTextColor, placeholderColor: searchTheme.inputPlaceholderTextColor, clearIcon: generateClearIcon(color: theme.rootController.navigationSearchBar.inputClearButtonColor), barBackgroundColor: searchTheme.backgroundColor, barSeparatorColor: searchTheme.separatorColor, plainBackgroundColor: searchTheme.backgroundColor, accentColor: searchTheme.accentColor, accentContrastColor: searchTheme.backgroundColor, menuBackgroundColor: searchTheme.backgroundColor, segmentedControlBackgroundImage: nil, segmentedControlSelectedImage: nil, segmentedControlHighlightedImage: nil, segmentedControlDividerImage: nil), avatarPlaceholder: nil) } public func legacyLocationController(message: Message?, mapMedia: TelegramMediaMap, context: AccountContext, openPeer: @escaping (Peer) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D, Int32) -> Void, stopLiveLocation: @escaping () -> Void, openUrl: @escaping (String) -> Void) -> ViewController { let legacyLocation = TGLocationMediaAttachment() legacyLocation.latitude = mapMedia.latitude legacyLocation.longitude = mapMedia.longitude if let venue = mapMedia.venue { legacyLocation.venue = TGVenueAttachment(title: venue.title, address: venue.address, provider: venue.provider, venueId: venue.id, type: venue.type) } let presentationData = context.sharedContext.currentPresentationData.with { $0 } let legacyController = LegacyController(presentation: .navigation, theme: presentationData.theme, strings: presentationData.strings) legacyController.navigationPresentation = .modal let controller: TGLocationViewController if let message = message { let legacyMessage = makeLegacyMessage(message) let legacyAuthor: AnyObject? = message.author.flatMap(makeLegacyPeer) let updatedLocations = SSignal(generator: { subscriber in let disposable = topPeerActiveLiveLocationMessages(viewTracker: context.account.viewTracker, accountPeerId: context.account.peerId, peerId: message.id.peerId).start(next: { (_, messages) in var result: [Any] = [] let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) loop: for message in messages { var liveBroadcastingTimeout: Int32 = 0 mediaLoop: for media in message.media { if let map = media as? TelegramMediaMap, let timeout = map.liveBroadcastingTimeout { liveBroadcastingTimeout = timeout break mediaLoop } } let legacyMessage = makeLegacyMessage(message) guard let legacyAuthor = message.author.flatMap(makeLegacyPeer) else { continue loop } let remainingTime = max(0, message.timestamp + liveBroadcastingTimeout - currentTime) if legacyMessage.locationAttachment?.period != 0 { let hasOwnSession = message.localTags.contains(.OutgoingLiveLocation) var isOwn = false if !message.flags.contains(.Incoming) { isOwn = true } else if let peer = message.peers[message.id.peerId] as? TelegramChannel { isOwn = peer.hasPermission(.sendMessages) } let liveLocation = TGLiveLocation(message: legacyMessage, peer: legacyAuthor, hasOwnSession: hasOwnSession, isOwnLocation: isOwn, isExpired: remainingTime == 0)! result.append(liveLocation) } } subscriber?.putNext(result) }) return SBlockDisposable(block: { disposable.dispose() }) })! if let liveBroadcastingTimeout = mapMedia.liveBroadcastingTimeout { let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) let remainingTime = max(0, message.timestamp + liveBroadcastingTimeout - currentTime) let messageLiveLocation = TGLiveLocation(message: legacyMessage, peer: legacyAuthor, hasOwnSession: false, isOwnLocation: false, isExpired: remainingTime == 0)! controller = TGLocationViewController(context: legacyController.context, liveLocation: messageLiveLocation) if remainingTime == 0 { let freezeLocations: [Any] = [messageLiveLocation] controller.setLiveLocationsSignal(.single(freezeLocations)) } else { controller.setLiveLocationsSignal(updatedLocations) } } else { controller = TGLocationViewController(context: legacyController.context, message: legacyMessage, peer: legacyAuthor)! controller.receivingPeer = message.peers[message.id.peerId].flatMap(makeLegacyPeer) controller.setLiveLocationsSignal(updatedLocations) } let namespacesWithEnabledLiveLocation: Set = Set([ Namespaces.Peer.CloudChannel, Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudUser ]) if namespacesWithEnabledLiveLocation.contains(message.id.peerId.namespace) { controller.allowLiveLocationSharing = true } } else { let attachment = TGLocationMediaAttachment() attachment.latitude = mapMedia.latitude attachment.longitude = mapMedia.longitude controller = TGLocationViewController(context: legacyController.context, locationAttachment: attachment, peer: nil) } controller.remainingTimeForMessage = { message in return legacyRemainingTime(message: message!) } controller.liveLocationStarted = { [weak legacyController] coordinate, period in sendLiveLocation(coordinate, period) legacyController?.dismiss() } controller.liveLocationStopped = { [weak legacyController] in stopLiveLocation() legacyController?.dismiss() } let theme = (context.sharedContext.currentPresentationData.with { $0 }).theme controller.pallete = legacyLocationPalette(from: theme) controller.modalMode = true controller.presentActionsMenu = { [weak legacyController] legacyLocation, directions in if let strongLegacyController = legacyController, let location = legacyLocation { let map = telegramMap(for: location) let presentationData = context.sharedContext.currentPresentationData.with { $0 } let shareAction = OpenInControllerAction(title: presentationData.strings.Conversation_ContextMenuShare, action: { strongLegacyController.present(ShareController(context: context, subject: .mapMedia(map), externalShare: true), in: .window(.root), with: nil) }) strongLegacyController.present(OpenInActionSheetController(context: context, item: .location(location: map, withDirections: directions), additionalAction: !directions ? shareAction : nil, openUrl: openUrl), in: .window(.root), with: nil) } } controller.onViewDidAppear = { [weak controller] in if let strongController = controller { strongController.view.disablesInteractiveModalDismiss = true strongController.view.disablesInteractiveTransitionGestureRecognizer = true strongController.locationMapView.interactiveTransitionGestureRecognizerTest = { point -> Bool in return point.x > 30.0 } } } legacyController.navigationItem.title = controller.navigationItem.title controller.updateRightBarItem = { item, action, animated in if action { legacyController.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationShareIcon(theme), style: .plain, target: controller, action: #selector(controller.actionsButtonPressed)) } else { legacyController.navigationItem.setRightBarButton(item, animated: animated) } } legacyController.bind(controller: controller) let presentationDisposable = context.sharedContext.presentationData.start(next: { [weak controller] presentationData in if let controller = controller { controller.pallete = legacyLocationPalette(from: presentationData.theme) } }) legacyController.disposables.add(presentationDisposable) return legacyController }