Live location improvements

This commit is contained in:
Ilya Laktyushin 2020-10-17 15:32:31 +04:00
parent f2ad10b937
commit 6299cd9a18
20 changed files with 5604 additions and 4643 deletions

View File

@ -5837,3 +5837,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

@ -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)
@ -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.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.setFillColor(UIColor(rgb: 0x3393fe).cgColor) var locations: [CGFloat] = [0.0, 0.5, 1.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]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
context.move(to: CGPoint(x: 14.0, y: 0.0)) context.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: 0.0, y: bounds.size.height), options: CGGradientDrawingOptions())
context.addLine(to: CGPoint(x: 19.0, y: 7.0)) })
context.addLine(to: CGPoint(x: 9.0, y: 7.0)) }
context.closePath()
context.fillPath() 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 {
@ -107,6 +118,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()
@ -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 {
@ -448,6 +546,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
} }
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

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

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
@ -35,9 +36,12 @@ 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
init(toggleMapModeSelection: @escaping () -> Void, updateMapMode: @escaping (LocationMapMode) -> Void, goToUserLocation: @escaping () -> Void, goToCoordinate: @escaping (CLLocationCoordinate2D) -> Void, requestDirections: @escaping () -> Void, share: @escaping () -> Void, setupProximityNotification: @escaping () -> Void) { let sendLiveLocation: (CLLocationCoordinate2D) -> Void
let stopLiveLocation: () -> 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) {
self.toggleMapModeSelection = toggleMapModeSelection self.toggleMapModeSelection = toggleMapModeSelection
self.updateMapMode = updateMapMode self.updateMapMode = updateMapMode
self.goToUserLocation = goToUserLocation self.goToUserLocation = goToUserLocation
@ -45,23 +49,31 @@ 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
} }
} }
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) { 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 }
@ -89,7 +101,7 @@ public final class LocationViewController: ViewController {
strongSelf.controllerNode.updatePresentationData(presentationData) strongSelf.controllerNode.updatePresentationData(presentationData)
} }
}) })
self.interaction = LocationViewInteraction(toggleMapModeSelection: { [weak self] in self.interaction = LocationViewInteraction(toggleMapModeSelection: { [weak self] in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -126,27 +138,115 @@ 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()
}) })
self.scrollToTop = { [weak self] in self.scrollToTop = { [weak self] in
@ -170,8 +270,14 @@ 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
}
} }
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {

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
} }
} }
@ -123,13 +132,29 @@ private enum LocationViewEntry: Comparable, Identifiable {
}, getDirections: { }, getDirections: {
interaction?.requestDirections() interaction?.requestDirections()
}) })
case let .toggleLiveLocation(theme, title, subtitle): case let .toggleLiveLocation(theme, 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 { }
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 entries: [LocationViewEntry] = []
var annotations: [LocationPinAnnotation] = []
entries.append(.info(presentationData.theme, mapMedia, address, distance, eta)) 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 {
@ -277,18 +391,21 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
case .custom: case .custom:
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
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 +472,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 +492,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 +501,39 @@ 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 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 +549,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

@ -231,10 +231,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1893427255] = { return Api.Update.parse_updateChannelAvailableMessages($0) } dict[1893427255] = { return Api.Update.parse_updateChannelAvailableMessages($0) }
dict[-513517117] = { return Api.Update.parse_updateDialogUnreadMark($0) } dict[-513517117] = { return Api.Update.parse_updateDialogUnreadMark($0) }
dict[1180041828] = { return Api.Update.parse_updateLangPackTooLong($0) } dict[1180041828] = { return Api.Update.parse_updateLangPackTooLong($0) }
dict[1279515160] = { return Api.Update.parse_updateUserPinnedMessage($0) }
dict[-1398708869] = { return Api.Update.parse_updateMessagePoll($0) } dict[-1398708869] = { return Api.Update.parse_updateMessagePoll($0) }
dict[1421875280] = { return Api.Update.parse_updateChatDefaultBannedRights($0) } dict[1421875280] = { return Api.Update.parse_updateChatDefaultBannedRights($0) }
dict[-519195831] = { return Api.Update.parse_updateChatPinnedMessage($0) }
dict[422972864] = { return Api.Update.parse_updateFolderPeers($0) } dict[422972864] = { return Api.Update.parse_updateFolderPeers($0) }
dict[1852826908] = { return Api.Update.parse_updateDialogPinned($0) } dict[1852826908] = { return Api.Update.parse_updateDialogPinned($0) }
dict[-99664734] = { return Api.Update.parse_updatePinnedDialogs($0) } dict[-99664734] = { return Api.Update.parse_updatePinnedDialogs($0) }
@ -258,6 +256,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1178116716] = { return Api.Update.parse_updateReadChannelDiscussionOutbox($0) } dict[1178116716] = { return Api.Update.parse_updateReadChannelDiscussionOutbox($0) }
dict[610945826] = { return Api.Update.parse_updatePeerBlocked($0) } dict[610945826] = { return Api.Update.parse_updatePeerBlocked($0) }
dict[-13975905] = { return Api.Update.parse_updateChannelUserTyping($0) } dict[-13975905] = { return Api.Update.parse_updateChannelUserTyping($0) }
dict[-309990731] = { return Api.Update.parse_updatePinnedMessages($0) }
dict[-2054649973] = { return Api.Update.parse_updatePinnedChannelMessages($0) } dict[-2054649973] = { return Api.Update.parse_updatePinnedChannelMessages($0) }
dict[136574537] = { return Api.messages.VotesList.parse_votesList($0) } dict[136574537] = { return Api.messages.VotesList.parse_votesList($0) }
dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) } dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) }
@ -541,8 +540,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1963717851] = { return Api.WallPaper.parse_wallPaperNoFile($0) } dict[-1963717851] = { return Api.WallPaper.parse_wallPaperNoFile($0) }
dict[-1938715001] = { return Api.messages.Messages.parse_messages($0) } dict[-1938715001] = { return Api.messages.Messages.parse_messages($0) }
dict[1951620897] = { return Api.messages.Messages.parse_messagesNotModified($0) } dict[1951620897] = { return Api.messages.Messages.parse_messagesNotModified($0) }
dict[-1725551049] = { return Api.messages.Messages.parse_channelMessages($0) } dict[1682413576] = { return Api.messages.Messages.parse_channelMessages($0) }
dict[-923939298] = { return Api.messages.Messages.parse_messagesSlice($0) } dict[978610270] = { return Api.messages.Messages.parse_messagesSlice($0) }
dict[-1022713000] = { return Api.Invoice.parse_invoice($0) } dict[-1022713000] = { return Api.Invoice.parse_invoice($0) }
dict[1933519201] = { return Api.PeerSettings.parse_peerSettings($0) } dict[1933519201] = { return Api.PeerSettings.parse_peerSettings($0) }
dict[1577067778] = { return Api.auth.SentCode.parse_sentCode($0) } dict[1577067778] = { return Api.auth.SentCode.parse_sentCode($0) }

