Various improvements

This commit is contained in:
Ilya Laktyushin 2020-10-19 02:01:27 +04:00
parent fe9610eee9
commit e45a3c0ee9
23 changed files with 4544 additions and 4288 deletions

View File

@ -5838,8 +5838,14 @@ Any member of this group will be able to see messages in the channel.";
"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"; "Notification.ProximityReached" = "%1$@ is now within %2$@ from you";
"Location.ProximityNotification.Title" = "Notification"; "Location.ProximityNotification.Title" = "Notification";
"Location.ProximityNotification.Notify" = "Notify me within %@"; "Location.ProximityNotification.Notify" = "Notify me within %@";
"Location.ProximityNotification.AlreadyClose" = "You are already closer than %@"; "Location.ProximityNotification.AlreadyClose" = "You are already closer than %@";
"Location.ProximityNotification.DistanceKM" = "KM";
"Location.ProximityNotification.DistanceMI" = "MI";
"Location.LiveLocationRequired.Title" = "Share Location";
"Location.LiveLocationRequired.Description" = "For the alert to work, please share your live location in this chat.";
"Location.LiveLocationRequired.ShareLocation" = "Share Location";

View File

@ -236,18 +236,7 @@ public func legacyLocationController(message: Message?, mapMedia: TelegramMediaM
legacyController?.dismiss() legacyController?.dismiss()
} }
controller.liveLocationStopped = { [weak legacyController] in controller.liveLocationStopped = { [weak legacyController] in
if let message = message, let locationManager = context.sharedContext.locationManager { stopLiveLocation()
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

@ -231,7 +231,7 @@ class LocationPinAnnotationView: MKAnnotationView {
self.setPeer(context: annotation.context, theme: annotation.theme, peer: peer) self.setPeer(context: annotation.context, theme: annotation.theme, peer: peer)
self.setSelected(true, animated: false) self.setSelected(true, animated: false)
} else if let location = annotation.location { } else if let location = annotation.location {
let venueType = annotation.location?.venue?.type ?? "" let venueType = location.venue?.type ?? ""
let color = venueType.isEmpty ? annotation.theme.list.itemAccentColor : venueIconColor(type: venueType) let color = venueType.isEmpty ? annotation.theme.list.itemAccentColor : venueIconColor(type: venueType)
self.backgroundNode.image = generateTintedImage(image: UIImage(bundleImageName: "Location/PinBackground"), color: color) self.backgroundNode.image = generateTintedImage(image: UIImage(bundleImageName: "Location/PinBackground"), color: color)
self.iconNode.setSignal(venueIcon(postbox: annotation.context.account.postbox, type: venueType, background: false)) self.iconNode.setSignal(venueIcon(postbox: annotation.context.account.postbox, type: venueType, background: false))

View File

@ -9,6 +9,7 @@ import SwiftSignalKit
import AccountContext import AccountContext
import SolidRoundedButtonNode import SolidRoundedButtonNode
import TelegramPresentationData import TelegramPresentationData
import TelegramStringFormatting
import PresentationDataUtils import PresentationDataUtils
import CoreLocation import CoreLocation
@ -26,18 +27,20 @@ final class LocationDistancePickerScreen: ViewController {
private let context: AccountContext private let context: AccountContext
private let style: LocationDistancePickerScreenStyle private let style: LocationDistancePickerScreenStyle
private let currentDistance: Double? private let distances: Signal<[Double], NoError>
private let updated: (Int32?) -> Void private let updated: (Int32?) -> Void
private let completion: (Int32?) -> Void private let completion: (Int32?) -> Void
private let willDismiss: () -> Void
private var presentationDataDisposable: Disposable? private var presentationDataDisposable: Disposable?
init(context: AccountContext, style: LocationDistancePickerScreenStyle, currentDistance: Double?, updated: @escaping (Int32?) -> Void, completion: @escaping (Int32?) -> Void) { init(context: AccountContext, style: LocationDistancePickerScreenStyle, distances: Signal<[Double], NoError>, updated: @escaping (Int32?) -> Void, completion: @escaping (Int32?) -> Void, willDismiss: @escaping () -> Void) {
self.context = context self.context = context
self.style = style self.style = style
self.currentDistance = currentDistance self.distances = distances
self.updated = updated self.updated = updated
self.completion = completion self.completion = completion
self.willDismiss = willDismiss
super.init(navigationBarPresentationData: nil) super.init(navigationBarPresentationData: nil)
@ -64,7 +67,7 @@ final class LocationDistancePickerScreen: ViewController {
} }
override public func loadDisplayNode() { override public func loadDisplayNode() {
self.displayNode = LocationDistancePickerScreenNode(context: self.context, style: self.style, currentDistance: self.currentDistance) self.displayNode = LocationDistancePickerScreenNode(context: self.context, style: self.style, distances: self.distances)
self.controllerNode.updated = { [weak self] distance in self.controllerNode.updated = { [weak self] distance in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -79,12 +82,13 @@ final class LocationDistancePickerScreen: ViewController {
strongSelf.dismiss() strongSelf.dismiss()
} }
self.controllerNode.dismiss = { [weak self] in self.controllerNode.dismiss = { [weak self] in
self?.updated(nil)
self?.presentingViewController?.dismiss(animated: false, completion: nil) self?.presentingViewController?.dismiss(animated: false, completion: nil)
} }
self.controllerNode.cancel = { [weak self] in self.controllerNode.cancel = { [weak self] in
self?.dismiss() self?.dismiss()
} }
self.controllerNode.update()
} }
override public func loadView() { override public func loadView() {
@ -101,6 +105,7 @@ final class LocationDistancePickerScreen: ViewController {
} }
override public func dismiss(completion: (() -> Void)? = nil) { override public func dismiss(completion: (() -> Void)? = nil) {
self.willDismiss()
self.controllerNode.animateOut(completion: completion) self.controllerNode.animateOut(completion: completion)
} }
@ -145,7 +150,7 @@ private class TimerPickerView: UIPickerView {
} }
} }
private var timerValues: [Int32] = { private var unitValues: [Int32] = {
var values: [Int32] = [] var values: [Int32] = []
for i in 0 ..< 99 { for i in 0 ..< 99 {
values.append(Int32(i)) values.append(Int32(i))
@ -153,7 +158,7 @@ private var timerValues: [Int32] = {
return values return values
}() }()
private var smallerTimerValues: [Int32] = { private var smallUnitValues: [Int32] = {
var values: [Int32] = [] var values: [Int32] = []
for i in 0 ..< 100 { for i in 0 ..< 100 {
values.append(Int32(i)) values.append(Int32(i))
@ -165,7 +170,7 @@ class LocationDistancePickerScreenNode: ViewControllerTracingNode, UIScrollViewD
private let context: AccountContext private let context: AccountContext
private let controllerStyle: LocationDistancePickerScreenStyle private let controllerStyle: LocationDistancePickerScreenStyle
private var presentationData: PresentationData private var presentationData: PresentationData
private let currentDistance: Double? private var distances: [Double] = []
private let dimNode: ASDisplayNode private let dimNode: ASDisplayNode
private let wrappingScrollNode: ASScrollNode private let wrappingScrollNode: ASScrollNode
@ -182,17 +187,18 @@ class LocationDistancePickerScreenNode: ViewControllerTracingNode, UIScrollViewD
private var containerLayout: (ContainerViewLayout, CGFloat)? private var containerLayout: (ContainerViewLayout, CGFloat)?
private var distancesDisposable: Disposable?
var updated: ((Int32) -> Void)? var updated: ((Int32) -> Void)?
var completion: ((Int32) -> Void)? var completion: ((Int32) -> Void)?
var dismiss: (() -> Void)? var dismiss: (() -> Void)?
var cancel: (() -> Void)? var cancel: (() -> Void)?
init(context: AccountContext, style: LocationDistancePickerScreenStyle, currentDistance: Double?) { init(context: AccountContext, style: LocationDistancePickerScreenStyle, distances: Signal<[Double], NoError>) {
self.context = context self.context = context
self.controllerStyle = style self.controllerStyle = style
self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.currentDistance = currentDistance
self.wrappingScrollNode = ASScrollNode() self.wrappingScrollNode = ASScrollNode()
self.wrappingScrollNode.view.alwaysBounceVertical = true self.wrappingScrollNode.view.alwaysBounceVertical = true
self.wrappingScrollNode.view.delaysContentTouches = false self.wrappingScrollNode.view.delaysContentTouches = false
@ -232,11 +238,11 @@ class LocationDistancePickerScreenNode: ViewControllerTracingNode, UIScrollViewD
self.contentBackgroundNode = ASDisplayNode() self.contentBackgroundNode = ASDisplayNode()
self.contentBackgroundNode.backgroundColor = backgroundColor self.contentBackgroundNode.backgroundColor = backgroundColor
let title = "Notification"
self.titleNode = ASTextNode() self.titleNode = ASTextNode()
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(17.0), textColor: textColor) self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.Location_ProximityNotification_Title, font: Font.bold(17.0), textColor: textColor)
self.textNode = ImmediateTextNode() self.textNode = ImmediateTextNode()
self.textNode.alpha = 0.0
self.cancelButton = HighlightableButtonNode() self.cancelButton = HighlightableButtonNode()
self.cancelButton.setTitle(self.presentationData.strings.Common_Cancel, with: Font.regular(17.0), with: accentColor, for: .normal) self.cancelButton.setTitle(self.presentationData.strings.Common_Cancel, with: Font.regular(17.0), with: accentColor, for: .normal)
@ -270,19 +276,29 @@ class LocationDistancePickerScreenNode: ViewControllerTracingNode, UIScrollViewD
if let strongSelf = self, let pickerView = strongSelf.pickerView { if let strongSelf = self, let pickerView = strongSelf.pickerView {
strongSelf.doneButton.isUserInteractionEnabled = false strongSelf.doneButton.isUserInteractionEnabled = false
let largeValue = timerValues[pickerView.selectedRow(inComponent: 0)] let largeValue = unitValues[pickerView.selectedRow(inComponent: 0)]
let smallValue = smallerTimerValues[pickerView.selectedRow(inComponent: 1)] let smallValue = smallUnitValues[pickerView.selectedRow(inComponent: 1)]
var value = largeValue * 1000 + smallValue * 10 var value = largeValue * 1000 + smallValue * 10
value = Int32(Double(value) * 1.60934) if !strongSelf.usesMetricSystem() {
value = Int32(Double(value) * 1.60934)
}
strongSelf.completion?(value) strongSelf.completion?(value)
} }
} }
self.setupPickerView() self.setupPickerView()
Queue.mainQueue().after(0.5) { self.distancesDisposable = (distances
self.updateDoneButtonTitle() |> deliverOnMainQueue).start(next: { [weak self] distances in
} if let strongSelf = self {
strongSelf.distances = distances
strongSelf.updateDoneButtonTitle()
}
})
}
deinit {
self.distancesDisposable?.dispose()
} }
func setupPickerView() { func setupPickerView() {
@ -295,49 +311,78 @@ class LocationDistancePickerScreenNode: ViewControllerTracingNode, UIScrollViewD
pickerView.dataSource = self pickerView.dataSource = self
pickerView.delegate = self pickerView.delegate = self
pickerView.selectRow(0, inComponent: 0, animated: false) pickerView.selectRow(0, inComponent: 0, animated: false)
pickerView.selectRow(30, inComponent: 1, animated: false)
if self.usesMetricSystem() {
pickerView.selectRow(30, inComponent: 1, animated: false)
} else {
pickerView.selectRow(20, inComponent: 1, animated: false)
}
self.contentContainerNode.view.addSubview(pickerView) self.contentContainerNode.view.addSubview(pickerView)
self.pickerView = pickerView self.pickerView = pickerView
self.updateDoneButtonTitle() self.updateDoneButtonTitle()
} }
private func usesMetricSystem() -> Bool {
return localeWithStrings(self.presentationData.strings).usesMetricSystem
}
func numberOfComponents(in pickerView: UIPickerView) -> Int { func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 2 return 3
} }
private func updateDoneButtonTitle() { private func updateDoneButtonTitle() {
if let pickerView = self.pickerView { if let pickerView = self.pickerView {
let largeValue = timerValues[pickerView.selectedRow(inComponent: 0)] let largeValue = unitValues[pickerView.selectedRow(inComponent: 0)]
let smallValue = smallerTimerValues[pickerView.selectedRow(inComponent: 1)] let smallValue = smallUnitValues[pickerView.selectedRow(inComponent: 1)]
var value = largeValue * 1000 + smallValue * 10 var value = largeValue * 1000 + smallValue * 10
value = Int32(Double(value) * 1.60934) if !self.usesMetricSystem() {
let distance = stringForDistance(strings: context.sharedContext.currentPresentationData.with { $0 }.strings, distance: CLLocationDistance(value)) value = Int32(Double(value) * 1.60934)
}
let distance = stringForDistance(strings: self.presentationData.strings, distance: CLLocationDistance(value))
self.doneButton.title = self.presentationData.strings.Location_ProximityNotification_Notify(distance).0
self.textNode.attributedText = NSAttributedString(string: self.presentationData.strings.Location_ProximityNotification_AlreadyClose(distance).0, font: Font.regular(14.0), textColor: self.presentationData.theme.actionSheet.secondaryTextColor)
if let (layout, navigationBarHeight) = self.containerLayout {
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
}
self.updated?(value) if let distance = self.distances.first, Double(value) > distance {
self.doneButton.title = "Notify me within \(distance)" self.doneButton.alpha = 0.0
if let currentDistance = self.currentDistance, value > Int32(currentDistance) {
self.doneButton.alpha = 0.4
self.doneButton.isUserInteractionEnabled = false self.doneButton.isUserInteractionEnabled = false
self.textNode.alpha = 1.0
} else { } else {
self.doneButton.alpha = 1.0 self.doneButton.alpha = 1.0
self.doneButton.isUserInteractionEnabled = true self.doneButton.isUserInteractionEnabled = true
self.textNode.alpha = 0.0
} }
} }
} }
fileprivate func update() {
if let pickerView = self.pickerView {
let largeValue = unitValues[pickerView.selectedRow(inComponent: 0)]
let smallValue = smallUnitValues[pickerView.selectedRow(inComponent: 1)]
var value = largeValue * 1000 + smallValue * 10
if !self.usesMetricSystem() {
value = Int32(Double(value) * 1.60934)
}
self.updated?(value)
}
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
self.updateDoneButtonTitle() self.updateDoneButtonTitle()
self.update()
} }
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if component == 0 { if component == 0 {
return timerValues.count return unitValues.count
} else if component == 1 { } else if component == 1 {
return smallerTimerValues.count return smallUnitValues.count
} else { } else {
return 1 return 1
} }
@ -345,13 +390,13 @@ class LocationDistancePickerScreenNode: ViewControllerTracingNode, UIScrollViewD
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if component == 0 { if component == 0 {
let value = timerValues[row] let value = unitValues[row]
return "\(value)" return "\(value)"
} else if component == 1 { } else if component == 1 {
let value = String(format: "%.2d", smallerTimerValues[row]) let value = String(format: "%.2d", smallUnitValues[row])
return ".\(value)" return ".\(value)"
} else { } else {
return "MI" return self.usesMetricSystem() ? self.presentationData.strings.Location_ProximityNotification_DistanceKM : self.presentationData.strings.Location_ProximityNotification_DistanceMI
} }
} }
@ -490,7 +535,11 @@ class LocationDistancePickerScreenNode: ViewControllerTracingNode, UIScrollViewD
let buttonInset: CGFloat = 16.0 let buttonInset: CGFloat = 16.0
let doneButtonHeight = self.doneButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition) 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)) let doneButtonFrame = CGRect(x: buttonInset, y: contentHeight - doneButtonHeight - insets.bottom - 16.0 - buttonOffset, width: contentFrame.width, height: doneButtonHeight)
transition.updateFrame(node: self.doneButton, frame: doneButtonFrame)
let textSize = self.textNode.updateLayout(CGSize(width: width, height: titleHeight))
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((width - textSize.width) / 2.0), y: floor(doneButtonFrame.center.y - textSize.height / 2.0)), size: textSize))
self.pickerView?.frame = CGRect(origin: CGPoint(x: 0.0, y: 54.0), size: CGSize(width: contentFrame.width, height: pickerHeight)) self.pickerView?.frame = CGRect(origin: CGPoint(x: 0.0, y: 54.0), size: CGSize(width: contentFrame.width, height: pickerHeight))

