Merge branch 'master' into experiments/widgetkit-widget

This commit is contained in:
Ali 2020-10-19 01:07:49 +04:00
commit e5f9e76b61
63 changed files with 5775 additions and 4603 deletions

View File

@ -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 %@";

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,7 +456,10 @@ class LocationPinAnnotationView: MKAnnotationView {
self.addSubnode(avatarNode) self.addSubnode(avatarNode)
} }
avatarNode.setPeer(context: context, theme: theme, peer: peer) if self.previousPeerId != peer.id {
self.previousPeerId = peer.id
avatarNode.setPeer(context: context, theme: theme, peer: peer)
}
} }
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
} }
let shareAction = OpenInControllerAction(title: strongSelf.presentationData.strings.Conversation_ContextMenuShare, action: { if let location = getLocation(from: strongSelf.subject) {
strongSelf.present(ShareController(context: context, subject: .mapMedia(mapMedia), externalShare: true), in: .window(.root), with: nil) let shareAction = OpenInControllerAction(title: strongSelf.presentationData.strings.Conversation_ContextMenuShare, action: {
}) 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) })
}, setupProximityNotification: { [weak self] in strongSelf.present(OpenInActionSheetController(context: context, item: .location(location: location, withDirections: false), additionalAction: shareAction, openUrl: params.openUrl), in: .window(.root), with: nil)
}
}, 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()
} }
} }

View File

@ -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,8 +53,12 @@ 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, _, _):
return .liveLocation(message.id.peerId) if let author = message.author {
return .liveLocation(author.id)
} else {
return .liveLocation(message.id.peerId)
}
} }
} }
@ -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 {
address = .single(venueAddress)
} else if mapMedia.liveBroadcastingTimeout == nil {
address = .single(nil)
|> then(
reverseGeocodeLocation(latitude: mapMedia.latitude, longitude: mapMedia.longitude)
|> map { placemark -> String? in
return placemark?.compactDisplayAddress ?? ""
}
)
} else {
address = .single(nil)
eta = .single(nil) eta = .single(nil)
|> then(driveEta(coordinate: location.coordinate))
if let venue = location.venue, let venueAddress = venue.address, !venueAddress.isEmpty {
address = .single(venueAddress)
} else {
address = .single(nil)
|> then(
reverseGeocodeLocation(latitude: location.latitude, longitude: location.longitude)
|> map { placemark -> String? in
return placemark?.compactDisplayAddress ?? ""
}
)
}
}
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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 { let _ = arguments.loadDetailedGraph(graph, Int64(date.timeIntervalSince1970) * 1000).start(next: { graph in
completion(data) if let graph = graph, case let .Loaded(_, data) = graph {
} else { completion(data)
let _ = arguments.loadDetailedGraph(graph, Int64(date.timeIntervalSince1970) * 1000).start(next: { graph in }
if let graph = graph, case let .Loaded(_, data) = graph { })
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 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -746,7 +746,11 @@ 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 {
strongSelf.chatDisplayNode.sendCurrentMessage(silentPosting: silentPosting) if let _ = strongSelf.presentationInterfaceState.recordedMediaPreview {
strongSelf.sendMediaRecording(silently: silentPosting)
} else {
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 {
@ -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)])
} }
} }

View File

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

View File

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

View File

@ -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? {
return self.contentContainers.first(where: { $0.contentMessageStableId == stableId })?.sourceNode ?? self.mainContextSourceNode if self.contentContainers.count > 1 {
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) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -63,33 +63,20 @@ 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) }, stopLiveLocation: {
}, stopLiveLocation: { params.context.liveLocationManager?.cancelLiveLocation(peerId: params.message.id.peerId)
params.context.liveLocationManager?.cancelLiveLocation(peerId: params.message.id.peerId) }, 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, subject: params.message, params: controllerParams)
let controller = LocationViewController(context: params.context, mapMedia: mapMedia, 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

View File

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

View File

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

View File

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