no message

This commit is contained in:
Peter 2018-01-11 22:44:38 +04:00
parent 6a47e739a3
commit 3a998955b7
17 changed files with 804 additions and 48 deletions

View File

@ -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 */,

View File

@ -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]) {

View File

@ -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
}
}

View File

@ -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")
}

View File

@ -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 {

View 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)
}
}
}
}
}

View File

@ -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 {

View File

@ -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

View File

@ -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
}

View 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
}
}
}

View 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
}
}
}

View File

@ -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
}
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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)
}

View File

@ -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)

View File

@ -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 {