View File

@ -233,7 +233,6 @@ final class LocationInfoListItemNode: ListViewItemNode {
subtitleNode.frame = subtitleFrame subtitleNode.frame = subtitleFrame
let separatorHeight = UIScreenPixel let separatorHeight = UIScreenPixel
let topHighlightInset: CGFloat = separatorHeight
let iconNodeFrame = CGRect(origin: CGPoint(x: params.leftInset + inset, y: 10.0), size: CGSize(width: iconSize, height: iconSize)) let iconNodeFrame = CGRect(origin: CGPoint(x: params.leftInset + inset, y: 10.0), size: CGSize(width: iconSize, height: iconSize))
strongSelf.venueIconNode.frame = iconNodeFrame strongSelf.venueIconNode.frame = iconNodeFrame

View File

@ -39,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: (CLLocationCoordinate2D, Bool) -> Void private let setupProximityNotification: (Bool) -> Void
private var displayingPlacesButton = false private var displayingPlacesButton = false
private var proximityNotification: Bool? private var proximityNotification: Bool?
@ -57,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 (CLLocationCoordinate2D, Bool) -> Void = { _, _ in }, showPlacesInThisArea: @escaping () -> Void = {}) { init(presentationData: PresentationData, toggleMapModeSelection: @escaping () -> Void, goToUserLocation: @escaping () -> Void, setupProximityNotification: @escaping (Bool) -> Void = { _ in }, showPlacesInThisArea: @escaping () -> Void = {}) {
self.presentationData = presentationData self.presentationData = presentationData
self.toggleMapModeSelection = toggleMapModeSelection self.toggleMapModeSelection = toggleMapModeSelection
self.goToUserLocation = goToUserLocation self.goToUserLocation = goToUserLocation
@ -219,8 +219,8 @@ final class LocationMapHeaderNode: ASDisplayNode {
} }
@objc private func notificationPressed() { @objc private func notificationPressed() {
if let proximityNotification = self.proximityNotification, let location = self.mapNode.currentUserLocation { if let proximityNotification = self.proximityNotification {
self.setupProximityNotification(location.coordinate, proximityNotification) self.setupProximityNotification(proximityNotification)
} }
} }

View File

@ -68,21 +68,26 @@ private class LocationMapView: MKMapView, UIGestureRecognizerDelegate {
} }
} }
private let arrowImageSize = CGSize(width: 90.0, height: 90.0)
func generateHeadingArrowImage() -> UIImage? { func generateHeadingArrowImage() -> UIImage? {
return generateImage(CGSize(width: 88.0, height: 88.0), contextGenerator: { size, context in return generateImage(arrowImageSize, 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)) let center = CGPoint(x: arrowImageSize.width / 2.0, y: arrowImageSize.height / 2.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.move(to: center)
context.addArc(center: center, radius: arrowImageSize.width / 2.0, startAngle: CGFloat.pi / 2.0 + CGFloat.pi / 8.0, endAngle: CGFloat.pi / 2.0 - CGFloat.pi / 8.0, clockwise: true)
context.clip() context.clip()
var locations: [CGFloat] = [0.0, 0.5, 1.0] var locations: [CGFloat] = [0.0, 0.4, 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 colors: [CGColor] = [UIColor(rgb: 0x007ee5, alpha: 0.5).cgColor, UIColor(rgb: 0x007ee5, alpha: 0.3).cgColor, UIColor(rgb: 0x007ee5, alpha: 0.0).cgColor]
let colorSpace = CGColorSpaceCreateDeviceRGB() let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)! let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: 0.0, y: bounds.size.height), options: CGGradientDrawingOptions()) context.drawRadialGradient(gradient, startCenter: center, startRadius: 11.0, endCenter: center, endRadius: arrowImageSize.width / 2.0, options: .drawsAfterEndLocation)
context.setBlendMode(.clear)
context.fillEllipse(in: CGRect(x: (arrowImageSize.width - 22.0) / 2.0, y: (arrowImageSize.height - 22.0) / 2.0, width: 22.0, height: 22.0))
}) })
} }
@ -98,6 +103,86 @@ private func generateProximityDim(size: CGSize, rect: CGRect) -> UIImage {
} }
final class LocationMapNode: ASDisplayNode, MKMapViewDelegate { final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
class ProximityCircleRenderer: MKCircleRenderer {
override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {
super.draw(mapRect, zoomScale: zoomScale, in: context)
context.saveGState()
let mapBoundingRect = self.circle.boundingMapRect
let mapPoint = MKMapPoint(x: mapBoundingRect.midX, y: mapBoundingRect.maxY)
let drawingPoint = point(for: mapPoint)
if let image = generateTintedImage(image: UIImage(bundleImageName: "Location/ProximityIcon"), color: self.strokeColor ?? .black) {
let imageSize = CGSize(width: floor(image.size.width / zoomScale * self.contentScaleFactor * 0.75), height: floor(image.size.height / zoomScale * self.contentScaleFactor * 0.75))
context.translateBy(x: drawingPoint.x, y: drawingPoint.y)
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: -drawingPoint.x, y: -drawingPoint.y)
let imageRect = CGRect(x: floor((drawingPoint.x - imageSize.width / 2.0)), y: floor((drawingPoint.y - imageSize.height / 2.0)), width: imageSize.width, height: imageSize.height)
context.clear(imageRect)
context.draw(image.cgImage!, in: imageRect)
}
context.restoreGState()
}
}
class InvertedProximityCircle: NSObject, MKOverlay {
var coordinate: CLLocationCoordinate2D
var radius: Double
var alpha: CGFloat {
didSet {
self.alphaTransition = (oldValue, CACurrentMediaTime(), 0.3)
}
}
var alphaTransition: (from: CGFloat, startTimestamp: Double, duration: Double)?
var boundingMapRect: MKMapRect {
return MKMapRect.world
}
init(center coord: CLLocationCoordinate2D, radius: Double, alpha: CGFloat = 0.0) {
self.coordinate = coord
self.radius = radius
self.alpha = alpha
}
}
class InvertedProximityCircleRenderer: MKOverlayRenderer {
var radius: Double = 0.0
var fillColor: UIColor = UIColor(rgb: 0x000000, alpha: 0.4)
override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {
guard let overlay = self.overlay as? InvertedProximityCircle else {
return
}
var alpha: CGFloat = overlay.alpha
if let transition = overlay.alphaTransition {
var t = (CACurrentMediaTime() - transition.startTimestamp) / transition.duration
t = min(1.0, max(0.0, t))
alpha = transition.from + (alpha - transition.from) * CGFloat(t)
}
context.setAlpha(alpha)
let path = UIBezierPath(rect: CGRect(x: mapRect.origin.x, y: mapRect.origin.y, width: mapRect.size.width, height: mapRect.size.height))
let radiusInMap = overlay.radius * MKMapPointsPerMeterAtLatitude(overlay.coordinate.latitude)
let mapSize: MKMapSize = MKMapSize(width: radiusInMap, height: radiusInMap)
let regionOrigin = MKMapPoint(overlay.coordinate)
var regionRect: MKMapRect = MKMapRect(origin: regionOrigin, size: mapSize)
regionRect = regionRect.offsetBy(dx: -radiusInMap / 2.0, dy: -radiusInMap / 2.0);
regionRect = regionRect.intersection(MKMapRect.world);
let excludePath: UIBezierPath = UIBezierPath(roundedRect: CGRect(x: regionRect.origin.x, y: regionRect.origin.y, width: regionRect.size.width, height: regionRect.size.height), cornerRadius: CGFloat(regionRect.size.width) / 2.0)
path.append(excludePath)
context.setFillColor(fillColor.cgColor);
context.addPath(path.cgPath);
context.fillPath(using: .evenOdd)
}
}
private weak var currentInvertedCircleRenderer: InvertedProximityCircleRenderer?
private let locationPromise = Promise<CLLocation?>(nil) private let locationPromise = Promise<CLLocation?>(nil)
private let pickerAnnotationContainerView: PickerAnnotationContainerView private let pickerAnnotationContainerView: PickerAnnotationContainerView
@ -119,37 +204,96 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
var annotationSelected: ((LocationPinAnnotation?) -> Void)? var annotationSelected: ((LocationPinAnnotation?) -> Void)?
var userLocationAnnotationSelected: (() -> Void)? var userLocationAnnotationSelected: (() -> Void)?
var proximityDimView = UIImageView() var indicatorOverlay: InvertedProximityCircle?
var proximityRadius: Double? { var proximityIndicatorRadius: Double? {
didSet { didSet {
if let radius = self.proximityRadius, let mapView = self.mapView { if let activeProximityRadius = self.proximityIndicatorRadius {
let region = MKCoordinateRegion(center: mapView.userLocation.coordinate, latitudinalMeters: radius * 2.0, longitudinalMeters: radius * 2.0) if let location = self.currentUserLocation, activeProximityRadius != oldValue {
let rect = mapView.convert(region, toRectTo: mapView) let indicatorOverlay: InvertedProximityCircle
if proximityDimView.image == nil { if let current = self.indicatorOverlay {
proximityDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) indicatorOverlay = current
} indicatorOverlay.radius = activeProximityRadius
self.mapView?.removeOverlay(indicatorOverlay)
if oldValue == 0 { self.mapView?.addOverlay(indicatorOverlay)
UIView.transition(with: proximityDimView, duration: 0.2, options: .transitionCrossDissolve) { } else {
self.proximityDimView.image = generateProximityDim(size: mapView.bounds.size, rect: rect) indicatorOverlay = InvertedProximityCircle(center: location.coordinate, radius: activeProximityRadius)
} completion: { _ in self.mapView?.addOverlay(indicatorOverlay)
indicatorOverlay.alpha = 1.0
self.updateAnimations()
} }
} else { self.indicatorOverlay = indicatorOverlay
proximityDimView.image = generateProximityDim(size: mapView.bounds.size, rect: rect)
} }
} else { } else {
if proximityDimView.image != nil { if let indicatorOverlay = self.indicatorOverlay {
UIView.transition(with: proximityDimView, duration: 0.2, options: .transitionCrossDissolve) { indicatorOverlay.alpha = 0.0
self.proximityDimView.image = nil self.updateAnimations()
} completion: { _ in
}
} }
} }
} }
} }
private var animator: ConstantDisplayLinkAnimator?
private func updateAnimations() {
guard let mapView = self.mapView else {
return
}
var animate = false
let timestamp = CACurrentMediaTime()
if let indicatorOverlay = self.indicatorOverlay, let transition = indicatorOverlay.alphaTransition {
if transition.startTimestamp + transition.duration < timestamp {
indicatorOverlay.alphaTransition = nil
if indicatorOverlay.alpha.isZero {
self.indicatorOverlay = nil
mapView.removeOverlay(indicatorOverlay)
}
} else {
animate = true
}
}
if animate {
let animator: ConstantDisplayLinkAnimator
if let current = self.animator {
animator = current
} else {
animator = ConstantDisplayLinkAnimator(update: { [weak self] in
self?.updateAnimations()
})
animator.frameInterval = 2
self.animator = animator
}
animator.isPaused = false
} else {
self.animator?.isPaused = true
}
self.currentInvertedCircleRenderer?.setNeedsDisplay()
}
private 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)
}
}
}
}
override init() { override init() {
self.pickerAnnotationContainerView = PickerAnnotationContainerView() self.pickerAnnotationContainerView = PickerAnnotationContainerView()
self.pickerAnnotationContainerView.isHidden = true self.pickerAnnotationContainerView.isHidden = true
@ -192,8 +336,6 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
return false return false
} }
self.mapView?.addSubview(self.proximityDimView)
self.view.addSubview(self.pickerAnnotationContainerView) self.view.addSubview(self.pickerAnnotationContainerView)
} }
@ -345,47 +487,55 @@ 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 { func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if let circle = overlay as? MKCircle { if let invertedCircle = overlay as? InvertedProximityCircle {
let renderer = MKCircleRenderer(circle: circle) let renderer = InvertedProximityCircleRenderer(overlay: invertedCircle)
self.currentInvertedCircleRenderer = renderer
return renderer
} else if let circle = overlay as? MKCircle {
let renderer = ProximityCircleRenderer(circle: circle)
renderer.fillColor = .clear renderer.fillColor = .clear
renderer.strokeColor = UIColor(rgb: 0xc3baaf) renderer.strokeColor = UIColor(rgb: 0xc3baaf)
renderer.lineWidth = 1.0 renderer.lineWidth = 0.75
renderer.lineDashPattern = [5, 3] renderer.lineDashPattern = [5, 4]
return renderer return renderer
} else { } else {
return MKOverlayRenderer() return MKOverlayRenderer()
} }
} }
var distance: Double? { func mapView(_ mapView: MKMapView, didAdd renderers: [MKOverlayRenderer]) {
if let annotation = self.annotations.first, let location = self.currentUserLocation { for renderer in renderers {
return location.distance(from: CLLocation(latitude: annotation.coordinate.latitude, longitude: annotation.coordinate.longitude)) if let renderer = renderer as? InvertedProximityCircleRenderer {
renderer.alpha = 0.0
UIView.animate(withDuration: 0.3) {
renderer.alpha = 1.0
}
}
}
}
var distancesToAllAnnotations: Signal<[Double], NoError> {
let poll = Signal<[LocationPinAnnotation], NoError> { [weak self] subscriber in
if let strongSelf = self {
subscriber.putNext(strongSelf.annotations)
}
subscriber.putCompletion()
return EmptyDisposable
}
let annotationsPoll = (poll |> then(.complete() |> delay(3.0, queue: Queue.concurrentDefaultQueue()))) |> restart
return combineLatest(self.userLocation, annotationsPoll)
|> map { userLocation, annotations -> [Double] in
var distances: [Double] = []
if let userLocation = userLocation {
for annotation in annotations {
distances.append(userLocation.distance(from: CLLocation(latitude: annotation.coordinate.latitude, longitude: annotation.coordinate.longitude)))
}
}
return distances.sorted()
} }
return nil
} }
var currentUserLocation: CLLocation? { var currentUserLocation: CLLocation? {
@ -567,7 +717,6 @@ 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

@ -31,10 +31,7 @@ final class LocationOptionsNode: ASDisplayNode {
self.addSubnode(self.separatorNode) self.addSubnode(self.separatorNode)
self.addSubnode(self.segmentedControlNode) self.addSubnode(self.segmentedControlNode)
self.segmentedControlNode.selectedIndexChanged = { [weak self] index in self.segmentedControlNode.selectedIndexChanged = { index in
guard let strongSelf = self else {
return
}
switch index { switch index {
case 0: case 0:
updateMapMode(.map) updateMapMode(.map)

View File

@ -12,6 +12,7 @@ import ItemListUI
import ItemListVenueItem import ItemListVenueItem
import ActivityIndicator import ActivityIndicator
import TelegramPresentationData import TelegramPresentationData
import TelegramStringFormatting
import AccountContext import AccountContext
import AppBundle import AppBundle
import CoreLocation import CoreLocation

View File

@ -7,6 +7,7 @@ import Postbox
import TelegramCore import TelegramCore
import SyncCore import SyncCore
import TelegramPresentationData import TelegramPresentationData
import TelegramStringFormatting
import TelegramUIPreferences import TelegramUIPreferences
import MergeLists import MergeLists
import AccountContext import AccountContext

View File

@ -70,24 +70,6 @@ public func nearbyVenues(account: Account, latitude: Double, longitude: Double,
} }
} }
private var sharedDistanceFormatter: MKDistanceFormatter?
func stringForDistance(strings: PresentationStrings, distance: CLLocationDistance) -> String {
let distanceFormatter: MKDistanceFormatter
if let currentDistanceFormatter = sharedDistanceFormatter {
distanceFormatter = currentDistanceFormatter
} else {
distanceFormatter = MKDistanceFormatter()
distanceFormatter.unitStyle = .full
sharedDistanceFormatter = distanceFormatter
}
let locale = localeWithStrings(strings)
if distanceFormatter.locale != locale {
distanceFormatter.locale = locale
}
return distanceFormatter.string(fromDistance: distance)
}
func stringForEstimatedDuration(strings: PresentationStrings, eta: Double) -> String? { func stringForEstimatedDuration(strings: PresentationStrings, eta: Double) -> String? {
if eta > 0.0 && eta < 60.0 * 60.0 * 10.0 { if eta > 0.0 && eta < 60.0 * 60.0 * 10.0 {
let eta = max(eta, 60.0) let eta = max(eta, 60.0)

View File

@ -42,13 +42,13 @@ class LocationViewInteraction {
let goToCoordinate: (CLLocationCoordinate2D) -> Void let goToCoordinate: (CLLocationCoordinate2D) -> Void
let requestDirections: () -> Void let requestDirections: () -> Void
let share: () -> Void let share: () -> Void
let setupProximityNotification: (CLLocationCoordinate2D, Bool) -> Void let setupProximityNotification: (Bool, MessageId?) -> Void
let updateSendActionHighlight: (Bool) -> Void let updateSendActionHighlight: (Bool) -> Void
let sendLiveLocation: (CLLocationCoordinate2D) -> Void let sendLiveLocation: (CLLocationCoordinate2D, Int32?) -> Void
let stopLiveLocation: () -> Void let stopLiveLocation: () -> Void
let updateRightBarButton: (LocationViewRightBarButton) -> Void let updateRightBarButton: (LocationViewRightBarButton) -> Void
init(toggleMapModeSelection: @escaping () -> Void, updateMapMode: @escaping (LocationMapMode) -> Void, goToUserLocation: @escaping () -> Void, goToCoordinate: @escaping (CLLocationCoordinate2D) -> Void, requestDirections: @escaping () -> Void, share: @escaping () -> Void, setupProximityNotification: @escaping (CLLocationCoordinate2D, Bool) -> Void, updateSendActionHighlight: @escaping (Bool) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D) -> Void, stopLiveLocation: @escaping () -> Void, updateRightBarButton: @escaping (LocationViewRightBarButton) -> Void) { init(toggleMapModeSelection: @escaping () -> Void, updateMapMode: @escaping (LocationMapMode) -> Void, goToUserLocation: @escaping () -> Void, goToCoordinate: @escaping (CLLocationCoordinate2D) -> Void, requestDirections: @escaping () -> Void, share: @escaping () -> Void, setupProximityNotification: @escaping (Bool, MessageId?) -> Void, updateSendActionHighlight: @escaping (Bool) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D, Int32?) -> Void, stopLiveLocation: @escaping () -> Void, updateRightBarButton: @escaping (LocationViewRightBarButton) -> Void) {
self.toggleMapModeSelection = toggleMapModeSelection self.toggleMapModeSelection = toggleMapModeSelection
self.updateMapMode = updateMapMode self.updateMapMode = updateMapMode
self.goToUserLocation = goToUserLocation self.goToUserLocation = goToUserLocation
@ -166,18 +166,23 @@ public final class LocationViewController: ViewController {
}) })
strongSelf.present(OpenInActionSheetController(context: context, item: .location(location: location, withDirections: false), additionalAction: shareAction, openUrl: params.openUrl), in: .window(.root), with: nil) strongSelf.present(OpenInActionSheetController(context: context, item: .location(location: location, withDirections: false), additionalAction: shareAction, openUrl: params.openUrl), in: .window(.root), with: nil)
} }
}, setupProximityNotification: { [weak self] coordinate, reset in }, setupProximityNotification: { [weak self] reset, messageId in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
if reset { if reset {
strongSelf.controllerNode.setProximityRadius(radius: nil) strongSelf.controllerNode.updateState { state -> LocationViewState in
var state = state
state.proximityRadius = nil
return state
}
CURRENT_DISTANCE = nil CURRENT_DISTANCE = nil
} else { } else {
strongSelf.controllerNode.setProximityIndicator(radius: 0) strongSelf.controllerNode.setProximityIndicator(radius: 0)
let controller = LocationDistancePickerScreen(context: context, style: .default, currentDistance: strongSelf.controllerNode.headerNode.mapNode.distance, updated: { [weak self] distance in let controller = LocationDistancePickerScreen(context: context, style: .default, distances: strongSelf.controllerNode.headerNode.mapNode.distancesToAllAnnotations, updated: { [weak self] distance in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
@ -186,13 +191,25 @@ public final class LocationViewController: ViewController {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
strongSelf.controllerNode.setProximityIndicator(radius: nil)
if let distance = distance { if let messageId = messageId {
strongSelf.controllerNode.setProximityRadius(radius: distance) if let distance = distance {
strongSelf.controllerNode.updateState { state -> LocationViewState in
var state = state
state.proximityRadius = Double(distance)
return state
}
let _ = requestProximityNotification(postbox: context.account.postbox, network: context.account.network, messageId: messageId, distance: distance).start()
CURRENT_DISTANCE = Double(distance)
}
} else {
let _ = requestProximityNotification(postbox: context.account.postbox, network: context.account.network, messageId: subject.id, distance: distance, coordinate: (coordinate.latitude, longitude: coordinate.longitude, accuracyRadius: nil)).start() }
}, willDismiss: { [weak self] in
CURRENT_DISTANCE = Double(distance) if let strongSelf = self {
strongSelf.controllerNode.setProximityIndicator(radius: nil)
} }
}) })
strongSelf.present(controller, in: .window(.root)) strongSelf.present(controller, in: .window(.root))
@ -202,7 +219,7 @@ public final class LocationViewController: ViewController {
return return
} }
strongSelf.controllerNode.updateSendActionHighlight(highlighted) strongSelf.controllerNode.updateSendActionHighlight(highlighted)
}, sendLiveLocation: { [weak self] coordinate in }, sendLiveLocation: { [weak self] coordinate, distance in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }

View File

@ -10,11 +10,11 @@ import MergeLists
import ItemListUI import ItemListUI
import ItemListVenueItem import ItemListVenueItem
import TelegramPresentationData import TelegramPresentationData
import TelegramStringFormatting
import AccountContext import AccountContext
import AppBundle import AppBundle
import CoreLocation import CoreLocation
import Geocoding import Geocoding
import TelegramStringFormatting
func getLocation(from message: Message) -> TelegramMediaMap? { func getLocation(from message: Message) -> TelegramMediaMap? {
return message.media.first(where: { $0 is TelegramMediaMap } ) as? TelegramMediaMap return message.media.first(where: { $0 is TelegramMediaMap } ) as? TelegramMediaMap
@ -143,12 +143,12 @@ private enum LocationViewEntry: Comparable, Identifiable {
if beginTimeAndTimeout != nil { if beginTimeAndTimeout != nil {
interaction?.stopLiveLocation() interaction?.stopLiveLocation()
} else if let coordinate = coordinate { } else if let coordinate = coordinate {
interaction?.sendLiveLocation(coordinate) interaction?.sendLiveLocation(coordinate, nil)
} }
}, highlighted: { highlight in }, highlighted: { highlight in
interaction?.updateSendActionHighlight(highlight) interaction?.updateSendActionHighlight(highlight)
}) })
case let .liveLocation(theme, message, distance, _): case let .liveLocation(_, message, distance, _):
let distanceString: String? let distanceString: String?
if let distance = distance { if let distance = distance {
distanceString = distance < 10 ? presentationData.strings.Map_YouAreHere : presentationData.strings.Map_DistanceAway(stringForDistance(strings: presentationData.strings, distance: distance)).0 distanceString = distance < 10 ? presentationData.strings.Map_YouAreHere : presentationData.strings.Map_DistanceAway(stringForDistance(strings: presentationData.strings, distance: distance)).0
@ -227,7 +227,10 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
self.listNode.verticalScrollIndicatorColor = UIColor(white: 0.0, alpha: 0.3) self.listNode.verticalScrollIndicatorColor = UIColor(white: 0.0, alpha: 0.3)
self.listNode.verticalScrollIndicatorFollowsOverscroll = true self.listNode.verticalScrollIndicatorFollowsOverscroll = true
self.headerNode = LocationMapHeaderNode(presentationData: presentationData, toggleMapModeSelection: interaction.toggleMapModeSelection, goToUserLocation: interaction.goToUserLocation, setupProximityNotification: interaction.setupProximityNotification) var setupProximityNotificationImpl: ((Bool) -> Void)?
self.headerNode = LocationMapHeaderNode(presentationData: presentationData, toggleMapModeSelection: interaction.toggleMapModeSelection, goToUserLocation: interaction.goToUserLocation, setupProximityNotification: { reset in
setupProximityNotificationImpl?(reset)
})
self.headerNode.mapNode.isRotateEnabled = false self.headerNode.mapNode.isRotateEnabled = false
self.optionsNode = LocationOptionsNode(presentationData: presentationData, updateMapMode: interaction.updateMapMode) self.optionsNode = LocationOptionsNode(presentationData: presentationData, updateMapMode: interaction.updateMapMode)
@ -270,6 +273,21 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
return messages return messages
} }
setupProximityNotificationImpl = { reset in
let _ = (liveLocations
|> take(1)
|> deliverOnMainQueue).start(next: { messages in
var ownMessageId: MessageId?
for message in messages {
if message.localTags.contains(.OutgoingLiveLocation) {
ownMessageId = message.id
break
}
}
interaction.setupProximityNotification(reset, ownMessageId)
})
}
let previousState = Atomic<LocationViewState?>(value: nil) let previousState = Atomic<LocationViewState?>(value: nil)
let previousUserAnnotation = Atomic<LocationPinAnnotation?>(value: nil) let previousUserAnnotation = Atomic<LocationPinAnnotation?>(value: nil)
let previousAnnotations = Atomic<[LocationPinAnnotation]>(value: []) let previousAnnotations = Atomic<[LocationPinAnnotation]>(value: [])
@ -499,13 +517,13 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
} }
private func dequeueTransition() { private func dequeueTransition() {
guard let layout = self.validLayout, let transition = self.enqueuedTransitions.first else { guard let _ = self.validLayout, let transition = self.enqueuedTransitions.first else {
return return
} }
self.enqueuedTransitions.remove(at: 0) self.enqueuedTransitions.remove(at: 0)
let 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: { _ in
}) })
} }
@ -526,10 +544,10 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
} }
} }
self.headerNode.mapNode.proximityRadius = Double(radius) self.headerNode.mapNode.proximityIndicatorRadius = Double(radius)
} else { } else {
self.headerNode.forceIsHidden = false self.headerNode.forceIsHidden = false
self.headerNode.mapNode.proximityRadius = nil self.headerNode.mapNode.proximityIndicatorRadius = nil
self.updateState { state in self.updateState { state in
var state = state var state = state
state.selectedLocation = .user state.selectedLocation = .user
@ -537,15 +555,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
} }
} }
} }
func setProximityRadius(radius: Int32?) {
self.updateState { state in
var state = state
state.proximityRadius = radius.flatMap { Double($0) }
return state
}
}
func showAll() { func showAll() {
self.headerNode.mapNode.showAll() self.headerNode.mapNode.showAll()
} }

