mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' into experiments/widgetkit-widget
This commit is contained in:
commit
e5f9e76b61
@ -5839,3 +5839,9 @@ Any member of this group will be able to see messages in the channel.";
|
|||||||
"Conversation.ContextMenuSelectAll_any" = "Select All %@ Items";
|
"Conversation.ContextMenuSelectAll_any" = "Select All %@ Items";
|
||||||
|
|
||||||
"Conversation.Dice.u1F3B0" = "Send a slot machine emoji to try your luck.";
|
"Conversation.Dice.u1F3B0" = "Send a slot machine emoji to try your luck.";
|
||||||
|
|
||||||
|
"Notification.ProximityReached" = "%2$@ is now within %2$@ from you";
|
||||||
|
|
||||||
|
"Location.ProximityNotification.Title" = "Notification";
|
||||||
|
"Location.ProximityNotification.Notify" = "Notify me within %@";
|
||||||
|
"Location.ProximityNotification.AlreadyClose" = "You are already closer than %@";
|
||||||
|
@ -105,7 +105,6 @@ private final class DateSelectionActionSheetItemNode: ActionSheetItemNode {
|
|||||||
|
|
||||||
self.pickerView = UIDatePicker()
|
self.pickerView = UIDatePicker()
|
||||||
self.pickerView.timeZone = TimeZone(secondsFromGMT: 0)
|
self.pickerView.timeZone = TimeZone(secondsFromGMT: 0)
|
||||||
self.pickerView.setValue(theme.primaryTextColor, forKey: "textColor")
|
|
||||||
self.pickerView.datePickerMode = .countDownTimer
|
self.pickerView.datePickerMode = .countDownTimer
|
||||||
self.pickerView.datePickerMode = .date
|
self.pickerView.datePickerMode = .date
|
||||||
self.pickerView.date = Date(timeIntervalSince1970: Double(roundDateToDays(currentValue)))
|
self.pickerView.date = Date(timeIntervalSince1970: Double(roundDateToDays(currentValue)))
|
||||||
@ -121,6 +120,7 @@ private final class DateSelectionActionSheetItemNode: ActionSheetItemNode {
|
|||||||
} else {
|
} else {
|
||||||
self.pickerView.maximumDate = Date(timeIntervalSince1970: Double(Int32.max - 1))
|
self.pickerView.maximumDate = Date(timeIntervalSince1970: Double(Int32.max - 1))
|
||||||
}
|
}
|
||||||
|
self.pickerView.setValue(theme.primaryTextColor, forKey: "textColor")
|
||||||
|
|
||||||
super.init(theme: theme)
|
super.init(theme: theme)
|
||||||
|
|
||||||
|
@ -9,9 +9,9 @@ public enum DeviceLocationMode: Int32 {
|
|||||||
private final class DeviceLocationSubscriber {
|
private final class DeviceLocationSubscriber {
|
||||||
let id: Int32
|
let id: Int32
|
||||||
let mode: DeviceLocationMode
|
let mode: DeviceLocationMode
|
||||||
let update: (CLLocationCoordinate2D) -> Void
|
let update: (CLLocationCoordinate2D, Double, Double?) -> Void
|
||||||
|
|
||||||
init(id: Int32, mode: DeviceLocationMode, update: @escaping (CLLocationCoordinate2D) -> Void) {
|
init(id: Int32, mode: DeviceLocationMode, update: @escaping (CLLocationCoordinate2D, Double, Double?) -> Void) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.update = update
|
self.update = update
|
||||||
@ -39,7 +39,8 @@ public final class DeviceLocationManager: NSObject {
|
|||||||
private var subscribers: [DeviceLocationSubscriber] = []
|
private var subscribers: [DeviceLocationSubscriber] = []
|
||||||
private var currentTopMode: DeviceLocationMode?
|
private var currentTopMode: DeviceLocationMode?
|
||||||
|
|
||||||
private var currentLocation: CLLocationCoordinate2D?
|
private var currentLocation: (CLLocationCoordinate2D, Double)?
|
||||||
|
private var currentHeading: CLHeading?
|
||||||
|
|
||||||
public init(queue: Queue, log: ((String) -> Void)? = nil) {
|
public init(queue: Queue, log: ((String) -> Void)? = nil) {
|
||||||
assert(queue.isCurrent())
|
assert(queue.isCurrent())
|
||||||
@ -60,7 +61,7 @@ public final class DeviceLocationManager: NSObject {
|
|||||||
self.manager.pausesLocationUpdatesAutomatically = false
|
self.manager.pausesLocationUpdatesAutomatically = false
|
||||||
}
|
}
|
||||||
|
|
||||||
public func push(mode: DeviceLocationMode, updated: @escaping (CLLocationCoordinate2D) -> Void) -> Disposable {
|
public func push(mode: DeviceLocationMode, updated: @escaping (CLLocationCoordinate2D, Double, Double?) -> Void) -> Disposable {
|
||||||
assert(self.queue.isCurrent())
|
assert(self.queue.isCurrent())
|
||||||
|
|
||||||
let id = self.nextSubscriberId
|
let id = self.nextSubscriberId
|
||||||
@ -68,7 +69,7 @@ public final class DeviceLocationManager: NSObject {
|
|||||||
self.subscribers.append(DeviceLocationSubscriber(id: id, mode: mode, update: updated))
|
self.subscribers.append(DeviceLocationSubscriber(id: id, mode: mode, update: updated))
|
||||||
|
|
||||||
if let currentLocation = self.currentLocation {
|
if let currentLocation = self.currentLocation {
|
||||||
updated(currentLocation)
|
updated(currentLocation.0, currentLocation.1, self.currentHeading?.magneticHeading)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.updateTopMode()
|
self.updateTopMode()
|
||||||
@ -107,6 +108,7 @@ public final class DeviceLocationManager: NSObject {
|
|||||||
self.manager.requestAlwaysAuthorization()
|
self.manager.requestAlwaysAuthorization()
|
||||||
}
|
}
|
||||||
self.manager.startUpdatingLocation()
|
self.manager.startUpdatingLocation()
|
||||||
|
self.manager.startUpdatingHeading()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.currentLocation = nil
|
self.currentLocation = nil
|
||||||
@ -123,9 +125,22 @@ extension DeviceLocationManager: CLLocationManagerDelegate {
|
|||||||
|
|
||||||
if let location = locations.first {
|
if let location = locations.first {
|
||||||
if self.currentTopMode != nil {
|
if self.currentTopMode != nil {
|
||||||
self.currentLocation = location.coordinate
|
self.currentLocation = (location.coordinate, location.horizontalAccuracy)
|
||||||
for subscriber in self.subscribers {
|
for subscriber in self.subscribers {
|
||||||
subscriber.update(location.coordinate)
|
subscriber.update(location.coordinate, location.horizontalAccuracy, self.currentHeading?.magneticHeading)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
|
||||||
|
assert(self.queue.isCurrent())
|
||||||
|
|
||||||
|
if self.currentTopMode != nil {
|
||||||
|
self.currentHeading = newHeading
|
||||||
|
if let currentLocation = self.currentLocation {
|
||||||
|
for subscriber in self.subscribers {
|
||||||
|
subscriber.update(currentLocation.0, currentLocation.1, newHeading.magneticHeading)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -135,7 +150,7 @@ extension DeviceLocationManager: CLLocationManagerDelegate {
|
|||||||
public func currentLocationManagerCoordinate(manager: DeviceLocationManager, timeout timeoutValue: Double) -> Signal<CLLocationCoordinate2D?, NoError> {
|
public func currentLocationManagerCoordinate(manager: DeviceLocationManager, timeout timeoutValue: Double) -> Signal<CLLocationCoordinate2D?, NoError> {
|
||||||
return (
|
return (
|
||||||
Signal { subscriber in
|
Signal { subscriber in
|
||||||
let disposable = manager.push(mode: .precise, updated: { coordinate in
|
let disposable = manager.push(mode: .precise, updated: { coordinate, _, _ in
|
||||||
subscriber.putNext(coordinate)
|
subscriber.putNext(coordinate)
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
})
|
})
|
||||||
|
@ -44,15 +44,6 @@ public class BaseLinesChartController: BaseChartController {
|
|||||||
self.setBackButtonVisibilityClosure?(isZoomed, animated)
|
self.setBackButtonVisibilityClosure?(isZoomed, animated)
|
||||||
|
|
||||||
updateChartRangeTitle(animated: animated)
|
updateChartRangeTitle(animated: animated)
|
||||||
|
|
||||||
let initial = initialChartsCollection
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) {
|
|
||||||
if let lastDate = initial.axisValues.last {
|
|
||||||
TimeInterval.animationDurationMultipler = 0.00001
|
|
||||||
self.didTapZoomIn(date: lastDate, pointIndex: initial.axisValues.count - 1)
|
|
||||||
TimeInterval.animationDurationMultipler = 1.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateChartRangeTitle(animated: Bool) {
|
func updateChartRangeTitle(animated: Bool) {
|
||||||
|
@ -816,7 +816,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let map = TelegramMediaMap(latitude: latitude, longitude: longitude, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil)
|
let map = TelegramMediaMap(latitude: latitude, longitude: longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil)
|
||||||
let attributes: [InstantPageImageAttribute] = [InstantPageMapAttribute(zoom: zoom, dimensions: dimensions.cgSize)]
|
let attributes: [InstantPageImageAttribute] = [InstantPageMapAttribute(zoom: zoom, dimensions: dimensions.cgSize)]
|
||||||
|
|
||||||
var contentSize = CGSize(width: boundingWidth - safeInset * 2.0, height: 0.0)
|
var contentSize = CGSize(width: boundingWidth - safeInset * 2.0, height: 0.0)
|
||||||
|
@ -67,7 +67,7 @@
|
|||||||
|
|
||||||
+ (instancetype)controllerWithContext:(id<LegacyComponentsContext>)context assetGroup:(TGMediaAssetGroup *)assetGroup intent:(TGMediaAssetsControllerIntent)intent recipientName:(NSString *)recipientName saveEditedPhotos:(bool)saveEditedPhotos allowGrouping:(bool)allowGrouping inhibitSelection:(bool)inhibitSelection selectionLimit:(int)selectionLimit
|
+ (instancetype)controllerWithContext:(id<LegacyComponentsContext>)context assetGroup:(TGMediaAssetGroup *)assetGroup intent:(TGMediaAssetsControllerIntent)intent recipientName:(NSString *)recipientName saveEditedPhotos:(bool)saveEditedPhotos allowGrouping:(bool)allowGrouping inhibitSelection:(bool)inhibitSelection selectionLimit:(int)selectionLimit
|
||||||
{
|
{
|
||||||
if (intent != TGMediaAssetsControllerSendMediaIntent)
|
if (intent != TGMediaAssetsControllerSendMediaIntent && intent != TGMediaAssetsControllerSendFileIntent)
|
||||||
allowGrouping = false;
|
allowGrouping = false;
|
||||||
|
|
||||||
TGMediaAssetsController *assetsController = [[TGMediaAssetsController alloc] initWithContext:context intent:intent saveEditedPhotos:saveEditedPhotos allowGrouping:allowGrouping selectionLimit:selectionLimit];
|
TGMediaAssetsController *assetsController = [[TGMediaAssetsController alloc] initWithContext:context intent:intent saveEditedPhotos:saveEditedPhotos allowGrouping:allowGrouping selectionLimit:selectionLimit];
|
||||||
@ -735,6 +735,9 @@
|
|||||||
dict[@"fileName"] = assetData.fileName;
|
dict[@"fileName"] = assetData.fileName;
|
||||||
dict[@"mimeType"] = TGMimeTypeForFileUTI(assetData.fileUTI);
|
dict[@"mimeType"] = TGMimeTypeForFileUTI(assetData.fileUTI);
|
||||||
|
|
||||||
|
if (groupedId != nil)
|
||||||
|
dict[@"groupedId"] = groupedId;
|
||||||
|
|
||||||
id generatedItem = descriptionGenerator(dict, caption, entities, nil);
|
id generatedItem = descriptionGenerator(dict, caption, entities, nil);
|
||||||
return generatedItem;
|
return generatedItem;
|
||||||
}] catch:^SSignal *(id error)
|
}] catch:^SSignal *(id error)
|
||||||
@ -752,6 +755,9 @@
|
|||||||
dict[@"mimeType"] = TGMimeTypeForFileUTI(asset.uniformTypeIdentifier);
|
dict[@"mimeType"] = TGMimeTypeForFileUTI(asset.uniformTypeIdentifier);
|
||||||
dict[@"fileName"] = asset.fileName;
|
dict[@"fileName"] = asset.fileName;
|
||||||
|
|
||||||
|
if (groupedId != nil)
|
||||||
|
dict[@"groupedId"] = groupedId;
|
||||||
|
|
||||||
id generatedItem = descriptionGenerator(dict, caption, entities, nil);
|
id generatedItem = descriptionGenerator(dict, caption, entities, nil);
|
||||||
return generatedItem;
|
return generatedItem;
|
||||||
}];
|
}];
|
||||||
@ -971,6 +977,9 @@
|
|||||||
if (adjustments.paintingData.stickers.count > 0)
|
if (adjustments.paintingData.stickers.count > 0)
|
||||||
dict[@"stickers"] = adjustments.paintingData.stickers;
|
dict[@"stickers"] = adjustments.paintingData.stickers;
|
||||||
|
|
||||||
|
if (groupedId != nil)
|
||||||
|
dict[@"groupedId"] = groupedId;
|
||||||
|
|
||||||
id generatedItem = descriptionGenerator(dict, caption, entities, nil);
|
id generatedItem = descriptionGenerator(dict, caption, entities, nil);
|
||||||
return generatedItem;
|
return generatedItem;
|
||||||
}]];
|
}]];
|
||||||
|
@ -553,7 +553,7 @@ private func loadLegacyMessages(account: TemporaryAccount, basePath: String, acc
|
|||||||
if let v = item.venue {
|
if let v = item.venue {
|
||||||
venue = MapVenue(title: v.title ?? "", address: v.address ?? "", provider: v.provider == "" ? nil : v.provider, id: v.venueId == "" ? nil : v.venueId, type: v.type == "" ? nil : v.type)
|
venue = MapVenue(title: v.title ?? "", address: v.address ?? "", provider: v.provider == "" ? nil : v.provider, id: v.venueId == "" ? nil : v.venueId, type: v.type == "" ? nil : v.type)
|
||||||
}
|
}
|
||||||
parsedMedia.append(TelegramMediaMap(latitude: item.latitude, longitude: item.longitude, geoPlace: nil, venue: venue, liveBroadcastingTimeout: nil))
|
parsedMedia.append(TelegramMediaMap(latitude: item.latitude, longitude: item.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: venue, liveBroadcastingTimeout: nil))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -184,7 +184,7 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, String?, [Any]?, String?
|
|||||||
name = customName
|
name = customName
|
||||||
}
|
}
|
||||||
|
|
||||||
result["item" as NSString] = LegacyAssetItemWrapper(item: .file(data: .asset(asset.backingAsset), thumbnail: thumbnail, mimeType: mimeType, name: name, caption: caption), timer: nil, groupedId: nil)
|
result["item" as NSString] = LegacyAssetItemWrapper(item: .file(data: .asset(asset.backingAsset), thumbnail: thumbnail, mimeType: mimeType, name: name, caption: caption), timer: nil, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value)
|
||||||
} else {
|
} else {
|
||||||
result["item" as NSString] = LegacyAssetItemWrapper(item: .image(data: .asset(asset.backingAsset), thumbnail: thumbnail, caption: caption, stickers: []), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value)
|
result["item" as NSString] = LegacyAssetItemWrapper(item: .image(data: .asset(asset.backingAsset), thumbnail: thumbnail, caption: caption, stickers: []), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value)
|
||||||
}
|
}
|
||||||
|
@ -107,9 +107,9 @@ public final class LiveLocationManagerImpl: LiveLocationManager {
|
|||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if value {
|
if value {
|
||||||
let queue = strongSelf.queue
|
let queue = strongSelf.queue
|
||||||
strongSelf.deviceLocationDisposable.set(strongSelf.locationManager.push(mode: .precise, updated: { coordinate in
|
strongSelf.deviceLocationDisposable.set(strongSelf.locationManager.push(mode: .precise, updated: { coordinate, accuracyRadius, heading in
|
||||||
queue.async {
|
queue.async {
|
||||||
self?.updateDeviceCoordinate(coordinate)
|
self?.updateDeviceCoordinate(coordinate, accuracyRadius: accuracyRadius, heading: heading)
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
@ -158,7 +158,7 @@ public final class LiveLocationManagerImpl: LiveLocationManager {
|
|||||||
let addedStopped = stopMessageIds.subtracting(self.stopMessageIds)
|
let addedStopped = stopMessageIds.subtracting(self.stopMessageIds)
|
||||||
self.stopMessageIds = stopMessageIds
|
self.stopMessageIds = stopMessageIds
|
||||||
for id in addedStopped {
|
for id in addedStopped {
|
||||||
self.editMessageDisposables.set((requestEditLiveLocation(postbox: self.postbox, network: self.network, stateManager: self.stateManager, messageId: id, coordinate: nil)
|
self.editMessageDisposables.set((requestEditLiveLocation(postbox: self.postbox, network: self.network, stateManager: self.stateManager, messageId: id, coordinate: nil, heading: nil)
|
||||||
|> deliverOn(self.queue)).start(completed: { [weak self] in
|
|> deliverOn(self.queue)).start(completed: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.editMessageDisposables.set(nil, forKey: id)
|
strongSelf.editMessageDisposables.set(nil, forKey: id)
|
||||||
@ -207,13 +207,13 @@ public final class LiveLocationManagerImpl: LiveLocationManager {
|
|||||||
self.update(broadcastToMessageIds: updatedBroadcastToMessageIds, stopMessageIds: updatedStopMessageIds)
|
self.update(broadcastToMessageIds: updatedBroadcastToMessageIds, stopMessageIds: updatedStopMessageIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateDeviceCoordinate(_ coordinate: CLLocationCoordinate2D) {
|
private func updateDeviceCoordinate(_ coordinate: CLLocationCoordinate2D, accuracyRadius: Double, heading: Double?) {
|
||||||
assert(self.queue.isCurrent())
|
assert(self.queue.isCurrent())
|
||||||
|
|
||||||
let ids = self.broadcastToMessageIds
|
let ids = self.broadcastToMessageIds
|
||||||
let remainingIds = Atomic<Set<MessageId>>(value: Set(ids.keys))
|
let remainingIds = Atomic<Set<MessageId>>(value: Set(ids.keys))
|
||||||
for id in ids.keys {
|
for id in ids.keys {
|
||||||
self.editMessageDisposables.set((requestEditLiveLocation(postbox: self.postbox, network: self.network, stateManager: self.stateManager, messageId: id, coordinate: (latitude: coordinate.latitude, longitude: coordinate.longitude))
|
self.editMessageDisposables.set((requestEditLiveLocation(postbox: self.postbox, network: self.network, stateManager: self.stateManager, messageId: id, coordinate: (latitude: coordinate.latitude, longitude: coordinate.longitude, accuracyRadius: Int32(accuracyRadius)), heading: Int32(heading ?? 0))
|
||||||
|> deliverOn(self.queue)).start(completed: { [weak self] in
|
|> deliverOn(self.queue)).start(completed: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.editMessageDisposables.set(nil, forKey: id)
|
strongSelf.editMessageDisposables.set(nil, forKey: id)
|
||||||
@ -247,7 +247,7 @@ public final class LiveLocationManagerImpl: LiveLocationManager {
|
|||||||
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||||
for i in 0 ..< updatedMedia.count {
|
for i in 0 ..< updatedMedia.count {
|
||||||
if let media = updatedMedia[i] as? TelegramMediaMap, let _ = media.liveBroadcastingTimeout {
|
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))
|
updatedMedia[i] = TelegramMediaMap(latitude: media.latitude, longitude: media.longitude, heading: media.heading, accuracyRadius: media.accuracyRadius, 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, threadId: currentMessage.threadId, 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))
|
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, 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))
|
||||||
|
@ -35,6 +35,7 @@ static_library(
|
|||||||
"//submodules/PhoneNumberFormat:PhoneNumberFormat",
|
"//submodules/PhoneNumberFormat:PhoneNumberFormat",
|
||||||
"//submodules/PersistentStringHash:PersistentStringHash",
|
"//submodules/PersistentStringHash:PersistentStringHash",
|
||||||
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
|
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
|
||||||
|
"//submodules/LiveLocationTimerNode:LiveLocationTimerNode",
|
||||||
],
|
],
|
||||||
frameworks = [
|
frameworks = [
|
||||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||||
|
@ -36,6 +36,7 @@ swift_library(
|
|||||||
"//submodules/PhoneNumberFormat:PhoneNumberFormat",
|
"//submodules/PhoneNumberFormat:PhoneNumberFormat",
|
||||||
"//submodules/PersistentStringHash:PersistentStringHash",
|
"//submodules/PersistentStringHash:PersistentStringHash",
|
||||||
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
|
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
|
||||||
|
"//submodules/LiveLocationTimerNode:LiveLocationTimerNode",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
import Display
|
import Display
|
||||||
|
import SwiftSignalKit
|
||||||
import LegacyComponents
|
import LegacyComponents
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
import SyncCore
|
import SyncCore
|
||||||
@ -12,6 +13,7 @@ import LegacyUI
|
|||||||
import OpenInExternalAppUI
|
import OpenInExternalAppUI
|
||||||
import AppBundle
|
import AppBundle
|
||||||
import LocationResources
|
import LocationResources
|
||||||
|
import DeviceLocationManager
|
||||||
|
|
||||||
private func generateClearIcon(color: UIColor) -> UIImage? {
|
private func generateClearIcon(color: UIColor) -> UIImage? {
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: color)
|
return generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: color)
|
||||||
@ -120,7 +122,7 @@ private func telegramMap(for location: TGLocationMediaAttachment) -> TelegramMed
|
|||||||
} else {
|
} else {
|
||||||
mapVenue = nil
|
mapVenue = nil
|
||||||
}
|
}
|
||||||
return TelegramMediaMap(latitude: location.latitude, longitude: location.longitude, geoPlace: nil, venue: mapVenue, liveBroadcastingTimeout: nil)
|
return TelegramMediaMap(latitude: location.latitude, longitude: location.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: mapVenue, liveBroadcastingTimeout: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func legacyLocationPalette(from theme: PresentationTheme) -> TGLocationPallete {
|
func legacyLocationPalette(from theme: PresentationTheme) -> TGLocationPallete {
|
||||||
@ -234,7 +236,18 @@ public func legacyLocationController(message: Message?, mapMedia: TelegramMediaM
|
|||||||
legacyController?.dismiss()
|
legacyController?.dismiss()
|
||||||
}
|
}
|
||||||
controller.liveLocationStopped = { [weak legacyController] in
|
controller.liveLocationStopped = { [weak legacyController] in
|
||||||
stopLiveLocation()
|
if let message = message, let locationManager = context.sharedContext.locationManager {
|
||||||
|
let _ = (currentLocationManagerCoordinate(manager: locationManager, timeout: 5.0)
|
||||||
|
|> deliverOnMainQueue).start(next: { coordinate in
|
||||||
|
if let coordinate = coordinate {
|
||||||
|
let _ = requestProximityNotification(postbox: context.account.postbox, network: context.account.network, messageId: message.id, distance: 500, coordinate: (coordinate.latitude, longitude: coordinate.longitude, accuracyRadius: nil)).start()
|
||||||
|
} else {
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
// stopLiveLocation()
|
||||||
legacyController?.dismiss()
|
legacyController?.dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import TelegramPresentationData
|
|||||||
import ItemListUI
|
import ItemListUI
|
||||||
import LocationResources
|
import LocationResources
|
||||||
import AppBundle
|
import AppBundle
|
||||||
|
import LiveLocationTimerNode
|
||||||
|
|
||||||
public enum LocationActionListItemIcon: Equatable {
|
public enum LocationActionListItemIcon: Equatable {
|
||||||
case location
|
case location
|
||||||
@ -63,17 +64,17 @@ private func generateLocationIcon(theme: PresentationTheme) -> UIImage {
|
|||||||
})!
|
})!
|
||||||
}
|
}
|
||||||
|
|
||||||
private func generateLiveLocationIcon(theme: PresentationTheme) -> UIImage {
|
private func generateLiveLocationIcon(theme: PresentationTheme, stop: Bool) -> UIImage {
|
||||||
return generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in
|
return generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
context.setFillColor(UIColor(rgb: 0x6cc139).cgColor)
|
context.setFillColor(UIColor(rgb: stop ? 0xff6464 : 0x6cc139).cgColor)
|
||||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
|
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
|
||||||
context.scaleBy(x: 1.0, y: -1.0)
|
context.scaleBy(x: 1.0, y: -1.0)
|
||||||
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
|
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
|
||||||
|
|
||||||
if let image = generateTintedImage(image: UIImage(bundleImageName: "Location/SendLiveLocationIcon"), color: .white) {
|
if let image = generateTintedImage(image: UIImage(bundleImageName: "Location/SendLocationIcon"), color: .white) {
|
||||||
context.draw(image.cgImage!, in: CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size))
|
context.draw(image.cgImage!, in: CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size))
|
||||||
}
|
}
|
||||||
})!
|
})!
|
||||||
@ -85,15 +86,17 @@ final class LocationActionListItem: ListViewItem {
|
|||||||
let title: String
|
let title: String
|
||||||
let subtitle: String
|
let subtitle: String
|
||||||
let icon: LocationActionListItemIcon
|
let icon: LocationActionListItemIcon
|
||||||
|
let beginTimeAndTimeout: (Double, Double)?
|
||||||
let action: () -> Void
|
let action: () -> Void
|
||||||
let highlighted: (Bool) -> Void
|
let highlighted: (Bool) -> Void
|
||||||
|
|
||||||
public init(presentationData: ItemListPresentationData, account: Account, title: String, subtitle: String, icon: LocationActionListItemIcon, action: @escaping () -> Void, highlighted: @escaping (Bool) -> Void = { _ in }) {
|
public init(presentationData: ItemListPresentationData, account: Account, title: String, subtitle: String, icon: LocationActionListItemIcon, beginTimeAndTimeout: (Double, Double)?, action: @escaping () -> Void, highlighted: @escaping (Bool) -> Void = { _ in }) {
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.account = account
|
self.account = account
|
||||||
self.title = title
|
self.title = title
|
||||||
self.subtitle = subtitle
|
self.subtitle = subtitle
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
|
self.beginTimeAndTimeout = beginTimeAndTimeout
|
||||||
self.action = action
|
self.action = action
|
||||||
self.highlighted = highlighted
|
self.highlighted = highlighted
|
||||||
}
|
}
|
||||||
@ -144,6 +147,7 @@ final class LocationActionListItemNode: ListViewItemNode {
|
|||||||
private var subtitleNode: TextNode?
|
private var subtitleNode: TextNode?
|
||||||
private let iconNode: ASImageNode
|
private let iconNode: ASImageNode
|
||||||
private let venueIconNode: TransformImageNode
|
private let venueIconNode: TransformImageNode
|
||||||
|
private var timerNode: ChatMessageLiveLocationTimerNode?
|
||||||
|
|
||||||
private var item: LocationActionListItem?
|
private var item: LocationActionListItem?
|
||||||
private var layoutParams: ListViewItemLayoutParams?
|
private var layoutParams: ListViewItemLayoutParams?
|
||||||
@ -268,7 +272,7 @@ final class LocationActionListItemNode: ListViewItemNode {
|
|||||||
case .liveLocation, .stopLiveLocation:
|
case .liveLocation, .stopLiveLocation:
|
||||||
strongSelf.iconNode.isHidden = false
|
strongSelf.iconNode.isHidden = false
|
||||||
strongSelf.venueIconNode.isHidden = true
|
strongSelf.venueIconNode.isHidden = true
|
||||||
strongSelf.iconNode.image = generateLiveLocationIcon(theme: item.presentationData.theme)
|
strongSelf.iconNode.image = generateLiveLocationIcon(theme: item.presentationData.theme, stop: updatedIcon == .stopLiveLocation)
|
||||||
case let .venue(venue):
|
case let .venue(venue):
|
||||||
strongSelf.iconNode.isHidden = true
|
strongSelf.iconNode.isHidden = true
|
||||||
strongSelf.venueIconNode.isHidden = false
|
strongSelf.venueIconNode.isHidden = false
|
||||||
@ -308,6 +312,23 @@ final class LocationActionListItemNode: ListViewItemNode {
|
|||||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: contentSize.width, height: contentSize.height + topHighlightInset))
|
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: contentSize.width, height: contentSize.height + topHighlightInset))
|
||||||
strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width, height: separatorHeight))
|
strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width, height: separatorHeight))
|
||||||
strongSelf.separatorNode.isHidden = !hasSeparator
|
strongSelf.separatorNode.isHidden = !hasSeparator
|
||||||
|
|
||||||
|
if let (beginTimestamp, timeout) = item.beginTimeAndTimeout {
|
||||||
|
let timerNode: ChatMessageLiveLocationTimerNode
|
||||||
|
if let current = strongSelf.timerNode {
|
||||||
|
timerNode = current
|
||||||
|
} else {
|
||||||
|
timerNode = ChatMessageLiveLocationTimerNode()
|
||||||
|
strongSelf.addSubnode(timerNode)
|
||||||
|
strongSelf.timerNode = timerNode
|
||||||
|
}
|
||||||
|
let timerSize = CGSize(width: 28.0, height: 28.0)
|
||||||
|
timerNode.update(backgroundColor: item.presentationData.theme.list.itemAccentColor.withAlphaComponent(0.4), foregroundColor: item.presentationData.theme.list.itemAccentColor, textColor: item.presentationData.theme.list.itemAccentColor, beginTimestamp: beginTimestamp, timeout: timeout, strings: item.presentationData.strings)
|
||||||
|
timerNode.frame = CGRect(origin: CGPoint(x: contentSize.width - 16.0 - timerSize.width, y: floorToScreenPixels((contentSize.height - timerSize.height) / 2.0) - 2.0), size: timerSize)
|
||||||
|
} else if let timerNode = strongSelf.timerNode {
|
||||||
|
strongSelf.timerNode = nil
|
||||||
|
timerNode.removeFromSupernode()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -34,8 +34,11 @@ class LocationPinAnnotation: NSObject, MKAnnotation {
|
|||||||
var coordinate: CLLocationCoordinate2D
|
var coordinate: CLLocationCoordinate2D
|
||||||
let location: TelegramMediaMap?
|
let location: TelegramMediaMap?
|
||||||
let peer: Peer?
|
let peer: Peer?
|
||||||
|
let message: Message?
|
||||||
let forcedSelection: Bool
|
let forcedSelection: Bool
|
||||||
|
var heading: Int32?
|
||||||
|
|
||||||
|
var selfPeer: Peer?
|
||||||
var title: String? = ""
|
var title: String? = ""
|
||||||
var subtitle: String? = ""
|
var subtitle: String? = ""
|
||||||
|
|
||||||
@ -44,6 +47,7 @@ class LocationPinAnnotation: NSObject, MKAnnotation {
|
|||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.location = nil
|
self.location = nil
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
|
self.message = nil
|
||||||
self.coordinate = kCLLocationCoordinate2DInvalid
|
self.coordinate = kCLLocationCoordinate2DInvalid
|
||||||
self.forcedSelection = false
|
self.forcedSelection = false
|
||||||
super.init()
|
super.init()
|
||||||
@ -54,11 +58,29 @@ class LocationPinAnnotation: NSObject, MKAnnotation {
|
|||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.location = location
|
self.location = location
|
||||||
self.peer = nil
|
self.peer = nil
|
||||||
|
self.message = nil
|
||||||
self.coordinate = location.coordinate
|
self.coordinate = location.coordinate
|
||||||
self.forcedSelection = forcedSelection
|
self.forcedSelection = forcedSelection
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init(context: AccountContext, theme: PresentationTheme, message: Message, selfPeer: Peer?, heading: Int32?) {
|
||||||
|
self.context = context
|
||||||
|
self.theme = theme
|
||||||
|
self.location = nil
|
||||||
|
self.peer = nil
|
||||||
|
self.message = message
|
||||||
|
if let location = getLocation(from: message) {
|
||||||
|
self.coordinate = location.coordinate
|
||||||
|
} else {
|
||||||
|
self.coordinate = kCLLocationCoordinate2DInvalid
|
||||||
|
}
|
||||||
|
self.selfPeer = selfPeer
|
||||||
|
self.forcedSelection = false
|
||||||
|
self.heading = heading
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
var id: String {
|
var id: String {
|
||||||
if let peer = self.peer {
|
if let peer = self.peer {
|
||||||
return "\(peer.id.toInt64())"
|
return "\(peer.id.toInt64())"
|
||||||
@ -89,6 +111,7 @@ class LocationPinAnnotationLayer: CALayer {
|
|||||||
class LocationPinAnnotationView: MKAnnotationView {
|
class LocationPinAnnotationView: MKAnnotationView {
|
||||||
let shadowNode: ASImageNode
|
let shadowNode: ASImageNode
|
||||||
let backgroundNode: ASImageNode
|
let backgroundNode: ASImageNode
|
||||||
|
let arrowNode: ASImageNode
|
||||||
let smallNode: ASImageNode
|
let smallNode: ASImageNode
|
||||||
let iconNode: TransformImageNode
|
let iconNode: TransformImageNode
|
||||||
let smallIconNode: TransformImageNode
|
let smallIconNode: TransformImageNode
|
||||||
@ -118,6 +141,11 @@ class LocationPinAnnotationView: MKAnnotationView {
|
|||||||
self.shadowNode.bounds = CGRect(origin: CGPoint(), size: image.size)
|
self.shadowNode.bounds = CGRect(origin: CGPoint(), size: image.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.arrowNode = ASImageNode()
|
||||||
|
self.arrowNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 88.0, height: 88.0))
|
||||||
|
self.arrowNode.image = generateHeadingArrowImage()
|
||||||
|
self.arrowNode.isHidden = true
|
||||||
|
|
||||||
self.backgroundNode = ASImageNode()
|
self.backgroundNode = ASImageNode()
|
||||||
self.backgroundNode.image = UIImage(bundleImageName: "Location/PinBackground")
|
self.backgroundNode.image = UIImage(bundleImageName: "Location/PinBackground")
|
||||||
if let image = self.backgroundNode.image {
|
if let image = self.backgroundNode.image {
|
||||||
@ -147,6 +175,7 @@ class LocationPinAnnotationView: MKAnnotationView {
|
|||||||
self.addSubnode(self.dotNode)
|
self.addSubnode(self.dotNode)
|
||||||
|
|
||||||
self.addSubnode(self.shadowNode)
|
self.addSubnode(self.shadowNode)
|
||||||
|
self.addSubnode(self.arrowNode)
|
||||||
self.shadowNode.addSubnode(self.backgroundNode)
|
self.shadowNode.addSubnode(self.backgroundNode)
|
||||||
self.backgroundNode.addSubnode(self.iconNode)
|
self.backgroundNode.addSubnode(self.iconNode)
|
||||||
|
|
||||||
@ -177,7 +206,24 @@ class LocationPinAnnotationView: MKAnnotationView {
|
|||||||
override var annotation: MKAnnotation? {
|
override var annotation: MKAnnotation? {
|
||||||
didSet {
|
didSet {
|
||||||
if let annotation = self.annotation as? LocationPinAnnotation {
|
if let annotation = self.annotation as? LocationPinAnnotation {
|
||||||
if let peer = annotation.peer {
|
if let message = annotation.message {
|
||||||
|
self.iconNode.isHidden = true
|
||||||
|
self.dotNode.isHidden = false
|
||||||
|
self.backgroundNode.image = UIImage(bundleImageName: "Location/PinBackground")
|
||||||
|
|
||||||
|
if let author = message.author, let peer = message.peers[author.id] {
|
||||||
|
self.setPeer(context: annotation.context, theme: annotation.theme, peer: peer)
|
||||||
|
} else if let selfPeer = annotation.selfPeer {
|
||||||
|
self.setPeer(context: annotation.context, theme: annotation.theme, peer: selfPeer)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.isSelected {
|
||||||
|
self.dotNode.alpha = 0.0
|
||||||
|
self.shadowNode.isHidden = true
|
||||||
|
self.smallNode.isHidden = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if let peer = annotation.peer {
|
||||||
self.iconNode.isHidden = true
|
self.iconNode.isHidden = true
|
||||||
self.dotNode.isHidden = true
|
self.dotNode.isHidden = true
|
||||||
self.backgroundNode.image = UIImage(bundleImageName: "Location/PinBackground")
|
self.backgroundNode.image = UIImage(bundleImageName: "Location/PinBackground")
|
||||||
@ -396,6 +442,7 @@ class LocationPinAnnotationView: MKAnnotationView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var previousPeerId: PeerId?
|
||||||
func setPeer(context: AccountContext, theme: PresentationTheme, peer: Peer) {
|
func setPeer(context: AccountContext, theme: PresentationTheme, peer: Peer) {
|
||||||
let avatarNode: AvatarNode
|
let avatarNode: AvatarNode
|
||||||
if let currentAvatarNode = self.avatarNode {
|
if let currentAvatarNode = self.avatarNode {
|
||||||
@ -409,8 +456,11 @@ class LocationPinAnnotationView: MKAnnotationView {
|
|||||||
self.addSubnode(avatarNode)
|
self.addSubnode(avatarNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.previousPeerId != peer.id {
|
||||||
|
self.previousPeerId = peer.id
|
||||||
avatarNode.setPeer(context: context, theme: theme, peer: peer)
|
avatarNode.setPeer(context: context, theme: theme, peer: peer)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||||
super.traitCollectionDidChange(previousTraitCollection)
|
super.traitCollectionDidChange(previousTraitCollection)
|
||||||
|
499
submodules/LocationUI/Sources/LocationDistancePickerScreen.swift
Normal file
499
submodules/LocationUI/Sources/LocationDistancePickerScreen.swift
Normal file
@ -0,0 +1,499 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import Postbox
|
||||||
|
import TelegramCore
|
||||||
|
import SyncCore
|
||||||
|
import SwiftSignalKit
|
||||||
|
import AccountContext
|
||||||
|
import SolidRoundedButtonNode
|
||||||
|
import TelegramPresentationData
|
||||||
|
import PresentationDataUtils
|
||||||
|
import CoreLocation
|
||||||
|
|
||||||
|
enum LocationDistancePickerScreenStyle {
|
||||||
|
case `default`
|
||||||
|
case media
|
||||||
|
}
|
||||||
|
|
||||||
|
final class LocationDistancePickerScreen: ViewController {
|
||||||
|
private var controllerNode: LocationDistancePickerScreenNode {
|
||||||
|
return self.displayNode as! LocationDistancePickerScreenNode
|
||||||
|
}
|
||||||
|
|
||||||
|
private var animatedIn = false
|
||||||
|
|
||||||
|
private let context: AccountContext
|
||||||
|
private let style: LocationDistancePickerScreenStyle
|
||||||
|
private let currentDistance: Double?
|
||||||
|
private let updated: (Int32?) -> Void
|
||||||
|
private let completion: (Int32?) -> Void
|
||||||
|
|
||||||
|
private var presentationDataDisposable: Disposable?
|
||||||
|
|
||||||
|
init(context: AccountContext, style: LocationDistancePickerScreenStyle, currentDistance: Double?, updated: @escaping (Int32?) -> Void, completion: @escaping (Int32?) -> Void) {
|
||||||
|
self.context = context
|
||||||
|
self.style = style
|
||||||
|
self.currentDistance = currentDistance
|
||||||
|
self.updated = updated
|
||||||
|
self.completion = completion
|
||||||
|
|
||||||
|
super.init(navigationBarPresentationData: nil)
|
||||||
|
|
||||||
|
self.statusBar.statusBarStyle = .Ignore
|
||||||
|
|
||||||
|
self.blocksBackgroundWhenInOverlay = true
|
||||||
|
|
||||||
|
self.presentationDataDisposable = (context.sharedContext.presentationData
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.controllerNode.updatePresentationData(presentationData)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.statusBar.statusBarStyle = .Ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
required init(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.presentationDataDisposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func loadDisplayNode() {
|
||||||
|
self.displayNode = LocationDistancePickerScreenNode(context: self.context, style: self.style, currentDistance: self.currentDistance)
|
||||||
|
self.controllerNode.updated = { [weak self] distance in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.updated(distance)
|
||||||
|
}
|
||||||
|
self.controllerNode.completion = { [weak self] distance in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.completion(distance)
|
||||||
|
strongSelf.dismiss()
|
||||||
|
}
|
||||||
|
self.controllerNode.dismiss = { [weak self] in
|
||||||
|
self?.updated(nil)
|
||||||
|
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||||
|
}
|
||||||
|
self.controllerNode.cancel = { [weak self] in
|
||||||
|
self?.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func loadView() {
|
||||||
|
super.loadView()
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func viewDidAppear(_ animated: Bool) {
|
||||||
|
super.viewDidAppear(animated)
|
||||||
|
|
||||||
|
if !self.animatedIn {
|
||||||
|
self.animatedIn = true
|
||||||
|
self.controllerNode.animateIn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func dismiss(completion: (() -> Void)? = nil) {
|
||||||
|
self.controllerNode.animateOut(completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
|
super.containerLayoutUpdated(layout, transition: transition)
|
||||||
|
|
||||||
|
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TimerPickerView: UIPickerView {
|
||||||
|
var selectorColor: UIColor? = nil {
|
||||||
|
didSet {
|
||||||
|
for subview in self.subviews {
|
||||||
|
if subview.bounds.height <= 1.0 {
|
||||||
|
subview.backgroundColor = self.selectorColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didAddSubview(_ subview: UIView) {
|
||||||
|
super.didAddSubview(subview)
|
||||||
|
|
||||||
|
if let selectorColor = self.selectorColor {
|
||||||
|
if subview.bounds.height <= 1.0 {
|
||||||
|
subview.backgroundColor = selectorColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didMoveToWindow() {
|
||||||
|
super.didMoveToWindow()
|
||||||
|
|
||||||
|
if let selectorColor = self.selectorColor {
|
||||||
|
for subview in self.subviews {
|
||||||
|
if subview.bounds.height <= 1.0 {
|
||||||
|
subview.backgroundColor = selectorColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var timerValues: [Int32] = {
|
||||||
|
var values: [Int32] = []
|
||||||
|
for i in 0 ..< 99 {
|
||||||
|
values.append(Int32(i))
|
||||||
|
}
|
||||||
|
return values
|
||||||
|
}()
|
||||||
|
|
||||||
|
private var smallerTimerValues: [Int32] = {
|
||||||
|
var values: [Int32] = []
|
||||||
|
for i in 0 ..< 100 {
|
||||||
|
values.append(Int32(i))
|
||||||
|
}
|
||||||
|
return values
|
||||||
|
}()
|
||||||
|
|
||||||
|
class LocationDistancePickerScreenNode: ViewControllerTracingNode, UIScrollViewDelegate, UIPickerViewDataSource, UIPickerViewDelegate {
|
||||||
|
private let context: AccountContext
|
||||||
|
private let controllerStyle: LocationDistancePickerScreenStyle
|
||||||
|
private var presentationData: PresentationData
|
||||||
|
private let currentDistance: Double?
|
||||||
|
|
||||||
|
private let dimNode: ASDisplayNode
|
||||||
|
private let wrappingScrollNode: ASScrollNode
|
||||||
|
private let contentContainerNode: ASDisplayNode
|
||||||
|
private let effectNode: ASDisplayNode
|
||||||
|
private let backgroundNode: ASDisplayNode
|
||||||
|
private let contentBackgroundNode: ASDisplayNode
|
||||||
|
private let titleNode: ASTextNode
|
||||||
|
private let textNode: ImmediateTextNode
|
||||||
|
private let cancelButton: HighlightableButtonNode
|
||||||
|
private let doneButton: SolidRoundedButtonNode
|
||||||
|
|
||||||
|
private var pickerView: TimerPickerView?
|
||||||
|
|
||||||
|
private var containerLayout: (ContainerViewLayout, CGFloat)?
|
||||||
|
|
||||||
|
var updated: ((Int32) -> Void)?
|
||||||
|
var completion: ((Int32) -> Void)?
|
||||||
|
var dismiss: (() -> Void)?
|
||||||
|
var cancel: (() -> Void)?
|
||||||
|
|
||||||
|
init(context: AccountContext, style: LocationDistancePickerScreenStyle, currentDistance: Double?) {
|
||||||
|
self.context = context
|
||||||
|
self.controllerStyle = style
|
||||||
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
self.currentDistance = currentDistance
|
||||||
|
|
||||||
|
self.wrappingScrollNode = ASScrollNode()
|
||||||
|
self.wrappingScrollNode.view.alwaysBounceVertical = true
|
||||||
|
self.wrappingScrollNode.view.delaysContentTouches = false
|
||||||
|
self.wrappingScrollNode.view.canCancelContentTouches = true
|
||||||
|
|
||||||
|
self.dimNode = ASDisplayNode()
|
||||||
|
// self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
||||||
|
|
||||||
|
self.contentContainerNode = ASDisplayNode()
|
||||||
|
self.contentContainerNode.isOpaque = false
|
||||||
|
|
||||||
|
self.backgroundNode = ASDisplayNode()
|
||||||
|
self.backgroundNode.clipsToBounds = true
|
||||||
|
self.backgroundNode.cornerRadius = 16.0
|
||||||
|
|
||||||
|
let backgroundColor: UIColor
|
||||||
|
let textColor: UIColor
|
||||||
|
let accentColor: UIColor
|
||||||
|
let blurStyle: UIBlurEffect.Style
|
||||||
|
switch style {
|
||||||
|
case .default:
|
||||||
|
backgroundColor = self.presentationData.theme.actionSheet.itemBackgroundColor
|
||||||
|
textColor = self.presentationData.theme.actionSheet.primaryTextColor
|
||||||
|
accentColor = self.presentationData.theme.actionSheet.controlAccentColor
|
||||||
|
blurStyle = self.presentationData.theme.actionSheet.backgroundType == .light ? .light : .dark
|
||||||
|
case .media:
|
||||||
|
backgroundColor = UIColor(rgb: 0x1c1c1e)
|
||||||
|
textColor = .white
|
||||||
|
accentColor = self.presentationData.theme.actionSheet.controlAccentColor
|
||||||
|
blurStyle = .dark
|
||||||
|
}
|
||||||
|
|
||||||
|
self.effectNode = ASDisplayNode(viewBlock: {
|
||||||
|
return UIVisualEffectView(effect: UIBlurEffect(style: blurStyle))
|
||||||
|
})
|
||||||
|
|
||||||
|
self.contentBackgroundNode = ASDisplayNode()
|
||||||
|
self.contentBackgroundNode.backgroundColor = backgroundColor
|
||||||
|
|
||||||
|
let title = "Notification"
|
||||||
|
self.titleNode = ASTextNode()
|
||||||
|
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(17.0), textColor: textColor)
|
||||||
|
|
||||||
|
self.textNode = ImmediateTextNode()
|
||||||
|
|
||||||
|
self.cancelButton = HighlightableButtonNode()
|
||||||
|
self.cancelButton.setTitle(self.presentationData.strings.Common_Cancel, with: Font.regular(17.0), with: accentColor, for: .normal)
|
||||||
|
|
||||||
|
self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 52.0, cornerRadius: 11.0, gloss: false)
|
||||||
|
self.doneButton.title = self.presentationData.strings.Conversation_Timer_Send
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.backgroundColor = nil
|
||||||
|
self.isOpaque = false
|
||||||
|
|
||||||
|
self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
|
||||||
|
self.addSubnode(self.dimNode)
|
||||||
|
|
||||||
|
self.wrappingScrollNode.view.delegate = self
|
||||||
|
self.addSubnode(self.wrappingScrollNode)
|
||||||
|
|
||||||
|
self.wrappingScrollNode.addSubnode(self.backgroundNode)
|
||||||
|
self.wrappingScrollNode.addSubnode(self.contentContainerNode)
|
||||||
|
|
||||||
|
self.backgroundNode.addSubnode(self.effectNode)
|
||||||
|
self.backgroundNode.addSubnode(self.contentBackgroundNode)
|
||||||
|
self.contentContainerNode.addSubnode(self.titleNode)
|
||||||
|
self.contentContainerNode.addSubnode(self.textNode)
|
||||||
|
self.contentContainerNode.addSubnode(self.cancelButton)
|
||||||
|
self.contentContainerNode.addSubnode(self.doneButton)
|
||||||
|
|
||||||
|
self.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside)
|
||||||
|
self.doneButton.pressed = { [weak self] in
|
||||||
|
if let strongSelf = self, let pickerView = strongSelf.pickerView {
|
||||||
|
strongSelf.doneButton.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
let largeValue = timerValues[pickerView.selectedRow(inComponent: 0)]
|
||||||
|
let smallValue = smallerTimerValues[pickerView.selectedRow(inComponent: 1)]
|
||||||
|
var value = largeValue * 1000 + smallValue * 10
|
||||||
|
value = Int32(Double(value) * 1.60934)
|
||||||
|
strongSelf.completion?(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.setupPickerView()
|
||||||
|
|
||||||
|
Queue.mainQueue().after(0.5) {
|
||||||
|
self.updateDoneButtonTitle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupPickerView() {
|
||||||
|
if let pickerView = self.pickerView {
|
||||||
|
pickerView.removeFromSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
let pickerView = TimerPickerView()
|
||||||
|
pickerView.selectorColor = UIColor(rgb: 0xffffff, alpha: 0.18)
|
||||||
|
pickerView.dataSource = self
|
||||||
|
pickerView.delegate = self
|
||||||
|
pickerView.selectRow(0, inComponent: 0, animated: false)
|
||||||
|
pickerView.selectRow(30, inComponent: 1, animated: false)
|
||||||
|
|
||||||
|
self.contentContainerNode.view.addSubview(pickerView)
|
||||||
|
self.pickerView = pickerView
|
||||||
|
|
||||||
|
self.updateDoneButtonTitle()
|
||||||
|
}
|
||||||
|
|
||||||
|
func numberOfComponents(in pickerView: UIPickerView) -> Int {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateDoneButtonTitle() {
|
||||||
|
if let pickerView = self.pickerView {
|
||||||
|
let largeValue = timerValues[pickerView.selectedRow(inComponent: 0)]
|
||||||
|
let smallValue = smallerTimerValues[pickerView.selectedRow(inComponent: 1)]
|
||||||
|
|
||||||
|
var value = largeValue * 1000 + smallValue * 10
|
||||||
|
value = Int32(Double(value) * 1.60934)
|
||||||
|
let distance = stringForDistance(strings: context.sharedContext.currentPresentationData.with { $0 }.strings, distance: CLLocationDistance(value))
|
||||||
|
|
||||||
|
self.updated?(value)
|
||||||
|
self.doneButton.title = "Notify me within \(distance)"
|
||||||
|
|
||||||
|
if let currentDistance = self.currentDistance, value > Int32(currentDistance) {
|
||||||
|
self.doneButton.alpha = 0.4
|
||||||
|
self.doneButton.isUserInteractionEnabled = false
|
||||||
|
} else {
|
||||||
|
self.doneButton.alpha = 1.0
|
||||||
|
self.doneButton.isUserInteractionEnabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
|
||||||
|
self.updateDoneButtonTitle()
|
||||||
|
}
|
||||||
|
|
||||||
|
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
|
||||||
|
if component == 0 {
|
||||||
|
return timerValues.count
|
||||||
|
} else if component == 1 {
|
||||||
|
return smallerTimerValues.count
|
||||||
|
} else {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
|
||||||
|
if component == 0 {
|
||||||
|
let value = timerValues[row]
|
||||||
|
return "\(value)"
|
||||||
|
} else if component == 1 {
|
||||||
|
let value = String(format: "%.2d", smallerTimerValues[row])
|
||||||
|
return ".\(value)"
|
||||||
|
} else {
|
||||||
|
return "MI"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updatePresentationData(_ presentationData: PresentationData) {
|
||||||
|
let previousTheme = self.presentationData.theme
|
||||||
|
self.presentationData = presentationData
|
||||||
|
|
||||||
|
guard case .default = self.controllerStyle else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let effectView = self.effectNode.view as? UIVisualEffectView {
|
||||||
|
effectView.effect = UIBlurEffect(style: presentationData.theme.actionSheet.backgroundType == .light ? .light : .dark)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.contentBackgroundNode.backgroundColor = self.presentationData.theme.actionSheet.itemBackgroundColor
|
||||||
|
self.titleNode.attributedText = NSAttributedString(string: self.titleNode.attributedText?.string ?? "", font: Font.bold(17.0), textColor: self.presentationData.theme.actionSheet.primaryTextColor)
|
||||||
|
|
||||||
|
if previousTheme !== presentationData.theme, let (layout, navigationBarHeight) = self.containerLayout {
|
||||||
|
self.setupPickerView()
|
||||||
|
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.cancelButton.setTitle(self.presentationData.strings.Common_Cancel, with: Font.regular(17.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal)
|
||||||
|
self.doneButton.updateTheme(SolidRoundedButtonTheme(theme: self.presentationData.theme))
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didLoad() {
|
||||||
|
super.didLoad()
|
||||||
|
|
||||||
|
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
||||||
|
self.wrappingScrollNode.view.contentInsetAdjustmentBehavior = .never
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func cancelButtonPressed() {
|
||||||
|
self.cancel?()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||||
|
if case .ended = recognizer.state {
|
||||||
|
self.cancelButtonPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func animateIn() {
|
||||||
|
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||||
|
|
||||||
|
let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY
|
||||||
|
|
||||||
|
let dimPosition = self.dimNode.layer.position
|
||||||
|
self.dimNode.layer.animatePosition(from: CGPoint(x: dimPosition.x, y: dimPosition.y - offset), to: dimPosition, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
|
self.layer.animateBoundsOriginYAdditive(from: -offset, to: 0.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
|
}
|
||||||
|
|
||||||
|
func animateOut(completion: (() -> Void)? = nil) {
|
||||||
|
var dimCompleted = false
|
||||||
|
var offsetCompleted = false
|
||||||
|
|
||||||
|
let internalCompletion: () -> Void = { [weak self] in
|
||||||
|
if let strongSelf = self, dimCompleted && offsetCompleted {
|
||||||
|
strongSelf.dismiss?()
|
||||||
|
}
|
||||||
|
completion?()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in
|
||||||
|
dimCompleted = true
|
||||||
|
internalCompletion()
|
||||||
|
})
|
||||||
|
|
||||||
|
let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY
|
||||||
|
let dimPosition = self.dimNode.layer.position
|
||||||
|
self.dimNode.layer.animatePosition(from: dimPosition, to: CGPoint(x: dimPosition.x, y: dimPosition.y - offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||||
|
self.layer.animateBoundsOriginYAdditive(from: 0.0, to: -offset, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
|
||||||
|
offsetCompleted = true
|
||||||
|
internalCompletion()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
|
if self.bounds.contains(point) {
|
||||||
|
if !self.contentBackgroundNode.bounds.contains(self.convert(point, to: self.contentBackgroundNode)) {
|
||||||
|
return self.dimNode.view
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.hitTest(point, with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
||||||
|
let contentOffset = scrollView.contentOffset
|
||||||
|
let additionalTopHeight = max(0.0, -contentOffset.y)
|
||||||
|
|
||||||
|
if additionalTopHeight >= 30.0 {
|
||||||
|
self.cancelButtonPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
|
self.containerLayout = (layout, navigationBarHeight)
|
||||||
|
|
||||||
|
var insets = layout.insets(options: [.statusBar, .input])
|
||||||
|
let cleanInsets = layout.insets(options: [.statusBar])
|
||||||
|
insets.top = max(10.0, insets.top)
|
||||||
|
|
||||||
|
let buttonOffset: CGFloat = 0.0
|
||||||
|
let bottomInset: CGFloat = 10.0 + cleanInsets.bottom
|
||||||
|
let titleHeight: CGFloat = 54.0
|
||||||
|
var contentHeight = titleHeight + bottomInset + 52.0 + 17.0
|
||||||
|
let pickerHeight: CGFloat = min(216.0, layout.size.height - contentHeight)
|
||||||
|
contentHeight = titleHeight + bottomInset + 52.0 + 17.0 + pickerHeight + buttonOffset
|
||||||
|
|
||||||
|
let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: layout.safeInsets.left)
|
||||||
|
|
||||||
|
let sideInset = floor((layout.size.width - width) / 2.0)
|
||||||
|
let contentContainerFrame = CGRect(origin: CGPoint(x: sideInset, y: layout.size.height - contentHeight), size: CGSize(width: width, height: contentHeight))
|
||||||
|
let contentFrame = contentContainerFrame
|
||||||
|
|
||||||
|
var backgroundFrame = CGRect(origin: CGPoint(x: contentFrame.minX, y: contentFrame.minY), size: CGSize(width: contentFrame.width, height: contentFrame.height + 2000.0))
|
||||||
|
if backgroundFrame.minY < contentFrame.minY {
|
||||||
|
backgroundFrame.origin.y = contentFrame.minY
|
||||||
|
}
|
||||||
|
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
|
||||||
|
transition.updateFrame(node: self.effectNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
|
||||||
|
transition.updateFrame(node: self.contentBackgroundNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
|
||||||
|
transition.updateFrame(node: self.wrappingScrollNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||||
|
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||||
|
|
||||||
|
let titleSize = self.titleNode.measure(CGSize(width: width, height: titleHeight))
|
||||||
|
let titleFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - titleSize.width) / 2.0), y: 16.0), size: titleSize)
|
||||||
|
transition.updateFrame(node: self.titleNode, frame: titleFrame)
|
||||||
|
|
||||||
|
let cancelSize = self.cancelButton.measure(CGSize(width: width, height: titleHeight))
|
||||||
|
let cancelFrame = CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: cancelSize)
|
||||||
|
transition.updateFrame(node: self.cancelButton, frame: cancelFrame)
|
||||||
|
|
||||||
|
let buttonInset: CGFloat = 16.0
|
||||||
|
let doneButtonHeight = self.doneButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition)
|
||||||
|
transition.updateFrame(node: self.doneButton, frame: CGRect(x: buttonInset, y: contentHeight - doneButtonHeight - insets.bottom - 16.0 - buttonOffset, width: contentFrame.width, height: doneButtonHeight))
|
||||||
|
|
||||||
|
self.pickerView?.frame = CGRect(origin: CGPoint(x: 0.0, y: 54.0), size: CGSize(width: contentFrame.width, height: pickerHeight))
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame)
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ import AsyncDisplayKit
|
|||||||
import Display
|
import Display
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import AppBundle
|
import AppBundle
|
||||||
|
import CoreLocation
|
||||||
|
|
||||||
private let panelInset: CGFloat = 4.0
|
private let panelInset: CGFloat = 4.0
|
||||||
private let panelButtonSize = CGSize(width: 46.0, height: 46.0)
|
private let panelButtonSize = CGSize(width: 46.0, height: 46.0)
|
||||||
@ -38,7 +39,7 @@ final class LocationMapHeaderNode: ASDisplayNode {
|
|||||||
private let toggleMapModeSelection: () -> Void
|
private let toggleMapModeSelection: () -> Void
|
||||||
private let goToUserLocation: () -> Void
|
private let goToUserLocation: () -> Void
|
||||||
private let showPlacesInThisArea: () -> Void
|
private let showPlacesInThisArea: () -> Void
|
||||||
private let setupProximityNotification: () -> Void
|
private let setupProximityNotification: (CLLocationCoordinate2D, Bool) -> Void
|
||||||
|
|
||||||
private var displayingPlacesButton = false
|
private var displayingPlacesButton = false
|
||||||
private var proximityNotification: Bool?
|
private var proximityNotification: Bool?
|
||||||
@ -56,7 +57,7 @@ final class LocationMapHeaderNode: ASDisplayNode {
|
|||||||
|
|
||||||
private var validLayout: (ContainerViewLayout, CGFloat, CGFloat, CGFloat, CGSize)?
|
private var validLayout: (ContainerViewLayout, CGFloat, CGFloat, CGFloat, CGSize)?
|
||||||
|
|
||||||
init(presentationData: PresentationData, toggleMapModeSelection: @escaping () -> Void, goToUserLocation: @escaping () -> Void, setupProximityNotification: @escaping () -> Void = {}, showPlacesInThisArea: @escaping () -> Void = {}) {
|
init(presentationData: PresentationData, toggleMapModeSelection: @escaping () -> Void, goToUserLocation: @escaping () -> Void, setupProximityNotification: @escaping (CLLocationCoordinate2D, Bool) -> Void = { _, _ in }, showPlacesInThisArea: @escaping () -> Void = {}) {
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.toggleMapModeSelection = toggleMapModeSelection
|
self.toggleMapModeSelection = toggleMapModeSelection
|
||||||
self.goToUserLocation = goToUserLocation
|
self.goToUserLocation = goToUserLocation
|
||||||
@ -88,6 +89,8 @@ final class LocationMapHeaderNode: ASDisplayNode {
|
|||||||
|
|
||||||
self.notificationButtonNode = HighlightableButtonNode()
|
self.notificationButtonNode = HighlightableButtonNode()
|
||||||
self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/NotificationIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .normal)
|
self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/NotificationIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .normal)
|
||||||
|
self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Title Panels/MuteIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .selected)
|
||||||
|
self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Title Panels/MuteIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: [.selected, .highlighted])
|
||||||
|
|
||||||
self.placesBackgroundNode = ASImageNode()
|
self.placesBackgroundNode = ASImageNode()
|
||||||
self.placesBackgroundNode.contentMode = .scaleToFill
|
self.placesBackgroundNode.contentMode = .scaleToFill
|
||||||
@ -129,6 +132,7 @@ final class LocationMapHeaderNode: ASDisplayNode {
|
|||||||
func updateState(mapMode: LocationMapMode, displayingMapModeOptions: Bool, displayingPlacesButton: Bool, proximityNotification: Bool?, animated: Bool) {
|
func updateState(mapMode: LocationMapMode, displayingMapModeOptions: Bool, displayingPlacesButton: Bool, proximityNotification: Bool?, animated: Bool) {
|
||||||
self.mapNode.mapMode = mapMode
|
self.mapNode.mapMode = mapMode
|
||||||
self.infoButtonNode.isSelected = displayingMapModeOptions
|
self.infoButtonNode.isSelected = displayingMapModeOptions
|
||||||
|
self.notificationButtonNode.isSelected = proximityNotification ?? false
|
||||||
|
|
||||||
let updateLayout = self.displayingPlacesButton != displayingPlacesButton || self.proximityNotification != proximityNotification
|
let updateLayout = self.displayingPlacesButton != displayingPlacesButton || self.proximityNotification != proximityNotification
|
||||||
self.displayingPlacesButton = displayingPlacesButton
|
self.displayingPlacesButton = displayingPlacesButton
|
||||||
@ -190,10 +194,18 @@ final class LocationMapHeaderNode: ASDisplayNode {
|
|||||||
transition.updateFrame(node: self.optionsBackgroundNode, frame: CGRect(x: size.width - inset - panelButtonSize.width - panelInset * 2.0, y: navigationBarHeight + topPadding + inset, width: panelButtonSize.width + panelInset * 2.0, height: panelHeight + panelInset * 2.0))
|
transition.updateFrame(node: self.optionsBackgroundNode, frame: CGRect(x: size.width - inset - panelButtonSize.width - panelInset * 2.0, y: navigationBarHeight + topPadding + inset, width: panelButtonSize.width + panelInset * 2.0, height: panelHeight + panelInset * 2.0))
|
||||||
|
|
||||||
let alphaTransition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
|
let alphaTransition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
|
||||||
let optionsAlpha: CGFloat = size.height > 160.0 + navigationBarHeight ? 1.0 : 0.0
|
let optionsAlpha: CGFloat = size.height > 160.0 + navigationBarHeight && !self.forceIsHidden ? 1.0 : 0.0
|
||||||
alphaTransition.updateAlpha(node: self.optionsBackgroundNode, alpha: optionsAlpha)
|
alphaTransition.updateAlpha(node: self.optionsBackgroundNode, alpha: optionsAlpha)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var forceIsHidden: Bool = false {
|
||||||
|
didSet {
|
||||||
|
if let (layout, navigationBarHeight, topPadding, offset, size) = self.validLayout {
|
||||||
|
self.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, topPadding: topPadding, offset: offset, size: size, transition: .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func updateHighlight(_ highlighted: Bool) {
|
func updateHighlight(_ highlighted: Bool) {
|
||||||
self.shadowNode.image = generateShadowImage(theme: self.presentationData.theme, highlighted: highlighted)
|
self.shadowNode.image = generateShadowImage(theme: self.presentationData.theme, highlighted: highlighted)
|
||||||
}
|
}
|
||||||
@ -207,7 +219,9 @@ final class LocationMapHeaderNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc private func notificationPressed() {
|
@objc private func notificationPressed() {
|
||||||
self.setupProximityNotification()
|
if let proximityNotification = self.proximityNotification, let location = self.mapNode.currentUserLocation {
|
||||||
|
self.setupProximityNotification(location.coordinate, proximityNotification)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func placesPressed() {
|
@objc private func placesPressed() {
|
||||||
|
@ -68,22 +68,33 @@ private class LocationMapView: MKMapView, UIGestureRecognizerDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func generateHeadingArrowImage() -> UIImage? {
|
func generateHeadingArrowImage() -> UIImage? {
|
||||||
return generateImage(CGSize(width: 28.0, height: 28.0), contextGenerator: { size, context in
|
return generateImage(CGSize(width: 88.0, height: 88.0), contextGenerator: { size, context in
|
||||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||||
context.clear(bounds)
|
context.clear(bounds)
|
||||||
|
|
||||||
context.setFillColor(UIColor(rgb: 0x3393fe).cgColor)
|
context.move(to: CGPoint(x: 44.0, y: 44.0))
|
||||||
|
context.addArc(center: CGPoint(x: 44.0, y: 44.0), radius: 44.0, startAngle: CGFloat.pi / 2.0 + CGFloat.pi / 6.0, endAngle: CGFloat.pi / 2.0 - CGFloat.pi / 6.0, clockwise: true)
|
||||||
|
context.clip()
|
||||||
|
|
||||||
context.move(to: CGPoint(x: 14.0, y: 0.0))
|
var locations: [CGFloat] = [0.0, 0.5, 1.0]
|
||||||
context.addLine(to: CGPoint(x: 19.0, y: 7.0))
|
let colors: [CGColor] = [UIColor(rgb: 0x007ee5, alpha: 0.0).cgColor, UIColor(rgb: 0x007ee5, alpha: 0.75).cgColor, UIColor(rgb: 0x007ee5, alpha: 0.0).cgColor]
|
||||||
context.addLine(to: CGPoint(x: 9.0, y: 7.0))
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
context.closePath()
|
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||||
context.fillPath()
|
|
||||||
|
context.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: 0.0, y: bounds.size.height), options: CGGradientDrawingOptions())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private func generateProximityDim(size: CGSize, rect: CGRect) -> UIImage {
|
||||||
|
return generateImage(size, rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
context.setFillColor(UIColor(rgb: 0x000000, alpha: 0.4).cgColor)
|
||||||
|
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
context.setBlendMode(.clear)
|
context.setBlendMode(.clear)
|
||||||
context.fillEllipse(in: bounds.insetBy(dx: 5.0, dy: 5.0))
|
context.fillEllipse(in: rect)
|
||||||
})
|
})!
|
||||||
}
|
}
|
||||||
|
|
||||||
final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
||||||
@ -108,6 +119,37 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
|||||||
var annotationSelected: ((LocationPinAnnotation?) -> Void)?
|
var annotationSelected: ((LocationPinAnnotation?) -> Void)?
|
||||||
var userLocationAnnotationSelected: (() -> Void)?
|
var userLocationAnnotationSelected: (() -> Void)?
|
||||||
|
|
||||||
|
var proximityDimView = UIImageView()
|
||||||
|
var proximityRadius: Double? {
|
||||||
|
didSet {
|
||||||
|
if let radius = self.proximityRadius, let mapView = self.mapView {
|
||||||
|
let region = MKCoordinateRegion(center: mapView.userLocation.coordinate, latitudinalMeters: radius * 2.0, longitudinalMeters: radius * 2.0)
|
||||||
|
let rect = mapView.convert(region, toRectTo: mapView)
|
||||||
|
if proximityDimView.image == nil {
|
||||||
|
proximityDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
}
|
||||||
|
|
||||||
|
if oldValue == 0 {
|
||||||
|
UIView.transition(with: proximityDimView, duration: 0.2, options: .transitionCrossDissolve) {
|
||||||
|
self.proximityDimView.image = generateProximityDim(size: mapView.bounds.size, rect: rect)
|
||||||
|
} completion: { _ in
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
proximityDimView.image = generateProximityDim(size: mapView.bounds.size, rect: rect)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if proximityDimView.image != nil {
|
||||||
|
UIView.transition(with: proximityDimView, duration: 0.2, options: .transitionCrossDissolve) {
|
||||||
|
self.proximityDimView.image = nil
|
||||||
|
} completion: { _ in
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
self.pickerAnnotationContainerView = PickerAnnotationContainerView()
|
self.pickerAnnotationContainerView = PickerAnnotationContainerView()
|
||||||
self.pickerAnnotationContainerView.isHidden = true
|
self.pickerAnnotationContainerView.isHidden = true
|
||||||
@ -123,7 +165,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
|||||||
super.didLoad()
|
super.didLoad()
|
||||||
|
|
||||||
self.headingArrowView = UIImageView()
|
self.headingArrowView = UIImageView()
|
||||||
self.headingArrowView?.frame = CGRect(origin: CGPoint(), size: CGSize(width: 28.0, height: 28.0))
|
self.headingArrowView?.frame = CGRect(origin: CGPoint(), size: CGSize(width: 88.0, height: 88.0))
|
||||||
self.headingArrowView?.image = generateHeadingArrowImage()
|
self.headingArrowView?.image = generateHeadingArrowImage()
|
||||||
|
|
||||||
self.mapView?.interactiveTransitionGestureRecognizerTest = { p in
|
self.mapView?.interactiveTransitionGestureRecognizerTest = { p in
|
||||||
@ -151,6 +193,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.mapView?.addSubview(self.proximityDimView)
|
||||||
self.view.addSubview(self.pickerAnnotationContainerView)
|
self.view.addSubview(self.pickerAnnotationContainerView)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,6 +346,52 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var circleOverlay: MKCircle?
|
||||||
|
var activeProximityRadius: Double? {
|
||||||
|
didSet {
|
||||||
|
if let activeProximityRadius = self.activeProximityRadius {
|
||||||
|
if let circleOverlay = self.circleOverlay {
|
||||||
|
self.circleOverlay = nil
|
||||||
|
self.mapView?.removeOverlay(circleOverlay)
|
||||||
|
}
|
||||||
|
if let location = self.currentUserLocation {
|
||||||
|
let overlay = MKCircle(center: location.coordinate, radius: activeProximityRadius)
|
||||||
|
self.circleOverlay = overlay
|
||||||
|
self.mapView?.addOverlay(overlay)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let circleOverlay = self.circleOverlay {
|
||||||
|
self.circleOverlay = nil
|
||||||
|
self.mapView?.removeOverlay(circleOverlay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
|
||||||
|
if let circle = overlay as? MKCircle {
|
||||||
|
let renderer = MKCircleRenderer(circle: circle)
|
||||||
|
renderer.fillColor = .clear
|
||||||
|
renderer.strokeColor = UIColor(rgb: 0xc3baaf)
|
||||||
|
renderer.lineWidth = 1.0
|
||||||
|
renderer.lineDashPattern = [5, 3]
|
||||||
|
return renderer
|
||||||
|
} else {
|
||||||
|
return MKOverlayRenderer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var distance: Double? {
|
||||||
|
if let annotation = self.annotations.first, let location = self.currentUserLocation {
|
||||||
|
return location.distance(from: CLLocation(latitude: annotation.coordinate.latitude, longitude: annotation.coordinate.longitude))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentUserLocation: CLLocation? {
|
||||||
|
return self.mapView?.userLocation.location
|
||||||
|
}
|
||||||
|
|
||||||
var userLocation: Signal<CLLocation?, NoError> {
|
var userLocation: Signal<CLLocation?, NoError> {
|
||||||
return self.locationPromise.get()
|
return self.locationPromise.get()
|
||||||
}
|
}
|
||||||
@ -314,6 +403,13 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
|||||||
return mapView.convert(CGPoint(x: (mapView.frame.width + pinOffset.x) / 2.0, y: (mapView.frame.height + pinOffset.y) / 2.0), toCoordinateFrom: mapView)
|
return mapView.convert(CGPoint(x: (mapView.frame.width + pinOffset.x) / 2.0, y: (mapView.frame.height + pinOffset.y) / 2.0), toCoordinateFrom: mapView)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mapSpan: MKCoordinateSpan? {
|
||||||
|
guard let mapView = self.mapView else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return mapView.region.span
|
||||||
|
}
|
||||||
|
|
||||||
func resetAnnotationSelection() {
|
func resetAnnotationSelection() {
|
||||||
guard let mapView = self.mapView else {
|
guard let mapView = self.mapView else {
|
||||||
return
|
return
|
||||||
@ -359,6 +455,8 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
|||||||
var userLocationAnnotation: LocationPinAnnotation? = nil {
|
var userLocationAnnotation: LocationPinAnnotation? = nil {
|
||||||
didSet {
|
didSet {
|
||||||
if let annotation = self.userLocationAnnotation {
|
if let annotation = self.userLocationAnnotation {
|
||||||
|
self.customUserLocationAnnotationView?.removeFromSuperview()
|
||||||
|
|
||||||
let annotationView = LocationPinAnnotationView(annotation: annotation)
|
let annotationView = LocationPinAnnotationView(annotation: annotation)
|
||||||
annotationView.frame = annotationView.frame.offsetBy(dx: 21.0, dy: 22.0)
|
annotationView.frame = annotationView.frame.offsetBy(dx: 21.0, dy: 22.0)
|
||||||
if let parentView = self.userLocationAnnotationView {
|
if let parentView = self.userLocationAnnotationView {
|
||||||
@ -447,7 +545,29 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
|||||||
self.pinDisposable.set(nil)
|
self.pinDisposable.set(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func showAll(animated: Bool = true) {
|
||||||
|
guard let mapView = self.mapView else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var annotations: [MKAnnotation] = []
|
||||||
|
if let userAnnotation = self.userLocationAnnotation {
|
||||||
|
annotations.append(userAnnotation)
|
||||||
|
}
|
||||||
|
annotations.append(contentsOf: self.annotations)
|
||||||
|
|
||||||
|
var zoomRect: MKMapRect = MKMapRect()
|
||||||
|
for annotation in annotations {
|
||||||
|
let pointRegionRect = MKMapRect(region: MKCoordinateRegion(center: annotation.coordinate, latitudinalMeters: 100, longitudinalMeters: 100))
|
||||||
|
zoomRect = zoomRect.union(pointRegionRect)
|
||||||
|
}
|
||||||
|
|
||||||
|
let insets = UIEdgeInsets()
|
||||||
|
zoomRect = mapView.mapRectThatFits(zoomRect, edgePadding: insets)
|
||||||
|
mapView.setVisibleMapRect(zoomRect, animated: animated)
|
||||||
|
}
|
||||||
|
|
||||||
func updateLayout(size: CGSize) {
|
func updateLayout(size: CGSize) {
|
||||||
|
self.proximityDimView.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
self.pickerAnnotationContainerView.frame = CGRect(x: 0.0, y: floorToScreenPixels((size.height - size.width) / 2.0), width: size.width, height: size.width)
|
self.pickerAnnotationContainerView.frame = CGRect(x: 0.0, y: floorToScreenPixels((size.height - size.width) / 2.0), width: size.width, height: size.width)
|
||||||
if let pickerAnnotationView = self.pickerAnnotationView {
|
if let pickerAnnotationView = self.pickerAnnotationView {
|
||||||
pickerAnnotationView.center = CGPoint(x: self.pickerAnnotationContainerView.frame.width / 2.0, y: self.pickerAnnotationContainerView.frame.height / 2.0)
|
pickerAnnotationView.center = CGPoint(x: self.pickerAnnotationContainerView.frame.width / 2.0, y: self.pickerAnnotationContainerView.frame.height / 2.0)
|
||||||
|
@ -103,7 +103,7 @@ public final class LocationPickerController: ViewController {
|
|||||||
})
|
})
|
||||||
|
|
||||||
let locationWithTimeout: (CLLocationCoordinate2D, Int32?) -> TelegramMediaMap = { coordinate, timeout in
|
let locationWithTimeout: (CLLocationCoordinate2D, Int32?) -> TelegramMediaMap = { coordinate, timeout in
|
||||||
return TelegramMediaMap(latitude: coordinate.latitude, longitude: coordinate.longitude, geoPlace: nil, venue: nil, liveBroadcastingTimeout: timeout)
|
return TelegramMediaMap(latitude: coordinate.latitude, longitude: coordinate.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: nil, liveBroadcastingTimeout: timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.interaction = LocationPickerInteraction(sendLocation: { [weak self] coordinate in
|
self.interaction = LocationPickerInteraction(sendLocation: { [weak self] coordinate in
|
||||||
@ -168,7 +168,7 @@ public final class LocationPickerController: ViewController {
|
|||||||
}
|
}
|
||||||
let venueType = venue.venue?.type ?? ""
|
let venueType = venue.venue?.type ?? ""
|
||||||
if ["home", "work"].contains(venueType) {
|
if ["home", "work"].contains(venueType) {
|
||||||
completion(TelegramMediaMap(latitude: venue.latitude, longitude: venue.longitude, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil), nil)
|
completion(TelegramMediaMap(latitude: venue.latitude, longitude: venue.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil), nil)
|
||||||
} else {
|
} else {
|
||||||
completion(venue, nil)
|
completion(venue, nil)
|
||||||
}
|
}
|
||||||
|
@ -139,7 +139,7 @@ private enum LocationPickerEntry: Comparable, Identifiable {
|
|||||||
} else {
|
} else {
|
||||||
icon = .location
|
icon = .location
|
||||||
}
|
}
|
||||||
return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), account: account, title: title, subtitle: subtitle, icon: icon, action: {
|
return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), account: account, title: title, subtitle: subtitle, icon: icon, beginTimeAndTimeout: nil, action: {
|
||||||
if let venue = venue {
|
if let venue = venue {
|
||||||
interaction?.sendVenue(venue)
|
interaction?.sendVenue(venue)
|
||||||
} else if let coordinate = coordinate {
|
} else if let coordinate = coordinate {
|
||||||
@ -149,7 +149,7 @@ private enum LocationPickerEntry: Comparable, Identifiable {
|
|||||||
interaction?.updateSendActionHighlight(highlighted)
|
interaction?.updateSendActionHighlight(highlighted)
|
||||||
})
|
})
|
||||||
case let .liveLocation(_, title, subtitle, coordinate):
|
case let .liveLocation(_, title, subtitle, coordinate):
|
||||||
return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), account: account, title: title, subtitle: subtitle, icon: .liveLocation, action: {
|
return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), account: account, title: title, subtitle: subtitle, icon: .liveLocation, beginTimeAndTimeout: nil, action: {
|
||||||
if let coordinate = coordinate {
|
if let coordinate = coordinate {
|
||||||
interaction?.sendLiveLocation(coordinate)
|
interaction?.sendLiveLocation(coordinate)
|
||||||
}
|
}
|
||||||
@ -386,10 +386,10 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
|||||||
|> map { homeCoordinate, workCoordinate -> [TelegramMediaMap]? in
|
|> map { homeCoordinate, workCoordinate -> [TelegramMediaMap]? in
|
||||||
var venues: [TelegramMediaMap] = []
|
var venues: [TelegramMediaMap] = []
|
||||||
if let (latitude, longitude) = homeCoordinate, let address = homeAddress {
|
if let (latitude, longitude) = homeCoordinate, let address = homeAddress {
|
||||||
venues.append(TelegramMediaMap(latitude: latitude, longitude: longitude, geoPlace: nil, venue: MapVenue(title: presentationData.strings.Map_Home, address: address.displayString, provider: nil, id: "home", type: "home"), liveBroadcastingTimeout: nil))
|
venues.append(TelegramMediaMap(latitude: latitude, longitude: longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: MapVenue(title: presentationData.strings.Map_Home, address: address.displayString, provider: nil, id: "home", type: "home"), liveBroadcastingTimeout: nil))
|
||||||
}
|
}
|
||||||
if let (latitude, longitude) = workCoordinate, let address = workAddress {
|
if let (latitude, longitude) = workCoordinate, let address = workAddress {
|
||||||
venues.append(TelegramMediaMap(latitude: latitude, longitude: longitude, geoPlace: nil, venue: MapVenue(title: presentationData.strings.Map_Work, address: address.displayString, provider: nil, id: "work", type: "work"), liveBroadcastingTimeout: nil))
|
venues.append(TelegramMediaMap(latitude: latitude, longitude: longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: MapVenue(title: presentationData.strings.Map_Work, address: address.displayString, provider: nil, id: "work", type: "work"), liveBroadcastingTimeout: nil))
|
||||||
}
|
}
|
||||||
return venues
|
return venues
|
||||||
}
|
}
|
||||||
|
@ -179,7 +179,7 @@ final class LocationSearchContainerNode: ASDisplayNode {
|
|||||||
guard let placemarkLocation = placemark.location else {
|
guard let placemarkLocation = placemark.location else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
let location = TelegramMediaMap(latitude: placemarkLocation.coordinate.latitude, longitude: placemarkLocation.coordinate.longitude, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil)
|
let location = TelegramMediaMap(latitude: placemarkLocation.coordinate.latitude, longitude: placemarkLocation.coordinate.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil)
|
||||||
|
|
||||||
entries.append(LocationSearchEntry(index: index, theme: themeAndStrings.0, location: location, title: placemark.name ?? "Name", distance: placemarkLocation.distance(from: currentLocation)))
|
entries.append(LocationSearchEntry(index: index, theme: themeAndStrings.0, location: location, title: placemark.name ?? "Name", distance: placemarkLocation.distance(from: currentLocation)))
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import MapKit
|
|||||||
|
|
||||||
extension TelegramMediaMap {
|
extension TelegramMediaMap {
|
||||||
convenience init(coordinate: CLLocationCoordinate2D, liveBroadcastingTimeout: Int32? = nil) {
|
convenience init(coordinate: CLLocationCoordinate2D, liveBroadcastingTimeout: Int32? = nil) {
|
||||||
self.init(latitude: coordinate.latitude, longitude: coordinate.longitude, geoPlace: nil, venue: nil, liveBroadcastingTimeout: liveBroadcastingTimeout)
|
self.init(latitude: coordinate.latitude, longitude: coordinate.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: nil, liveBroadcastingTimeout: liveBroadcastingTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
var coordinate: CLLocationCoordinate2D {
|
var coordinate: CLLocationCoordinate2D {
|
||||||
|
@ -13,6 +13,7 @@ import CoreLocation
|
|||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
import OpenInExternalAppUI
|
import OpenInExternalAppUI
|
||||||
import ShareController
|
import ShareController
|
||||||
|
import DeviceAccess
|
||||||
|
|
||||||
public class LocationViewParams {
|
public class LocationViewParams {
|
||||||
let sendLiveLocation: (TelegramMediaMap) -> Void
|
let sendLiveLocation: (TelegramMediaMap) -> Void
|
||||||
@ -28,6 +29,12 @@ public class LocationViewParams {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum LocationViewRightBarButton {
|
||||||
|
case none
|
||||||
|
case share
|
||||||
|
case showAll
|
||||||
|
}
|
||||||
|
|
||||||
class LocationViewInteraction {
|
class LocationViewInteraction {
|
||||||
let toggleMapModeSelection: () -> Void
|
let toggleMapModeSelection: () -> Void
|
||||||
let updateMapMode: (LocationMapMode) -> Void
|
let updateMapMode: (LocationMapMode) -> Void
|
||||||
@ -35,9 +42,13 @@ class LocationViewInteraction {
|
|||||||
let goToCoordinate: (CLLocationCoordinate2D) -> Void
|
let goToCoordinate: (CLLocationCoordinate2D) -> Void
|
||||||
let requestDirections: () -> Void
|
let requestDirections: () -> Void
|
||||||
let share: () -> Void
|
let share: () -> Void
|
||||||
let setupProximityNotification: () -> Void
|
let setupProximityNotification: (CLLocationCoordinate2D, Bool) -> Void
|
||||||
|
let updateSendActionHighlight: (Bool) -> Void
|
||||||
|
let sendLiveLocation: (CLLocationCoordinate2D) -> Void
|
||||||
|
let stopLiveLocation: () -> Void
|
||||||
|
let updateRightBarButton: (LocationViewRightBarButton) -> Void
|
||||||
|
|
||||||
init(toggleMapModeSelection: @escaping () -> Void, updateMapMode: @escaping (LocationMapMode) -> Void, goToUserLocation: @escaping () -> Void, goToCoordinate: @escaping (CLLocationCoordinate2D) -> Void, requestDirections: @escaping () -> Void, share: @escaping () -> Void, setupProximityNotification: @escaping () -> Void) {
|
init(toggleMapModeSelection: @escaping () -> Void, updateMapMode: @escaping (LocationMapMode) -> Void, goToUserLocation: @escaping () -> Void, goToCoordinate: @escaping (CLLocationCoordinate2D) -> Void, requestDirections: @escaping () -> Void, share: @escaping () -> Void, setupProximityNotification: @escaping (CLLocationCoordinate2D, Bool) -> Void, updateSendActionHighlight: @escaping (Bool) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D) -> Void, stopLiveLocation: @escaping () -> Void, updateRightBarButton: @escaping (LocationViewRightBarButton) -> Void) {
|
||||||
self.toggleMapModeSelection = toggleMapModeSelection
|
self.toggleMapModeSelection = toggleMapModeSelection
|
||||||
self.updateMapMode = updateMapMode
|
self.updateMapMode = updateMapMode
|
||||||
self.goToUserLocation = goToUserLocation
|
self.goToUserLocation = goToUserLocation
|
||||||
@ -45,23 +56,34 @@ class LocationViewInteraction {
|
|||||||
self.requestDirections = requestDirections
|
self.requestDirections = requestDirections
|
||||||
self.share = share
|
self.share = share
|
||||||
self.setupProximityNotification = setupProximityNotification
|
self.setupProximityNotification = setupProximityNotification
|
||||||
|
self.updateSendActionHighlight = updateSendActionHighlight
|
||||||
|
self.sendLiveLocation = sendLiveLocation
|
||||||
|
self.stopLiveLocation = stopLiveLocation
|
||||||
|
self.updateRightBarButton = updateRightBarButton
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var CURRENT_DISTANCE: Double? = nil
|
||||||
|
|
||||||
public final class LocationViewController: ViewController {
|
public final class LocationViewController: ViewController {
|
||||||
private var controllerNode: LocationViewControllerNode {
|
private var controllerNode: LocationViewControllerNode {
|
||||||
return self.displayNode as! LocationViewControllerNode
|
return self.displayNode as! LocationViewControllerNode
|
||||||
}
|
}
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private var mapMedia: TelegramMediaMap
|
private var subject: Message
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
private var presentationDataDisposable: Disposable?
|
private var presentationDataDisposable: Disposable?
|
||||||
|
|
||||||
|
private let locationManager = LocationManager()
|
||||||
|
private var permissionDisposable: Disposable?
|
||||||
|
|
||||||
private var interaction: LocationViewInteraction?
|
private var interaction: LocationViewInteraction?
|
||||||
|
|
||||||
public init(context: AccountContext, mapMedia: TelegramMediaMap, params: LocationViewParams) {
|
private var rightBarButtonAction: LocationViewRightBarButton = .none
|
||||||
|
|
||||||
|
public init(context: AccountContext, subject: Message, params: LocationViewParams) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.mapMedia = mapMedia
|
self.subject = subject
|
||||||
|
|
||||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
@ -71,8 +93,6 @@ public final class LocationViewController: ViewController {
|
|||||||
|
|
||||||
self.title = self.presentationData.strings.Map_LocationTitle
|
self.title = self.presentationData.strings.Map_LocationTitle
|
||||||
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Close, style: .plain, target: self, action: #selector(self.cancelPressed))
|
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Close, style: .plain, target: self, action: #selector(self.cancelPressed))
|
||||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationShareIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.sharePressed))
|
|
||||||
self.navigationItem.rightBarButtonItem?.accessibilityLabel = self.presentationData.strings.VoiceOver_MessageContextShare
|
|
||||||
|
|
||||||
self.presentationDataDisposable = (context.sharedContext.presentationData
|
self.presentationDataDisposable = (context.sharedContext.presentationData
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||||
@ -83,7 +103,7 @@ public final class LocationViewController: ViewController {
|
|||||||
|
|
||||||
strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: strongSelf.presentationData.theme).withUpdatedSeparatorColor(.clear), strings: NavigationBarStrings(presentationStrings: strongSelf.presentationData.strings)))
|
strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: strongSelf.presentationData.theme).withUpdatedSeparatorColor(.clear), strings: NavigationBarStrings(presentationStrings: strongSelf.presentationData.strings)))
|
||||||
|
|
||||||
strongSelf.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationShareIcon(strongSelf.presentationData.theme), style: .plain, target: strongSelf, action: #selector(strongSelf.sharePressed))
|
strongSelf.updateRightBarButton()
|
||||||
|
|
||||||
if strongSelf.isNodeLoaded {
|
if strongSelf.isNodeLoaded {
|
||||||
strongSelf.controllerNode.updatePresentationData(presentationData)
|
strongSelf.controllerNode.updatePresentationData(presentationData)
|
||||||
@ -126,27 +146,124 @@ public final class LocationViewController: ViewController {
|
|||||||
strongSelf.controllerNode.updateState { state in
|
strongSelf.controllerNode.updateState { state in
|
||||||
var state = state
|
var state = state
|
||||||
state.displayingMapModeOptions = false
|
state.displayingMapModeOptions = false
|
||||||
state.selectedLocation = .coordinate(coordinate)
|
state.selectedLocation = .coordinate(coordinate, false)
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
}, requestDirections: { [weak self] in
|
}, requestDirections: { [weak self] in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.present(OpenInActionSheetController(context: context, item: .location(location: mapMedia, withDirections: true), additionalAction: nil, openUrl: params.openUrl), in: .window(.root), with: nil)
|
if let location = getLocation(from: strongSelf.subject) {
|
||||||
|
strongSelf.present(OpenInActionSheetController(context: context, item: .location(location: location, withDirections: true), additionalAction: nil, openUrl: params.openUrl), in: .window(.root), with: nil)
|
||||||
|
}
|
||||||
}, share: { [weak self] in
|
}, share: { [weak self] in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if let location = getLocation(from: strongSelf.subject) {
|
||||||
let shareAction = OpenInControllerAction(title: strongSelf.presentationData.strings.Conversation_ContextMenuShare, action: {
|
let shareAction = OpenInControllerAction(title: strongSelf.presentationData.strings.Conversation_ContextMenuShare, action: {
|
||||||
strongSelf.present(ShareController(context: context, subject: .mapMedia(mapMedia), externalShare: true), in: .window(.root), with: nil)
|
strongSelf.present(ShareController(context: context, subject: .mapMedia(location), externalShare: true), in: .window(.root), with: nil)
|
||||||
})
|
})
|
||||||
strongSelf.present(OpenInActionSheetController(context: context, item: .location(location: mapMedia, withDirections: false), additionalAction: shareAction, openUrl: params.openUrl), in: .window(.root), with: nil)
|
strongSelf.present(OpenInActionSheetController(context: context, item: .location(location: location, withDirections: false), additionalAction: shareAction, openUrl: params.openUrl), in: .window(.root), with: nil)
|
||||||
}, setupProximityNotification: { [weak self] in
|
}
|
||||||
|
}, setupProximityNotification: { [weak self] coordinate, reset in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if reset {
|
||||||
|
strongSelf.controllerNode.setProximityRadius(radius: nil)
|
||||||
|
CURRENT_DISTANCE = nil
|
||||||
|
} else {
|
||||||
|
strongSelf.controllerNode.setProximityIndicator(radius: 0)
|
||||||
|
|
||||||
|
let controller = LocationDistancePickerScreen(context: context, style: .default, currentDistance: strongSelf.controllerNode.headerNode.mapNode.distance, updated: { [weak self] distance in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.controllerNode.setProximityIndicator(radius: distance)
|
||||||
|
}, completion: { [weak self] distance in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.controllerNode.setProximityIndicator(radius: nil)
|
||||||
|
if let distance = distance {
|
||||||
|
strongSelf.controllerNode.setProximityRadius(radius: distance)
|
||||||
|
|
||||||
|
let _ = requestProximityNotification(postbox: context.account.postbox, network: context.account.network, messageId: subject.id, distance: distance, coordinate: (coordinate.latitude, longitude: coordinate.longitude, accuracyRadius: nil)).start()
|
||||||
|
|
||||||
|
CURRENT_DISTANCE = Double(distance)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
strongSelf.present(controller, in: .window(.root))
|
||||||
|
}
|
||||||
|
}, updateSendActionHighlight: { [weak self] highlighted in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.controllerNode.updateSendActionHighlight(highlighted)
|
||||||
|
}, sendLiveLocation: { [weak self] coordinate in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
DeviceAccess.authorizeAccess(to: .location(.live), locationManager: strongSelf.locationManager, presentationData: strongSelf.presentationData, present: { c, a in
|
||||||
|
strongSelf.present(c, in: .window(.root), with: a)
|
||||||
|
}, openSettings: {
|
||||||
|
strongSelf.context.sharedContext.applicationBindings.openSettings()
|
||||||
|
}) { [weak self] authorized in
|
||||||
|
guard let strongSelf = self, authorized else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let controller = ActionSheetController(presentationData: strongSelf.presentationData)
|
||||||
|
var title = strongSelf.presentationData.strings.Map_LiveLocationGroupDescription
|
||||||
|
if subject.id.peerId.namespace == Namespaces.Peer.CloudUser {
|
||||||
|
// title = strongSelf.presentationData.strings.Map_LiveLocationPrivateDescription(receiver.compactDisplayTitle).0
|
||||||
|
}
|
||||||
|
controller.setItemGroups([
|
||||||
|
ActionSheetItemGroup(items: [
|
||||||
|
ActionSheetTextItem(title: title),
|
||||||
|
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor15Minutes, color: .accent, action: { [weak self, weak controller] in
|
||||||
|
controller?.dismissAnimated()
|
||||||
|
if let strongSelf = self {
|
||||||
|
params.sendLiveLocation(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: 15 * 60))
|
||||||
|
strongSelf.dismiss()
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor1Hour, color: .accent, action: { [weak self, weak controller] in
|
||||||
|
controller?.dismissAnimated()
|
||||||
|
if let strongSelf = self {
|
||||||
|
params.sendLiveLocation(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: 60 * 60 - 1))
|
||||||
|
strongSelf.dismiss()
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor8Hours, color: .accent, action: { [weak self, weak controller] in
|
||||||
|
controller?.dismissAnimated()
|
||||||
|
if let strongSelf = self {
|
||||||
|
params.sendLiveLocation(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: 8 * 60 * 60))
|
||||||
|
strongSelf.dismiss()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
ActionSheetItemGroup(items: [
|
||||||
|
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak controller] in
|
||||||
|
controller?.dismissAnimated()
|
||||||
|
})
|
||||||
|
])
|
||||||
|
])
|
||||||
|
strongSelf.present(controller, in: .window(.root))
|
||||||
|
}
|
||||||
|
}, stopLiveLocation: { [weak self] in
|
||||||
|
params.stopLiveLocation()
|
||||||
|
self?.dismiss()
|
||||||
|
}, updateRightBarButton: { [weak self] action in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if action != strongSelf.rightBarButtonAction {
|
||||||
|
strongSelf.rightBarButtonAction = action
|
||||||
|
strongSelf.updateRightBarButton()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
self.scrollToTop = { [weak self] in
|
self.scrollToTop = { [weak self] in
|
||||||
@ -170,8 +287,26 @@ public final class LocationViewController: ViewController {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.displayNode = LocationViewControllerNode(context: self.context, presentationData: self.presentationData, mapMedia: self.mapMedia, interaction: interaction)
|
self.displayNode = LocationViewControllerNode(context: self.context, presentationData: self.presentationData, subject: self.subject, interaction: interaction)
|
||||||
self.displayNodeDidLoad()
|
self.displayNodeDidLoad()
|
||||||
|
|
||||||
|
self.controllerNode.updateState { state -> LocationViewState in
|
||||||
|
var state = state
|
||||||
|
state.proximityRadius = CURRENT_DISTANCE
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateRightBarButton() {
|
||||||
|
switch self.rightBarButtonAction {
|
||||||
|
case .none:
|
||||||
|
self.navigationItem.rightBarButtonItem = nil
|
||||||
|
case .share:
|
||||||
|
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationShareIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.sharePressed))
|
||||||
|
self.navigationItem.rightBarButtonItem?.accessibilityLabel = self.presentationData.strings.VoiceOver_MessageContextShare
|
||||||
|
case .showAll:
|
||||||
|
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Map_LiveLocationShowAll, style: .plain, target: self, action: #selector(self.showAllPressed))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
@ -189,7 +324,7 @@ public final class LocationViewController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc private func showAllPressed() {
|
@objc private func showAllPressed() {
|
||||||
self.dismiss()
|
self.controllerNode.showAll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,11 @@ import AccountContext
|
|||||||
import AppBundle
|
import AppBundle
|
||||||
import CoreLocation
|
import CoreLocation
|
||||||
import Geocoding
|
import Geocoding
|
||||||
|
import TelegramStringFormatting
|
||||||
|
|
||||||
|
func getLocation(from message: Message) -> TelegramMediaMap? {
|
||||||
|
return message.media.first(where: { $0 is TelegramMediaMap } ) as? TelegramMediaMap
|
||||||
|
}
|
||||||
|
|
||||||
private func areMessagesEqual(_ lhsMessage: Message, _ rhsMessage: Message) -> Bool {
|
private func areMessagesEqual(_ lhsMessage: Message, _ rhsMessage: Message) -> Bool {
|
||||||
if lhsMessage.stableVersion != rhsMessage.stableVersion {
|
if lhsMessage.stableVersion != rhsMessage.stableVersion {
|
||||||
@ -39,8 +44,8 @@ private enum LocationViewEntryId: Hashable {
|
|||||||
|
|
||||||
private enum LocationViewEntry: Comparable, Identifiable {
|
private enum LocationViewEntry: Comparable, Identifiable {
|
||||||
case info(PresentationTheme, TelegramMediaMap, String?, Double?, Double?)
|
case info(PresentationTheme, TelegramMediaMap, String?, Double?, Double?)
|
||||||
case toggleLiveLocation(PresentationTheme, String, String)
|
case toggleLiveLocation(PresentationTheme, String, String, CLLocationCoordinate2D?, Double?, Double?)
|
||||||
case liveLocation(PresentationTheme, Message, Int)
|
case liveLocation(PresentationTheme, Message, Double?, Int)
|
||||||
|
|
||||||
var stableId: LocationViewEntryId {
|
var stableId: LocationViewEntryId {
|
||||||
switch self {
|
switch self {
|
||||||
@ -48,10 +53,14 @@ private enum LocationViewEntry: Comparable, Identifiable {
|
|||||||
return .info
|
return .info
|
||||||
case .toggleLiveLocation:
|
case .toggleLiveLocation:
|
||||||
return .toggleLiveLocation
|
return .toggleLiveLocation
|
||||||
case let .liveLocation(_, message, _):
|
case let .liveLocation(_, message, _, _):
|
||||||
|
if let author = message.author {
|
||||||
|
return .liveLocation(author.id)
|
||||||
|
} else {
|
||||||
return .liveLocation(message.id.peerId)
|
return .liveLocation(message.id.peerId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static func ==(lhs: LocationViewEntry, rhs: LocationViewEntry) -> Bool {
|
static func ==(lhs: LocationViewEntry, rhs: LocationViewEntry) -> Bool {
|
||||||
switch lhs {
|
switch lhs {
|
||||||
@ -61,14 +70,14 @@ private enum LocationViewEntry: Comparable, Identifiable {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .toggleLiveLocation(lhsTheme, lhsTitle, lhsSubtitle):
|
case let .toggleLiveLocation(lhsTheme, lhsTitle, lhsSubtitle, lhsCoordinate, lhsBeginTimestamp, lhsTimeout):
|
||||||
if case let .toggleLiveLocation(rhsTheme, rhsTitle, rhsSubtitle) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle {
|
if case let .toggleLiveLocation(rhsTheme, rhsTitle, rhsSubtitle, rhsCoordinate, rhsBeginTimestamp, rhsTimeout) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsCoordinate == rhsCoordinate, lhsBeginTimestamp == rhsBeginTimestamp, lhsTimeout == rhsTimeout {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .liveLocation(lhsTheme, lhsMessage, lhsIndex):
|
case let .liveLocation(lhsTheme, lhsMessage, lhsDistance, lhsIndex):
|
||||||
if case let .liveLocation(rhsTheme, rhsMessage, rhsIndex) = rhs, lhsTheme === rhsTheme, areMessagesEqual(lhsMessage, rhsMessage), lhsIndex == rhsIndex {
|
if case let .liveLocation(rhsTheme, rhsMessage, rhsDistance, rhsIndex) = rhs, lhsTheme === rhsTheme, areMessagesEqual(lhsMessage, rhsMessage), lhsDistance == rhsDistance, lhsIndex == rhsIndex {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@ -92,11 +101,11 @@ private enum LocationViewEntry: Comparable, Identifiable {
|
|||||||
case .liveLocation:
|
case .liveLocation:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case let .liveLocation(_, _, lhsIndex):
|
case let .liveLocation(_, _, _, lhsIndex):
|
||||||
switch rhs {
|
switch rhs {
|
||||||
case .info, .toggleLiveLocation:
|
case .info, .toggleLiveLocation:
|
||||||
return false
|
return false
|
||||||
case let .liveLocation(_, _, rhsIndex):
|
case let .liveLocation(_, _, _, rhsIndex):
|
||||||
return lhsIndex < rhsIndex
|
return lhsIndex < rhsIndex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,7 +113,7 @@ private enum LocationViewEntry: Comparable, Identifiable {
|
|||||||
|
|
||||||
func item(account: Account, presentationData: PresentationData, interaction: LocationViewInteraction?) -> ListViewItem {
|
func item(account: Account, presentationData: PresentationData, interaction: LocationViewInteraction?) -> ListViewItem {
|
||||||
switch self {
|
switch self {
|
||||||
case let .info(theme, location, address, distance, time):
|
case let .info(_, location, address, distance, time):
|
||||||
let addressString: String?
|
let addressString: String?
|
||||||
if let address = address {
|
if let address = address {
|
||||||
addressString = address
|
addressString = address
|
||||||
@ -123,13 +132,29 @@ private enum LocationViewEntry: Comparable, Identifiable {
|
|||||||
}, getDirections: {
|
}, getDirections: {
|
||||||
interaction?.requestDirections()
|
interaction?.requestDirections()
|
||||||
})
|
})
|
||||||
case let .toggleLiveLocation(theme, title, subtitle):
|
case let .toggleLiveLocation(_, title, subtitle, coordinate, beginTimstamp, timeout):
|
||||||
return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), account: account, title: title, subtitle: subtitle, icon: .liveLocation, action: {
|
let beginTimeAndTimeout: (Double, Double)?
|
||||||
// if let coordinate = coordinate {
|
if let beginTimstamp = beginTimstamp, let timeout = timeout {
|
||||||
// interaction?.sendLiveLocation(coordinate)
|
beginTimeAndTimeout = (beginTimstamp, timeout)
|
||||||
// }
|
} else {
|
||||||
|
beginTimeAndTimeout = nil
|
||||||
|
}
|
||||||
|
return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), account: account, title: title, subtitle: subtitle, icon: beginTimeAndTimeout != nil ? .stopLiveLocation : .liveLocation, beginTimeAndTimeout: beginTimeAndTimeout, action: {
|
||||||
|
if beginTimeAndTimeout != nil {
|
||||||
|
interaction?.stopLiveLocation()
|
||||||
|
} else if let coordinate = coordinate {
|
||||||
|
interaction?.sendLiveLocation(coordinate)
|
||||||
|
}
|
||||||
|
}, highlighted: { highlight in
|
||||||
|
interaction?.updateSendActionHighlight(highlight)
|
||||||
})
|
})
|
||||||
case let .liveLocation(theme, message, _):
|
case let .liveLocation(theme, message, distance, _):
|
||||||
|
let distanceString: String?
|
||||||
|
if let distance = distance {
|
||||||
|
distanceString = distance < 10 ? presentationData.strings.Map_YouAreHere : presentationData.strings.Map_DistanceAway(stringForDistance(strings: presentationData.strings, distance: distance)).0
|
||||||
|
} else {
|
||||||
|
distanceString = nil
|
||||||
|
}
|
||||||
return ItemListTextItem(presentationData: ItemListPresentationData(presentationData), text: .plain(""), sectionId: 0)
|
return ItemListTextItem(presentationData: ItemListPresentationData(presentationData), text: .plain(""), sectionId: 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -148,7 +173,7 @@ private func preparedTransition(from fromEntries: [LocationViewEntry], to toEntr
|
|||||||
enum LocationViewLocation: Equatable {
|
enum LocationViewLocation: Equatable {
|
||||||
case initial
|
case initial
|
||||||
case user
|
case user
|
||||||
case coordinate(CLLocationCoordinate2D)
|
case coordinate(CLLocationCoordinate2D, Bool)
|
||||||
case custom
|
case custom
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,11 +181,13 @@ struct LocationViewState {
|
|||||||
var mapMode: LocationMapMode
|
var mapMode: LocationMapMode
|
||||||
var displayingMapModeOptions: Bool
|
var displayingMapModeOptions: Bool
|
||||||
var selectedLocation: LocationViewLocation
|
var selectedLocation: LocationViewLocation
|
||||||
|
var proximityRadius: Double?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
self.mapMode = .map
|
self.mapMode = .map
|
||||||
self.displayingMapModeOptions = false
|
self.displayingMapModeOptions = false
|
||||||
self.selectedLocation = .initial
|
self.selectedLocation = .initial
|
||||||
|
self.proximityRadius = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,11 +195,11 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
|
|||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
private let presentationDataPromise: Promise<PresentationData>
|
private let presentationDataPromise: Promise<PresentationData>
|
||||||
private var mapMedia: TelegramMediaMap
|
private var subject: Message
|
||||||
private let interaction: LocationViewInteraction
|
private let interaction: LocationViewInteraction
|
||||||
|
|
||||||
private let listNode: ListView
|
private let listNode: ListView
|
||||||
private let headerNode: LocationMapHeaderNode
|
let headerNode: LocationMapHeaderNode
|
||||||
private let optionsNode: LocationOptionsNode
|
private let optionsNode: LocationOptionsNode
|
||||||
|
|
||||||
private var enqueuedTransitions: [LocationViewTransaction] = []
|
private var enqueuedTransitions: [LocationViewTransaction] = []
|
||||||
@ -185,11 +212,11 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
|
|||||||
private var validLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)?
|
private var validLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)?
|
||||||
private var listOffset: CGFloat?
|
private var listOffset: CGFloat?
|
||||||
|
|
||||||
init(context: AccountContext, presentationData: PresentationData, mapMedia: TelegramMediaMap, interaction: LocationViewInteraction) {
|
init(context: AccountContext, presentationData: PresentationData, subject: Message, interaction: LocationViewInteraction) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.presentationDataPromise = Promise(presentationData)
|
self.presentationDataPromise = Promise(presentationData)
|
||||||
self.mapMedia = mapMedia
|
self.subject = subject
|
||||||
self.interaction = interaction
|
self.interaction = interaction
|
||||||
|
|
||||||
self.state = LocationViewState()
|
self.state = LocationViewState()
|
||||||
@ -213,44 +240,131 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
|
|||||||
self.addSubnode(self.headerNode)
|
self.addSubnode(self.headerNode)
|
||||||
self.addSubnode(self.optionsNode)
|
self.addSubnode(self.optionsNode)
|
||||||
|
|
||||||
let distance: Signal<Double?, NoError> = .single(nil)
|
let userLocation: Signal<CLLocation?, NoError> = .single(nil)
|
||||||
|> then(
|
|> then(
|
||||||
throttledUserLocation(self.headerNode.mapNode.userLocation)
|
throttledUserLocation(self.headerNode.mapNode.userLocation)
|
||||||
|> map { userLocation -> Double? in
|
|
||||||
let location = CLLocation(latitude: mapMedia.latitude, longitude: mapMedia.longitude)
|
|
||||||
return userLocation.flatMap { location.distance(from: $0) }
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
let address: Signal<String?, NoError>
|
|
||||||
var eta: Signal<Double?, NoError> = .single(nil)
|
var eta: Signal<Double?, NoError> = .single(nil)
|
||||||
|> then(
|
var address: Signal<String?, NoError> = .single(nil)
|
||||||
driveEta(coordinate: mapMedia.coordinate)
|
|
||||||
)
|
if let location = getLocation(from: subject), location.liveBroadcastingTimeout == nil {
|
||||||
if let venue = mapMedia.venue, let venueAddress = venue.address, !venueAddress.isEmpty {
|
eta = .single(nil)
|
||||||
|
|> then(driveEta(coordinate: location.coordinate))
|
||||||
|
|
||||||
|
if let venue = location.venue, let venueAddress = venue.address, !venueAddress.isEmpty {
|
||||||
address = .single(venueAddress)
|
address = .single(venueAddress)
|
||||||
} else if mapMedia.liveBroadcastingTimeout == nil {
|
} else {
|
||||||
address = .single(nil)
|
address = .single(nil)
|
||||||
|> then(
|
|> then(
|
||||||
reverseGeocodeLocation(latitude: mapMedia.latitude, longitude: mapMedia.longitude)
|
reverseGeocodeLocation(latitude: location.latitude, longitude: location.longitude)
|
||||||
|> map { placemark -> String? in
|
|> map { placemark -> String? in
|
||||||
return placemark?.compactDisplayAddress ?? ""
|
return placemark?.compactDisplayAddress ?? ""
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else {
|
}
|
||||||
address = .single(nil)
|
}
|
||||||
eta = .single(nil)
|
|
||||||
|
let liveLocations = topPeerActiveLiveLocationMessages(viewTracker: context.account.viewTracker, accountPeerId: context.account.peerId, peerId: subject.id.peerId)
|
||||||
|
|> map { _, messages -> [Message] in
|
||||||
|
return messages
|
||||||
}
|
}
|
||||||
|
|
||||||
let previousState = Atomic<LocationViewState?>(value: nil)
|
let previousState = Atomic<LocationViewState?>(value: nil)
|
||||||
|
let previousUserAnnotation = Atomic<LocationPinAnnotation?>(value: nil)
|
||||||
let previousAnnotations = Atomic<[LocationPinAnnotation]>(value: [])
|
let previousAnnotations = Atomic<[LocationPinAnnotation]>(value: [])
|
||||||
let previousEntries = Atomic<[LocationViewEntry]?>(value: nil)
|
let previousEntries = Atomic<[LocationViewEntry]?>(value: nil)
|
||||||
|
|
||||||
self.disposable = (combineLatest(self.presentationDataPromise.get(), self.statePromise.get(), self.headerNode.mapNode.userLocation, distance, address, eta)
|
let selfPeer = context.account.postbox.transaction { transaction -> Peer? in
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData, state, userLocation, distance, address, eta in
|
return transaction.getPeer(context.account.peerId)
|
||||||
if let strongSelf = self {
|
}
|
||||||
var entries: [LocationViewEntry] = []
|
|
||||||
|
|
||||||
entries.append(.info(presentationData.theme, mapMedia, address, distance, eta))
|
self.disposable = (combineLatest(self.presentationDataPromise.get(), self.statePromise.get(), selfPeer, liveLocations, self.headerNode.mapNode.userLocation, userLocation, address, eta)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] presentationData, state, selfPeer, liveLocations, userLocation, distance, address, eta in
|
||||||
|
if let strongSelf = self, let location = getLocation(from: subject) {
|
||||||
|
var entries: [LocationViewEntry] = []
|
||||||
|
var annotations: [LocationPinAnnotation] = []
|
||||||
|
var userAnnotation: LocationPinAnnotation? = nil
|
||||||
|
var effectiveLiveLocations: [Message] = liveLocations
|
||||||
|
|
||||||
|
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||||
|
|
||||||
|
var proximityNotification: Bool? = nil
|
||||||
|
var index: Int = 0
|
||||||
|
|
||||||
|
if location.liveBroadcastingTimeout == nil {
|
||||||
|
let subjectLocation = CLLocation(latitude: location.latitude, longitude: location.longitude)
|
||||||
|
let distance = userLocation.flatMap { subjectLocation.distance(from: $0) }
|
||||||
|
|
||||||
|
entries.append(.info(presentationData.theme, location, address, distance, eta))
|
||||||
|
|
||||||
|
annotations.append(LocationPinAnnotation(context: context, theme: presentationData.theme, location: location, forcedSelection: true))
|
||||||
|
} else {
|
||||||
|
var activeOwnLiveLocation: Message?
|
||||||
|
for message in effectiveLiveLocations {
|
||||||
|
if message.localTags.contains(.OutgoingLiveLocation) {
|
||||||
|
activeOwnLiveLocation = message
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let title: String
|
||||||
|
let subtitle: String
|
||||||
|
let beginTime: Double?
|
||||||
|
let timeout: Double?
|
||||||
|
|
||||||
|
if let message = activeOwnLiveLocation {
|
||||||
|
var liveBroadcastingTimeout: Int32 = 0
|
||||||
|
if let location = getLocation(from: message), let timeout = location.liveBroadcastingTimeout {
|
||||||
|
liveBroadcastingTimeout = timeout
|
||||||
|
}
|
||||||
|
title = presentationData.strings.Map_StopLiveLocation
|
||||||
|
|
||||||
|
var updateTimestamp = message.timestamp
|
||||||
|
for attribute in message.attributes {
|
||||||
|
if let attribute = attribute as? EditedMessageAttribute {
|
||||||
|
updateTimestamp = attribute.date
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subtitle = stringForRelativeLiveLocationTimestamp(strings: presentationData.strings, relativeTimestamp: updateTimestamp, relativeTo: currentTime, dateTimeFormat: presentationData.dateTimeFormat)
|
||||||
|
beginTime = Double(message.timestamp)
|
||||||
|
timeout = Double(liveBroadcastingTimeout)
|
||||||
|
} else {
|
||||||
|
title = presentationData.strings.Map_ShareLiveLocation
|
||||||
|
subtitle = presentationData.strings.Map_ShareLiveLocation
|
||||||
|
beginTime = nil
|
||||||
|
timeout = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.append(.toggleLiveLocation(presentationData.theme, title, subtitle, userLocation?.coordinate, beginTime, timeout))
|
||||||
|
if effectiveLiveLocations.isEmpty {
|
||||||
|
effectiveLiveLocations = [subject]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for message in effectiveLiveLocations {
|
||||||
|
var liveBroadcastingTimeout: Int32 = 0
|
||||||
|
if let location = getLocation(from: message), let timeout = location.liveBroadcastingTimeout {
|
||||||
|
liveBroadcastingTimeout = timeout
|
||||||
|
}
|
||||||
|
let remainingTime = max(0, message.timestamp + liveBroadcastingTimeout - currentTime)
|
||||||
|
if message.flags.contains(.Incoming) && remainingTime != 0 {
|
||||||
|
proximityNotification = state.proximityRadius != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let subjectLocation = CLLocation(latitude: location.latitude, longitude: location.longitude)
|
||||||
|
let distance = userLocation.flatMap { subjectLocation.distance(from: $0) }
|
||||||
|
entries.append(.liveLocation(presentationData.theme, message, distance, index))
|
||||||
|
|
||||||
|
if message.localTags.contains(.OutgoingLiveLocation), let selfPeer = selfPeer {
|
||||||
|
userAnnotation = LocationPinAnnotation(context: context, theme: presentationData.theme, message: message, selfPeer: selfPeer, heading: nil)
|
||||||
|
} else {
|
||||||
|
annotations.append(LocationPinAnnotation(context: context, theme: presentationData.theme, message: message, selfPeer: nil, heading: nil))
|
||||||
|
}
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
|
||||||
let previousEntries = previousEntries.swap(entries)
|
let previousEntries = previousEntries.swap(entries)
|
||||||
let previousState = previousState.swap(state)
|
let previousState = previousState.swap(state)
|
||||||
@ -258,17 +372,17 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
|
|||||||
let transition = preparedTransition(from: previousEntries ?? [], to: entries, account: context.account, presentationData: presentationData, interaction: strongSelf.interaction)
|
let transition = preparedTransition(from: previousEntries ?? [], to: entries, account: context.account, presentationData: presentationData, interaction: strongSelf.interaction)
|
||||||
strongSelf.enqueueTransition(transition)
|
strongSelf.enqueueTransition(transition)
|
||||||
|
|
||||||
strongSelf.headerNode.updateState(mapMode: state.mapMode, displayingMapModeOptions: state.displayingMapModeOptions, displayingPlacesButton: false, proximityNotification: false, animated: false)
|
strongSelf.headerNode.updateState(mapMode: state.mapMode, displayingMapModeOptions: state.displayingMapModeOptions, displayingPlacesButton: false, proximityNotification: proximityNotification, animated: false)
|
||||||
|
|
||||||
switch state.selectedLocation {
|
switch state.selectedLocation {
|
||||||
case .initial:
|
case .initial:
|
||||||
if previousState?.selectedLocation != .initial {
|
if previousState?.selectedLocation != .initial {
|
||||||
strongSelf.headerNode.mapNode.setMapCenter(coordinate: mapMedia.coordinate, span: viewMapSpan, animated: previousState != nil)
|
strongSelf.headerNode.mapNode.setMapCenter(coordinate: location.coordinate, span: viewMapSpan, animated: previousState != nil)
|
||||||
}
|
}
|
||||||
case let .coordinate(coordinate):
|
case let .coordinate(coordinate, defaultSpan):
|
||||||
if let previousState = previousState, case let .coordinate(previousCoordinate) = previousState.selectedLocation, previousCoordinate == coordinate {
|
if let previousState = previousState, case let .coordinate(previousCoordinate, _) = previousState.selectedLocation, previousCoordinate == coordinate {
|
||||||
} else {
|
} else {
|
||||||
strongSelf.headerNode.mapNode.setMapCenter(coordinate: coordinate, span: viewMapSpan, animated: true)
|
strongSelf.headerNode.mapNode.setMapCenter(coordinate: coordinate, span: defaultSpan ? defaultMapSpan : viewMapSpan, animated: true)
|
||||||
}
|
}
|
||||||
case .user:
|
case .user:
|
||||||
if previousState?.selectedLocation != .user, let userLocation = userLocation {
|
if previousState?.selectedLocation != .user, let userLocation = userLocation {
|
||||||
@ -278,17 +392,32 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
let annotations: [LocationPinAnnotation] = [LocationPinAnnotation(context: context, theme: presentationData.theme, location: mapMedia, forcedSelection: true)]
|
|
||||||
|
|
||||||
let previousAnnotations = previousAnnotations.swap(annotations)
|
let previousAnnotations = previousAnnotations.swap(annotations)
|
||||||
|
let previousUserAnnotation = previousUserAnnotation.swap(userAnnotation)
|
||||||
|
if (userAnnotation == nil) != (previousUserAnnotation == nil) {
|
||||||
|
strongSelf.headerNode.mapNode.userLocationAnnotation = userAnnotation
|
||||||
|
}
|
||||||
if annotations != previousAnnotations {
|
if annotations != previousAnnotations {
|
||||||
strongSelf.headerNode.mapNode.annotations = annotations
|
strongSelf.headerNode.mapNode.annotations = annotations
|
||||||
}
|
}
|
||||||
|
|
||||||
|
strongSelf.headerNode.mapNode.activeProximityRadius = state.proximityRadius
|
||||||
|
|
||||||
|
let rightBarButtonAction: LocationViewRightBarButton
|
||||||
|
if location.liveBroadcastingTimeout != nil {
|
||||||
|
if liveLocations.count > 1 {
|
||||||
|
rightBarButtonAction = .showAll
|
||||||
|
} else {
|
||||||
|
rightBarButtonAction = .none
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rightBarButtonAction = .share
|
||||||
|
}
|
||||||
|
strongSelf.interaction.updateRightBarButton(rightBarButtonAction)
|
||||||
|
|
||||||
if let (layout, navigationBarHeight) = strongSelf.validLayout {
|
if let (layout, navigationBarHeight) = strongSelf.validLayout {
|
||||||
var updateLayout = false
|
var updateLayout = false
|
||||||
var transition: ContainedViewLayoutTransition = .animated(duration: 0.45, curve: .spring)
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.45, curve: .spring)
|
||||||
|
|
||||||
if previousState?.displayingMapModeOptions != state.displayingMapModeOptions {
|
if previousState?.displayingMapModeOptions != state.displayingMapModeOptions {
|
||||||
updateLayout = true
|
updateLayout = true
|
||||||
}
|
}
|
||||||
@ -355,6 +484,10 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
|
|||||||
self.statePromise.set(.single(self.state))
|
self.statePromise.set(.single(self.state))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateSendActionHighlight(_ highlighted: Bool) {
|
||||||
|
self.headerNode.updateHighlight(highlighted)
|
||||||
|
}
|
||||||
|
|
||||||
private func enqueueTransition(_ transition: LocationViewTransaction) {
|
private func enqueueTransition(_ transition: LocationViewTransaction) {
|
||||||
self.enqueuedTransitions.append(transition)
|
self.enqueuedTransitions.append(transition)
|
||||||
|
|
||||||
@ -371,9 +504,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
|
|||||||
}
|
}
|
||||||
self.enqueuedTransitions.remove(at: 0)
|
self.enqueuedTransitions.remove(at: 0)
|
||||||
|
|
||||||
var options = ListViewDeleteAndInsertOptions()
|
let options = ListViewDeleteAndInsertOptions()
|
||||||
|
|
||||||
|
|
||||||
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in
|
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -382,6 +513,43 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
|
|||||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setProximityIndicator(radius: Int32?) {
|
||||||
|
if let radius = radius {
|
||||||
|
self.headerNode.forceIsHidden = true
|
||||||
|
|
||||||
|
if var coordinate = self.headerNode.mapNode.currentUserLocation?.coordinate, let span = self.headerNode.mapNode.mapSpan {
|
||||||
|
coordinate.latitude -= span.latitudeDelta * 0.11
|
||||||
|
self.updateState { state in
|
||||||
|
var state = state
|
||||||
|
state.selectedLocation = .coordinate(coordinate, true)
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.headerNode.mapNode.proximityRadius = Double(radius)
|
||||||
|
} else {
|
||||||
|
self.headerNode.forceIsHidden = false
|
||||||
|
self.headerNode.mapNode.proximityRadius = nil
|
||||||
|
self.updateState { state in
|
||||||
|
var state = state
|
||||||
|
state.selectedLocation = .user
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setProximityRadius(radius: Int32?) {
|
||||||
|
self.updateState { state in
|
||||||
|
var state = state
|
||||||
|
state.proximityRadius = radius.flatMap { Double($0) }
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func showAll() {
|
||||||
|
self.headerNode.mapNode.showAll()
|
||||||
|
}
|
||||||
|
|
||||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
let isFirstLayout = self.validLayout == nil
|
let isFirstLayout = self.validLayout == nil
|
||||||
self.validLayout = (layout, navigationHeight)
|
self.validLayout = (layout, navigationHeight)
|
||||||
@ -397,7 +565,11 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let overlap: CGFloat = 6.0
|
let overlap: CGFloat = 6.0
|
||||||
let topInset: CGFloat = layout.size.height - layout.intrinsicInsets.bottom - 126.0 - overlap
|
var topInset: CGFloat = layout.size.height - layout.intrinsicInsets.bottom - 126.0 - overlap
|
||||||
|
if let location = getLocation(from: self.subject), location.liveBroadcastingTimeout != nil {
|
||||||
|
topInset += 66.0
|
||||||
|
}
|
||||||
|
|
||||||
let headerHeight: CGFloat
|
let headerHeight: CGFloat
|
||||||
if let listOffset = self.listOffset {
|
if let listOffset = self.listOffset {
|
||||||
headerHeight = max(0.0, listOffset + overlap)
|
headerHeight = max(0.0, listOffset + overlap)
|
||||||
|
@ -564,11 +564,11 @@ private func rightEnabledByDefault(channelPeer: Peer, right: TelegramChatAdminRi
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private func areAllAdminRightsEnabled(_ flags: TelegramChatAdminRightsFlags, group: Bool) -> Bool {
|
private func areAllAdminRightsEnabled(_ flags: TelegramChatAdminRightsFlags, group: Bool, except: TelegramChatAdminRightsFlags) -> Bool {
|
||||||
if group {
|
if group {
|
||||||
return TelegramChatAdminRightsFlags.groupSpecific.intersection(flags) == TelegramChatAdminRightsFlags.groupSpecific
|
return TelegramChatAdminRightsFlags.groupSpecific.subtracting(except).intersection(flags) == TelegramChatAdminRightsFlags.groupSpecific.subtracting(except)
|
||||||
} else {
|
} else {
|
||||||
return TelegramChatAdminRightsFlags.broadcastSpecific.intersection(flags) == TelegramChatAdminRightsFlags.broadcastSpecific
|
return TelegramChatAdminRightsFlags.broadcastSpecific.subtracting(except).intersection(flags) == TelegramChatAdminRightsFlags.broadcastSpecific.subtracting(except)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -682,7 +682,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
|
|||||||
entries.append(.addAdminsInfo(presentationData.theme, currentRightsFlags.contains(.canAddAdmins) ? presentationData.strings.Channel_EditAdmin_PermissinAddAdminOn : presentationData.strings.Channel_EditAdmin_PermissinAddAdminOff))
|
entries.append(.addAdminsInfo(presentationData.theme, currentRightsFlags.contains(.canAddAdmins) ? presentationData.strings.Channel_EditAdmin_PermissinAddAdminOn : presentationData.strings.Channel_EditAdmin_PermissinAddAdminOff))
|
||||||
}
|
}
|
||||||
|
|
||||||
if let admin = admin as? TelegramUser, admin.botInfo == nil && !admin.isDeleted && channel.flags.contains(.isCreator) && areAllAdminRightsEnabled(currentRightsFlags, group: isGroup) {
|
if let admin = admin as? TelegramUser, admin.botInfo == nil && !admin.isDeleted && channel.flags.contains(.isCreator) && areAllAdminRightsEnabled(currentRightsFlags, group: isGroup, except: .canBeAnonymous) {
|
||||||
canTransfer = true
|
canTransfer = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -794,7 +794,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
|
|||||||
entries.append(.addAdminsInfo(presentationData.theme, currentRightsFlags.contains(.canAddAdmins) ? presentationData.strings.Channel_EditAdmin_PermissinAddAdminOn : presentationData.strings.Channel_EditAdmin_PermissinAddAdminOff))
|
entries.append(.addAdminsInfo(presentationData.theme, currentRightsFlags.contains(.canAddAdmins) ? presentationData.strings.Channel_EditAdmin_PermissinAddAdminOn : presentationData.strings.Channel_EditAdmin_PermissinAddAdminOff))
|
||||||
}
|
}
|
||||||
|
|
||||||
if let admin = admin as? TelegramUser, case .creator = group.role, admin.botInfo == nil && !admin.isDeleted && areAllAdminRightsEnabled(currentRightsFlags, group: true) {
|
if let admin = admin as? TelegramUser, case .creator = group.role, admin.botInfo == nil && !admin.isDeleted && areAllAdminRightsEnabled(currentRightsFlags, group: true, except: .canBeAnonymous) {
|
||||||
entries.append(.transfer(presentationData.theme, presentationData.strings.Group_EditAdmin_TransferOwnership))
|
entries.append(.transfer(presentationData.theme, presentationData.strings.Group_EditAdmin_TransferOwnership))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2112,7 +2112,7 @@ public func groupInfoController(context: AccountContext, peerId originalPeerId:
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
let mapMedia = TelegramMediaMap(latitude: location.latitude, longitude: location.longitude, geoPlace: nil, venue: MapVenue(title: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), address: location.address, provider: nil, id: nil, type: nil), liveBroadcastingTimeout: nil)
|
let mapMedia = TelegramMediaMap(latitude: location.latitude, longitude: location.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: MapVenue(title: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), address: location.address, provider: nil, id: nil, type: nil), liveBroadcastingTimeout: nil)
|
||||||
let controller = legacyLocationController(message: nil, mapMedia: mapMedia, context: context, openPeer: { _ in }, sendLiveLocation: { _, _ in }, stopLiveLocation: {}, openUrl: { url in
|
let controller = legacyLocationController(message: nil, mapMedia: mapMedia, context: context, openPeer: { _ in }, sendLiveLocation: { _, _ in }, stopLiveLocation: {}, openUrl: { url in
|
||||||
context.sharedContext.applicationBindings.openUrl(url)
|
context.sharedContext.applicationBindings.openUrl(url)
|
||||||
})
|
})
|
||||||
|
@ -94,7 +94,6 @@ private final class PeerBanTimeoutActionSheetItemNode: ActionSheetItemNode {
|
|||||||
self.valueChanged = valueChanged
|
self.valueChanged = valueChanged
|
||||||
|
|
||||||
self.pickerView = UIDatePicker()
|
self.pickerView = UIDatePicker()
|
||||||
self.pickerView.setValue(theme.primaryTextColor, forKey: "textColor")
|
|
||||||
self.pickerView.datePickerMode = .countDownTimer
|
self.pickerView.datePickerMode = .countDownTimer
|
||||||
self.pickerView.datePickerMode = .date
|
self.pickerView.datePickerMode = .date
|
||||||
self.pickerView.date = Date(timeIntervalSince1970: Double(roundDateToDays(currentValue)))
|
self.pickerView.date = Date(timeIntervalSince1970: Double(roundDateToDays(currentValue)))
|
||||||
@ -104,6 +103,7 @@ private final class PeerBanTimeoutActionSheetItemNode: ActionSheetItemNode {
|
|||||||
if #available(iOS 13.4, *) {
|
if #available(iOS 13.4, *) {
|
||||||
self.pickerView.preferredDatePickerStyle = .wheels
|
self.pickerView.preferredDatePickerStyle = .wheels
|
||||||
}
|
}
|
||||||
|
self.pickerView.setValue(theme.primaryTextColor, forKey: "textColor")
|
||||||
|
|
||||||
super.init(theme: theme)
|
super.init(theme: theme)
|
||||||
|
|
||||||
|
@ -385,7 +385,7 @@ public func themeAutoNightSettingsController(context: AccountContext) -> ViewCon
|
|||||||
|
|
||||||
let forceUpdateLocation: () -> Void = {
|
let forceUpdateLocation: () -> Void = {
|
||||||
let locationCoordinates = Signal<(Double, Double), NoError> { subscriber in
|
let locationCoordinates = Signal<(Double, Double), NoError> { subscriber in
|
||||||
return context.sharedContext.locationManager!.push(mode: DeviceLocationMode.precise, updated: { coordinate in
|
return context.sharedContext.locationManager!.push(mode: DeviceLocationMode.precise, updated: { coordinate, _, _ in
|
||||||
subscriber.putNext((coordinate.latitude, coordinate.longitude))
|
subscriber.putNext((coordinate.latitude, coordinate.longitude))
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
})
|
})
|
||||||
|
@ -99,7 +99,6 @@ private final class ThemeAutoNightTimeSelectionActionSheetItemNode: ActionSheetI
|
|||||||
self.valueChanged = valueChanged
|
self.valueChanged = valueChanged
|
||||||
|
|
||||||
self.pickerView = UIDatePicker()
|
self.pickerView = UIDatePicker()
|
||||||
self.pickerView.setValue(theme.primaryTextColor, forKey: "textColor")
|
|
||||||
self.pickerView.datePickerMode = .countDownTimer
|
self.pickerView.datePickerMode = .countDownTimer
|
||||||
self.pickerView.datePickerMode = .time
|
self.pickerView.datePickerMode = .time
|
||||||
self.pickerView.timeZone = TimeZone(secondsFromGMT: 0)
|
self.pickerView.timeZone = TimeZone(secondsFromGMT: 0)
|
||||||
@ -108,6 +107,7 @@ private final class ThemeAutoNightTimeSelectionActionSheetItemNode: ActionSheetI
|
|||||||
if #available(iOS 13.4, *) {
|
if #available(iOS 13.4, *) {
|
||||||
self.pickerView.preferredDatePickerStyle = .wheels
|
self.pickerView.preferredDatePickerStyle = .wheels
|
||||||
}
|
}
|
||||||
|
self.pickerView.setValue(theme.primaryTextColor, forKey: "textColor")
|
||||||
|
|
||||||
super.init(theme: theme)
|
super.init(theme: theme)
|
||||||
|
|
||||||
|
@ -260,9 +260,9 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
|
|||||||
let disposable = TGShareLocationSignals.locationMessageContent(for: url).start(next: { value in
|
let disposable = TGShareLocationSignals.locationMessageContent(for: url).start(next: { value in
|
||||||
if let value = value as? TGShareLocationResult {
|
if let value = value as? TGShareLocationResult {
|
||||||
if let title = value.title {
|
if let title = value.title {
|
||||||
subscriber.putNext(.done(.media(.media(.standalone(media: TelegramMediaMap(latitude: value.latitude, longitude: value.longitude, geoPlace: nil, venue: MapVenue(title: title, address: value.address, provider: value.provider, id: value.venueId, type: value.venueType), liveBroadcastingTimeout: nil))))))
|
subscriber.putNext(.done(.media(.media(.standalone(media: TelegramMediaMap(latitude: value.latitude, longitude: value.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: MapVenue(title: title, address: value.address, provider: value.provider, id: value.venueId, type: value.venueType), liveBroadcastingTimeout: nil))))))
|
||||||
} else {
|
} else {
|
||||||
subscriber.putNext(.done(.media(.media(.standalone(media: TelegramMediaMap(latitude: value.latitude, longitude: value.longitude, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil))))))
|
subscriber.putNext(.done(.media(.media(.standalone(media: TelegramMediaMap(latitude: value.latitude, longitude: value.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil))))))
|
||||||
}
|
}
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
} else if let value = value as? String {
|
} else if let value = value as? String {
|
||||||
|
@ -18,12 +18,12 @@ import GraphUI
|
|||||||
private final class ChannelStatsControllerArguments {
|
private final class ChannelStatsControllerArguments {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let loadDetailedGraph: (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>
|
let loadDetailedGraph: (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>
|
||||||
let openMessage: (MessageId) -> Void
|
let openMessageStats: (MessageId) -> Void
|
||||||
|
|
||||||
init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>, openMessage: @escaping (MessageId) -> Void) {
|
init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>, openMessage: @escaping (MessageId) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.loadDetailedGraph = loadDetailedGraph
|
self.loadDetailedGraph = loadDetailedGraph
|
||||||
self.openMessage = openMessage
|
self.openMessageStats = openMessage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,7 +329,7 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
}, sectionId: self.section, style: .blocks)
|
}, sectionId: self.section, style: .blocks)
|
||||||
case let .post(_, _, _, _, message, interactions):
|
case let .post(_, _, _, _, message, interactions):
|
||||||
return StatsMessageItem(context: arguments.context, presentationData: presentationData, message: message, views: interactions.views, forwards: interactions.forwards, sectionId: self.section, style: .blocks, action: {
|
return StatsMessageItem(context: arguments.context, presentationData: presentationData, message: message, views: interactions.views, forwards: interactions.forwards, sectionId: self.section, style: .blocks, action: {
|
||||||
arguments.openMessage(message.id)
|
arguments.openMessageStats(message.id)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -406,7 +406,7 @@ private func channelStatsControllerEntries(data: ChannelStats?, messages: [Messa
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func channelStatsController(context: AccountContext, peerId: PeerId, cachedPeerData: CachedPeerData) -> ViewController {
|
public func channelStatsController(context: AccountContext, peerId: PeerId, cachedPeerData: CachedPeerData) -> ViewController {
|
||||||
var navigateToMessageImpl: ((MessageId) -> Void)?
|
var openMessageStatsImpl: ((MessageId) -> Void)?
|
||||||
|
|
||||||
let actionsDisposable = DisposableSet()
|
let actionsDisposable = DisposableSet()
|
||||||
let dataPromise = Promise<ChannelStats?>(nil)
|
let dataPromise = Promise<ChannelStats?>(nil)
|
||||||
@ -439,7 +439,7 @@ public func channelStatsController(context: AccountContext, peerId: PeerId, cach
|
|||||||
let arguments = ChannelStatsControllerArguments(context: context, loadDetailedGraph: { graph, x -> Signal<StatsGraph?, NoError> in
|
let arguments = ChannelStatsControllerArguments(context: context, loadDetailedGraph: { graph, x -> Signal<StatsGraph?, NoError> in
|
||||||
return statsContext.loadDetailedGraph(graph, x: x)
|
return statsContext.loadDetailedGraph(graph, x: x)
|
||||||
}, openMessage: { messageId in
|
}, openMessage: { messageId in
|
||||||
navigateToMessageImpl?(messageId)
|
openMessageStatsImpl?(messageId)
|
||||||
})
|
})
|
||||||
|
|
||||||
let messageView = context.account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId), index: .upperBound, anchorIndex: .upperBound, count: 100, fixedCombinedReadStates: nil)
|
let messageView = context.account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId), index: .upperBound, anchorIndex: .upperBound, count: 100, fixedCombinedReadStates: nil)
|
||||||
@ -495,9 +495,9 @@ public func channelStatsController(context: AccountContext, peerId: PeerId, cach
|
|||||||
controller.didDisappear = { [weak controller] _ in
|
controller.didDisappear = { [weak controller] _ in
|
||||||
controller?.clearItemNodesHighlight(animated: true)
|
controller?.clearItemNodesHighlight(animated: true)
|
||||||
}
|
}
|
||||||
navigateToMessageImpl = { [weak controller] messageId in
|
openMessageStatsImpl = { [weak controller] messageId in
|
||||||
if let navigationController = controller?.navigationController as? NavigationController {
|
if let navigationController = controller?.navigationController as? NavigationController {
|
||||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(messageId.peerId), subject: .message(id: messageId, highlight: true), keepStack: .always, useExisting: false, purposefulAction: {}, peekData: nil))
|
controller?.push(messageStatsController(context: context, messageId: messageId, cachedPeerData: cachedPeerData))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return controller
|
return controller
|
||||||
|
@ -39,7 +39,7 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
case overview(PresentationTheme, MessageStats, Int32?)
|
case overview(PresentationTheme, MessageStats, Int32?)
|
||||||
|
|
||||||
case interactionsTitle(PresentationTheme, String)
|
case interactionsTitle(PresentationTheme, String)
|
||||||
case interactionsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, StatsGraph?, ChartType)
|
case interactionsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
|
||||||
|
|
||||||
case publicForwardsTitle(PresentationTheme, String)
|
case publicForwardsTitle(PresentationTheme, String)
|
||||||
case publicForward(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Message)
|
case publicForward(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Message)
|
||||||
@ -92,8 +92,8 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .interactionsGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsDetailedGraph, lhsType):
|
case let .interactionsGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType):
|
||||||
if case let .interactionsGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsDetailedGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsDetailedGraph == rhsDetailedGraph, lhsType == rhsType {
|
if case let .interactionsGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@ -126,17 +126,13 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||||
case let .overview(_, stats, publicShares):
|
case let .overview(_, stats, publicShares):
|
||||||
return MessageStatsOverviewItem(presentationData: presentationData, stats: stats, publicShares: publicShares, sectionId: self.section, style: .blocks)
|
return MessageStatsOverviewItem(presentationData: presentationData, stats: stats, publicShares: publicShares, sectionId: self.section, style: .blocks)
|
||||||
case let .interactionsGraph(_, _, _, graph, detailedGraph, type):
|
case let .interactionsGraph(_, _, _, graph, type):
|
||||||
return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, getDetailsData: { date, completion in
|
return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, getDetailsData: { date, completion in
|
||||||
if let detailedGraph = detailedGraph, case let .Loaded(_, data) = detailedGraph {
|
|
||||||
completion(data)
|
|
||||||
} else {
|
|
||||||
let _ = arguments.loadDetailedGraph(graph, Int64(date.timeIntervalSince1970) * 1000).start(next: { graph in
|
let _ = arguments.loadDetailedGraph(graph, Int64(date.timeIntervalSince1970) * 1000).start(next: { graph in
|
||||||
if let graph = graph, case let .Loaded(_, data) = graph {
|
if let graph = graph, case let .Loaded(_, data) = graph {
|
||||||
completion(data)
|
completion(data)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}, sectionId: self.section, style: .blocks)
|
}, sectionId: self.section, style: .blocks)
|
||||||
case let .publicForward(_, _, _, _, message):
|
case let .publicForward(_, _, _, _, message):
|
||||||
var views: Int = 0
|
var views: Int = 0
|
||||||
@ -165,7 +161,7 @@ private func messageStatsControllerEntries(data: MessageStats?, messages: Search
|
|||||||
|
|
||||||
if !data.interactionsGraph.isEmpty {
|
if !data.interactionsGraph.isEmpty {
|
||||||
entries.append(.interactionsTitle(presentationData.theme, presentationData.strings.Stats_MessageInteractionsTitle.uppercased()))
|
entries.append(.interactionsTitle(presentationData.theme, presentationData.strings.Stats_MessageInteractionsTitle.uppercased()))
|
||||||
entries.append(.interactionsGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.interactionsGraph, data.detailedInteractionsGraph, .twoAxisStep))
|
entries.append(.interactionsGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.interactionsGraph, .twoAxisStep))
|
||||||
}
|
}
|
||||||
|
|
||||||
if let messages = messages, !messages.messages.isEmpty {
|
if let messages = messages, !messages.messages.isEmpty {
|
||||||
|
@ -45,6 +45,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||||||
case botSentSecureValues(types: [SentSecureValueType])
|
case botSentSecureValues(types: [SentSecureValueType])
|
||||||
case peerJoined
|
case peerJoined
|
||||||
case phoneNumberRequest
|
case phoneNumberRequest
|
||||||
|
case geoProximityReached(distance: Int32)
|
||||||
|
|
||||||
public init(decoder: PostboxDecoder) {
|
public init(decoder: PostboxDecoder) {
|
||||||
let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0)
|
let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0)
|
||||||
@ -95,6 +96,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||||||
self = .peerJoined
|
self = .peerJoined
|
||||||
case 20:
|
case 20:
|
||||||
self = .phoneNumberRequest
|
self = .phoneNumberRequest
|
||||||
|
case 21:
|
||||||
|
self = .geoProximityReached(distance: (decoder.decodeInt32ForKey("dst", orElse: 0)))
|
||||||
default:
|
default:
|
||||||
self = .unknown
|
self = .unknown
|
||||||
}
|
}
|
||||||
@ -180,6 +183,9 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||||||
encoder.encodeInt32(19, forKey: "_rawValue")
|
encoder.encodeInt32(19, forKey: "_rawValue")
|
||||||
case .phoneNumberRequest:
|
case .phoneNumberRequest:
|
||||||
encoder.encodeInt32(20, forKey: "_rawValue")
|
encoder.encodeInt32(20, forKey: "_rawValue")
|
||||||
|
case let .geoProximityReached(distance):
|
||||||
|
encoder.encodeInt32(21, forKey: "_rawValue")
|
||||||
|
encoder.encodeInt32(distance, forKey: "dst")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,6 +133,8 @@ public final class MapVenue: PostboxCoding, Equatable {
|
|||||||
public final class TelegramMediaMap: Media {
|
public final class TelegramMediaMap: Media {
|
||||||
public let latitude: Double
|
public let latitude: Double
|
||||||
public let longitude: Double
|
public let longitude: Double
|
||||||
|
public let heading: Double?
|
||||||
|
public let accuracyRadius: Double?
|
||||||
public let geoPlace: NamedGeoPlace?
|
public let geoPlace: NamedGeoPlace?
|
||||||
public let venue: MapVenue?
|
public let venue: MapVenue?
|
||||||
public let liveBroadcastingTimeout: Int32?
|
public let liveBroadcastingTimeout: Int32?
|
||||||
@ -140,9 +142,11 @@ public final class TelegramMediaMap: Media {
|
|||||||
public let id: MediaId? = nil
|
public let id: MediaId? = nil
|
||||||
public let peerIds: [PeerId] = []
|
public let peerIds: [PeerId] = []
|
||||||
|
|
||||||
public init(latitude: Double, longitude: Double, geoPlace: NamedGeoPlace?, venue: MapVenue?, liveBroadcastingTimeout: Int32?) {
|
public init(latitude: Double, longitude: Double, heading: Double?, accuracyRadius: Double?, geoPlace: NamedGeoPlace?, venue: MapVenue?, liveBroadcastingTimeout: Int32?) {
|
||||||
self.latitude = latitude
|
self.latitude = latitude
|
||||||
self.longitude = longitude
|
self.longitude = longitude
|
||||||
|
self.heading = heading
|
||||||
|
self.accuracyRadius = accuracyRadius
|
||||||
self.geoPlace = geoPlace
|
self.geoPlace = geoPlace
|
||||||
self.venue = venue
|
self.venue = venue
|
||||||
self.liveBroadcastingTimeout = liveBroadcastingTimeout
|
self.liveBroadcastingTimeout = liveBroadcastingTimeout
|
||||||
@ -151,6 +155,8 @@ public final class TelegramMediaMap: Media {
|
|||||||
public init(decoder: PostboxDecoder) {
|
public init(decoder: PostboxDecoder) {
|
||||||
self.latitude = decoder.decodeDoubleForKey("la", orElse: 0.0)
|
self.latitude = decoder.decodeDoubleForKey("la", orElse: 0.0)
|
||||||
self.longitude = decoder.decodeDoubleForKey("lo", orElse: 0.0)
|
self.longitude = decoder.decodeDoubleForKey("lo", orElse: 0.0)
|
||||||
|
self.heading = decoder.decodeOptionalDoubleForKey("hdg")
|
||||||
|
self.accuracyRadius = decoder.decodeOptionalDoubleForKey("acc")
|
||||||
self.geoPlace = decoder.decodeObjectForKey("gp", decoder: { NamedGeoPlace(decoder: $0) }) as? NamedGeoPlace
|
self.geoPlace = decoder.decodeObjectForKey("gp", decoder: { NamedGeoPlace(decoder: $0) }) as? NamedGeoPlace
|
||||||
self.venue = decoder.decodeObjectForKey("ve", decoder: { MapVenue(decoder: $0) }) as? MapVenue
|
self.venue = decoder.decodeObjectForKey("ve", decoder: { MapVenue(decoder: $0) }) as? MapVenue
|
||||||
self.liveBroadcastingTimeout = decoder.decodeOptionalInt32ForKey("bt")
|
self.liveBroadcastingTimeout = decoder.decodeOptionalInt32ForKey("bt")
|
||||||
@ -159,6 +165,16 @@ public final class TelegramMediaMap: Media {
|
|||||||
public func encode(_ encoder: PostboxEncoder) {
|
public func encode(_ encoder: PostboxEncoder) {
|
||||||
encoder.encodeDouble(self.latitude, forKey: "la")
|
encoder.encodeDouble(self.latitude, forKey: "la")
|
||||||
encoder.encodeDouble(self.longitude, forKey: "lo")
|
encoder.encodeDouble(self.longitude, forKey: "lo")
|
||||||
|
if let heading = self.heading {
|
||||||
|
encoder.encodeDouble(heading, forKey: "hdg")
|
||||||
|
} else {
|
||||||
|
encoder.encodeNil(forKey: "hdg")
|
||||||
|
}
|
||||||
|
if let accuracyRadius = self.accuracyRadius {
|
||||||
|
encoder.encodeDouble(accuracyRadius, forKey: "acc")
|
||||||
|
} else {
|
||||||
|
encoder.encodeNil(forKey: "acc")
|
||||||
|
}
|
||||||
if let geoPlace = self.geoPlace {
|
if let geoPlace = self.geoPlace {
|
||||||
encoder.encodeObject(geoPlace, forKey: "gp")
|
encoder.encodeObject(geoPlace, forKey: "gp")
|
||||||
} else {
|
} else {
|
||||||
@ -181,6 +197,12 @@ public final class TelegramMediaMap: Media {
|
|||||||
if self.latitude != other.latitude || self.longitude != other.longitude {
|
if self.latitude != other.latitude || self.longitude != other.longitude {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if self.heading != other.heading {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if self.accuracyRadius != other.accuracyRadius {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if self.geoPlace != other.geoPlace {
|
if self.geoPlace != other.geoPlace {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -3781,15 +3781,14 @@ public extension Api {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func requestProximityNotification(flags: Int32, peer: Api.InputPeer, msgId: Int32, ownLocation: Api.InputGeoPoint?, maxDistance: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
public static func requestProximityNotification(flags: Int32, peer: Api.InputPeer, msgId: Int32, maxDistance: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
buffer.appendInt32(-699657935)
|
buffer.appendInt32(-1322540260)
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
peer.serialize(buffer, true)
|
peer.serialize(buffer, true)
|
||||||
serializeInt32(msgId, buffer: buffer, boxed: false)
|
serializeInt32(msgId, buffer: buffer, boxed: false)
|
||||||
if Int(flags) & Int(1 << 0) != 0 {ownLocation!.serialize(buffer, true)}
|
|
||||||
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(maxDistance!, buffer: buffer, boxed: false)}
|
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(maxDistance!, buffer: buffer, boxed: false)}
|
||||||
return (FunctionDescription(name: "messages.requestProximityNotification", parameters: [("flags", flags), ("peer", peer), ("msgId", msgId), ("ownLocation", ownLocation), ("maxDistance", maxDistance)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
return (FunctionDescription(name: "messages.requestProximityNotification", parameters: [("flags", flags), ("peer", peer), ("msgId", msgId), ("maxDistance", maxDistance)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
||||||
let reader = BufferReader(buffer)
|
let reader = BufferReader(buffer)
|
||||||
var result: Api.Bool?
|
var result: Api.Bool?
|
||||||
if let signature = reader.readInt32() {
|
if let signature = reader.readInt32() {
|
||||||
|
@ -1115,7 +1115,7 @@ public final class AccountViewTracker {
|
|||||||
return account.postbox.transaction { transaction -> Void in
|
return account.postbox.transaction { transaction -> Void in
|
||||||
for result in results {
|
for result in results {
|
||||||
switch result {
|
switch result {
|
||||||
case let .stickerSet(_, _, documents):
|
case let .stickerSet(_, _, documents)?:
|
||||||
for document in documents {
|
for document in documents {
|
||||||
if let file = telegramMediaFileFromApiDocument(document) {
|
if let file = telegramMediaFileFromApiDocument(document) {
|
||||||
if transaction.getMedia(file.fileId) != nil {
|
if transaction.getMedia(file.fileId) != nil {
|
||||||
|
@ -424,14 +424,14 @@ extension ChatContextResultMessage {
|
|||||||
}
|
}
|
||||||
self = .text(text: message, entities: parsedEntities, disableUrlPreview: (flags & (1 << 0)) != 0, replyMarkup: parsedReplyMarkup)
|
self = .text(text: message, entities: parsedEntities, disableUrlPreview: (flags & (1 << 0)) != 0, replyMarkup: parsedReplyMarkup)
|
||||||
case let .botInlineMessageMediaGeo(_, geo, period, replyMarkup):
|
case let .botInlineMessageMediaGeo(_, geo, period, replyMarkup):
|
||||||
let media = telegramMediaMapFromApiGeoPoint(geo, title: nil, address: nil, provider: nil, venueId: nil, venueType: nil, liveBroadcastingTimeout: period)
|
let media = telegramMediaMapFromApiGeoPoint(geo, title: nil, address: nil, provider: nil, venueId: nil, venueType: nil, liveBroadcastingTimeout: period, heading: nil)
|
||||||
var parsedReplyMarkup: ReplyMarkupMessageAttribute?
|
var parsedReplyMarkup: ReplyMarkupMessageAttribute?
|
||||||
if let replyMarkup = replyMarkup {
|
if let replyMarkup = replyMarkup {
|
||||||
parsedReplyMarkup = ReplyMarkupMessageAttribute(apiMarkup: replyMarkup)
|
parsedReplyMarkup = ReplyMarkupMessageAttribute(apiMarkup: replyMarkup)
|
||||||
}
|
}
|
||||||
self = .mapLocation(media: media, replyMarkup: parsedReplyMarkup)
|
self = .mapLocation(media: media, replyMarkup: parsedReplyMarkup)
|
||||||
case let .botInlineMessageMediaVenue(_, geo, title, address, provider, venueId, venueType, replyMarkup):
|
case let .botInlineMessageMediaVenue(_, geo, title, address, provider, venueId, venueType, replyMarkup):
|
||||||
let media = telegramMediaMapFromApiGeoPoint(geo, title: title, address: address, provider: provider, venueId: venueId, venueType: venueType, liveBroadcastingTimeout: nil)
|
let media = telegramMediaMapFromApiGeoPoint(geo, title: title, address: address, provider: provider, venueId: venueId, venueType: venueType, liveBroadcastingTimeout: nil, heading: nil)
|
||||||
var parsedReplyMarkup: ReplyMarkupMessageAttribute?
|
var parsedReplyMarkup: ReplyMarkupMessageAttribute?
|
||||||
if let replyMarkup = replyMarkup {
|
if let replyMarkup = replyMarkup {
|
||||||
parsedReplyMarkup = ReplyMarkupMessageAttribute(apiMarkup: replyMarkup)
|
parsedReplyMarkup = ReplyMarkupMessageAttribute(apiMarkup: replyMarkup)
|
||||||
|
@ -26,7 +26,7 @@ public func requestUpdatesXml(account: Account, source: String) -> Signal<Data,
|
|||||||
|> castError(InternalUpdaterError.self)
|
|> castError(InternalUpdaterError.self)
|
||||||
|> mapToSignal { result in
|
|> mapToSignal { result in
|
||||||
switch result {
|
switch result {
|
||||||
case let .channelMessages(_, _, _, apiMessages, apiChats, apiUsers):
|
case let .channelMessages(_, _, _, _, apiMessages, apiChats, apiUsers):
|
||||||
if let apiMessage = apiMessages.first, let storeMessage = StoreMessage(apiMessage: apiMessage) {
|
if let apiMessage = apiMessages.first, let storeMessage = StoreMessage(apiMessage: apiMessage) {
|
||||||
|
|
||||||
var peers: [PeerId: Peer] = [:]
|
var peers: [PeerId: Peer] = [:]
|
||||||
@ -93,7 +93,7 @@ public func downloadAppUpdate(account: Account, source: String, messageId: Int32
|
|||||||
|> castError(InternalUpdaterError.self)
|
|> castError(InternalUpdaterError.self)
|
||||||
|> mapToSignal { messages in
|
|> mapToSignal { messages in
|
||||||
switch messages {
|
switch messages {
|
||||||
case let .channelMessages(_, _, _, apiMessages, apiChats, apiUsers):
|
case let .channelMessages(_, _, _, _, apiMessages, apiChats, apiUsers):
|
||||||
|
|
||||||
var peers: [PeerId: Peer] = [:]
|
var peers: [PeerId: Peer] = [:]
|
||||||
for chat in apiChats {
|
for chat in apiChats {
|
||||||
|
@ -133,8 +133,10 @@ private func requestActivity(postbox: Postbox, network: Network, accountPeerId:
|
|||||||
if let _ = peer as? TelegramUser {
|
if let _ = peer as? TelegramUser {
|
||||||
if let presence = transaction.getPeerPresence(peerId: peerId) as? TelegramUserPresence {
|
if let presence = transaction.getPeerPresence(peerId: peerId) as? TelegramUserPresence {
|
||||||
switch presence.status {
|
switch presence.status {
|
||||||
case .none, .recently, .lastWeek, .lastMonth:
|
case .none, .lastWeek, .lastMonth:
|
||||||
return .complete()
|
return .complete()
|
||||||
|
case .recently:
|
||||||
|
break
|
||||||
case let .present(statusTimestamp):
|
case let .present(statusTimestamp):
|
||||||
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
||||||
if statusTimestamp < timestamp {
|
if statusTimestamp < timestamp {
|
||||||
|
@ -142,12 +142,16 @@ func mediaContentToUpload(network: Network, postbox: Postbox, auxiliaryMethods:
|
|||||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(input, text), reuploadInfo: nil)))
|
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(input, text), reuploadInfo: nil)))
|
||||||
} else if let map = media as? TelegramMediaMap {
|
} else if let map = media as? TelegramMediaMap {
|
||||||
let input: Api.InputMedia
|
let input: Api.InputMedia
|
||||||
|
var geoFlags: Int32 = 0
|
||||||
|
if let _ = map.accuracyRadius {
|
||||||
|
geoFlags |= 1 << 0
|
||||||
|
}
|
||||||
if let liveBroadcastingTimeout = map.liveBroadcastingTimeout {
|
if let liveBroadcastingTimeout = map.liveBroadcastingTimeout {
|
||||||
input = .inputMediaGeoLive(flags: 1 << 1, geoPoint: Api.InputGeoPoint.inputGeoPoint(flags: 0, lat: map.latitude, long: map.longitude, accuracyRadius: nil), heading: 0, period: liveBroadcastingTimeout)
|
input = .inputMediaGeoLive(flags: 1 << 1, geoPoint: Api.InputGeoPoint.inputGeoPoint(flags: geoFlags, lat: map.latitude, long: map.longitude, accuracyRadius: map.accuracyRadius.flatMap({ Int32($0) })), heading: 0, period: liveBroadcastingTimeout)
|
||||||
} else if let venue = map.venue {
|
} else if let venue = map.venue {
|
||||||
input = .inputMediaVenue(geoPoint: Api.InputGeoPoint.inputGeoPoint(flags: 0, lat: map.latitude, long: map.longitude, accuracyRadius: nil), title: venue.title, address: venue.address ?? "", provider: venue.provider ?? "", venueId: venue.id ?? "", venueType: venue.type ?? "")
|
input = .inputMediaVenue(geoPoint: Api.InputGeoPoint.inputGeoPoint(flags: geoFlags, lat: map.latitude, long: map.longitude, accuracyRadius: map.accuracyRadius.flatMap({ Int32($0) })), title: venue.title, address: venue.address ?? "", provider: venue.provider ?? "", venueId: venue.id ?? "", venueType: venue.type ?? "")
|
||||||
} else {
|
} else {
|
||||||
input = .inputMediaGeoPoint(geoPoint: Api.InputGeoPoint.inputGeoPoint(flags: 0, lat: map.latitude, long: map.longitude, accuracyRadius: nil))
|
input = .inputMediaGeoPoint(geoPoint: Api.InputGeoPoint.inputGeoPoint(flags: geoFlags, lat: map.latitude, long: map.longitude, accuracyRadius: map.accuracyRadius.flatMap({ Int32($0) })))
|
||||||
}
|
}
|
||||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(input, text), reuploadInfo: nil)))
|
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(input, text), reuploadInfo: nil)))
|
||||||
} else if let poll = media as? TelegramMediaPoll {
|
} else if let poll = media as? TelegramMediaPoll {
|
||||||
|
@ -794,11 +794,11 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
|
|||||||
case let .decryptedMessageMediaWebPage(url):
|
case let .decryptedMessageMediaWebPage(url):
|
||||||
parsedMedia.append(TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.LocalWebpage, id: arc4random64()), content: .Pending(0, url)))
|
parsedMedia.append(TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.LocalWebpage, id: arc4random64()), content: .Pending(0, url)))
|
||||||
case let .decryptedMessageMediaGeoPoint(lat, long):
|
case let .decryptedMessageMediaGeoPoint(lat, long):
|
||||||
parsedMedia.append(TelegramMediaMap(latitude: lat, longitude: long, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil))
|
parsedMedia.append(TelegramMediaMap(latitude: lat, longitude: long, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil))
|
||||||
case let .decryptedMessageMediaContact(phoneNumber, firstName, lastName, userId):
|
case let .decryptedMessageMediaContact(phoneNumber, firstName, lastName, userId):
|
||||||
parsedMedia.append(TelegramMediaContact(firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, peerId: userId == 0 ? nil : PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), vCardData: nil))
|
parsedMedia.append(TelegramMediaContact(firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, peerId: userId == 0 ? nil : PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), vCardData: nil))
|
||||||
case let .decryptedMessageMediaVenue(lat, long, title, address, provider, venueId):
|
case let .decryptedMessageMediaVenue(lat, long, title, address, provider, venueId):
|
||||||
parsedMedia.append(TelegramMediaMap(latitude: lat, longitude: long, geoPlace: nil, venue: MapVenue(title: title, address: address, provider: provider, id: venueId, type: nil), liveBroadcastingTimeout: nil))
|
parsedMedia.append(TelegramMediaMap(latitude: lat, longitude: long, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: MapVenue(title: title, address: address, provider: provider, id: venueId, type: nil), liveBroadcastingTimeout: nil))
|
||||||
case .decryptedMessageMediaEmpty:
|
case .decryptedMessageMediaEmpty:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -1013,11 +1013,11 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
|
|||||||
case let .decryptedMessageMediaWebPage(url):
|
case let .decryptedMessageMediaWebPage(url):
|
||||||
parsedMedia.append(TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.LocalWebpage, id: arc4random64()), content: .Pending(0, url)))
|
parsedMedia.append(TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.LocalWebpage, id: arc4random64()), content: .Pending(0, url)))
|
||||||
case let .decryptedMessageMediaGeoPoint(lat, long):
|
case let .decryptedMessageMediaGeoPoint(lat, long):
|
||||||
parsedMedia.append(TelegramMediaMap(latitude: lat, longitude: long, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil))
|
parsedMedia.append(TelegramMediaMap(latitude: lat, longitude: long, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil))
|
||||||
case let .decryptedMessageMediaContact(phoneNumber, firstName, lastName, userId):
|
case let .decryptedMessageMediaContact(phoneNumber, firstName, lastName, userId):
|
||||||
parsedMedia.append(TelegramMediaContact(firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, peerId: userId == 0 ? nil : PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), vCardData: nil))
|
parsedMedia.append(TelegramMediaContact(firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, peerId: userId == 0 ? nil : PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), vCardData: nil))
|
||||||
case let .decryptedMessageMediaVenue(lat, long, title, address, provider, venueId):
|
case let .decryptedMessageMediaVenue(lat, long, title, address, provider, venueId):
|
||||||
parsedMedia.append(TelegramMediaMap(latitude: lat, longitude: long, geoPlace: nil, venue: MapVenue(title: title, address: address, provider: provider, id: venueId, type: nil), liveBroadcastingTimeout: nil))
|
parsedMedia.append(TelegramMediaMap(latitude: lat, longitude: long, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: MapVenue(title: title, address: address, provider: provider, id: venueId, type: nil), liveBroadcastingTimeout: nil))
|
||||||
case .decryptedMessageMediaEmpty:
|
case .decryptedMessageMediaEmpty:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -1251,11 +1251,11 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
|
|||||||
case let .decryptedMessageMediaWebPage(url):
|
case let .decryptedMessageMediaWebPage(url):
|
||||||
parsedMedia.append(TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.LocalWebpage, id: arc4random64()), content: .Pending(0, url)))
|
parsedMedia.append(TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.LocalWebpage, id: arc4random64()), content: .Pending(0, url)))
|
||||||
case let .decryptedMessageMediaGeoPoint(lat, long):
|
case let .decryptedMessageMediaGeoPoint(lat, long):
|
||||||
parsedMedia.append(TelegramMediaMap(latitude: lat, longitude: long, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil))
|
parsedMedia.append(TelegramMediaMap(latitude: lat, longitude: long, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil))
|
||||||
case let .decryptedMessageMediaContact(phoneNumber, firstName, lastName, userId):
|
case let .decryptedMessageMediaContact(phoneNumber, firstName, lastName, userId):
|
||||||
parsedMedia.append(TelegramMediaContact(firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, peerId: userId == 0 ? nil : PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), vCardData: nil))
|
parsedMedia.append(TelegramMediaContact(firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, peerId: userId == 0 ? nil : PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), vCardData: nil))
|
||||||
case let .decryptedMessageMediaVenue(lat, long, title, address, provider, venueId):
|
case let .decryptedMessageMediaVenue(lat, long, title, address, provider, venueId):
|
||||||
parsedMedia.append(TelegramMediaMap(latitude: lat, longitude: long, geoPlace: nil, venue: MapVenue(title: title, address: address, provider: provider, id: venueId, type: nil), liveBroadcastingTimeout: nil))
|
parsedMedia.append(TelegramMediaMap(latitude: lat, longitude: long, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: MapVenue(title: title, address: address, provider: provider, id: venueId, type: nil), liveBroadcastingTimeout: nil))
|
||||||
case .decryptedMessageMediaEmpty:
|
case .decryptedMessageMediaEmpty:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -252,7 +252,7 @@ private func requestEditMessageInternal(postbox: Postbox, network: Network, stat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func requestEditLiveLocation(postbox: Postbox, network: Network, stateManager: AccountStateManager, messageId: MessageId, coordinate: (latitude: Double, longitude: Double)?) -> Signal<Void, NoError> {
|
public func requestEditLiveLocation(postbox: Postbox, network: Network, stateManager: AccountStateManager, messageId: MessageId, coordinate: (latitude: Double, longitude: Double, accuracyRadius: Int32?)?, heading: Int32?) -> Signal<Void, NoError> {
|
||||||
return postbox.transaction { transaction -> (Api.InputPeer, TelegramMediaMap)? in
|
return postbox.transaction { transaction -> (Api.InputPeer, TelegramMediaMap)? in
|
||||||
guard let inputPeer = transaction.getPeer(messageId.peerId).flatMap(apiInputPeer) else {
|
guard let inputPeer = transaction.getPeer(messageId.peerId).flatMap(apiInputPeer) else {
|
||||||
return nil
|
return nil
|
||||||
@ -273,7 +273,11 @@ public func requestEditLiveLocation(postbox: Postbox, network: Network, stateMan
|
|||||||
}
|
}
|
||||||
let inputMedia: Api.InputMedia
|
let inputMedia: Api.InputMedia
|
||||||
if let coordinate = coordinate, let liveBroadcastingTimeout = media.liveBroadcastingTimeout {
|
if let coordinate = coordinate, let liveBroadcastingTimeout = media.liveBroadcastingTimeout {
|
||||||
inputMedia = .inputMediaGeoLive(flags: 1 << 1, geoPoint: .inputGeoPoint(flags: 0, lat: coordinate.latitude, long: coordinate.longitude, accuracyRadius: nil), heading: 0, period: liveBroadcastingTimeout)
|
var geoFlags: Int32 = 0
|
||||||
|
if let _ = coordinate.accuracyRadius {
|
||||||
|
geoFlags |= 1 << 0
|
||||||
|
}
|
||||||
|
inputMedia = .inputMediaGeoLive(flags: 1 << 1, geoPoint: .inputGeoPoint(flags: geoFlags, lat: coordinate.latitude, long: coordinate.longitude, accuracyRadius: coordinate.accuracyRadius.flatMap({ Int32($0) })), heading: heading ?? 0, period: liveBroadcastingTimeout)
|
||||||
} else {
|
} else {
|
||||||
inputMedia = .inputMediaGeoLive(flags: 1 << 0, geoPoint: .inputGeoPoint(flags: 0, lat: media.latitude, long: media.longitude, accuracyRadius: nil), heading: 0, period: nil)
|
inputMedia = .inputMediaGeoLive(flags: 1 << 0, geoPoint: .inputGeoPoint(flags: 0, lat: media.latitude, long: media.longitude, accuracyRadius: nil), heading: 0, period: nil)
|
||||||
}
|
}
|
||||||
@ -305,3 +309,41 @@ public func requestEditLiveLocation(postbox: Postbox, network: Network, stateMan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func requestProximityNotification(postbox: Postbox, network: Network, messageId: MessageId, distance: Int32, coordinate: (latitude: Double, longitude: Double, accuracyRadius: Int32?)) -> Signal<Void, NoError> {
|
||||||
|
return postbox.transaction { transaction -> Api.InputPeer? in
|
||||||
|
return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer)
|
||||||
|
}
|
||||||
|
|> mapToSignal { inputPeer -> Signal<Void, NoError> in
|
||||||
|
guard let inputPeer = inputPeer else {
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
let flags: Int32 = 1 << 0
|
||||||
|
return network.request(Api.functions.messages.requestProximityNotification(flags: flags, peer: inputPeer, msgId: messageId.id, maxDistance: distance))
|
||||||
|
|> map(Optional.init)
|
||||||
|
|> `catch` { _ -> Signal<Api.Bool?, NoError> in
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
|> mapToSignal { _ -> Signal<Void, NoError> in
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func cancelProximityNotification(postbox: Postbox, network: Network, messageId: MessageId) -> Signal<Void, NoError> {
|
||||||
|
return postbox.transaction { transaction -> Api.InputPeer? in
|
||||||
|
return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer)
|
||||||
|
}
|
||||||
|
|> mapToSignal { inputPeer -> Signal<Void, NoError> in
|
||||||
|
guard let inputPeer = inputPeer else {
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
return network.request(Api.functions.messages.requestProximityNotification(flags: 1 << 1, peer: inputPeer, msgId: messageId.id, maxDistance: nil))
|
||||||
|
|> map(Optional.init)
|
||||||
|
|> `catch` { _ -> Signal<Api.Bool?, NoError> in
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
|> mapToSignal { _ -> Signal<Void, NoError> in
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -83,7 +83,7 @@ private func mergedState(transaction: Transaction, state: SearchMessagesPeerStat
|
|||||||
users = apiUsers
|
users = apiUsers
|
||||||
totalCount = Int32(messages.count)
|
totalCount = Int32(messages.count)
|
||||||
nextRate = nil
|
nextRate = nil
|
||||||
case let .messagesSlice(_, count, _, apiNextRate, apiMessages, apiChats, apiUsers):
|
case let .messagesSlice(_, count, apiNextRate, _, apiMessages, apiChats, apiUsers):
|
||||||
messages = apiMessages
|
messages = apiMessages
|
||||||
chats = apiChats
|
chats = apiChats
|
||||||
users = apiUsers
|
users = apiUsers
|
||||||
|
@ -255,13 +255,13 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI
|
|||||||
let mediaContact = TelegramMediaContact(firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, peerId: contactPeerId, vCardData: vcard.isEmpty ? nil : vcard)
|
let mediaContact = TelegramMediaContact(firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, peerId: contactPeerId, vCardData: vcard.isEmpty ? nil : vcard)
|
||||||
return (mediaContact, nil)
|
return (mediaContact, nil)
|
||||||
case let .messageMediaGeo(geo):
|
case let .messageMediaGeo(geo):
|
||||||
let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: nil, address: nil, provider: nil, venueId: nil, venueType: nil, liveBroadcastingTimeout: nil)
|
let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: nil, address: nil, provider: nil, venueId: nil, venueType: nil, liveBroadcastingTimeout: nil, heading: nil)
|
||||||
return (mediaMap, nil)
|
return (mediaMap, nil)
|
||||||
case let .messageMediaVenue(geo, title, address, provider, venueId, venueType):
|
case let .messageMediaVenue(geo, title, address, provider, venueId, venueType):
|
||||||
let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: title, address: address, provider: provider, venueId: venueId, venueType: venueType, liveBroadcastingTimeout: nil)
|
let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: title, address: address, provider: provider, venueId: venueId, venueType: venueType, liveBroadcastingTimeout: nil, heading: nil)
|
||||||
return (mediaMap, nil)
|
return (mediaMap, nil)
|
||||||
case let .messageMediaGeoLive(geo, heading, period):
|
case let .messageMediaGeoLive(geo, heading, period):
|
||||||
let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: nil, address: nil, provider: nil, venueId: nil, venueType: nil, liveBroadcastingTimeout: period)
|
let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: nil, address: nil, provider: nil, venueId: nil, venueType: nil, liveBroadcastingTimeout: period, heading: heading)
|
||||||
return (mediaMap, nil)
|
return (mediaMap, nil)
|
||||||
case let .messageMediaDocument(_, document, ttlSeconds):
|
case let .messageMediaDocument(_, document, ttlSeconds):
|
||||||
if let document = document {
|
if let document = document {
|
||||||
|
@ -58,7 +58,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
|
|||||||
case .messageActionContactSignUp:
|
case .messageActionContactSignUp:
|
||||||
return TelegramMediaAction(action: .peerJoined)
|
return TelegramMediaAction(action: .peerJoined)
|
||||||
case let .messageActionGeoProximityReached(fromId, toId, distance):
|
case let .messageActionGeoProximityReached(fromId, toId, distance):
|
||||||
return nil
|
return TelegramMediaAction(action: .geoProximityReached(distance: distance))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,15 +4,15 @@ import TelegramApi
|
|||||||
|
|
||||||
import SyncCore
|
import SyncCore
|
||||||
|
|
||||||
func telegramMediaMapFromApiGeoPoint(_ geo: Api.GeoPoint, title: String?, address: String?, provider: String?, venueId: String?, venueType: String?, liveBroadcastingTimeout: Int32?) -> TelegramMediaMap {
|
func telegramMediaMapFromApiGeoPoint(_ geo: Api.GeoPoint, title: String?, address: String?, provider: String?, venueId: String?, venueType: String?, liveBroadcastingTimeout: Int32?, heading: Int32?) -> TelegramMediaMap {
|
||||||
var venue: MapVenue?
|
var venue: MapVenue?
|
||||||
if let title = title {
|
if let title = title {
|
||||||
venue = MapVenue(title: title, address: address, provider: provider, id: venueId, type: venueType)
|
venue = MapVenue(title: title, address: address, provider: provider, id: venueId, type: venueType)
|
||||||
}
|
}
|
||||||
switch geo {
|
switch geo {
|
||||||
case let .geoPoint(_, long, lat, accessHash, accuracyRadius):
|
case let .geoPoint(_, long, lat, accessHash, accuracyRadius):
|
||||||
return TelegramMediaMap(latitude: lat, longitude: long, geoPlace: nil, venue: venue, liveBroadcastingTimeout: liveBroadcastingTimeout)
|
return TelegramMediaMap(latitude: lat, longitude: long, heading: heading.flatMap { Double($0) }, accuracyRadius: accuracyRadius.flatMap { Double($0) }, geoPlace: nil, venue: venue, liveBroadcastingTimeout: liveBroadcastingTimeout)
|
||||||
case .geoPointEmpty:
|
case .geoPointEmpty:
|
||||||
return TelegramMediaMap(latitude: 0.0, longitude: 0.0, geoPlace: nil, venue: venue, liveBroadcastingTimeout: liveBroadcastingTimeout)
|
return TelegramMediaMap(latitude: 0.0, longitude: 0.0, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: venue, liveBroadcastingTimeout: liveBroadcastingTimeout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ public func requestUpdatePinnedMessage(account: Account, peerId: PeerId, update:
|
|||||||
return (transaction.getPeer(peerId), transaction.getPeerCachedData(peerId: peerId))
|
return (transaction.getPeer(peerId), transaction.getPeerCachedData(peerId: peerId))
|
||||||
}
|
}
|
||||||
|> mapError { _ -> UpdatePinnedMessageError in
|
|> mapError { _ -> UpdatePinnedMessageError in
|
||||||
|
return .generic
|
||||||
}
|
}
|
||||||
|> mapToSignal { peer, cachedPeerData -> Signal<Void, UpdatePinnedMessageError> in
|
|> mapToSignal { peer, cachedPeerData -> Signal<Void, UpdatePinnedMessageError> in
|
||||||
guard let peer = peer, let inputPeer = apiInputPeer(peer) else {
|
guard let peer = peer, let inputPeer = apiInputPeer(peer) else {
|
||||||
@ -102,6 +103,7 @@ public func requestUpdatePinnedMessage(account: Account, peerId: PeerId, update:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> mapError { _ -> UpdatePinnedMessageError in
|
|> mapError { _ -> UpdatePinnedMessageError in
|
||||||
|
return .generic
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -442,6 +442,8 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
|||||||
attributedString = addAttributesToStringWithRanges(strings.Notification_Joined(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
attributedString = addAttributesToStringWithRanges(strings.Notification_Joined(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
||||||
case .phoneNumberRequest:
|
case .phoneNumberRequest:
|
||||||
attributedString = nil
|
attributedString = nil
|
||||||
|
case let .geoProximityReached(distance):
|
||||||
|
attributedString = NSAttributedString(string: "\(message.peers[message.id.peerId]?.compactDisplayTitle ?? "") is now within \(distance) m from you", font: titleFont, textColor: primaryTextColor)
|
||||||
case .unknown:
|
case .unknown:
|
||||||
attributedString = nil
|
attributedString = nil
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
@ -746,8 +746,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
}, sendCurrentMessage: { [weak self] silentPosting in
|
}, sendCurrentMessage: { [weak self] silentPosting in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
if let _ = strongSelf.presentationInterfaceState.recordedMediaPreview {
|
||||||
|
strongSelf.sendMediaRecording(silently: silentPosting)
|
||||||
|
} else {
|
||||||
strongSelf.chatDisplayNode.sendCurrentMessage(silentPosting: silentPosting)
|
strongSelf.chatDisplayNode.sendCurrentMessage(silentPosting: silentPosting)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}, sendMessage: { [weak self] text in
|
}, sendMessage: { [weak self] text in
|
||||||
guard let strongSelf = self, canSendMessagesToChat(strongSelf.presentationInterfaceState) else {
|
guard let strongSelf = self, canSendMessagesToChat(strongSelf.presentationInterfaceState) else {
|
||||||
return
|
return
|
||||||
@ -1090,7 +1094,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
|> deliverOnMainQueue).start(next: { coordinate in
|
|> deliverOnMainQueue).start(next: { coordinate in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if let coordinate = coordinate {
|
if let coordinate = coordinate {
|
||||||
strongSelf.sendMessages([.message(text: "", attributes: [], mediaReference: .standalone(media: TelegramMediaMap(latitude: coordinate.latitude, longitude: coordinate.longitude, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil)), replyToMessageId: nil, localGroupingKey: nil)])
|
strongSelf.sendMessages([.message(text: "", attributes: [], mediaReference: .standalone(media: TelegramMediaMap(latitude: coordinate.latitude, longitude: coordinate.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil)), replyToMessageId: nil, localGroupingKey: nil)])
|
||||||
} else {
|
} else {
|
||||||
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {})]), in: .window(.root))
|
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {})]), in: .window(.root))
|
||||||
}
|
}
|
||||||
@ -3701,6 +3705,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.chatDisplayNode.historyNode.scrolledToSomeIndex = { [weak self] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.scrolledToMessageId.set(nil)
|
||||||
|
}
|
||||||
|
|
||||||
self.chatDisplayNode.historyNode.maxVisibleMessageIndexUpdated = { [weak self] index in
|
self.chatDisplayNode.historyNode.maxVisibleMessageIndexUpdated = { [weak self] index in
|
||||||
if let strongSelf = self, !strongSelf.historyNavigationStack.isEmpty {
|
if let strongSelf = self, !strongSelf.historyNavigationStack.isEmpty {
|
||||||
strongSelf.historyNavigationStack.filterOutIndicesLessThan(index)
|
strongSelf.historyNavigationStack.filterOutIndicesLessThan(index)
|
||||||
@ -3754,7 +3765,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
stationaryItemRange = (maxInsertedItem + 1, Int.max)
|
stationaryItemRange = (maxInsertedItem + 1, Int.max)
|
||||||
}
|
}
|
||||||
|
|
||||||
mappedTransition = (ChatHistoryListViewTransition(historyView: transition.historyView, deleteItems: deleteItems, insertItems: insertItems, updateItems: transition.updateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: stationaryItemRange, initialData: transition.initialData, keyboardButtonsMessage: transition.keyboardButtonsMessage, cachedData: transition.cachedData, cachedDataMessages: transition.cachedDataMessages, readStateData: transition.readStateData, scrolledToIndex: transition.scrolledToIndex, peerType: transition.peerType, networkType: transition.networkType, animateIn: false, reason: transition.reason, flashIndicators: transition.flashIndicators), updateSizeAndInsets)
|
mappedTransition = (ChatHistoryListViewTransition(historyView: transition.historyView, deleteItems: deleteItems, insertItems: insertItems, updateItems: transition.updateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: stationaryItemRange, initialData: transition.initialData, keyboardButtonsMessage: transition.keyboardButtonsMessage, cachedData: transition.cachedData, cachedDataMessages: transition.cachedDataMessages, readStateData: transition.readStateData, scrolledToIndex: transition.scrolledToIndex, scrolledToSomeIndex: transition.scrolledToSomeIndex, peerType: transition.peerType, networkType: transition.networkType, animateIn: false, reason: transition.reason, flashIndicators: transition.flashIndicators), updateSizeAndInsets)
|
||||||
})
|
})
|
||||||
|
|
||||||
if let mappedTransition = mappedTransition {
|
if let mappedTransition = mappedTransition {
|
||||||
@ -4639,8 +4650,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
strongSelf.lockMediaRecorder()
|
strongSelf.lockMediaRecorder()
|
||||||
}, deleteRecordedMedia: { [weak self] in
|
}, deleteRecordedMedia: { [weak self] in
|
||||||
self?.deleteMediaRecording()
|
self?.deleteMediaRecording()
|
||||||
}, sendRecordedMedia: { [weak self] in
|
}, sendRecordedMedia: { [weak self] silently in
|
||||||
self?.sendMediaRecording()
|
self?.sendMediaRecording(silently: silently)
|
||||||
}, displayRestrictedInfo: { [weak self] subject, displayType in
|
}, displayRestrictedInfo: { [weak self] subject, displayType in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
@ -7171,6 +7182,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
let message: EnqueueMessage = .message(text: "", attributes: [], mediaReference: .standalone(media: file), replyToMessageId: replyMessageId, localGroupingKey: groupingKey)
|
let message: EnqueueMessage = .message(text: "", attributes: [], mediaReference: .standalone(media: file), replyToMessageId: replyMessageId, localGroupingKey: groupingKey)
|
||||||
messages.append(message)
|
messages.append(message)
|
||||||
}
|
}
|
||||||
|
if let _ = groupingKey, results.count % 10 == 0 {
|
||||||
|
groupingKey = arc4random64()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !messages.isEmpty {
|
if !messages.isEmpty {
|
||||||
@ -8268,7 +8282,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private func sendMediaRecording() {
|
private func sendMediaRecording(silently: Bool) {
|
||||||
self.chatDisplayNode.updateRecordedMediaDeleted(false)
|
self.chatDisplayNode.updateRecordedMediaDeleted(false)
|
||||||
if let recordedMediaPreview = self.presentationInterfaceState.recordedMediaPreview {
|
if let recordedMediaPreview = self.presentationInterfaceState.recordedMediaPreview {
|
||||||
if let _ = self.presentationInterfaceState.slowmodeState, !self.presentationInterfaceState.isScheduledMessages {
|
if let _ = self.presentationInterfaceState.slowmodeState, !self.presentationInterfaceState.isScheduledMessages {
|
||||||
@ -8288,7 +8302,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
self.sendMessages([.message(text: "", attributes: [], mediaReference: .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: arc4random64()), partialReference: nil, resource: recordedMediaPreview.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: Int(recordedMediaPreview.fileSize), attributes: [.Audio(isVoice: true, duration: Int(recordedMediaPreview.duration), title: nil, performer: nil, waveform: waveformBuffer)])), replyToMessageId: self.presentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil)])
|
var attributes: [MessageAttribute] = []
|
||||||
|
if silently {
|
||||||
|
attributes.append(NotificationInfoMessageAttribute(flags: .muted))
|
||||||
|
}
|
||||||
|
|
||||||
|
self.sendMessages([.message(text: "", attributes: attributes, mediaReference: .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: arc4random64()), partialReference: nil, resource: recordedMediaPreview.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: Int(recordedMediaPreview.fileSize), attributes: [.Audio(isVoice: true, duration: Int(recordedMediaPreview.duration), title: nil, performer: nil, waveform: waveformBuffer)])), replyToMessageId: self.presentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +84,6 @@ private final class ChatDateSelectorItemNode: ActionSheetItemNode {
|
|||||||
self.valueChanged = valueChanged
|
self.valueChanged = valueChanged
|
||||||
|
|
||||||
self.pickerView = UIDatePicker()
|
self.pickerView = UIDatePicker()
|
||||||
self.pickerView.setValue(theme.primaryTextColor, forKey: "textColor")
|
|
||||||
self.pickerView.datePickerMode = .countDownTimer
|
self.pickerView.datePickerMode = .countDownTimer
|
||||||
self.pickerView.datePickerMode = .date
|
self.pickerView.datePickerMode = .date
|
||||||
self.pickerView.locale = Locale(identifier: strings.baseLanguageCode)
|
self.pickerView.locale = Locale(identifier: strings.baseLanguageCode)
|
||||||
@ -96,6 +95,8 @@ private final class ChatDateSelectorItemNode: ActionSheetItemNode {
|
|||||||
self.pickerView.preferredDatePickerStyle = .wheels
|
self.pickerView.preferredDatePickerStyle = .wheels
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.pickerView.setValue(theme.primaryTextColor, forKey: "textColor")
|
||||||
|
|
||||||
super.init(theme: theme)
|
super.init(theme: theme)
|
||||||
|
|
||||||
self.view.addSubview(self.pickerView)
|
self.view.addSubview(self.pickerView)
|
||||||
|
@ -165,6 +165,7 @@ struct ChatHistoryViewTransition {
|
|||||||
let cachedDataMessages: [MessageId: Message]?
|
let cachedDataMessages: [MessageId: Message]?
|
||||||
let readStateData: [PeerId: ChatHistoryCombinedInitialReadStateData]?
|
let readStateData: [PeerId: ChatHistoryCombinedInitialReadStateData]?
|
||||||
let scrolledToIndex: MessageHistoryAnchorIndex?
|
let scrolledToIndex: MessageHistoryAnchorIndex?
|
||||||
|
let scrolledToSomeIndex: Bool
|
||||||
let animateIn: Bool
|
let animateIn: Bool
|
||||||
let reason: ChatHistoryViewTransitionReason
|
let reason: ChatHistoryViewTransitionReason
|
||||||
let flashIndicators: Bool
|
let flashIndicators: Bool
|
||||||
@ -184,6 +185,7 @@ struct ChatHistoryListViewTransition {
|
|||||||
let cachedDataMessages: [MessageId: Message]?
|
let cachedDataMessages: [MessageId: Message]?
|
||||||
let readStateData: [PeerId: ChatHistoryCombinedInitialReadStateData]?
|
let readStateData: [PeerId: ChatHistoryCombinedInitialReadStateData]?
|
||||||
let scrolledToIndex: MessageHistoryAnchorIndex?
|
let scrolledToIndex: MessageHistoryAnchorIndex?
|
||||||
|
let scrolledToSomeIndex: Bool
|
||||||
let peerType: MediaAutoDownloadPeerType
|
let peerType: MediaAutoDownloadPeerType
|
||||||
let networkType: MediaAutoDownloadNetworkType
|
let networkType: MediaAutoDownloadNetworkType
|
||||||
let animateIn: Bool
|
let animateIn: Bool
|
||||||
@ -345,7 +347,7 @@ private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLoca
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func mappedChatHistoryViewListTransition(context: AccountContext, chatLocation: ChatLocation, associatedData: ChatMessageItemAssociatedData, controllerInteraction: ChatControllerInteraction, mode: ChatHistoryListMode, lastHeaderId: Int64, transition: ChatHistoryViewTransition) -> ChatHistoryListViewTransition {
|
private func mappedChatHistoryViewListTransition(context: AccountContext, chatLocation: ChatLocation, associatedData: ChatMessageItemAssociatedData, controllerInteraction: ChatControllerInteraction, mode: ChatHistoryListMode, lastHeaderId: Int64, transition: ChatHistoryViewTransition) -> ChatHistoryListViewTransition {
|
||||||
return ChatHistoryListViewTransition(historyView: transition.historyView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, entries: transition.insertEntries), updateItems: mappedUpdateEntries(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, initialData: transition.initialData, keyboardButtonsMessage: transition.keyboardButtonsMessage, cachedData: transition.cachedData, cachedDataMessages: transition.cachedDataMessages, readStateData: transition.readStateData, scrolledToIndex: transition.scrolledToIndex, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, animateIn: transition.animateIn, reason: transition.reason, flashIndicators: transition.flashIndicators)
|
return ChatHistoryListViewTransition(historyView: transition.historyView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, entries: transition.insertEntries), updateItems: mappedUpdateEntries(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, initialData: transition.initialData, keyboardButtonsMessage: transition.keyboardButtonsMessage, cachedData: transition.cachedData, cachedDataMessages: transition.cachedDataMessages, readStateData: transition.readStateData, scrolledToIndex: transition.scrolledToIndex, scrolledToSomeIndex: transition.scrolledToSomeIndex, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, animateIn: transition.animateIn, reason: transition.reason, flashIndicators: transition.flashIndicators)
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class ChatHistoryTransactionOpaqueState {
|
private final class ChatHistoryTransactionOpaqueState {
|
||||||
@ -526,6 +528,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
var maxVisibleMessageIndexUpdated: ((MessageIndex) -> Void)?
|
var maxVisibleMessageIndexUpdated: ((MessageIndex) -> Void)?
|
||||||
|
|
||||||
var scrolledToIndex: ((MessageHistoryAnchorIndex) -> Void)?
|
var scrolledToIndex: ((MessageHistoryAnchorIndex) -> Void)?
|
||||||
|
var scrolledToSomeIndex: (() -> Void)?
|
||||||
var beganDragging: (() -> Void)?
|
var beganDragging: (() -> Void)?
|
||||||
|
|
||||||
private let hasVisiblePlayableItemNodesPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
private let hasVisiblePlayableItemNodesPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||||
@ -1499,10 +1502,12 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func scrollToStartOfHistory() {
|
public func scrollToStartOfHistory() {
|
||||||
|
self.beganDragging?()
|
||||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Scroll(index: .lowerBound, anchorIndex: .lowerBound, sourceIndex: .upperBound, scrollPosition: .bottom(0.0), animated: true, highlight: false), id: self.takeNextHistoryLocationId())
|
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Scroll(index: .lowerBound, anchorIndex: .lowerBound, sourceIndex: .upperBound, scrollPosition: .bottom(0.0), animated: true, highlight: false), id: self.takeNextHistoryLocationId())
|
||||||
}
|
}
|
||||||
|
|
||||||
public func scrollToEndOfHistory() {
|
public func scrollToEndOfHistory() {
|
||||||
|
self.beganDragging?()
|
||||||
switch self.visibleContentOffset() {
|
switch self.visibleContentOffset() {
|
||||||
case .known(0.0):
|
case .known(0.0):
|
||||||
break
|
break
|
||||||
@ -1775,6 +1780,8 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.scrolledToIndex?(scrolledToIndex)
|
strongSelf.scrolledToIndex?(scrolledToIndex)
|
||||||
}
|
}
|
||||||
|
} else if transition.scrolledToSomeIndex {
|
||||||
|
self?.scrolledToSomeIndex?()
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.hasActiveTransition = false
|
strongSelf.hasActiveTransition = false
|
||||||
|
@ -261,7 +261,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(size: CGSize, contentOrigin: CGPoint, presentationData: ChatPresentationData, graphics: PrincipalThemeEssentialGraphics, backgroundType: ChatMessageBackgroundType, messageSelection: Bool?) {
|
func update(size: CGSize, contentOrigin: CGPoint, index: Int, presentationData: ChatPresentationData, graphics: PrincipalThemeEssentialGraphics, backgroundType: ChatMessageBackgroundType, messageSelection: Bool?) {
|
||||||
self.currentParams = (size, contentOrigin, presentationData, graphics, backgroundType, messageSelection)
|
self.currentParams = (size, contentOrigin, presentationData, graphics, backgroundType, messageSelection)
|
||||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||||
|
|
||||||
@ -280,8 +280,14 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
self.selectionBackgroundNode = selectionBackgroundNode
|
self.selectionBackgroundNode = selectionBackgroundNode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var selectionBackgroundFrame = bounds.offsetBy(dx: contentOrigin.x, dy: contentOrigin.y)
|
||||||
|
if index == 0 && contentOrigin.y > 0.0 {
|
||||||
|
selectionBackgroundFrame.origin.y -= contentOrigin.y
|
||||||
|
selectionBackgroundFrame.size.height += contentOrigin.y
|
||||||
|
}
|
||||||
|
|
||||||
self.selectionBackgroundNode?.backgroundColor = messageTheme.accentTextColor.withAlphaComponent(0.08)
|
self.selectionBackgroundNode?.backgroundColor = messageTheme.accentTextColor.withAlphaComponent(0.08)
|
||||||
self.selectionBackgroundNode?.frame = bounds.offsetBy(dx: contentOrigin.x, dy: contentOrigin.y)
|
self.selectionBackgroundNode?.frame = selectionBackgroundFrame
|
||||||
} else if let selectionBackgroundNode = self.selectionBackgroundNode {
|
} else if let selectionBackgroundNode = self.selectionBackgroundNode {
|
||||||
self.selectionBackgroundNode = nil
|
self.selectionBackgroundNode = nil
|
||||||
selectionBackgroundNode.removeFromSupernode()
|
selectionBackgroundNode.removeFromSupernode()
|
||||||
@ -314,7 +320,6 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
}
|
}
|
||||||
private var replyInfoNode: ChatMessageReplyInfoNode?
|
private var replyInfoNode: ChatMessageReplyInfoNode?
|
||||||
|
|
||||||
private var contentContainersMaskView: UIImageView?
|
|
||||||
private var contentContainersWrapperNode: ASDisplayNode
|
private var contentContainersWrapperNode: ASDisplayNode
|
||||||
private var contentContainers: [ContentContainer] = []
|
private var contentContainers: [ContentContainer] = []
|
||||||
private(set) var contentNodes: [ChatMessageBubbleContentNode] = []
|
private(set) var contentNodes: [ChatMessageBubbleContentNode] = []
|
||||||
@ -382,7 +387,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
case .action, .optionalAction:
|
case .action, .optionalAction:
|
||||||
return false
|
return false
|
||||||
case let .openContextMenu(_, selectAll, _):
|
case let .openContextMenu(_, selectAll, _):
|
||||||
return selectAll || strongSelf.contentContainers.isEmpty
|
return selectAll || strongSelf.contentContainers.count < 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -1781,6 +1786,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
if mosaicIndex == 0 {
|
if mosaicIndex == 0 {
|
||||||
if !headerSize.height.isZero {
|
if !headerSize.height.isZero {
|
||||||
contentNodesHeight += 7.0
|
contentNodesHeight += 7.0
|
||||||
|
totalContentNodesHeight += 7.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2168,6 +2174,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var index = 0
|
||||||
var hasSelection = false
|
var hasSelection = false
|
||||||
for (stableId, relativeFrame, itemSelection) in contentContainerNodeFrames {
|
for (stableId, relativeFrame, itemSelection) in contentContainerNodeFrames {
|
||||||
if let itemSelection = itemSelection, itemSelection {
|
if let itemSelection = itemSelection, itemSelection {
|
||||||
@ -2188,6 +2195,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strongSelf.contentContainers.count < 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
let location = location.offsetBy(dx: 0.0, dy: strongContainerNode.frame.minY)
|
let location = location.offsetBy(dx: 0.0, dy: strongContainerNode.frame.minY)
|
||||||
if !strongSelf.backgroundNode.frame.contains(location) {
|
if !strongSelf.backgroundNode.frame.contains(location) {
|
||||||
return false
|
return false
|
||||||
@ -2273,9 +2284,13 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
contentContainer = container
|
contentContainer = container
|
||||||
}
|
}
|
||||||
|
|
||||||
contentContainer?.sourceNode.frame = CGRect(origin: CGPoint(), size: relativeFrame.size)
|
let containerFrame = CGRect(origin: relativeFrame.origin, size: CGSize(width: params.width, height: relativeFrame.size.height))
|
||||||
contentContainer?.sourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: relativeFrame.size)
|
contentContainer?.sourceNode.frame = CGRect(origin: CGPoint(), size: containerFrame.size)
|
||||||
contentContainer?.containerNode.frame = relativeFrame
|
contentContainer?.sourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: containerFrame.size)
|
||||||
|
// contentContainer?.sourceNode.backgroundColor = UIColor.blue.withAlphaComponent(0.5)
|
||||||
|
|
||||||
|
contentContainer?.containerNode.frame = containerFrame
|
||||||
|
// contentContainer?.containerNode.backgroundColor = UIColor.red.withAlphaComponent(0.5)
|
||||||
|
|
||||||
contentContainer?.sourceNode.contentRect = CGRect(origin: CGPoint(x: backgroundFrame.minX + incomingOffset, y: 0.0), size: relativeFrame.size)
|
contentContainer?.sourceNode.contentRect = CGRect(origin: CGPoint(x: backgroundFrame.minX + incomingOffset, y: 0.0), size: relativeFrame.size)
|
||||||
contentContainer?.containerNode.targetNodeForActivationProgressContentRect = relativeFrame.offsetBy(dx: backgroundFrame.minX + incomingOffset, dy: 0.0)
|
contentContainer?.containerNode.targetNodeForActivationProgressContentRect = relativeFrame.offsetBy(dx: backgroundFrame.minX + incomingOffset, dy: 0.0)
|
||||||
@ -2284,7 +2299,11 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
contentContainer?.sourceNode.layoutUpdated?(relativeFrame.size)
|
contentContainer?.sourceNode.layoutUpdated?(relativeFrame.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
contentContainer?.update(size: relativeFrame.size, contentOrigin: contentOrigin, presentationData: item.presentationData, graphics: graphics, backgroundType: backgroundType, messageSelection: itemSelection)
|
contentContainer?.update(size: relativeFrame.size, contentOrigin: contentOrigin, index: index, presentationData: item.presentationData, graphics: graphics, backgroundType: backgroundType, messageSelection: itemSelection)
|
||||||
|
|
||||||
|
print("container \(relativeFrame)")
|
||||||
|
|
||||||
|
index += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasSelection {
|
if hasSelection {
|
||||||
@ -2296,7 +2315,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
strongSelf.contentContainersWrapperNode.view.mask = currentMaskView
|
strongSelf.contentContainersWrapperNode.view.mask = currentMaskView
|
||||||
}
|
}
|
||||||
|
|
||||||
currentMaskView?.frame = CGRect(origin: contentOrigin, size: backgroundFrame.size).insetBy(dx: -1.0, dy: -1.0)
|
currentMaskView?.frame = CGRect(origin: CGPoint(x: backgroundFrame.minX, y: 0.0), size: backgroundFrame.size).insetBy(dx: -1.0, dy: -1.0)
|
||||||
currentMaskView?.image = bubbleMaskForType(backgroundType, graphics: graphics)
|
currentMaskView?.image = bubbleMaskForType(backgroundType, graphics: graphics)
|
||||||
} else {
|
} else {
|
||||||
strongSelf.contentContainersWrapperNode.view.mask = nil
|
strongSelf.contentContainersWrapperNode.view.mask = nil
|
||||||
@ -2327,6 +2346,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
|
|
||||||
let contextSourceNode: ContextExtractedContentContainingNode = strongSelf.contentContainers.first(where: { $0.contentMessageStableId == contentNodeMessage.stableId })?.sourceNode ?? strongSelf.mainContextSourceNode
|
let contextSourceNode: ContextExtractedContentContainingNode = strongSelf.contentContainers.first(where: { $0.contentMessageStableId == contentNodeMessage.stableId })?.sourceNode ?? strongSelf.mainContextSourceNode
|
||||||
|
|
||||||
|
print(contextSourceNode.debugDescription)
|
||||||
contextSourceNode.contentNode.addSubnode(contentNode)
|
contextSourceNode.contentNode.addSubnode(contentNode)
|
||||||
|
|
||||||
contentNode.visibility = strongSelf.visibility
|
contentNode.visibility = strongSelf.visibility
|
||||||
@ -2373,6 +2393,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
let previousContentNodeFrame = contentNode.frame
|
let previousContentNodeFrame = contentNode.frame
|
||||||
contentNode.frame = contentNodeFrame
|
contentNode.frame = contentNodeFrame
|
||||||
|
|
||||||
|
print("frame \(contentNodeFrame.debugDescription)")
|
||||||
|
|
||||||
if case let .System(duration) = animation {
|
if case let .System(duration) = animation {
|
||||||
var animateFrame = false
|
var animateFrame = false
|
||||||
var animateAlpha = false
|
var animateAlpha = false
|
||||||
@ -3479,7 +3501,11 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func getMessageContextSourceNode(stableId: UInt32?) -> ContextExtractedContentContainingNode? {
|
override func getMessageContextSourceNode(stableId: UInt32?) -> ContextExtractedContentContainingNode? {
|
||||||
|
if self.contentContainers.count > 1 {
|
||||||
return self.contentContainers.first(where: { $0.contentMessageStableId == stableId })?.sourceNode ?? self.mainContextSourceNode
|
return self.contentContainers.first(where: { $0.contentMessageStableId == stableId })?.sourceNode ?? self.mainContextSourceNode
|
||||||
|
} else {
|
||||||
|
return self.mainContextSourceNode
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func addAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) {
|
override func addAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) {
|
||||||
|
@ -104,7 +104,7 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
if case let .linear(_, bottom) = position {
|
if case let .linear(_, bottom) = position {
|
||||||
if case .Neighbour(_, _, .condensed) = bottom {
|
if case .Neighbour(_, _, .condensed) = bottom {
|
||||||
if selectedFile?.isMusic ?? false {
|
if selectedFile?.isMusic ?? false {
|
||||||
bottomInset -= 14.0
|
// bottomInset -= 14.0
|
||||||
} else {
|
} else {
|
||||||
bottomInset -= 10.0
|
bottomInset -= 10.0
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ final class ChatPanelInterfaceInteraction {
|
|||||||
let stopMediaRecording: () -> Void
|
let stopMediaRecording: () -> Void
|
||||||
let lockMediaRecording: () -> Void
|
let lockMediaRecording: () -> Void
|
||||||
let deleteRecordedMedia: () -> Void
|
let deleteRecordedMedia: () -> Void
|
||||||
let sendRecordedMedia: () -> Void
|
let sendRecordedMedia: (Bool) -> Void
|
||||||
let displayRestrictedInfo: (ChatPanelRestrictionInfoSubject, ChatPanelRestrictionInfoDisplayType) -> Void
|
let displayRestrictedInfo: (ChatPanelRestrictionInfoSubject, ChatPanelRestrictionInfoDisplayType) -> Void
|
||||||
let displayVideoUnmuteTip: (CGPoint?) -> Void
|
let displayVideoUnmuteTip: (CGPoint?) -> Void
|
||||||
let switchMediaRecordingMode: () -> Void
|
let switchMediaRecordingMode: () -> Void
|
||||||
@ -163,7 +163,7 @@ final class ChatPanelInterfaceInteraction {
|
|||||||
stopMediaRecording: @escaping () -> Void,
|
stopMediaRecording: @escaping () -> Void,
|
||||||
lockMediaRecording: @escaping () -> Void,
|
lockMediaRecording: @escaping () -> Void,
|
||||||
deleteRecordedMedia: @escaping () -> Void,
|
deleteRecordedMedia: @escaping () -> Void,
|
||||||
sendRecordedMedia: @escaping () -> Void,
|
sendRecordedMedia: @escaping (Bool) -> Void,
|
||||||
displayRestrictedInfo: @escaping (ChatPanelRestrictionInfoSubject, ChatPanelRestrictionInfoDisplayType) -> Void,
|
displayRestrictedInfo: @escaping (ChatPanelRestrictionInfoSubject, ChatPanelRestrictionInfoDisplayType) -> Void,
|
||||||
displayVideoUnmuteTip: @escaping (CGPoint?) -> Void,
|
displayVideoUnmuteTip: @escaping (CGPoint?) -> Void,
|
||||||
switchMediaRecordingMode: @escaping () -> Void,
|
switchMediaRecordingMode: @escaping () -> Void,
|
||||||
|
@ -91,7 +91,7 @@ final class ChatRecentActionsController: TelegramBaseController {
|
|||||||
}, stopMediaRecording: {
|
}, stopMediaRecording: {
|
||||||
}, lockMediaRecording: {
|
}, lockMediaRecording: {
|
||||||
}, deleteRecordedMedia: {
|
}, deleteRecordedMedia: {
|
||||||
}, sendRecordedMedia: {
|
}, sendRecordedMedia: { _ in
|
||||||
}, displayRestrictedInfo: { _, _ in
|
}, displayRestrictedInfo: { _, _ in
|
||||||
}, displayVideoUnmuteTip: { _ in
|
}, displayVideoUnmuteTip: { _ in
|
||||||
}, switchMediaRecordingMode: {
|
}, switchMediaRecordingMode: {
|
||||||
|
@ -1037,7 +1037,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
|||||||
return []
|
return []
|
||||||
}, to: &text, entities: &entities)
|
}, to: &text, entities: &entities)
|
||||||
|
|
||||||
let mediaMap = TelegramMediaMap(latitude: updated.latitude, longitude: updated.longitude, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil)
|
let mediaMap = TelegramMediaMap(latitude: updated.latitude, longitude: updated.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil)
|
||||||
|
|
||||||
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: [], media: [mediaMap], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
|
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: [], media: [mediaMap], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
|
||||||
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes()))
|
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes()))
|
||||||
|
@ -291,7 +291,7 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc func sendPressed() {
|
@objc func sendPressed() {
|
||||||
self.interfaceInteraction?.sendRecordedMedia()
|
self.interfaceInteraction?.sendRecordedMedia(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func waveformPressed() {
|
@objc func waveformPressed() {
|
||||||
|
@ -180,7 +180,6 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
|
|||||||
|
|
||||||
let pickerView = UIDatePicker()
|
let pickerView = UIDatePicker()
|
||||||
pickerView.timeZone = TimeZone(secondsFromGMT: 0)
|
pickerView.timeZone = TimeZone(secondsFromGMT: 0)
|
||||||
pickerView.setValue(textColor, forKey: "textColor")
|
|
||||||
pickerView.datePickerMode = .countDownTimer
|
pickerView.datePickerMode = .countDownTimer
|
||||||
pickerView.datePickerMode = .dateAndTime
|
pickerView.datePickerMode = .dateAndTime
|
||||||
pickerView.locale = Locale.current
|
pickerView.locale = Locale.current
|
||||||
@ -191,6 +190,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
|
|||||||
if #available(iOS 13.4, *) {
|
if #available(iOS 13.4, *) {
|
||||||
pickerView.preferredDatePickerStyle = .wheels
|
pickerView.preferredDatePickerStyle = .wheels
|
||||||
}
|
}
|
||||||
|
pickerView.setValue(textColor, forKey: "textColor")
|
||||||
self.pickerView = pickerView
|
self.pickerView = pickerView
|
||||||
|
|
||||||
self.updateMinimumDate(currentTime: currentTime)
|
self.updateMinimumDate(currentTime: currentTime)
|
||||||
|
@ -63,10 +63,9 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
|
|||||||
return nil
|
return nil
|
||||||
}))
|
}))
|
||||||
return true
|
return true
|
||||||
case let .map(mapMedia):
|
case .map:
|
||||||
params.dismissInput()
|
params.dismissInput()
|
||||||
|
|
||||||
if mapMedia.liveBroadcastingTimeout == nil {
|
|
||||||
let controllerParams = LocationViewParams(sendLiveLocation: { location in
|
let controllerParams = LocationViewParams(sendLiveLocation: { location in
|
||||||
let outMessage: EnqueueMessage = .message(text: "", attributes: [], mediaReference: .standalone(media: location), replyToMessageId: nil, localGroupingKey: nil)
|
let outMessage: EnqueueMessage = .message(text: "", attributes: [], mediaReference: .standalone(media: location), replyToMessageId: nil, localGroupingKey: nil)
|
||||||
params.enqueueMessage(outMessage)
|
params.enqueueMessage(outMessage)
|
||||||
@ -75,21 +74,9 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
|
|||||||
}, openUrl: params.openUrl, openPeer: { peer in
|
}, openUrl: params.openUrl, openPeer: { peer in
|
||||||
params.openPeer(peer, .info)
|
params.openPeer(peer, .info)
|
||||||
})
|
})
|
||||||
let controller = LocationViewController(context: params.context, mapMedia: mapMedia, params: controllerParams)
|
let controller = LocationViewController(context: params.context, subject: params.message, params: controllerParams)
|
||||||
controller.navigationPresentation = .modal
|
controller.navigationPresentation = .modal
|
||||||
params.navigationController?.pushViewController(controller)
|
params.navigationController?.pushViewController(controller)
|
||||||
} else {
|
|
||||||
let controller = legacyLocationController(message: params.message, mapMedia: mapMedia, context: params.context, openPeer: { peer in
|
|
||||||
params.openPeer(peer, .info)
|
|
||||||
}, sendLiveLocation: { coordinate, period in
|
|
||||||
let outMessage: EnqueueMessage = .message(text: "", attributes: [], mediaReference: .standalone(media: TelegramMediaMap(latitude: coordinate.latitude, longitude: coordinate.longitude, geoPlace: nil, venue: nil, liveBroadcastingTimeout: period)), replyToMessageId: nil, localGroupingKey: nil)
|
|
||||||
params.enqueueMessage(outMessage)
|
|
||||||
}, stopLiveLocation: {
|
|
||||||
params.context.liveLocationManager?.cancelLiveLocation(peerId: params.message.id.peerId)
|
|
||||||
}, openUrl: params.openUrl)
|
|
||||||
controller.navigationPresentation = .modal
|
|
||||||
params.navigationController?.pushViewController(controller)
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
case let .stickerPack(reference):
|
case let .stickerPack(reference):
|
||||||
let controller = StickerPackScreen(context: params.context, mainStickerPack: reference, stickerPacks: [reference], parentNavigationController: params.navigationController, sendSticker: params.sendSticker, actionPerformed: { info, items, action in
|
let controller = StickerPackScreen(context: params.context, mainStickerPack: reference, stickerPacks: [reference], parentNavigationController: params.navigationController, sendSticker: params.sendSticker, actionPerformed: { info, items, action in
|
||||||
|
@ -397,7 +397,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
|
|||||||
}, stopMediaRecording: {
|
}, stopMediaRecording: {
|
||||||
}, lockMediaRecording: {
|
}, lockMediaRecording: {
|
||||||
}, deleteRecordedMedia: {
|
}, deleteRecordedMedia: {
|
||||||
}, sendRecordedMedia: {
|
}, sendRecordedMedia: { _ in
|
||||||
}, displayRestrictedInfo: { _, _ in
|
}, displayRestrictedInfo: { _, _ in
|
||||||
}, displayVideoUnmuteTip: { _ in
|
}, displayVideoUnmuteTip: { _ in
|
||||||
}, switchMediaRecordingMode: {
|
}, switchMediaRecordingMode: {
|
||||||
@ -3087,7 +3087,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let currentPeerId = currentPeerId {
|
if false, let currentPeerId = currentPeerId {
|
||||||
if let navigationController = (strongSelf.controller?.navigationController as? NavigationController) {
|
if let navigationController = (strongSelf.controller?.navigationController as? NavigationController) {
|
||||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(currentPeerId)))
|
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(currentPeerId)))
|
||||||
}
|
}
|
||||||
@ -3645,7 +3645,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
|||||||
}
|
}
|
||||||
let context = self.context
|
let context = self.context
|
||||||
let presentationData = self.presentationData
|
let presentationData = self.presentationData
|
||||||
let mapMedia = TelegramMediaMap(latitude: location.latitude, longitude: location.longitude, geoPlace: nil, venue: MapVenue(title: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), address: location.address, provider: nil, id: nil, type: nil), liveBroadcastingTimeout: nil)
|
let mapMedia = TelegramMediaMap(latitude: location.latitude, longitude: location.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: MapVenue(title: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), address: location.address, provider: nil, id: nil, type: nil), liveBroadcastingTimeout: nil)
|
||||||
let locationController = legacyLocationController(message: nil, mapMedia: mapMedia, context: context, openPeer: { _ in }, sendLiveLocation: { _, _ in }, stopLiveLocation: {}, openUrl: { url in
|
let locationController = legacyLocationController(message: nil, mapMedia: mapMedia, context: context, openPeer: { _ in }, sendLiveLocation: { _, _ in }, stopLiveLocation: {}, openUrl: { url in
|
||||||
context.sharedContext.applicationBindings.openUrl(url)
|
context.sharedContext.applicationBindings.openUrl(url)
|
||||||
})
|
})
|
||||||
|
@ -89,6 +89,7 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie
|
|||||||
}
|
}
|
||||||
|
|
||||||
var scrolledToIndex: MessageHistoryAnchorIndex?
|
var scrolledToIndex: MessageHistoryAnchorIndex?
|
||||||
|
var scrolledToSomeIndex = false
|
||||||
|
|
||||||
if let scrollPosition = scrollPosition {
|
if let scrollPosition = scrollPosition {
|
||||||
switch scrollPosition {
|
switch scrollPosition {
|
||||||
@ -160,6 +161,7 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie
|
|||||||
var index = 0
|
var index = 0
|
||||||
for entry in toView.filteredEntries.reversed() {
|
for entry in toView.filteredEntries.reversed() {
|
||||||
if !scrollIndex.isLess(than: entry.index) {
|
if !scrollIndex.isLess(than: entry.index) {
|
||||||
|
scrolledToSomeIndex = true
|
||||||
scrollToItem = ListViewScrollToItem(index: index, position: position, animated: animated, curve: .Default(duration: nil), directionHint: directionHint)
|
scrollToItem = ListViewScrollToItem(index: index, position: position, animated: animated, curve: .Default(duration: nil), directionHint: directionHint)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -173,5 +175,5 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie
|
|||||||
options.insert(.Synchronous)
|
options.insert(.Synchronous)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ChatHistoryViewTransition(historyView: toView, deleteItems: adjustedDeleteIndices, insertEntries: adjustedIndicesAndItems, updateEntries: adjustedUpdateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: stationaryItemRange, initialData: initialData, keyboardButtonsMessage: keyboardButtonsMessage, cachedData: cachedData, cachedDataMessages: cachedDataMessages, readStateData: readStateData, scrolledToIndex: scrolledToIndex, animateIn: animateIn, reason: reason, flashIndicators: flashIndicators)
|
return ChatHistoryViewTransition(historyView: toView, deleteItems: adjustedDeleteIndices, insertEntries: adjustedIndicesAndItems, updateEntries: adjustedUpdateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: stationaryItemRange, initialData: initialData, keyboardButtonsMessage: keyboardButtonsMessage, cachedData: cachedData, cachedDataMessages: cachedDataMessages, readStateData: readStateData, scrolledToIndex: scrolledToIndex, scrolledToSomeIndex: scrolledToSomeIndex || scrolledToIndex != nil, animateIn: animateIn, reason: reason, flashIndicators: flashIndicators)
|
||||||
}
|
}
|
||||||
|
@ -203,7 +203,7 @@ final class WatchSendMessageHandler: WatchRequestHandler {
|
|||||||
messageSignal = .single((.message(text: args.text, attributes: [], mediaReference: nil, replyToMessageId: replyMessageId, localGroupingKey: nil), peerId))
|
messageSignal = .single((.message(text: args.text, attributes: [], mediaReference: nil, replyToMessageId: replyMessageId, localGroupingKey: nil), peerId))
|
||||||
} else if let args = subscription as? TGBridgeSendLocationMessageSubscription, let location = args.location {
|
} else if let args = subscription as? TGBridgeSendLocationMessageSubscription, let location = args.location {
|
||||||
let peerId = makePeerIdFromBridgeIdentifier(args.peerId)
|
let peerId = makePeerIdFromBridgeIdentifier(args.peerId)
|
||||||
let map = TelegramMediaMap(latitude: location.latitude, longitude: location.longitude, geoPlace: nil, venue: makeVenue(from: location.venue), liveBroadcastingTimeout: nil)
|
let map = TelegramMediaMap(latitude: location.latitude, longitude: location.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: makeVenue(from: location.venue), liveBroadcastingTimeout: nil)
|
||||||
messageSignal = .single((.message(text: "", attributes: [], mediaReference: .standalone(media: map), replyToMessageId: nil, localGroupingKey: nil), peerId))
|
messageSignal = .single((.message(text: "", attributes: [], mediaReference: .standalone(media: map), replyToMessageId: nil, localGroupingKey: nil), peerId))
|
||||||
} else if let args = subscription as? TGBridgeSendStickerMessageSubscription {
|
} else if let args = subscription as? TGBridgeSendStickerMessageSubscription {
|
||||||
let peerId = makePeerIdFromBridgeIdentifier(args.peerId)
|
let peerId = makePeerIdFromBridgeIdentifier(args.peerId)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user