mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-22 13:34:44 +00:00
Various improvements
This commit is contained in:
parent
fe9610eee9
commit
e45a3c0ee9
@ -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.";
|
||||
|
||||
"Notification.ProximityReached" = "%2$@ is now within %2$@ from you";
|
||||
"Notification.ProximityReached" = "%1$@ is now within %2$@ from you";
|
||||
|
||||
"Location.ProximityNotification.Title" = "Notification";
|
||||
"Location.ProximityNotification.Notify" = "Notify me within %@";
|
||||
"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";
|
||||
|
@ -236,18 +236,7 @@ public func legacyLocationController(message: Message?, mapMedia: TelegramMediaM
|
||||
legacyController?.dismiss()
|
||||
}
|
||||
controller.liveLocationStopped = { [weak legacyController] in
|
||||
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()
|
||||
stopLiveLocation()
|
||||
legacyController?.dismiss()
|
||||
}
|
||||
|
||||
|
@ -231,7 +231,7 @@ class LocationPinAnnotationView: MKAnnotationView {
|
||||
self.setPeer(context: annotation.context, theme: annotation.theme, peer: peer)
|
||||
self.setSelected(true, animated: false)
|
||||
} 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)
|
||||
self.backgroundNode.image = generateTintedImage(image: UIImage(bundleImageName: "Location/PinBackground"), color: color)
|
||||
self.iconNode.setSignal(venueIcon(postbox: annotation.context.account.postbox, type: venueType, background: false))
|
||||
|
@ -9,6 +9,7 @@ import SwiftSignalKit
|
||||
import AccountContext
|
||||
import SolidRoundedButtonNode
|
||||
import TelegramPresentationData
|
||||
import TelegramStringFormatting
|
||||
import PresentationDataUtils
|
||||
import CoreLocation
|
||||
|
||||
@ -26,18 +27,20 @@ final class LocationDistancePickerScreen: ViewController {
|
||||
|
||||
private let context: AccountContext
|
||||
private let style: LocationDistancePickerScreenStyle
|
||||
private let currentDistance: Double?
|
||||
private let distances: Signal<[Double], NoError>
|
||||
private let updated: (Int32?) -> Void
|
||||
private let completion: (Int32?) -> Void
|
||||
private let willDismiss: () -> Void
|
||||
|
||||
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.style = style
|
||||
self.currentDistance = currentDistance
|
||||
self.distances = distances
|
||||
self.updated = updated
|
||||
self.completion = completion
|
||||
self.willDismiss = willDismiss
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
|
||||
@ -64,7 +67,7 @@ final class LocationDistancePickerScreen: ViewController {
|
||||
}
|
||||
|
||||
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
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -79,12 +82,13 @@ final class LocationDistancePickerScreen: ViewController {
|
||||
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()
|
||||
}
|
||||
|
||||
self.controllerNode.update()
|
||||
}
|
||||
|
||||
override public func loadView() {
|
||||
@ -101,6 +105,7 @@ final class LocationDistancePickerScreen: ViewController {
|
||||
}
|
||||
|
||||
override public func dismiss(completion: (() -> Void)? = nil) {
|
||||
self.willDismiss()
|
||||
self.controllerNode.animateOut(completion: completion)
|
||||
}
|
||||
|
||||
@ -145,7 +150,7 @@ private class TimerPickerView: UIPickerView {
|
||||
}
|
||||
}
|
||||
|
||||
private var timerValues: [Int32] = {
|
||||
private var unitValues: [Int32] = {
|
||||
var values: [Int32] = []
|
||||
for i in 0 ..< 99 {
|
||||
values.append(Int32(i))
|
||||
@ -153,7 +158,7 @@ private var timerValues: [Int32] = {
|
||||
return values
|
||||
}()
|
||||
|
||||
private var smallerTimerValues: [Int32] = {
|
||||
private var smallUnitValues: [Int32] = {
|
||||
var values: [Int32] = []
|
||||
for i in 0 ..< 100 {
|
||||
values.append(Int32(i))
|
||||
@ -165,7 +170,7 @@ class LocationDistancePickerScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
private let context: AccountContext
|
||||
private let controllerStyle: LocationDistancePickerScreenStyle
|
||||
private var presentationData: PresentationData
|
||||
private let currentDistance: Double?
|
||||
private var distances: [Double] = []
|
||||
|
||||
private let dimNode: ASDisplayNode
|
||||
private let wrappingScrollNode: ASScrollNode
|
||||
@ -182,17 +187,18 @@ class LocationDistancePickerScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
|
||||
private var containerLayout: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
private var distancesDisposable: Disposable?
|
||||
|
||||
var updated: ((Int32) -> Void)?
|
||||
var completion: ((Int32) -> Void)?
|
||||
var dismiss: (() -> Void)?
|
||||
var cancel: (() -> Void)?
|
||||
|
||||
init(context: AccountContext, style: LocationDistancePickerScreenStyle, currentDistance: Double?) {
|
||||
init(context: AccountContext, style: LocationDistancePickerScreenStyle, distances: Signal<[Double], NoError>) {
|
||||
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
|
||||
@ -232,11 +238,11 @@ class LocationDistancePickerScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
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.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.Location_ProximityNotification_Title, font: Font.bold(17.0), textColor: textColor)
|
||||
|
||||
self.textNode = ImmediateTextNode()
|
||||
self.textNode.alpha = 0.0
|
||||
|
||||
self.cancelButton = HighlightableButtonNode()
|
||||
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 {
|
||||
strongSelf.doneButton.isUserInteractionEnabled = false
|
||||
|
||||
let largeValue = timerValues[pickerView.selectedRow(inComponent: 0)]
|
||||
let smallValue = smallerTimerValues[pickerView.selectedRow(inComponent: 1)]
|
||||
let largeValue = unitValues[pickerView.selectedRow(inComponent: 0)]
|
||||
let smallValue = smallUnitValues[pickerView.selectedRow(inComponent: 1)]
|
||||
var value = largeValue * 1000 + smallValue * 10
|
||||
value = Int32(Double(value) * 1.60934)
|
||||
if !strongSelf.usesMetricSystem() {
|
||||
value = Int32(Double(value) * 1.60934)
|
||||
}
|
||||
strongSelf.completion?(value)
|
||||
}
|
||||
}
|
||||
|
||||
self.setupPickerView()
|
||||
|
||||
Queue.mainQueue().after(0.5) {
|
||||
self.updateDoneButtonTitle()
|
||||
}
|
||||
self.distancesDisposable = (distances
|
||||
|> deliverOnMainQueue).start(next: { [weak self] distances in
|
||||
if let strongSelf = self {
|
||||
strongSelf.distances = distances
|
||||
strongSelf.updateDoneButtonTitle()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.distancesDisposable?.dispose()
|
||||
}
|
||||
|
||||
func setupPickerView() {
|
||||
@ -295,49 +311,78 @@ class LocationDistancePickerScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
pickerView.dataSource = self
|
||||
pickerView.delegate = self
|
||||
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.pickerView = pickerView
|
||||
|
||||
self.updateDoneButtonTitle()
|
||||
}
|
||||
|
||||
private func usesMetricSystem() -> Bool {
|
||||
return localeWithStrings(self.presentationData.strings).usesMetricSystem
|
||||
}
|
||||
|
||||
func numberOfComponents(in pickerView: UIPickerView) -> Int {
|
||||
return 2
|
||||
return 3
|
||||
}
|
||||
|
||||
private func updateDoneButtonTitle() {
|
||||
if let pickerView = self.pickerView {
|
||||
let largeValue = timerValues[pickerView.selectedRow(inComponent: 0)]
|
||||
let smallValue = smallerTimerValues[pickerView.selectedRow(inComponent: 1)]
|
||||
let largeValue = unitValues[pickerView.selectedRow(inComponent: 0)]
|
||||
let smallValue = smallUnitValues[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))
|
||||
if !self.usesMetricSystem() {
|
||||
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)
|
||||
self.doneButton.title = "Notify me within \(distance)"
|
||||
|
||||
if let currentDistance = self.currentDistance, value > Int32(currentDistance) {
|
||||
self.doneButton.alpha = 0.4
|
||||
if let distance = self.distances.first, Double(value) > distance {
|
||||
self.doneButton.alpha = 0.0
|
||||
self.doneButton.isUserInteractionEnabled = false
|
||||
self.textNode.alpha = 1.0
|
||||
} else {
|
||||
self.doneButton.alpha = 1.0
|
||||
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) {
|
||||
self.updateDoneButtonTitle()
|
||||
self.update()
|
||||
}
|
||||
|
||||
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
|
||||
if component == 0 {
|
||||
return timerValues.count
|
||||
return unitValues.count
|
||||
} else if component == 1 {
|
||||
return smallerTimerValues.count
|
||||
return smallUnitValues.count
|
||||
} else {
|
||||
return 1
|
||||
}
|
||||
@ -345,13 +390,13 @@ class LocationDistancePickerScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
|
||||
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
|
||||
if component == 0 {
|
||||
let value = timerValues[row]
|
||||
let value = unitValues[row]
|
||||
return "\(value)"
|
||||
} else if component == 1 {
|
||||
let value = String(format: "%.2d", smallerTimerValues[row])
|
||||
let value = String(format: "%.2d", smallUnitValues[row])
|
||||
return ".\(value)"
|
||||
} 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 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))
|
||||
|
||||
|
@ -233,7 +233,6 @@ final class LocationInfoListItemNode: ListViewItemNode {
|
||||
subtitleNode.frame = subtitleFrame
|
||||
|
||||
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))
|
||||
strongSelf.venueIconNode.frame = iconNodeFrame
|
||||
|
@ -39,7 +39,7 @@ final class LocationMapHeaderNode: ASDisplayNode {
|
||||
private let toggleMapModeSelection: () -> Void
|
||||
private let goToUserLocation: () -> Void
|
||||
private let showPlacesInThisArea: () -> Void
|
||||
private let setupProximityNotification: (CLLocationCoordinate2D, Bool) -> Void
|
||||
private let setupProximityNotification: (Bool) -> Void
|
||||
|
||||
private var displayingPlacesButton = false
|
||||
private var proximityNotification: Bool?
|
||||
@ -57,7 +57,7 @@ final class LocationMapHeaderNode: ASDisplayNode {
|
||||
|
||||
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.toggleMapModeSelection = toggleMapModeSelection
|
||||
self.goToUserLocation = goToUserLocation
|
||||
@ -219,8 +219,8 @@ final class LocationMapHeaderNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
@objc private func notificationPressed() {
|
||||
if let proximityNotification = self.proximityNotification, let location = self.mapNode.currentUserLocation {
|
||||
self.setupProximityNotification(location.coordinate, proximityNotification)
|
||||
if let proximityNotification = self.proximityNotification {
|
||||
self.setupProximityNotification(proximityNotification)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,21 +68,26 @@ private class LocationMapView: MKMapView, UIGestureRecognizerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
private let arrowImageSize = CGSize(width: 90.0, height: 90.0)
|
||||
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)
|
||||
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)
|
||||
let center = CGPoint(x: arrowImageSize.width / 2.0, y: arrowImageSize.height / 2.0)
|
||||
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()
|
||||
|
||||
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]
|
||||
var locations: [CGFloat] = [0.0, 0.4, 1.0]
|
||||
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 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 {
|
||||
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 pickerAnnotationContainerView: PickerAnnotationContainerView
|
||||
@ -119,37 +204,96 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
||||
var annotationSelected: ((LocationPinAnnotation?) -> Void)?
|
||||
var userLocationAnnotationSelected: (() -> Void)?
|
||||
|
||||
var proximityDimView = UIImageView()
|
||||
var proximityRadius: Double? {
|
||||
var indicatorOverlay: InvertedProximityCircle?
|
||||
var proximityIndicatorRadius: 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
|
||||
|
||||
if let activeProximityRadius = self.proximityIndicatorRadius {
|
||||
if let location = self.currentUserLocation, activeProximityRadius != oldValue {
|
||||
let indicatorOverlay: InvertedProximityCircle
|
||||
if let current = self.indicatorOverlay {
|
||||
indicatorOverlay = current
|
||||
indicatorOverlay.radius = activeProximityRadius
|
||||
self.mapView?.removeOverlay(indicatorOverlay)
|
||||
self.mapView?.addOverlay(indicatorOverlay)
|
||||
} else {
|
||||
indicatorOverlay = InvertedProximityCircle(center: location.coordinate, radius: activeProximityRadius)
|
||||
self.mapView?.addOverlay(indicatorOverlay)
|
||||
indicatorOverlay.alpha = 1.0
|
||||
self.updateAnimations()
|
||||
}
|
||||
} else {
|
||||
proximityDimView.image = generateProximityDim(size: mapView.bounds.size, rect: rect)
|
||||
self.indicatorOverlay = indicatorOverlay
|
||||
}
|
||||
} else {
|
||||
if proximityDimView.image != nil {
|
||||
UIView.transition(with: proximityDimView, duration: 0.2, options: .transitionCrossDissolve) {
|
||||
self.proximityDimView.image = nil
|
||||
} completion: { _ in
|
||||
|
||||
}
|
||||
if let indicatorOverlay = self.indicatorOverlay {
|
||||
indicatorOverlay.alpha = 0.0
|
||||
self.updateAnimations()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
self.pickerAnnotationContainerView = PickerAnnotationContainerView()
|
||||
self.pickerAnnotationContainerView.isHidden = true
|
||||
@ -192,8 +336,6 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
self.mapView?.addSubview(self.proximityDimView)
|
||||
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 {
|
||||
if let circle = overlay as? MKCircle {
|
||||
let renderer = MKCircleRenderer(circle: circle)
|
||||
if let invertedCircle = overlay as? InvertedProximityCircle {
|
||||
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.strokeColor = UIColor(rgb: 0xc3baaf)
|
||||
renderer.lineWidth = 1.0
|
||||
renderer.lineDashPattern = [5, 3]
|
||||
renderer.lineWidth = 0.75
|
||||
renderer.lineDashPattern = [5, 4]
|
||||
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))
|
||||
func mapView(_ mapView: MKMapView, didAdd renderers: [MKOverlayRenderer]) {
|
||||
for renderer in renderers {
|
||||
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? {
|
||||
@ -567,7 +717,6 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
||||
}
|
||||
|
||||
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)
|
||||
if let pickerAnnotationView = self.pickerAnnotationView {
|
||||
pickerAnnotationView.center = CGPoint(x: self.pickerAnnotationContainerView.frame.width / 2.0, y: self.pickerAnnotationContainerView.frame.height / 2.0)
|
||||
|
@ -31,10 +31,7 @@ final class LocationOptionsNode: ASDisplayNode {
|
||||
self.addSubnode(self.separatorNode)
|
||||
self.addSubnode(self.segmentedControlNode)
|
||||
|
||||
self.segmentedControlNode.selectedIndexChanged = { [weak self] index in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
self.segmentedControlNode.selectedIndexChanged = { index in
|
||||
switch index {
|
||||
case 0:
|
||||
updateMapMode(.map)
|
||||
|
@ -12,6 +12,7 @@ import ItemListUI
|
||||
import ItemListVenueItem
|
||||
import ActivityIndicator
|
||||
import TelegramPresentationData
|
||||
import TelegramStringFormatting
|
||||
import AccountContext
|
||||
import AppBundle
|
||||
import CoreLocation
|
||||
|
@ -7,6 +7,7 @@ import Postbox
|
||||
import TelegramCore
|
||||
import SyncCore
|
||||
import TelegramPresentationData
|
||||
import TelegramStringFormatting
|
||||
import TelegramUIPreferences
|
||||
import MergeLists
|
||||
import AccountContext
|
||||
|
@ -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? {
|
||||
if eta > 0.0 && eta < 60.0 * 60.0 * 10.0 {
|
||||
let eta = max(eta, 60.0)
|
||||
|
@ -42,13 +42,13 @@ class LocationViewInteraction {
|
||||
let goToCoordinate: (CLLocationCoordinate2D) -> Void
|
||||
let requestDirections: () -> Void
|
||||
let share: () -> Void
|
||||
let setupProximityNotification: (CLLocationCoordinate2D, Bool) -> Void
|
||||
let setupProximityNotification: (Bool, MessageId?) -> Void
|
||||
let updateSendActionHighlight: (Bool) -> Void
|
||||
let sendLiveLocation: (CLLocationCoordinate2D) -> Void
|
||||
let sendLiveLocation: (CLLocationCoordinate2D, Int32?) -> Void
|
||||
let stopLiveLocation: () -> Void
|
||||
let updateRightBarButton: (LocationViewRightBarButton) -> Void
|
||||
|
||||
init(toggleMapModeSelection: @escaping () -> Void, updateMapMode: @escaping (LocationMapMode) -> Void, goToUserLocation: @escaping () -> Void, goToCoordinate: @escaping (CLLocationCoordinate2D) -> Void, requestDirections: @escaping () -> Void, share: @escaping () -> Void, setupProximityNotification: @escaping (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.updateMapMode = updateMapMode
|
||||
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)
|
||||
}
|
||||
}, setupProximityNotification: { [weak self] coordinate, reset in
|
||||
}, setupProximityNotification: { [weak self] reset, messageId in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
if reset {
|
||||
strongSelf.controllerNode.setProximityRadius(radius: nil)
|
||||
strongSelf.controllerNode.updateState { state -> LocationViewState in
|
||||
var state = state
|
||||
state.proximityRadius = nil
|
||||
return state
|
||||
}
|
||||
|
||||
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
|
||||
let controller = LocationDistancePickerScreen(context: context, style: .default, distances: strongSelf.controllerNode.headerNode.mapNode.distancesToAllAnnotations, updated: { [weak self] distance in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -186,13 +191,25 @@ public final class LocationViewController: ViewController {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.controllerNode.setProximityIndicator(radius: nil)
|
||||
if let distance = distance {
|
||||
strongSelf.controllerNode.setProximityRadius(radius: distance)
|
||||
|
||||
if let messageId = messageId {
|
||||
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()
|
||||
|
||||
CURRENT_DISTANCE = Double(distance)
|
||||
}
|
||||
}, willDismiss: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.controllerNode.setProximityIndicator(radius: nil)
|
||||
}
|
||||
})
|
||||
strongSelf.present(controller, in: .window(.root))
|
||||
@ -202,7 +219,7 @@ public final class LocationViewController: ViewController {
|
||||
return
|
||||
}
|
||||
strongSelf.controllerNode.updateSendActionHighlight(highlighted)
|
||||
}, sendLiveLocation: { [weak self] coordinate in
|
||||
}, sendLiveLocation: { [weak self] coordinate, distance in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
@ -10,11 +10,11 @@ import MergeLists
|
||||
import ItemListUI
|
||||
import ItemListVenueItem
|
||||
import TelegramPresentationData
|
||||
import TelegramStringFormatting
|
||||
import AccountContext
|
||||
import AppBundle
|
||||
import CoreLocation
|
||||
import Geocoding
|
||||
import TelegramStringFormatting
|
||||
|
||||
func getLocation(from message: Message) -> TelegramMediaMap? {
|
||||
return message.media.first(where: { $0 is TelegramMediaMap } ) as? TelegramMediaMap
|
||||
@ -143,12 +143,12 @@ private enum LocationViewEntry: Comparable, Identifiable {
|
||||
if beginTimeAndTimeout != nil {
|
||||
interaction?.stopLiveLocation()
|
||||
} else if let coordinate = coordinate {
|
||||
interaction?.sendLiveLocation(coordinate)
|
||||
interaction?.sendLiveLocation(coordinate, nil)
|
||||
}
|
||||
}, highlighted: { highlight in
|
||||
interaction?.updateSendActionHighlight(highlight)
|
||||
})
|
||||
case let .liveLocation(theme, message, distance, _):
|
||||
case let .liveLocation(_, 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
|
||||
@ -227,7 +227,10 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
|
||||
self.listNode.verticalScrollIndicatorColor = UIColor(white: 0.0, alpha: 0.3)
|
||||
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.optionsNode = LocationOptionsNode(presentationData: presentationData, updateMapMode: interaction.updateMapMode)
|
||||
@ -270,6 +273,21 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
|
||||
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 previousUserAnnotation = Atomic<LocationPinAnnotation?>(value: nil)
|
||||
let previousAnnotations = Atomic<[LocationPinAnnotation]>(value: [])
|
||||
@ -499,13 +517,13 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
self.enqueuedTransitions.remove(at: 0)
|
||||
|
||||
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 {
|
||||
self.headerNode.forceIsHidden = false
|
||||
self.headerNode.mapNode.proximityRadius = nil
|
||||
self.headerNode.mapNode.proximityIndicatorRadius = nil
|
||||
self.updateState { state in
|
||||
var state = state
|
||||
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() {
|
||||
self.headerNode.mapNode.showAll()
|
||||
}
|
||||
|
@ -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 transaction.getPeer(messageId.peerId).flatMap(apiInputPeer)
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -25,3 +25,22 @@ public func shortStringForDistance(strings: PresentationStrings, distance: Int32
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
@ -443,7 +443,8 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
||||
case .phoneNumberRequest:
|
||||
attributedString = nil
|
||||
case let .geoProximityReached(distance):
|
||||
attributedString = NSAttributedString(string: "\(message.peers[message.id.peerId]?.compactDisplayTitle ?? "") is now within \(distance) m from you", font: titleFont, textColor: primaryTextColor)
|
||||
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:
|
||||
attributedString = nil
|
||||
}
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Location/ProximityIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Location/ProximityIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_mapnotify.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Location/ProximityIcon.imageset/ic_mapnotify.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Location/ProximityIcon.imageset/ic_mapnotify.pdf
vendored
Normal file
Binary file not shown.
Binary file not shown.
@ -49,6 +49,7 @@ enum ChatMessageBubbleRelativePosition {
|
||||
enum NeighbourSpacing {
|
||||
case `default`
|
||||
case condensed
|
||||
case overlap
|
||||
}
|
||||
|
||||
case None(ChatMessageBubbleMergeStatus)
|
||||
|
@ -42,7 +42,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
||||
var isUnsupportedMedia = false
|
||||
var isAction = false
|
||||
|
||||
var previousItemIsMusic = false
|
||||
var previousItemIsFile = false
|
||||
var hasFiles = false
|
||||
|
||||
outer: for (message, itemAttributes) in item.content {
|
||||
@ -53,7 +53,6 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
||||
}
|
||||
}
|
||||
|
||||
var isMusic = false
|
||||
var isFile = false
|
||||
inner: for media in message.media {
|
||||
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)))
|
||||
} else {
|
||||
var neighborSpacing: ChatMessageBubbleRelativePosition.NeighbourSpacing = .default
|
||||
if previousItemIsMusic {
|
||||
neighborSpacing = .condensed
|
||||
if previousItemIsFile {
|
||||
neighborSpacing = .overlap
|
||||
}
|
||||
isMusic = file.isMusic
|
||||
isFile = true
|
||||
hasFiles = true
|
||||
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 {
|
||||
isUnsupportedMedia = true
|
||||
}
|
||||
previousItemIsMusic = isMusic
|
||||
previousItemIsFile = isFile
|
||||
}
|
||||
|
||||
var messageText = message.text
|
||||
@ -230,18 +228,20 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
|
||||
func willUpdateIsExtractedToContextPreview(isExtractedToContextPreview: Bool, transition: ContainedViewLayoutTransition) {
|
||||
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 {
|
||||
} else if let currentParams = self.currentParams {
|
||||
let backgroundNode = ChatMessageBackground()
|
||||
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)
|
||||
self.sourceNode.contentNode.insertSubnode(backgroundNode, at: 0)
|
||||
self.backgroundNode = backgroundNode
|
||||
@ -250,8 +250,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
}
|
||||
|
||||
if let currentParams = self.currentParams {
|
||||
self.backgroundNode?.updateLayout(size: currentParams.size, transition: .immediate)
|
||||
self.backgroundNode?.frame = CGRect(origin: currentParams.contentOrigin, size: currentParams.size)
|
||||
var backgroundFrame = CGRect(x: currentParams.contentOrigin.x - offset, y: currentParams.contentOrigin.y, width: currentParams.size.width + offset, height: currentParams.size.height)
|
||||
self.backgroundNode?.updateLayout(size: backgroundFrame.size, transition: .immediate)
|
||||
self.backgroundNode?.frame = backgroundFrame
|
||||
}
|
||||
} else if let backgroundNode = self.backgroundNode {
|
||||
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
|
||||
|
||||
@ -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))
|
||||
|
||||
contentNodePropertiesAndFinalize.append((contentNodeProperties, contentNodeFinalize, contentGroupId, itemSelection))
|
||||
contentNodePropertiesAndFinalize.append((contentNodeProperties, nil, contentNodeFinalize, contentGroupId, itemSelection))
|
||||
|
||||
maxContentWidth = max(maxContentWidth, size.width)
|
||||
} else {
|
||||
@ -1763,7 +1764,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
#endif
|
||||
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 totalContentNodesHeight: CGFloat = 0.0
|
||||
|
||||
let smallContainerGroupOverlap: CGFloat = 5.0
|
||||
let largeContainerGroupOverlap: CGFloat = 14.0
|
||||
var nextContainerGroupOverlap = smallContainerGroupOverlap
|
||||
|
||||
var mosaicStatusOrigin: CGPoint?
|
||||
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 {
|
||||
let mosaicIndex = i - mosaicRange.lowerBound
|
||||
@ -1808,7 +1813,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
|
||||
if currentContainerGroupId != contentGroupId {
|
||||
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
|
||||
currentContainerGroupId = contentGroupId
|
||||
@ -1822,12 +1832,20 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
totalContentNodesHeight += size.height
|
||||
}
|
||||
}
|
||||
contentSize.height += totalContentNodesHeight
|
||||
|
||||
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)?
|
||||
if let actionButtonsFinalize = actionButtonsFinalize {
|
||||
actionButtonsSizeAndApply = actionButtonsFinalize(maxContentWidth)
|
||||
|
@ -104,9 +104,9 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
if case let .linear(_, bottom) = position {
|
||||
if case .Neighbour(_, _, .condensed) = bottom {
|
||||
if selectedFile?.isMusic ?? false {
|
||||
// bottomInset -= 14.0
|
||||
bottomInset -= 14.0
|
||||
} else {
|
||||
bottomInset -= 10.0
|
||||
bottomInset -= 7.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user