View File

@ -309,7 +309,7 @@ public func requestEditLiveLocation(postbox: Postbox, network: Network, stateMan
} }
} }
public func requestProximityNotification(postbox: Postbox, network: Network, messageId: MessageId, distance: Int32, coordinate: (latitude: Double, longitude: Double, accuracyRadius: Int32?)) -> Signal<Void, NoError> { public func requestProximityNotification(postbox: Postbox, network: Network, messageId: MessageId, distance: Int32) -> Signal<Void, NoError> {
return postbox.transaction { transaction -> Api.InputPeer? in return postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer) return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer)
} }

View File

@ -25,3 +25,22 @@ public func shortStringForDistance(strings: PresentationStrings, distance: Int32
} }
return result return result
} }
private var sharedDistanceFormatter: MKDistanceFormatter?
public func stringForDistance(strings: PresentationStrings, distance: CLLocationDistance) -> String {
let distanceFormatter: MKDistanceFormatter
if let currentDistanceFormatter = sharedDistanceFormatter {
distanceFormatter = currentDistanceFormatter
} else {
distanceFormatter = MKDistanceFormatter()
distanceFormatter.unitStyle = .full
sharedDistanceFormatter = distanceFormatter
}
let locale = localeWithStrings(strings)
if distanceFormatter.locale != locale {
distanceFormatter.locale = locale
}
return distanceFormatter.string(fromDistance: distance)
}