View File

@ -1093,8 +1093,8 @@ public struct messages {
public enum Messages: TypeConstructorDescription { public enum Messages: TypeConstructorDescription {
case messages(messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) case messages(messages: [Api.Message], chats: [Api.Chat], users: [Api.User])
case messagesNotModified(count: Int32) case messagesNotModified(count: Int32)
case channelMessages(flags: Int32, pts: Int32, count: Int32, messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) case channelMessages(flags: Int32, pts: Int32, count: Int32, offsetIdOffset: Int32?, messages: [Api.Message], chats: [Api.Chat], users: [Api.User])
case messagesSlice(flags: Int32, count: Int32, nextRate: Int32?, messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) case messagesSlice(flags: Int32, count: Int32, nextRate: Int32?, offsetIdOffset: Int32?, messages: [Api.Message], chats: [Api.Chat], users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
@ -1124,13 +1124,14 @@ public struct messages {
} }
serializeInt32(count, buffer: buffer, boxed: false) serializeInt32(count, buffer: buffer, boxed: false)
break break
case .channelMessages(let flags, let pts, let count, let messages, let chats, let users): case .channelMessages(let flags, let pts, let count, let offsetIdOffset, let messages, let chats, let users):
if boxed { if boxed {
buffer.appendInt32(-1725551049) buffer.appendInt32(1682413576)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(pts, buffer: buffer, boxed: false) serializeInt32(pts, buffer: buffer, boxed: false)
serializeInt32(count, buffer: buffer, boxed: false) serializeInt32(count, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 2) != 0 {serializeInt32(offsetIdOffset!, buffer: buffer, boxed: false)}
buffer.appendInt32(481674261) buffer.appendInt32(481674261)
buffer.appendInt32(Int32(messages.count)) buffer.appendInt32(Int32(messages.count))
for item in messages { for item in messages {
@ -1147,13 +1148,14 @@ public struct messages {
item.serialize(buffer, true) item.serialize(buffer, true)
} }
break break
case .messagesSlice(let flags, let count, let nextRate, let messages, let chats, let users): case .messagesSlice(let flags, let count, let nextRate, let offsetIdOffset, let messages, let chats, let users):
if boxed { if boxed {
buffer.appendInt32(-923939298) buffer.appendInt32(978610270)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(count, buffer: buffer, boxed: false) serializeInt32(count, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(nextRate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 0) != 0 {serializeInt32(nextRate!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 2) != 0 {serializeInt32(offsetIdOffset!, buffer: buffer, boxed: false)}
buffer.appendInt32(481674261) buffer.appendInt32(481674261)
buffer.appendInt32(Int32(messages.count)) buffer.appendInt32(Int32(messages.count))
for item in messages { for item in messages {
@ -1179,10 +1181,10 @@ public struct messages {
return ("messages", [("messages", messages), ("chats", chats), ("users", users)]) return ("messages", [("messages", messages), ("chats", chats), ("users", users)])
case .messagesNotModified(let count): case .messagesNotModified(let count):
return ("messagesNotModified", [("count", count)]) return ("messagesNotModified", [("count", count)])
case .channelMessages(let flags, let pts, let count, let messages, let chats, let users): case .channelMessages(let flags, let pts, let count, let offsetIdOffset, let messages, let chats, let users):
return ("channelMessages", [("flags", flags), ("pts", pts), ("count", count), ("messages", messages), ("chats", chats), ("users", users)]) return ("channelMessages", [("flags", flags), ("pts", pts), ("count", count), ("offsetIdOffset", offsetIdOffset), ("messages", messages), ("chats", chats), ("users", users)])
case .messagesSlice(let flags, let count, let nextRate, let messages, let chats, let users): case .messagesSlice(let flags, let count, let nextRate, let offsetIdOffset, let messages, let chats, let users):
return ("messagesSlice", [("flags", flags), ("count", count), ("nextRate", nextRate), ("messages", messages), ("chats", chats), ("users", users)]) return ("messagesSlice", [("flags", flags), ("count", count), ("nextRate", nextRate), ("offsetIdOffset", offsetIdOffset), ("messages", messages), ("chats", chats), ("users", users)])
} }
} }
@ -1227,26 +1229,29 @@ public struct messages {
_2 = reader.readInt32() _2 = reader.readInt32()
var _3: Int32? var _3: Int32?
_3 = reader.readInt32() _3 = reader.readInt32()
var _4: [Api.Message]? var _4: Int32?
if Int(_1!) & Int(1 << 2) != 0 {_4 = reader.readInt32() }
var _5: [Api.Message]?
if let _ = reader.readInt32() { if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self)
} }
var _5: [Api.Chat]? var _6: [Api.Chat]?
if let _ = reader.readInt32() { if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
} }
var _6: [Api.User]? var _7: [Api.User]?
if let _ = reader.readInt32() { if let _ = reader.readInt32() {
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
} }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = _3 != nil let _c3 = _3 != nil
let _c4 = _4 != nil let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil
let _c5 = _5 != nil let _c5 = _5 != nil
let _c6 = _6 != nil let _c6 = _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { let _c7 = _7 != nil
return Api.messages.Messages.channelMessages(flags: _1!, pts: _2!, count: _3!, messages: _4!, chats: _5!, users: _6!) if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.messages.Messages.channelMessages(flags: _1!, pts: _2!, count: _3!, offsetIdOffset: _4, messages: _5!, chats: _6!, users: _7!)
} }
else { else {
return nil return nil
@ -1259,26 +1264,29 @@ public struct messages {
_2 = reader.readInt32() _2 = reader.readInt32()
var _3: Int32? var _3: Int32?
if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() }
var _4: [Api.Message]? var _4: Int32?
if Int(_1!) & Int(1 << 2) != 0 {_4 = reader.readInt32() }
var _5: [Api.Message]?
if let _ = reader.readInt32() { if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self)
} }
var _5: [Api.Chat]? var _6: [Api.Chat]?
if let _ = reader.readInt32() { if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
} }
var _6: [Api.User]? var _7: [Api.User]?
if let _ = reader.readInt32() { if let _ = reader.readInt32() {
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
} }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
let _c4 = _4 != nil let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil
let _c5 = _5 != nil let _c5 = _5 != nil
let _c6 = _6 != nil let _c6 = _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { let _c7 = _7 != nil
return Api.messages.Messages.messagesSlice(flags: _1!, count: _2!, nextRate: _3, messages: _4!, chats: _5!, users: _6!) if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.messages.Messages.messagesSlice(flags: _1!, count: _2!, nextRate: _3, offsetIdOffset: _4, messages: _5!, chats: _6!, users: _7!)
} }
else { else {
return nil return nil
@ -6158,10 +6166,8 @@ public extension Api {
case updateChannelAvailableMessages(channelId: Int32, availableMinId: Int32) case updateChannelAvailableMessages(channelId: Int32, availableMinId: Int32)
case updateDialogUnreadMark(flags: Int32, peer: Api.DialogPeer) case updateDialogUnreadMark(flags: Int32, peer: Api.DialogPeer)
case updateLangPackTooLong(langCode: String) case updateLangPackTooLong(langCode: String)
case updateUserPinnedMessage(userId: Int32, id: Int32)
case updateMessagePoll(flags: Int32, pollId: Int64, poll: Api.Poll?, results: Api.PollResults) case updateMessagePoll(flags: Int32, pollId: Int64, poll: Api.Poll?, results: Api.PollResults)
case updateChatDefaultBannedRights(peer: Api.Peer, defaultBannedRights: Api.ChatBannedRights, version: Int32) case updateChatDefaultBannedRights(peer: Api.Peer, defaultBannedRights: Api.ChatBannedRights, version: Int32)
case updateChatPinnedMessage(chatId: Int32, id: Int32, version: Int32)
case updateFolderPeers(folderPeers: [Api.FolderPeer], pts: Int32, ptsCount: Int32) case updateFolderPeers(folderPeers: [Api.FolderPeer], pts: Int32, ptsCount: Int32)
case updateDialogPinned(flags: Int32, folderId: Int32?, peer: Api.DialogPeer) case updateDialogPinned(flags: Int32, folderId: Int32?, peer: Api.DialogPeer)
case updatePinnedDialogs(flags: Int32, folderId: Int32?, order: [Api.DialogPeer]?) case updatePinnedDialogs(flags: Int32, folderId: Int32?, order: [Api.DialogPeer]?)
@ -6185,6 +6191,7 @@ public extension Api {
case updateReadChannelDiscussionOutbox(channelId: Int32, topMsgId: Int32, readMaxId: Int32) case updateReadChannelDiscussionOutbox(channelId: Int32, topMsgId: Int32, readMaxId: Int32)
case updatePeerBlocked(peerId: Api.Peer, blocked: Api.Bool) case updatePeerBlocked(peerId: Api.Peer, blocked: Api.Bool)
case updateChannelUserTyping(flags: Int32, channelId: Int32, topMsgId: Int32?, userId: Int32, action: Api.SendMessageAction) case updateChannelUserTyping(flags: Int32, channelId: Int32, topMsgId: Int32?, userId: Int32, action: Api.SendMessageAction)
case updatePinnedMessages(flags: Int32, peer: Api.Peer, messages: [Int32], pts: Int32, ptsCount: Int32)
case updatePinnedChannelMessages(flags: Int32, channelId: Int32, messages: [Int32], pts: Int32, ptsCount: Int32) case updatePinnedChannelMessages(flags: Int32, channelId: Int32, messages: [Int32], pts: Int32, ptsCount: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
@ -6669,13 +6676,6 @@ public extension Api {
} }
serializeString(langCode, buffer: buffer, boxed: false) serializeString(langCode, buffer: buffer, boxed: false)
break break
case .updateUserPinnedMessage(let userId, let id):
if boxed {
buffer.appendInt32(1279515160)
}
serializeInt32(userId, buffer: buffer, boxed: false)
serializeInt32(id, buffer: buffer, boxed: false)
break
case .updateMessagePoll(let flags, let pollId, let poll, let results): case .updateMessagePoll(let flags, let pollId, let poll, let results):
if boxed { if boxed {
buffer.appendInt32(-1398708869) buffer.appendInt32(-1398708869)
@ -6693,14 +6693,6 @@ public extension Api {
defaultBannedRights.serialize(buffer, true) defaultBannedRights.serialize(buffer, true)
serializeInt32(version, buffer: buffer, boxed: false) serializeInt32(version, buffer: buffer, boxed: false)
break break
case .updateChatPinnedMessage(let chatId, let id, let version):
if boxed {
buffer.appendInt32(-519195831)
}
serializeInt32(chatId, buffer: buffer, boxed: false)
serializeInt32(id, buffer: buffer, boxed: false)
serializeInt32(version, buffer: buffer, boxed: false)
break
case .updateFolderPeers(let folderPeers, let pts, let ptsCount): case .updateFolderPeers(let folderPeers, let pts, let ptsCount):
if boxed { if boxed {
buffer.appendInt32(422972864) buffer.appendInt32(422972864)
@ -6908,6 +6900,20 @@ public extension Api {
serializeInt32(userId, buffer: buffer, boxed: false) serializeInt32(userId, buffer: buffer, boxed: false)
action.serialize(buffer, true) action.serialize(buffer, true)
break break
case .updatePinnedMessages(let flags, let peer, let messages, let pts, let ptsCount):
if boxed {
buffer.appendInt32(-309990731)
}
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(messages.count))
for item in messages {
serializeInt32(item, buffer: buffer, boxed: false)
}
serializeInt32(pts, buffer: buffer, boxed: false)
serializeInt32(ptsCount, buffer: buffer, boxed: false)
break
case .updatePinnedChannelMessages(let flags, let channelId, let messages, let pts, let ptsCount): case .updatePinnedChannelMessages(let flags, let channelId, let messages, let pts, let ptsCount):
if boxed { if boxed {
buffer.appendInt32(-2054649973) buffer.appendInt32(-2054649973)
@ -7043,14 +7049,10 @@ public extension Api {
return ("updateDialogUnreadMark", [("flags", flags), ("peer", peer)]) return ("updateDialogUnreadMark", [("flags", flags), ("peer", peer)])
case .updateLangPackTooLong(let langCode): case .updateLangPackTooLong(let langCode):
return ("updateLangPackTooLong", [("langCode", langCode)]) return ("updateLangPackTooLong", [("langCode", langCode)])
case .updateUserPinnedMessage(let userId, let id):
return ("updateUserPinnedMessage", [("userId", userId), ("id", id)])
case .updateMessagePoll(let flags, let pollId, let poll, let results): case .updateMessagePoll(let flags, let pollId, let poll, let results):
return ("updateMessagePoll", [("flags", flags), ("pollId", pollId), ("poll", poll), ("results", results)]) return ("updateMessagePoll", [("flags", flags), ("pollId", pollId), ("poll", poll), ("results", results)])
case .updateChatDefaultBannedRights(let peer, let defaultBannedRights, let version): case .updateChatDefaultBannedRights(let peer, let defaultBannedRights, let version):
return ("updateChatDefaultBannedRights", [("peer", peer), ("defaultBannedRights", defaultBannedRights), ("version", version)]) return ("updateChatDefaultBannedRights", [("peer", peer), ("defaultBannedRights", defaultBannedRights), ("version", version)])
case .updateChatPinnedMessage(let chatId, let id, let version):
return ("updateChatPinnedMessage", [("chatId", chatId), ("id", id), ("version", version)])
case .updateFolderPeers(let folderPeers, let pts, let ptsCount): case .updateFolderPeers(let folderPeers, let pts, let ptsCount):
return ("updateFolderPeers", [("folderPeers", folderPeers), ("pts", pts), ("ptsCount", ptsCount)]) return ("updateFolderPeers", [("folderPeers", folderPeers), ("pts", pts), ("ptsCount", ptsCount)])
case .updateDialogPinned(let flags, let folderId, let peer): case .updateDialogPinned(let flags, let folderId, let peer):
@ -7097,6 +7099,8 @@ public extension Api {
return ("updatePeerBlocked", [("peerId", peerId), ("blocked", blocked)]) return ("updatePeerBlocked", [("peerId", peerId), ("blocked", blocked)])
case .updateChannelUserTyping(let flags, let channelId, let topMsgId, let userId, let action): case .updateChannelUserTyping(let flags, let channelId, let topMsgId, let userId, let action):
return ("updateChannelUserTyping", [("flags", flags), ("channelId", channelId), ("topMsgId", topMsgId), ("userId", userId), ("action", action)]) return ("updateChannelUserTyping", [("flags", flags), ("channelId", channelId), ("topMsgId", topMsgId), ("userId", userId), ("action", action)])
case .updatePinnedMessages(let flags, let peer, let messages, let pts, let ptsCount):
return ("updatePinnedMessages", [("flags", flags), ("peer", peer), ("messages", messages), ("pts", pts), ("ptsCount", ptsCount)])
case .updatePinnedChannelMessages(let flags, let channelId, let messages, let pts, let ptsCount): case .updatePinnedChannelMessages(let flags, let channelId, let messages, let pts, let ptsCount):
return ("updatePinnedChannelMessages", [("flags", flags), ("channelId", channelId), ("messages", messages), ("pts", pts), ("ptsCount", ptsCount)]) return ("updatePinnedChannelMessages", [("flags", flags), ("channelId", channelId), ("messages", messages), ("pts", pts), ("ptsCount", ptsCount)])
} }
@ -8062,20 +8066,6 @@ public extension Api {
return nil return nil
} }
} }
public static func parse_updateUserPinnedMessage(_ reader: BufferReader) -> Update? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.Update.updateUserPinnedMessage(userId: _1!, id: _2!)
}
else {
return nil
}
}
public static func parse_updateMessagePoll(_ reader: BufferReader) -> Update? { public static func parse_updateMessagePoll(_ reader: BufferReader) -> Update? {
var _1: Int32? var _1: Int32?
_1 = reader.readInt32() _1 = reader.readInt32()
@ -8121,23 +8111,6 @@ public extension Api {
return nil return nil
} }
} }
public static func parse_updateChatPinnedMessage(_ reader: BufferReader) -> Update? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
_3 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.Update.updateChatPinnedMessage(chatId: _1!, id: _2!, version: _3!)
}
else {
return nil
}
}
public static func parse_updateFolderPeers(_ reader: BufferReader) -> Update? { public static func parse_updateFolderPeers(_ reader: BufferReader) -> Update? {
var _1: [Api.FolderPeer]? var _1: [Api.FolderPeer]?
if let _ = reader.readInt32() { if let _ = reader.readInt32() {
@ -8550,6 +8523,33 @@ public extension Api {
return nil return nil
} }
} }
public static func parse_updatePinnedMessages(_ reader: BufferReader) -> Update? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Api.Peer?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.Peer
}
var _3: [Int32]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
}
var _4: Int32?
_4 = reader.readInt32()
var _5: Int32?
_5 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.Update.updatePinnedMessages(flags: _1!, peer: _2!, messages: _3!, pts: _4!, ptsCount: _5!)
}
else {
return nil
}
}
public static func parse_updatePinnedChannelMessages(_ reader: BufferReader) -> Update? { public static func parse_updatePinnedChannelMessages(_ reader: BufferReader) -> Update? {
var _1: Int32? var _1: Int32?
_1 = reader.readInt32() _1 = reader.readInt32()

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

@ -442,8 +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 .geoProximityReached: case let .geoProximityReached(distance):
attributedString = nil 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

@ -320,7 +320,7 @@ final class AuthorizedApplicationContext {
} }
} }
if chatIsVisible { if false, chatIsVisible {
return return
} }

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, heading: nil, accuracyRadius: nil, 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