From 3a998955b7a66270147a12f34897bc3c19afdc79 Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 11 Jan 2018 22:44:38 +0400 Subject: [PATCH] no message --- TelegramUI.xcodeproj/project.pbxproj | 28 ++ TelegramUI/ChatController.swift | 59 +++- TelegramUI/ChatHistoryListNode.swift | 59 ++-- .../ChatMessageLiveLocationPositionNode.swift | 2 + .../ChatMessageMapBubbleContentNode.swift | 6 +- TelegramUI/DeviceLocationManager.swift | 130 +++++++++ TelegramUI/ItemListRevealOptionsNode.swift | 4 +- TelegramUI/LegacyLocationController.swift | 27 +- TelegramUI/LegacyLocationPicker.swift | 17 +- TelegramUI/LiveLocationManager.swift | 216 +++++++++++++++ TelegramUI/LiveLocationSummaryManager.swift | 260 ++++++++++++++++++ TelegramUI/MessageContentKind.swift | 17 +- TelegramUI/OpenChatMessage.swift | 4 + TelegramUI/PhotoResources.swift | 4 +- TelegramUI/StoredMessageFromSearchPeer.swift | 2 +- TelegramUI/TelegramApplicationContext.swift | 11 +- TelegramUI/ThemeSettingsChatPreviewItem.swift | 6 +- 17 files changed, 804 insertions(+), 48 deletions(-) create mode 100644 TelegramUI/DeviceLocationManager.swift create mode 100644 TelegramUI/LiveLocationManager.swift create mode 100644 TelegramUI/LiveLocationSummaryManager.swift diff --git a/TelegramUI.xcodeproj/project.pbxproj b/TelegramUI.xcodeproj/project.pbxproj index 29f33359e1..5f577ea00f 100644 --- a/TelegramUI.xcodeproj/project.pbxproj +++ b/TelegramUI.xcodeproj/project.pbxproj @@ -60,6 +60,9 @@ D033C60B1F0D306E0044EABA /* TelegramVideoNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D033C60A1F0D306E0044EABA /* TelegramVideoNode.swift */; }; D0430B001FF4570500A35ADD /* WebController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0430AFF1FF4570500A35ADD /* WebController.swift */; }; D0430B021FF4584100A35ADD /* WebControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0430B011FF4584100A35ADD /* WebControllerNode.swift */; }; + D046142E2004DB3700EC0EF2 /* LiveLocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D046142D2004DB3700EC0EF2 /* LiveLocationManager.swift */; }; + D04614372005094E00EC0EF2 /* DeviceLocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04614362005094E00EC0EF2 /* DeviceLocationManager.swift */; }; + D0461439200514F000EC0EF2 /* LiveLocationSummaryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0461438200514F000EC0EF2 /* LiveLocationSummaryManager.swift */; }; D0471B491EFD59170074D609 /* BotCheckoutControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0471B481EFD59170074D609 /* BotCheckoutControllerNode.swift */; }; D0471B4B1EFD64AC0074D609 /* BotCheckoutHeaderItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0471B4A1EFD64AC0074D609 /* BotCheckoutHeaderItem.swift */; }; D0471B4F1EFD84600074D609 /* BotCheckoutPriceItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0471B4E1EFD84600074D609 /* BotCheckoutPriceItem.swift */; }; @@ -1172,6 +1175,9 @@ D042C6891E8DAAB000C863B0 /* ChatItemGalleryFooterContentNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatItemGalleryFooterContentNode.swift; sourceTree = ""; }; D0430AFF1FF4570500A35ADD /* WebController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebController.swift; sourceTree = ""; }; D0430B011FF4584100A35ADD /* WebControllerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebControllerNode.swift; sourceTree = ""; }; + D046142D2004DB3700EC0EF2 /* LiveLocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveLocationManager.swift; sourceTree = ""; }; + D04614362005094E00EC0EF2 /* DeviceLocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceLocationManager.swift; sourceTree = ""; }; + D0461438200514F000EC0EF2 /* LiveLocationSummaryManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveLocationSummaryManager.swift; sourceTree = ""; }; D04662801E68BA64006FAFC4 /* TransformOutgoingMessageMedia.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransformOutgoingMessageMedia.swift; sourceTree = ""; }; D0471B481EFD59170074D609 /* BotCheckoutControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BotCheckoutControllerNode.swift; sourceTree = ""; }; D0471B4A1EFD64AC0074D609 /* BotCheckoutHeaderItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BotCheckoutHeaderItem.swift; sourceTree = ""; }; @@ -2467,6 +2473,23 @@ name = Web; sourceTree = ""; }; + D046142C2004DB1D00EC0EF2 /* Live Location Manager */ = { + isa = PBXGroup; + children = ( + D046142D2004DB3700EC0EF2 /* LiveLocationManager.swift */, + D0461438200514F000EC0EF2 /* LiveLocationSummaryManager.swift */, + ); + name = "Live Location Manager"; + sourceTree = ""; + }; + D04614352005093B00EC0EF2 /* Device Location */ = { + isa = PBXGroup; + children = ( + D04614362005094E00EC0EF2 /* DeviceLocationManager.swift */, + ); + name = "Device Location"; + sourceTree = ""; + }; D0471B521EFD8EBC0074D609 /* Resources */ = { isa = PBXGroup; children = ( @@ -4665,7 +4688,9 @@ D0F69E911D6B8C8E0046BCD6 /* Utils */ = { isa = PBXGroup; children = ( + D04614352005093B00EC0EF2 /* Device Location */, D025A4241F79428300563950 /* Fetch Manager */, + D046142C2004DB1D00EC0EF2 /* Live Location Manager */, D0B844551DAC3AEE005F29E1 /* PresenceStrings.swift */, D08775081E3E59DE00A97350 /* PeerNotificationSoundStrings.swift */, D0F69E931D6B8C9B0046BCD6 /* ProgressiveImage.swift */, @@ -5225,6 +5250,7 @@ D0EC6D201EB9F58800EBF1C3 /* PeerAvatar.swift in Sources */, D0EC6D211EB9F58800EBF1C3 /* FileResources.swift in Sources */, D0EC6FB31EBA114200EBF1C3 /* aec_core_neon.cc in Sources */, + D0461439200514F000EC0EF2 /* LiveLocationSummaryManager.swift in Sources */, D056CD781FF2A6EE00880D28 /* ChatMessageSwipeToReplyNode.swift in Sources */, D0CE67941F7DB45100FFB557 /* ChatMessageContactBubbleContentNode.swift in Sources */, D0943AFE1FDAE454001522CC /* ChatMultipleAvatarsNavigationNode.swift in Sources */, @@ -5399,6 +5425,7 @@ D0EC6D851EB9F58800EBF1C3 /* ChatHistoryLocation.swift in Sources */, D0EC6D861EB9F58800EBF1C3 /* ChatAvatarNavigationNode.swift in Sources */, D0EC6D871EB9F58800EBF1C3 /* ChatTitleView.swift in Sources */, + D04614372005094E00EC0EF2 /* DeviceLocationManager.swift in Sources */, D0EC6D881EB9F58800EBF1C3 /* ChatControllerTitlePanelNodeContainer.swift in Sources */, D0EC6D891EB9F58800EBF1C3 /* ChatSecretAutoremoveTimerActionSheet.swift in Sources */, D0EC6D8A1EB9F58800EBF1C3 /* ChatInfo.swift in Sources */, @@ -5802,6 +5829,7 @@ D0EC6E861EB9F58900EBF1C3 /* UIImage+WebP.m in Sources */, D0EC6E871EB9F58900EBF1C3 /* FastBlur.m in Sources */, D0ACCB1C1EC5FF4B0079D8BF /* ChatMessageCallBubbleContentNode.swift in Sources */, + D046142E2004DB3700EC0EF2 /* LiveLocationManager.swift in Sources */, D05677511F4CA0C2001B723E /* InstantPagePeerReferenceItem.swift in Sources */, D0EC6E881EB9F58900EBF1C3 /* FFMpegSwResample.m in Sources */, D0EC6E891EB9F58900EBF1C3 /* FrameworkBundle.swift in Sources */, diff --git a/TelegramUI/ChatController.swift b/TelegramUI/ChatController.swift index fb0fa6d584..6a0d929fc1 100644 --- a/TelegramUI/ChatController.swift +++ b/TelegramUI/ChatController.swift @@ -2537,21 +2537,52 @@ public final class ChatController: TelegramController { } private func presentMapPicker() { - self.chatDisplayNode.dismissInput() - self.present(legacyLocationPickerController(sendLocation: { [weak self] coordinate, venue in - if let strongSelf = self { - let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId - strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ - if let strongSelf = self { - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } - }) - } - }) - let message: EnqueueMessage = .message(text: "", attributes: [], media: TelegramMediaMap(latitude: coordinate.latitude, longitude: coordinate.longitude, geoPlace: nil, venue: venue, liveBroadcastingTimeout: nil), replyToMessageId: replyMessageId, localGroupingKey: nil) - strongSelf.sendMessages([message]) + guard let peer = self.presentationInterfaceState.peer else { + return + } + let selfPeerId: PeerId + if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + selfPeerId = peer.id + } else { + selfPeerId = self.account.peerId + } + let _ = (self.account.postbox.modify { modifier -> Peer? in + return modifier.getPeer(selfPeerId) + } + |> deliverOnMainQueue).start(next: { [weak self] selfPeer in + guard let strongSelf = self, let selfPeer = selfPeer else { + return } - }, theme: self.presentationData.theme), in: .window(.root)) + + strongSelf.chatDisplayNode.dismissInput() + strongSelf.present(legacyLocationPickerController(selfPeer: selfPeer, peer: peer, sendLocation: { coordinate, venue in + if let strongSelf = self { + let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId + strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ + if let strongSelf = self { + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } + }) + } + }) + let message: EnqueueMessage = .message(text: "", attributes: [], media: TelegramMediaMap(latitude: coordinate.latitude, longitude: coordinate.longitude, geoPlace: nil, venue: venue, liveBroadcastingTimeout: nil), replyToMessageId: replyMessageId, localGroupingKey: nil) + strongSelf.sendMessages([message]) + } + }, sendLiveLocation: { [weak self] coordinate, period in + if let strongSelf = self { + let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId + strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ + if let strongSelf = self { + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } + }) + } + }) + let message: EnqueueMessage = .message(text: "", attributes: [], media: TelegramMediaMap(latitude: coordinate.latitude, longitude: coordinate.longitude, geoPlace: nil, venue: nil, liveBroadcastingTimeout: period), replyToMessageId: replyMessageId, localGroupingKey: nil) + strongSelf.sendMessages([message]) + } + }, theme: strongSelf.presentationData.theme), in: .window(.root)) + }) } private func sendMessages(_ messages: [EnqueueMessage]) { diff --git a/TelegramUI/ChatHistoryListNode.swift b/TelegramUI/ChatHistoryListNode.swift index 4ce4b8d1bf..061a85ab61 100644 --- a/TelegramUI/ChatHistoryListNode.swift +++ b/TelegramUI/ChatHistoryListNode.swift @@ -513,26 +513,51 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { var messageIdsWithViewCount: [MessageId] = [] var messageIdsWithUnseenPersonalMention: [MessageId] = [] for i in (indexRange.0 ... indexRange.1) { - if case let .MessageEntry(message, _, _, _, _) = historyView.filteredEntries[i] { - var hasUnconsumedMention = false - var hasUnsonsumedContent = false - if message.tags.contains(.unseenPersonalMessage) { - for attribute in message.attributes { - if let attribute = attribute as? ConsumablePersonalMentionMessageAttribute, !attribute.pending { - hasUnconsumedMention = true + switch historyView.filteredEntries[i] { + case let .MessageEntry(message, _, _, _, _): + var hasUnconsumedMention = false + var hasUnsonsumedContent = false + if message.tags.contains(.unseenPersonalMessage) { + for attribute in message.attributes { + if let attribute = attribute as? ConsumablePersonalMentionMessageAttribute, !attribute.pending { + hasUnconsumedMention = true + } } } - } - for attribute in message.attributes { - if attribute is ViewCountMessageAttribute { - messageIdsWithViewCount.append(message.id) - } else if let attribute = attribute as? ConsumableContentMessageAttribute, !attribute.consumed { - hasUnsonsumedContent = true + for attribute in message.attributes { + if attribute is ViewCountMessageAttribute { + messageIdsWithViewCount.append(message.id) + } else if let attribute = attribute as? ConsumableContentMessageAttribute, !attribute.consumed { + hasUnsonsumedContent = true + } } - } - if hasUnconsumedMention && !hasUnsonsumedContent { - messageIdsWithUnseenPersonalMention.append(message.id) - } + if hasUnconsumedMention && !hasUnsonsumedContent { + messageIdsWithUnseenPersonalMention.append(message.id) + } + case let .MessageGroupEntry(_, messages, _): + for (message, _, _) in messages { + var hasUnconsumedMention = false + var hasUnsonsumedContent = false + if message.tags.contains(.unseenPersonalMessage) { + for attribute in message.attributes { + if let attribute = attribute as? ConsumablePersonalMentionMessageAttribute, !attribute.pending { + hasUnconsumedMention = true + } + } + } + for attribute in message.attributes { + if attribute is ViewCountMessageAttribute { + messageIdsWithViewCount.append(message.id) + } else if let attribute = attribute as? ConsumableContentMessageAttribute, !attribute.consumed { + hasUnsonsumedContent = true + } + } + if hasUnconsumedMention && !hasUnsonsumedContent { + messageIdsWithUnseenPersonalMention.append(message.id) + } + } + default: + break } } diff --git a/TelegramUI/ChatMessageLiveLocationPositionNode.swift b/TelegramUI/ChatMessageLiveLocationPositionNode.swift index 9943703251..5b2e9b320e 100644 --- a/TelegramUI/ChatMessageLiveLocationPositionNode.swift +++ b/TelegramUI/ChatMessageLiveLocationPositionNode.swift @@ -13,6 +13,7 @@ private func addPulseAnimations(layer: CALayer) { scaleAnimation.duration = 3.0 scaleAnimation.repeatCount = Float.infinity scaleAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) + scaleAnimation.beginTime = 1.0 layer.add(scaleAnimation, forKey: "pulse-scale") let opacityAnimation = CAKeyframeAnimation(keyPath: "opacity") @@ -20,6 +21,7 @@ private func addPulseAnimations(layer: CALayer) { opacityAnimation.keyTimes = [0.0 as NSNumber, 0.4 as NSNumber, 0.62 as NSNumber, 1.0 as NSNumber] opacityAnimation.duration = 3.0 opacityAnimation.repeatCount = Float.infinity + opacityAnimation.beginTime = 1.0 layer.add(opacityAnimation, forKey: "pulse-opacity") } diff --git a/TelegramUI/ChatMessageMapBubbleContentNode.swift b/TelegramUI/ChatMessageMapBubbleContentNode.swift index 12031d4db9..2d20040823 100644 --- a/TelegramUI/ChatMessageMapBubbleContentNode.swift +++ b/TelegramUI/ChatMessageMapBubbleContentNode.swift @@ -145,7 +145,11 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { let maxTextWidth: CGFloat if activeLiveBroadcastingTimeout != nil { - imageCorners = chatMessageBubbleImageContentCorners(relativeContentPosition: position, normalRadius: layoutConstants.image.defaultCornerRadius, mergedRadius: layoutConstants.image.mergedCornerRadius, mergedWithAnotherContentRadius: layoutConstants.image.contentMergedCornerRadius) + var relativePosition = position + if case let .linear(top, _) = position { + relativePosition = .linear(top: top, bottom: ChatMessageBubbleRelativePosition.Neighbour) + } + imageCorners = chatMessageBubbleImageContentCorners(relativeContentPosition: relativePosition, normalRadius: layoutConstants.image.defaultCornerRadius, mergedRadius: layoutConstants.image.mergedCornerRadius, mergedWithAnotherContentRadius: layoutConstants.image.contentMergedCornerRadius) maxTextWidth = constrainedSize.width - bubbleInsets.left + bubbleInsets.right - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right - 40.0 } else { diff --git a/TelegramUI/DeviceLocationManager.swift b/TelegramUI/DeviceLocationManager.swift new file mode 100644 index 0000000000..49c6732624 --- /dev/null +++ b/TelegramUI/DeviceLocationManager.swift @@ -0,0 +1,130 @@ +import Foundation +import CoreLocation +import SwiftSignalKit +import TelegramCore + +enum DeviceLocationMode: Int32 { + case precise = 0 +} + +private final class DeviceLocationSubscriber { + let id: Int32 + let mode: DeviceLocationMode + let update: (CLLocationCoordinate2D) -> Void + + init(id: Int32, mode: DeviceLocationMode, update: @escaping (CLLocationCoordinate2D) -> Void) { + self.id = id + self.mode = mode + self.update = update + } +} + +private func getTopMode(subscribers: [DeviceLocationSubscriber]) -> DeviceLocationMode? { + var mode: DeviceLocationMode? + for subscriber in subscribers { + if mode == nil || subscriber.mode.rawValue > mode!.rawValue { + mode = subscriber.mode + } + } + return mode +} + +final class DeviceLocationManager: NSObject, CLLocationManagerDelegate { + private let queue: Queue + + private let manager: CLLocationManager + private var requestedAuthorization = false + + private var nextSubscriberId: Int32 = 0 + private var subscribers: [DeviceLocationSubscriber] = [] + private var currentTopMode: DeviceLocationMode? + + private var currentLocation: CLLocationCoordinate2D? + + init(queue: Queue) { + assert(queue.isCurrent()) + + self.queue = queue + self.manager = CLLocationManager() + + super.init() + + if #available(iOSApplicationExtension 9.0, *) { + self.manager.allowsBackgroundLocationUpdates = true + } + self.manager.delegate = self + self.manager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters + self.manager.distanceFilter = 20.0 + self.manager.activityType = .other + self.manager.pausesLocationUpdatesAutomatically = false + } + + func push(mode: DeviceLocationMode, updated: @escaping (CLLocationCoordinate2D) -> Void) -> Disposable { + assert(self.queue.isCurrent()) + + let id = self.nextSubscriberId + self.nextSubscriberId += 1 + self.subscribers.append(DeviceLocationSubscriber(id: id, mode: mode, update: updated)) + + if let currentLocation = self.currentLocation { + updated(currentLocation) + } + + self.updateTopMode() + + let queue = self.queue + return ActionDisposable { [weak queue, weak self] in + if let queue = queue { + queue.async { + if let strongSelf = self { + loop: for i in 0 ..< strongSelf.subscribers.count { + if strongSelf.subscribers[i].id == id { + strongSelf.subscribers.remove(at: i) + break loop + } + } + + strongSelf.updateTopMode() + } + } + } + } + } + + private func updateTopMode() { + assert(self.queue.isCurrent()) + + let previousTopMode = self.currentTopMode + let topMode = getTopMode(subscribers: self.subscribers) + if topMode != previousTopMode { + self.currentTopMode = topMode + if let topMode = topMode { + Logger.shared.log("DeviceLocation", "setting mode \(topMode)") + if previousTopMode == nil { + if !self.requestedAuthorization { + self.requestedAuthorization = true + self.manager.requestAlwaysAuthorization() + } + self.manager.startUpdatingLocation() + } + } else { + self.currentLocation = nil + self.manager.stopUpdatingLocation() + Logger.shared.log("DeviceLocation", "stopped") + } + } + } + + func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { + assert(self.queue.isCurrent()) + + if let location = locations.first { + if self.currentTopMode != nil { + self.currentLocation = location.coordinate + for subscriber in self.subscribers { + subscriber.update(location.coordinate) + } + } + } + } +} diff --git a/TelegramUI/ItemListRevealOptionsNode.swift b/TelegramUI/ItemListRevealOptionsNode.swift index 8061f4da6d..ebe3afe5a5 100644 --- a/TelegramUI/ItemListRevealOptionsNode.swift +++ b/TelegramUI/ItemListRevealOptionsNode.swift @@ -63,7 +63,7 @@ final class ItemListRevealOptionNode: ASDisplayNode { override func didLoad() { super.didLoad() - if let url = frameworkBundle.url(forResource: "mute", withExtension: "json") { + /*if let url = frameworkBundle.url(forResource: "mute", withExtension: "json") { let animView = LOTAnimationView(contentsOf: url) animView.frame = CGRect(origin: CGPoint(), size: CGSize(width: 50.0, height: 50.0)) self.animView = animView @@ -74,7 +74,7 @@ final class ItemListRevealOptionNode: ASDisplayNode { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2.0, execute: { animView.play() }) - } + }*/ } override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { diff --git a/TelegramUI/LegacyLocationController.swift b/TelegramUI/LegacyLocationController.swift index 457a86dd77..08b8622086 100644 --- a/TelegramUI/LegacyLocationController.swift +++ b/TelegramUI/LegacyLocationController.swift @@ -8,7 +8,7 @@ private func generateClearIcon(color: UIColor) -> UIImage? { return generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: color) } -private func makeLegacyPeer(_ peer: Peer) -> AnyObject? { +func makeLegacyPeer(_ peer: Peer) -> AnyObject? { if let user = peer as? TelegramUser { let legacyUser = TGUser() legacyUser.uid = user.id.id @@ -104,7 +104,7 @@ private func legacyRemainingTime(message: TGMessage) -> SSignal { }) } -func legacyLocationController(message: Message, mapMedia: TelegramMediaMap, account: Account, openPeer: @escaping (Peer) -> Void) -> ViewController { +func legacyLocationController(message: Message, mapMedia: TelegramMediaMap, account: Account, openPeer: @escaping (Peer) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D, Int32) -> Void, stopLiveLocation: @escaping () -> Void) -> ViewController { let legacyAuthor: AnyObject? = message.author.flatMap(makeLegacyPeer) let legacyLocation = TGLocationMediaAttachment() @@ -128,15 +128,24 @@ func legacyLocationController(message: Message, mapMedia: TelegramMediaMap, acco let messageLiveLocation = TGLiveLocation(message: legacyMessage, peer: legacyAuthor, hasOwnSession: false, isOwnLocation: false, isExpired: remainingTime == 0)! controller = TGLocationViewController(context: legacyController.context, liveLocation: messageLiveLocation) + 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() + } if remainingTime == 0 { let freezeLocations: [Any] = [messageLiveLocation] controller.setLiveLocationsSignal(.single(freezeLocations)) } else { let updatedLocations = SSignal(generator: { subscriber in - let disposable = topPeerActiveLiveLocationMessages(account: account, peerId: message.id.peerId).start(next: { messages in + let disposable = topPeerActiveLiveLocationMessages(viewTracker: account.viewTracker, peerId: message.id.peerId).start(next: { messages in var result: [Any] = [] let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) loop: for message in messages { @@ -146,7 +155,15 @@ func legacyLocationController(message: Message, mapMedia: TelegramMediaMap, acco } let remainingTime = max(0, message.timestamp + liveBroadcastingTimeout - currentTime) if legacyMessage.locationAttachment?.period != 0 { - let liveLocation = TGLiveLocation(message: legacyMessage, peer: legacyAuthor, hasOwnSession: false, isOwnLocation: false, isExpired: remainingTime == 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.hasAdminRights(.canPostMessages) + } + + let liveLocation = TGLiveLocation(message: legacyMessage, peer: legacyAuthor, hasOwnSession: hasOwnSession, isOwnLocation: isOwn, isExpired: remainingTime == 0)! result.append(liveLocation) } } @@ -169,7 +186,7 @@ func legacyLocationController(message: Message, mapMedia: TelegramMediaMap, acco Namespaces.Peer.CloudUser ]) if namespacesWithEnabledLiveLocation.contains(message.id.peerId.namespace) { - //controller.allowLiveLocationSharing = true + controller.allowLiveLocationSharing = true } let theme = (account.telegramApplicationContext.currentPresentationData.with { $0 }).theme diff --git a/TelegramUI/LegacyLocationPicker.swift b/TelegramUI/LegacyLocationPicker.swift index 6d8f60b368..38b712b016 100644 --- a/TelegramUI/LegacyLocationPicker.swift +++ b/TelegramUI/LegacyLocationPicker.swift @@ -2,17 +2,28 @@ import Foundation import Display import LegacyComponents import TelegramCore +import Postbox private func generateClearIcon(color: UIColor) -> UIImage? { return generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: color) } -func legacyLocationPickerController(sendLocation: @escaping (CLLocationCoordinate2D, MapVenue?) -> Void, theme: PresentationTheme) -> ViewController { +func legacyLocationPickerController(selfPeer: Peer, peer: Peer, sendLocation: @escaping (CLLocationCoordinate2D, MapVenue?) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D, Int32) -> Void, theme: PresentationTheme) -> ViewController { let legacyController = LegacyController(presentation: .modal(animateIn: true), theme: theme) let controller = TGLocationPickerController(context: legacyController.context, intent: TGLocationPickerControllerDefaultIntent)! + controller.peer = makeLegacyPeer(selfPeer) + controller.receivingPeer = makeLegacyPeer(peer) let listTheme = theme.list let searchTheme = theme.rootController.activeNavigationSearchBar controller.pallete = 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: listTheme.controlSecondaryColor, 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.activeNavigationSearchBar.inputClearButtonColor), barBackgroundColor: searchTheme.backgroundColor, barSeparatorColor: searchTheme.separatorColor, plainBackgroundColor: searchTheme.backgroundColor, accentColor: searchTheme.accentColor, accentContrastColor: searchTheme.accentColor, menuBackgroundColor: searchTheme.backgroundColor, segmentedControlBackgroundImage: nil, segmentedControlSelectedImage: nil, segmentedControlHighlightedImage: nil, segmentedControlDividerImage: nil), avatarPlaceholder: nil) + let namespacesWithEnabledLiveLocation: Set = Set([ + Namespaces.Peer.CloudChannel, + Namespaces.Peer.CloudGroup, + Namespaces.Peer.CloudUser + ]) + if namespacesWithEnabledLiveLocation.contains(peer.id.namespace) { + controller.allowLiveLocationSharing = true + } let navigationController = TGNavigationController(controllers: [controller])! controller.navigation_setDismiss({ [weak legacyController] in legacyController?.dismiss() @@ -24,5 +35,9 @@ func legacyLocationPickerController(sendLocation: @escaping (CLLocationCoordinat }) legacyController?.dismiss() } + controller.liveLocationStarted = { [weak legacyController] coordinate, period in + sendLiveLocation(coordinate, period) + legacyController?.dismiss() + } return legacyController } diff --git a/TelegramUI/LiveLocationManager.swift b/TelegramUI/LiveLocationManager.swift new file mode 100644 index 0000000000..899af109e4 --- /dev/null +++ b/TelegramUI/LiveLocationManager.swift @@ -0,0 +1,216 @@ +import Foundation +import TelegramCore +import Postbox +import SwiftSignalKit +import CoreLocation + +public final class LiveLocationManager { + private let queue = Queue.mainQueue() + + private let postbox: Postbox + private let network: Network + private let stateManager: AccountStateManager + private let locationManager: DeviceLocationManager + + let summaryManager: LiveLocationSummaryManager + + private var requiredLocationTypeDisposable: Disposable? + private let hasActiveMessagesToBroadcast = ValuePromise(false, ignoreRepeated: true) + + + public var isPolling: Signal { + return self.pollingOnce.get() + } + private let pollingOnce = ValuePromise(false, ignoreRepeated: true) + private var pollingOnceValue = false { + didSet { + self.pollingOnce.set(self.pollingOnceValue) + } + } + + private let deviceLocationDisposable = MetaDisposable() + private var messagesDisposable: Disposable? + + private var broadcastToMessageIds = Set() + private var stopMessageIds = Set() + + private let editMessageDisposables = DisposableDict() + + init(postbox: Postbox, network: Network, accountPeerId: PeerId, viewTracker: AccountViewTracker, stateManager: AccountStateManager, locationManager: DeviceLocationManager, inForeground: Signal) { + self.postbox = postbox + self.network = network + self.stateManager = stateManager + self.locationManager = locationManager + + self.summaryManager = LiveLocationSummaryManager(queue: self.queue, postbox: postbox, accountPeerId: accountPeerId, viewTracker: viewTracker) + + let viewKey: PostboxViewKey = .localMessageTag(.OutgoingLiveLocation) + self.messagesDisposable = (postbox.combinedView(keys: [viewKey]) + |> deliverOn(self.queue)).start(next: { [weak self] view in + if let strongSelf = self { + let timestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + + var broadcastToMessageIds = Set() + var stopMessageIds = Set() + + if let view = view.views[viewKey] as? LocalMessageTagsView { + for message in view.messages.values { + if !message.flags.contains(.Incoming) { + if message.flags.intersection([.Failed, .Unsent]).isEmpty { + var activeLiveBroadcastingTimeout: Int32? + for media in message.media { + if let telegramMap = media as? TelegramMediaMap { + if let liveBroadcastingTimeout = telegramMap.liveBroadcastingTimeout { + if message.timestamp + liveBroadcastingTimeout > timestamp { + activeLiveBroadcastingTimeout = liveBroadcastingTimeout + } + } + } + } + if let _ = activeLiveBroadcastingTimeout { + broadcastToMessageIds.insert(message.id) + } else { + stopMessageIds.insert(message.id) + } + } + } else { + assertionFailure() + } + } + } + + strongSelf.update(broadcastToMessageIds: broadcastToMessageIds, stopMessageIds: stopMessageIds) + } + }) + + self.requiredLocationTypeDisposable = (combineLatest( + inForeground |> deliverOn(self.queue), + self.hasActiveMessagesToBroadcast.get() |> deliverOn(self.queue), + self.pollingOnce.get() |> deliverOn(self.queue) + ) + |> map { inForeground, hasActiveMessagesToBroadcast, pollingOnce -> Bool in + if (inForeground || pollingOnce) && hasActiveMessagesToBroadcast { + return true + } else { + return false + } + } + |> distinctUntilChanged + |> deliverOn(self.queue)).start(next: { [weak self] value in + if let strongSelf = self { + if value { + let queue = strongSelf.queue + strongSelf.deviceLocationDisposable.set(strongSelf.locationManager.push(mode: .precise, updated: { coordinate in + queue.async { + self?.updateDeviceCoordinate(coordinate) + } + })) + } else { + strongSelf.deviceLocationDisposable.set(nil) + } + } + }) + } + + deinit { + self.requiredLocationTypeDisposable?.dispose() + self.deviceLocationDisposable.dispose() + self.messagesDisposable?.dispose() + self.editMessageDisposables.dispose() + } + + private func update(broadcastToMessageIds: Set, stopMessageIds: Set) { + assert(self.queue.isCurrent()) + + if self.broadcastToMessageIds == broadcastToMessageIds && self.stopMessageIds == stopMessageIds { + return + } + + if self.broadcastToMessageIds != broadcastToMessageIds { + self.summaryManager.update(messageIds: broadcastToMessageIds) + } + + let wasEmpty = self.broadcastToMessageIds.isEmpty + self.broadcastToMessageIds = broadcastToMessageIds + + let removedFromActions = self.broadcastToMessageIds.union(self.stopMessageIds).subtracting(broadcastToMessageIds.union(stopMessageIds)) + for id in removedFromActions { + self.editMessageDisposables.set(nil, forKey: id) + } + + if !broadcastToMessageIds.isEmpty { + if wasEmpty { + self.hasActiveMessagesToBroadcast.set(true) + } + } else if !wasEmpty { + self.hasActiveMessagesToBroadcast.set(false) + } + + let addedStopped = stopMessageIds.subtracting(self.stopMessageIds) + self.stopMessageIds = stopMessageIds + for id in addedStopped { + self.editMessageDisposables.set((requestEditLiveLocation(postbox: self.postbox, network: self.network, stateManager: self.stateManager, messageId: id, coordinate: nil) + |> deliverOn(self.queue)).start(completed: { [weak self] in + if let strongSelf = self { + strongSelf.editMessageDisposables.set(nil, forKey: id) + } + }), forKey: id) + } + } + + private func updateDeviceCoordinate(_ coordinate: CLLocationCoordinate2D) { + assert(self.queue.isCurrent()) + + let ids = self.broadcastToMessageIds + let remainingIds = Atomic>(value: ids) + for id in ids { + self.editMessageDisposables.set((requestEditLiveLocation(postbox: self.postbox, network: self.network, stateManager: self.stateManager, messageId: id, coordinate: (latitude: coordinate.latitude, longitude: coordinate.longitude)) + |> deliverOn(self.queue)).start(completed: { [weak self] in + if let strongSelf = self { + strongSelf.editMessageDisposables.set(nil, forKey: id) + + let result = remainingIds.modify { current in + var current = current + current.remove(id) + return current + } + if result.isEmpty { + strongSelf.pollingOnceValue = false + } + } + }), forKey: id) + } + } + + func cancelLiveLocation(peerId: PeerId) { + assert(self.queue.isCurrent()) + + let ids = self.broadcastToMessageIds.filter({ $0.peerId == peerId }) + if !ids.isEmpty { + let _ = self.postbox.modify({ modifier -> Void in + for id in ids { + modifier.updateMessage(id, update: { currentMessage in + var storeForwardInfo: StoreMessageForwardInfo? + if let forwardInfo = currentMessage.forwardInfo { + storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature) + } + var updatedMedia = currentMessage.media + let timestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + for i in 0 ..< updatedMedia.count { + if let media = updatedMedia[i] as? TelegramMediaMap, let _ = media.liveBroadcastingTimeout { + updatedMedia[i] = TelegramMediaMap(latitude: media.latitude, longitude: media.longitude, geoPlace: media.geoPlace, venue: media.venue, liveBroadcastingTimeout: max(0, timestamp - currentMessage.timestamp - 1)) + } + } + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: updatedMedia)) + }) + } + }).start() + } + } + + public func pollOnce() { + if !self.broadcastToMessageIds.isEmpty { + self.pollingOnceValue = true + } + } +} diff --git a/TelegramUI/LiveLocationSummaryManager.swift b/TelegramUI/LiveLocationSummaryManager.swift new file mode 100644 index 0000000000..ed2d475d0f --- /dev/null +++ b/TelegramUI/LiveLocationSummaryManager.swift @@ -0,0 +1,260 @@ +import Foundation +import Postbox +import TelegramCore +import SwiftSignalKit + +private final class LiveLocationSummaryContext { + private let queue: Queue + private let postbox: Postbox + private var subscribers = Bag<([Peer]) -> Void>() + + var peerIds = Set() { + didSet { + assert(self.queue.isCurrent()) + + if self.peerIds != oldValue { + if self.peerIds.isEmpty { + self.disposable.set(nil) + self.peers = [] + } else { + self.disposable.set((self.postbox.multiplePeersView(Array(self.peerIds)) |> deliverOn(self.queue)).start(next: { [weak self] view in + if let strongSelf = self { + let peers: [Peer] = Array(view.peers.values) + strongSelf.peers = peers + } + })) + } + } + } + } + + private var peers: [Peer] = [] { + didSet { + assert(self.queue.isCurrent()) + + for f in self.subscribers.copyItems() { + f(self.peers) + } + } + } + + private let disposable = MetaDisposable() + + init(queue: Queue, postbox: Postbox) { + self.queue = queue + self.postbox = postbox + } + + deinit { + self.disposable.dispose() + } + + func subscribe() -> Signal<[Peer], NoError> { + let queue = self.queue + return Signal { [weak self] subscriber in + let disposable = MetaDisposable() + queue.async { + if let strongSelf = self { + let index = strongSelf.subscribers.add({ next in + subscriber.putNext(next) + }) + + subscriber.putNext(strongSelf.peers) + + disposable.set(ActionDisposable { [weak self] in + queue.async { + if let strongSelf = self { + strongSelf.subscribers.remove(index) + } + } + }) + } + } + return disposable + } + } +} + +private final class LiveLocationPeerSummaryContext { + private let queue: Queue + private let accountPeerId: PeerId + private let viewTracker: AccountViewTracker + private let peerId: PeerId + private let becameEmpty: () -> Void + + private var peers: [Peer] = [] { + didSet { + assert(self.queue.isCurrent()) + + for f in self.subscribers.copyItems() { + f(self.peers) + } + } + } + + private var _isActive: Bool = false + var isActive: Bool { + get { + return self._isActive + } set(value) { + if value != self._isActive { + let wasActive = self._isActive + self._isActive = value + if self._isActive != wasActive { + self.updateSubscription() + } + } + } + } + private var subscribers = Bag<([Peer]) -> Void>() + + var isEmpty: Bool { + return !self.isActive && self.subscribers.isEmpty + } + + private let peerDisposable = MetaDisposable() + + init(queue: Queue, accountPeerId: PeerId, viewTracker: AccountViewTracker, peerId: PeerId, becameEmpty: @escaping () -> Void) { + self.queue = queue + self.accountPeerId = accountPeerId + self.viewTracker = viewTracker + self.peerId = peerId + self.becameEmpty = becameEmpty + } + + deinit { + self.peerDisposable.dispose() + } + + func subscribe(_ f: @escaping ([Peer]) -> Void) -> Disposable { + let index = self.subscribers.add({ next in + f(next) + }) + + f(self.peers) + + let queue = self.queue + return ActionDisposable { [weak self] in + queue.async { + if let strongSelf = self { + strongSelf.subscribers.remove(index) + + if strongSelf.isEmpty { + strongSelf.becameEmpty() + } + } + } + } + } + + private func updateSubscription() { + if self.isActive { + self.peerDisposable.set((topPeerActiveLiveLocationMessages(viewTracker: self.viewTracker, peerId: self.peerId) + |> deliverOn(self.queue)).start(next: { [weak self] messages in + if let strongSelf = self { + var peers: [Peer] = [] + for message in messages { + if let author = message.author { + if author.id != strongSelf.accountPeerId && message.flags.contains(.Incoming) { + peers.append(author) + } + } + } + strongSelf.peers = peers + } + })) + } else { + self.peerDisposable.set(nil) + self.peers = [] + } + } +} + +final class LiveLocationSummaryManager { + private let queue: Queue + private let postbox: Postbox + private let accountPeerId: PeerId + private let viewTracker: AccountViewTracker + + private let globalContext: LiveLocationSummaryContext + private var peerContexts: [PeerId: LiveLocationPeerSummaryContext] = [:] + + init(queue: Queue, postbox: Postbox, accountPeerId: PeerId, viewTracker: AccountViewTracker) { + assert(queue.isCurrent()) + self.queue = queue + self.postbox = postbox + self.accountPeerId = accountPeerId + self.viewTracker = viewTracker + + self.globalContext = LiveLocationSummaryContext(queue: queue, postbox: postbox) + } + + func update(messageIds: Set) { + var peerIds = Set() + for id in messageIds { + peerIds.insert(id.peerId) + } + + var removedPeerIds: [PeerId] = [] + for peerId in self.peerContexts.keys { + if !peerIds.contains(peerId) { + removedPeerIds.append(peerId) + } + } + + for peerId in removedPeerIds { + if let _ = self.peerContexts[peerId] { + self.peerContexts.removeValue(forKey: peerId) + } else { + assertionFailure() + } + } + + for peerId in peerIds { + if self.peerContexts[peerId] == nil { + let context = LiveLocationPeerSummaryContext(queue: self.queue, accountPeerId: self.accountPeerId, viewTracker: self.viewTracker, peerId: peerId, becameEmpty: { [weak self] in + if let strongSelf = self, let context = strongSelf.peerContexts[peerId], context.isEmpty { + strongSelf.peerContexts.removeValue(forKey: peerId) + } + }) + self.peerContexts[peerId] = context + } + } + + for (peerId, context) in self.peerContexts { + context.isActive = peerIds.contains(peerId) + } + + self.globalContext.peerIds = peerIds + } + + func broadcastingToPeers() -> Signal<[Peer], NoError> { + return self.globalContext.subscribe() + } + + func peersBroadcastingTo(peerId: PeerId) -> Signal<[Peer], NoError> { + let queue = self.queue + return Signal { [weak self] subscriber in + let disposable = MetaDisposable() + queue.async { + if let strongSelf = self { + let context: LiveLocationPeerSummaryContext + if let current = strongSelf.peerContexts[peerId] { + context = current + } else { + context = LiveLocationPeerSummaryContext(queue: strongSelf.queue, accountPeerId: strongSelf.accountPeerId, viewTracker: strongSelf.viewTracker, peerId: peerId, becameEmpty: { + if let strongSelf = self, let context = strongSelf.peerContexts[peerId], context.isEmpty { + strongSelf.peerContexts.removeValue(forKey: peerId) + } + }) + } + + disposable.set(context.subscribe({ next in + subscriber.putNext(next) + })) + } + } + return disposable + } + } +} diff --git a/TelegramUI/MessageContentKind.swift b/TelegramUI/MessageContentKind.swift index d5e4db8084..102c545d0f 100644 --- a/TelegramUI/MessageContentKind.swift +++ b/TelegramUI/MessageContentKind.swift @@ -14,6 +14,7 @@ enum MessageContentKind: Equatable { case contact case game(String) case location + case liveLocation static func ==(lhs: MessageContentKind, rhs: MessageContentKind) -> Bool { switch lhs { @@ -83,6 +84,12 @@ enum MessageContentKind: Equatable { } else { return false } + case .liveLocation: + if case .liveLocation = rhs { + return true + } else { + return false + } } } } @@ -131,8 +138,12 @@ func messageContentKind(_ message: Message, strings: PresentationStrings, accoun return .contact case let game as TelegramMediaGame: return .game(game.title) - case _ as TelegramMediaMap: - return .location + case let location as TelegramMediaMap: + if location.liveBroadcastingTimeout != nil { + return .liveLocation + } else { + return .location + } case _ as TelegramMediaAction: return .text(plainServiceMessageString(strings: strings, message: message, accountPeerId: accountPeerId) ?? "") default: @@ -177,5 +188,7 @@ func descriptionStringForMessage(_ message: Message, strings: PresentationString return text case .location: return strings.Message_Location + case .liveLocation: + return strings.Message_LiveLocation } } diff --git a/TelegramUI/OpenChatMessage.swift b/TelegramUI/OpenChatMessage.swift index 8b1d57bac2..4c5da29d5d 100644 --- a/TelegramUI/OpenChatMessage.swift +++ b/TelegramUI/OpenChatMessage.swift @@ -77,6 +77,10 @@ func openChatMessage(account: Account, message: Message, reverseMessageGalleryOr dismissInput() present(legacyLocationController(message: message, mapMedia: mapMedia, account: account, openPeer: { peer in openPeer(peer, .info) + }, sendLiveLocation: { coordinate, period in + + }, stopLiveLocation: { + account.telegramApplicationContext.liveLocationManager?.cancelLiveLocation(peerId: message.id.peerId) }), nil) } else if let file = galleryMedia as? TelegramMediaFile, file.isSticker { for attribute in file.attributes { diff --git a/TelegramUI/PhotoResources.swift b/TelegramUI/PhotoResources.swift index aae58a04ad..369f74b03c 100644 --- a/TelegramUI/PhotoResources.swift +++ b/TelegramUI/PhotoResources.swift @@ -1736,7 +1736,9 @@ func chatMapSnapshotData(account: Account, resource: MapSnapshotMediaResource) - return Signal { subscriber in let fetchedDisposable = account.postbox.mediaBox.fetchedResource(resource, tag: nil).start() let dataDisposable = account.postbox.mediaBox.resourceData(resource).start(next: { next in - subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: [])) + if next.size != 0 { + subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: [])) + } }, error: subscriber.putError, completed: subscriber.putCompletion) return ActionDisposable { diff --git a/TelegramUI/StoredMessageFromSearchPeer.swift b/TelegramUI/StoredMessageFromSearchPeer.swift index 941e42d212..6a436b2f83 100644 --- a/TelegramUI/StoredMessageFromSearchPeer.swift +++ b/TelegramUI/StoredMessageFromSearchPeer.swift @@ -24,7 +24,7 @@ func storedMessageFromSearch(account: Account, message: Message) -> Signal @@ -50,8 +53,14 @@ public final class TelegramApplicationContext { public var navigateToCurrentCall: (() -> Void)? public var hasOngoingCall: Signal? - public init(applicationBindings: TelegramApplicationBindings, accountManager: AccountManager, currentPresentationData: PresentationData, presentationData: Signal, currentMediaDownloadSettings: AutomaticMediaDownloadSettings, automaticMediaDownloadSettings: Signal, postbox: Postbox) { + public init(applicationBindings: TelegramApplicationBindings, accountManager: AccountManager, currentPresentationData: PresentationData, presentationData: Signal, currentMediaDownloadSettings: AutomaticMediaDownloadSettings, automaticMediaDownloadSettings: Signal, postbox: Postbox, network: Network, accountPeerId: PeerId?, viewTracker: AccountViewTracker?, stateManager: AccountStateManager?) { self.mediaManager = MediaManager(postbox: postbox, inForeground: applicationBindings.applicationInForeground) + self.locationManager = DeviceLocationManager(queue: Queue.mainQueue()) + if let stateManager = stateManager, let accountPeerId = accountPeerId, let viewTracker = viewTracker { + self.liveLocationManager = LiveLocationManager(postbox: postbox, network: network, accountPeerId: accountPeerId, viewTracker: viewTracker, stateManager: stateManager, locationManager: self.locationManager, inForeground: applicationBindings.applicationInForeground) + } else { + self.liveLocationManager = nil + } self.applicationBindings = applicationBindings self.accountManager = accountManager self.fetchManager = FetchManager(postbox: postbox) diff --git a/TelegramUI/ThemeSettingsChatPreviewItem.swift b/TelegramUI/ThemeSettingsChatPreviewItem.swift index 739afac651..da42adeb8a 100644 --- a/TelegramUI/ThemeSettingsChatPreviewItem.swift +++ b/TelegramUI/ThemeSettingsChatPreviewItem.swift @@ -138,12 +138,12 @@ class ThemeSettingsChatPreviewItemNode: ListViewItemNode { peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: "Lucio", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, flags: []) let replyMessageId = MessageId(peerId: peerId, namespace: 0, id: 3) - messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], forwardInfo: nil, author: peers[peerId], text: "Reinhart, we need to find you some...", attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "Reinhart, we need to find you some...", attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) let chatPresentationData = ChatPresentationData(theme: item.theme, fontSize: item.fontSize, strings: item.strings, wallpaper: item.wallpaper, timeFormat: item.timeFormat) - let item2: ChatMessageItem = ChatMessageItem(presentationData: chatPresentationData, account: item.account, chatLocation: .peer(peerId), controllerInteraction: controllerInteraction, content: .message(message: Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], forwardInfo: nil, author: nil, text: "Ahh you kids today with techno music! Enjoy the classics, like Hasselhoff!", attributes: [ReplyMessageAttribute(messageId: replyMessageId)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), read: true, selection: .none), disableDate: true) - let item1: ChatMessageItem = ChatMessageItem(presentationData: chatPresentationData, account: item.account, chatLocation: .peer(peerId), controllerInteraction: controllerInteraction, content: .message(message: Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], forwardInfo: nil, author: TelegramUser(id: item.account.peerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, flags: []), text: "I can't take you seriously right now. Sorry..", attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), read: true, selection: .none), disableDate: true) + let item2: ChatMessageItem = ChatMessageItem(presentationData: chatPresentationData, account: item.account, chatLocation: .peer(peerId), controllerInteraction: controllerInteraction, content: .message(message: Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: nil, text: "Ahh you kids today with techno music! Enjoy the classics, like Hasselhoff!", attributes: [ReplyMessageAttribute(messageId: replyMessageId)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), read: true, selection: .none), disableDate: true) + let item1: ChatMessageItem = ChatMessageItem(presentationData: chatPresentationData, account: item.account, chatLocation: .peer(peerId), controllerInteraction: controllerInteraction, content: .message(message: Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: TelegramUser(id: item.account.peerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, flags: []), text: "I can't take you seriously right now. Sorry..", attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), read: true, selection: .none), disableDate: true) var node1: ListViewItemNode? if let current = currentNode1 {