View File

@ -443,7 +443,8 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
case .phoneNumberRequest: case .phoneNumberRequest:
attributedString = nil attributedString = nil
case let .geoProximityReached(distance): case let .geoProximityReached(distance):
attributedString = NSAttributedString(string: "\(message.peers[message.id.peerId]?.compactDisplayTitle ?? "") is now within \(distance) m from you", font: titleFont, textColor: primaryTextColor) let distanceString = stringForDistance(strings: strings, distance: Double(distance))
attributedString = addAttributesToStringWithRanges(strings.Notification_ProximityReached(authorName, distanceString), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
case .unknown: case .unknown:
attributedString = nil attributedString = nil
} }

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_mapnotify.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -49,6 +49,7 @@ enum ChatMessageBubbleRelativePosition {
enum NeighbourSpacing { enum NeighbourSpacing {
case `default` case `default`
case condensed case condensed
case overlap
} }
case None(ChatMessageBubbleMergeStatus) case None(ChatMessageBubbleMergeStatus)

View File

@ -42,7 +42,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
var isUnsupportedMedia = false var isUnsupportedMedia = false
var isAction = false var isAction = false
var previousItemIsMusic = false var previousItemIsFile = false
var hasFiles = false var hasFiles = false
outer: for (message, itemAttributes) in item.content { outer: for (message, itemAttributes) in item.content {
@ -53,7 +53,6 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
} }
} }
var isMusic = false
var isFile = false var isFile = false
inner: for media in message.media { inner: for media in message.media {
if let _ = media as? TelegramMediaImage { if let _ = media as? TelegramMediaImage {
@ -64,10 +63,9 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media, neighborSpacing: .default))) result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media, neighborSpacing: .default)))
} else { } else {
var neighborSpacing: ChatMessageBubbleRelativePosition.NeighbourSpacing = .default var neighborSpacing: ChatMessageBubbleRelativePosition.NeighbourSpacing = .default
if previousItemIsMusic { if previousItemIsFile {
neighborSpacing = .condensed neighborSpacing = .overlap
} }
isMusic = file.isMusic
isFile = true isFile = true
hasFiles = true hasFiles = true
result.append((message, ChatMessageFileBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: neighborSpacing))) result.append((message, ChatMessageFileBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: neighborSpacing)))
@ -100,7 +98,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
} else if let _ = media as? TelegramMediaUnsupported { } else if let _ = media as? TelegramMediaUnsupported {
isUnsupportedMedia = true isUnsupportedMedia = true
} }
previousItemIsMusic = isMusic previousItemIsFile = isFile
} }
var messageText = message.text var messageText = message.text
@ -230,18 +228,20 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
func willUpdateIsExtractedToContextPreview(isExtractedToContextPreview: Bool, transition: ContainedViewLayoutTransition) { func willUpdateIsExtractedToContextPreview(isExtractedToContextPreview: Bool, transition: ContainedViewLayoutTransition) {
if isExtractedToContextPreview { if isExtractedToContextPreview {
var offset: CGFloat = 0.0
var type: ChatMessageBackgroundType
if let currentParams = self.currentParams, case .incoming = currentParams.backgroundType {
type = .incoming(.Extracted)
offset += 5.0
} else {
type = .outgoing(.Extracted)
}
if let _ = self.backgroundNode { if let _ = self.backgroundNode {
} else if let currentParams = self.currentParams { } else if let currentParams = self.currentParams {
let backgroundNode = ChatMessageBackground() let backgroundNode = ChatMessageBackground()
backgroundNode.alpha = 0.0 backgroundNode.alpha = 0.0
var type: ChatMessageBackgroundType
if case .incoming = currentParams.backgroundType {
type = .incoming(.Extracted)
} else {
type = .outgoing(.Extracted)
}
backgroundNode.setType(type: type, highlighted: false, graphics: currentParams.graphics, maskMode: false, hasWallpaper: currentParams.presentationData.theme.wallpaper.hasWallpaper, transition: .immediate) backgroundNode.setType(type: type, highlighted: false, graphics: currentParams.graphics, maskMode: false, hasWallpaper: currentParams.presentationData.theme.wallpaper.hasWallpaper, transition: .immediate)
self.sourceNode.contentNode.insertSubnode(backgroundNode, at: 0) self.sourceNode.contentNode.insertSubnode(backgroundNode, at: 0)
self.backgroundNode = backgroundNode self.backgroundNode = backgroundNode
@ -250,8 +250,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
} }
if let currentParams = self.currentParams { if let currentParams = self.currentParams {
self.backgroundNode?.updateLayout(size: currentParams.size, transition: .immediate) var backgroundFrame = CGRect(x: currentParams.contentOrigin.x - offset, y: currentParams.contentOrigin.y, width: currentParams.size.width + offset, height: currentParams.size.height)
self.backgroundNode?.frame = CGRect(origin: currentParams.contentOrigin, size: currentParams.size) self.backgroundNode?.updateLayout(size: backgroundFrame.size, transition: .immediate)
self.backgroundNode?.frame = backgroundFrame
} }
} else if let backgroundNode = self.backgroundNode { } else if let backgroundNode = self.backgroundNode {
self.backgroundNode = nil self.backgroundNode = nil
@ -1603,7 +1604,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
} }
} }
var contentNodePropertiesAndFinalize: [(ChatMessageBubbleContentProperties, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void), UInt32?, Bool?)] = [] var contentNodePropertiesAndFinalize: [(ChatMessageBubbleContentProperties, ChatMessageBubbleContentPosition?, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void), UInt32?, Bool?)] = []
var maxContentWidth: CGFloat = headerSize.width var maxContentWidth: CGFloat = headerSize.width
@ -1719,7 +1720,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
let (_, contentNodeFinalize) = contentNodeLayout(framesAndPositions[mosaicIndex].0.size, .mosaic(position: ChatMessageBubbleContentMosaicPosition(topLeft: topLeft, topRight: topRight, bottomLeft: bottomLeft, bottomRight: bottomRight), wide: position.isWide)) let (_, contentNodeFinalize) = contentNodeLayout(framesAndPositions[mosaicIndex].0.size, .mosaic(position: ChatMessageBubbleContentMosaicPosition(topLeft: topLeft, topRight: topRight, bottomLeft: bottomLeft, bottomRight: bottomRight), wide: position.isWide))
contentNodePropertiesAndFinalize.append((contentNodeProperties, contentNodeFinalize, contentGroupId, itemSelection)) contentNodePropertiesAndFinalize.append((contentNodeProperties, nil, contentNodeFinalize, contentGroupId, itemSelection))
maxContentWidth = max(maxContentWidth, size.width) maxContentWidth = max(maxContentWidth, size.width)
} else { } else {
@ -1763,7 +1764,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
#endif #endif
maxContentWidth = max(maxContentWidth, contentNodeWidth) maxContentWidth = max(maxContentWidth, contentNodeWidth)
contentNodePropertiesAndFinalize.append((contentNodeProperties, contentNodeFinalize, contentGroupId, itemSelection)) contentNodePropertiesAndFinalize.append((contentNodeProperties, contentPosition, contentNodeFinalize, contentGroupId, itemSelection))
} }
} }
@ -1776,9 +1777,13 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
var contentNodesHeight: CGFloat = 0.0 var contentNodesHeight: CGFloat = 0.0
var totalContentNodesHeight: CGFloat = 0.0 var totalContentNodesHeight: CGFloat = 0.0
let smallContainerGroupOverlap: CGFloat = 5.0
let largeContainerGroupOverlap: CGFloat = 14.0
var nextContainerGroupOverlap = smallContainerGroupOverlap
var mosaicStatusOrigin: CGPoint? var mosaicStatusOrigin: CGPoint?
for i in 0 ..< contentNodePropertiesAndFinalize.count { for i in 0 ..< contentNodePropertiesAndFinalize.count {
let (properties, finalize, contentGroupId, itemSelection) = contentNodePropertiesAndFinalize[i] let (properties, _, finalize, contentGroupId, itemSelection) = contentNodePropertiesAndFinalize[i]
if let mosaicRange = mosaicRange, mosaicRange.contains(i), let (framesAndPositions, size) = calculatedGroupFramesAndSize { if let mosaicRange = mosaicRange, mosaicRange.contains(i), let (framesAndPositions, size) = calculatedGroupFramesAndSize {
let mosaicIndex = i - mosaicRange.lowerBound let mosaicIndex = i - mosaicRange.lowerBound
@ -1808,7 +1813,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
if currentContainerGroupId != contentGroupId { if currentContainerGroupId != contentGroupId {
if let containerGroupId = currentContainerGroupId { if let containerGroupId = currentContainerGroupId {
contentContainerNodeFrames.append((containerGroupId, CGRect(x: 0.0, y: totalContentNodesHeight - contentNodesHeight, width: maxContentWidth, height: contentNodesHeight), currentItemSelection)) var overlapOffset: CGFloat = 0.0
if !contentContainerNodeFrames.isEmpty {
overlapOffset = smallContainerGroupOverlap
totalContentNodesHeight -= smallContainerGroupOverlap
}
contentContainerNodeFrames.append((containerGroupId, CGRect(x: 0.0, y: totalContentNodesHeight - contentNodesHeight - overlapOffset, width: maxContentWidth, height: contentNodesHeight), currentItemSelection))
} }
contentNodesHeight = 0.0 contentNodesHeight = 0.0
currentContainerGroupId = contentGroupId currentContainerGroupId = contentGroupId
@ -1822,12 +1832,20 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
totalContentNodesHeight += size.height totalContentNodesHeight += size.height
} }
} }
contentSize.height += totalContentNodesHeight
if let containerGroupId = currentContainerGroupId { if let containerGroupId = currentContainerGroupId {
contentContainerNodeFrames.append((containerGroupId, CGRect(x: 0.0, y: totalContentNodesHeight - contentNodesHeight, width: maxContentWidth, height: contentNodesHeight), currentItemSelection)) var overlapOffset: CGFloat = 0.0
if !contentContainerNodeFrames.isEmpty {
overlapOffset = smallContainerGroupOverlap
}
contentContainerNodeFrames.append((containerGroupId, CGRect(x: 0.0, y: totalContentNodesHeight - contentNodesHeight - overlapOffset, width: maxContentWidth, height: contentNodesHeight), currentItemSelection))
if !overlapOffset.isZero {
totalContentNodesHeight -= smallContainerGroupOverlap
}
} }
contentSize.height += totalContentNodesHeight
var actionButtonsSizeAndApply: (CGSize, (Bool) -> ChatMessageActionButtonsNode)? var actionButtonsSizeAndApply: (CGSize, (Bool) -> ChatMessageActionButtonsNode)?
if let actionButtonsFinalize = actionButtonsFinalize { if let actionButtonsFinalize = actionButtonsFinalize {
actionButtonsSizeAndApply = actionButtonsFinalize(maxContentWidth) actionButtonsSizeAndApply = actionButtonsFinalize(maxContentWidth)

View File

@ -104,9 +104,9 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode {
if case let .linear(_, bottom) = position { if case let .linear(_, bottom) = position {
if case .Neighbour(_, _, .condensed) = bottom { if case .Neighbour(_, _, .condensed) = bottom {
if selectedFile?.isMusic ?? false { if selectedFile?.isMusic ?? false {
// bottomInset -= 14.0 bottomInset -= 14.0
} else { } else {
bottomInset -= 10.0 bottomInset -= 7.0
} }
} }
} }