mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-04 21:41:45 +00:00
no message
This commit is contained in:
parent
6a47e739a3
commit
3a998955b7
@ -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 = "<group>"; };
|
||||
D0430AFF1FF4570500A35ADD /* WebController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebController.swift; sourceTree = "<group>"; };
|
||||
D0430B011FF4584100A35ADD /* WebControllerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebControllerNode.swift; sourceTree = "<group>"; };
|
||||
D046142D2004DB3700EC0EF2 /* LiveLocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveLocationManager.swift; sourceTree = "<group>"; };
|
||||
D04614362005094E00EC0EF2 /* DeviceLocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceLocationManager.swift; sourceTree = "<group>"; };
|
||||
D0461438200514F000EC0EF2 /* LiveLocationSummaryManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveLocationSummaryManager.swift; sourceTree = "<group>"; };
|
||||
D04662801E68BA64006FAFC4 /* TransformOutgoingMessageMedia.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransformOutgoingMessageMedia.swift; sourceTree = "<group>"; };
|
||||
D0471B481EFD59170074D609 /* BotCheckoutControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BotCheckoutControllerNode.swift; sourceTree = "<group>"; };
|
||||
D0471B4A1EFD64AC0074D609 /* BotCheckoutHeaderItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BotCheckoutHeaderItem.swift; sourceTree = "<group>"; };
|
||||
@ -2467,6 +2473,23 @@
|
||||
name = Web;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D046142C2004DB1D00EC0EF2 /* Live Location Manager */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D046142D2004DB3700EC0EF2 /* LiveLocationManager.swift */,
|
||||
D0461438200514F000EC0EF2 /* LiveLocationSummaryManager.swift */,
|
||||
);
|
||||
name = "Live Location Manager";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D04614352005093B00EC0EF2 /* Device Location */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D04614362005094E00EC0EF2 /* DeviceLocationManager.swift */,
|
||||
);
|
||||
name = "Device Location";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
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 */,
|
||||
|
||||
@ -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]) {
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
130
TelegramUI/DeviceLocationManager.swift
Normal file
130
TelegramUI/DeviceLocationManager.swift
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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<PeerId.Namespace> = 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
|
||||
}
|
||||
|
||||
216
TelegramUI/LiveLocationManager.swift
Normal file
216
TelegramUI/LiveLocationManager.swift
Normal file
@ -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<Bool>(false, ignoreRepeated: true)
|
||||
|
||||
|
||||
public var isPolling: Signal<Bool, NoError> {
|
||||
return self.pollingOnce.get()
|
||||
}
|
||||
private let pollingOnce = ValuePromise<Bool>(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<MessageId>()
|
||||
private var stopMessageIds = Set<MessageId>()
|
||||
|
||||
private let editMessageDisposables = DisposableDict<MessageId>()
|
||||
|
||||
init(postbox: Postbox, network: Network, accountPeerId: PeerId, viewTracker: AccountViewTracker, stateManager: AccountStateManager, locationManager: DeviceLocationManager, inForeground: Signal<Bool, NoError>) {
|
||||
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<MessageId>()
|
||||
var stopMessageIds = Set<MessageId>()
|
||||
|
||||
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<MessageId>, stopMessageIds: Set<MessageId>) {
|
||||
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<Set<MessageId>>(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
|
||||
}
|
||||
}
|
||||
}
|
||||
260
TelegramUI/LiveLocationSummaryManager.swift
Normal file
260
TelegramUI/LiveLocationSummaryManager.swift
Normal file
@ -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<PeerId>() {
|
||||
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<MessageId>) {
|
||||
var peerIds = Set<PeerId>()
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -1736,7 +1736,9 @@ func chatMapSnapshotData(account: Account, resource: MapSnapshotMediaResource) -
|
||||
return Signal<Data?, NoError> { 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 {
|
||||
|
||||
@ -24,7 +24,7 @@ func storedMessageFromSearch(account: Account, message: Message) -> Signal<Void,
|
||||
}
|
||||
}
|
||||
|
||||
let storeMessage = StoreMessage(id: .Id(message.id), globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, timestamp: message.timestamp, flags: StoreMessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, forwardInfo: message.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: message.author?.id, text: message.text, attributes: message.attributes, media: message.media)
|
||||
let storeMessage = StoreMessage(id: .Id(message.id), globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, timestamp: message.timestamp, flags: StoreMessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: message.author?.id, text: message.text, attributes: message.attributes, media: message.media)
|
||||
|
||||
let _ = modifier.addMessages([storeMessage], location: .Random)
|
||||
}
|
||||
|
||||
@ -30,6 +30,9 @@ public final class TelegramApplicationContext {
|
||||
|
||||
public let mediaManager: MediaManager
|
||||
|
||||
let locationManager: DeviceLocationManager
|
||||
public let liveLocationManager: LiveLocationManager?
|
||||
|
||||
public let contactsManager = DeviceContactsManager()
|
||||
|
||||
public let currentPresentationData: Atomic<PresentationData>
|
||||
@ -50,8 +53,14 @@ public final class TelegramApplicationContext {
|
||||
public var navigateToCurrentCall: (() -> Void)?
|
||||
public var hasOngoingCall: Signal<Bool, NoError>?
|
||||
|
||||
public init(applicationBindings: TelegramApplicationBindings, accountManager: AccountManager, currentPresentationData: PresentationData, presentationData: Signal<PresentationData, NoError>, currentMediaDownloadSettings: AutomaticMediaDownloadSettings, automaticMediaDownloadSettings: Signal<AutomaticMediaDownloadSettings, NoError>, postbox: Postbox) {
|
||||
public init(applicationBindings: TelegramApplicationBindings, accountManager: AccountManager, currentPresentationData: PresentationData, presentationData: Signal<PresentationData, NoError>, currentMediaDownloadSettings: AutomaticMediaDownloadSettings, automaticMediaDownloadSettings: Signal<AutomaticMediaDownloadSettings, NoError>, 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)
|
||||
|
||||
@ -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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user