mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-25 09:32:46 +00:00
Location picker fixes
This commit is contained in:
parent
dcd23387b3
commit
bc4dc9807b
@ -499,4 +499,41 @@ extension DeviceContactAddressData {
|
|||||||
}
|
}
|
||||||
return dictionary
|
return dictionary
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var string: String {
|
||||||
|
var array: [String] = []
|
||||||
|
if !self.street1.isEmpty {
|
||||||
|
array.append(self.street1)
|
||||||
|
}
|
||||||
|
if !self.city.isEmpty {
|
||||||
|
array.append(self.city)
|
||||||
|
}
|
||||||
|
if !self.state.isEmpty {
|
||||||
|
array.append(self.state)
|
||||||
|
}
|
||||||
|
if !self.country.isEmpty {
|
||||||
|
array.append(self.country)
|
||||||
|
}
|
||||||
|
if !self.postcode.isEmpty {
|
||||||
|
array.append(self.postcode)
|
||||||
|
}
|
||||||
|
return array.joined(separator: " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
public var displayString: String {
|
||||||
|
var array: [String] = []
|
||||||
|
if !self.street1.isEmpty {
|
||||||
|
array.append(self.street1)
|
||||||
|
}
|
||||||
|
if !self.city.isEmpty {
|
||||||
|
array.append(self.city)
|
||||||
|
}
|
||||||
|
if !self.state.isEmpty {
|
||||||
|
array.append(self.state)
|
||||||
|
}
|
||||||
|
if !self.country.isEmpty {
|
||||||
|
array.append(self.country)
|
||||||
|
}
|
||||||
|
return array.joined(separator: ", ")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -174,8 +174,8 @@ public func venueIcon(postbox: Postbox, type: String, background: Bool) -> Signa
|
|||||||
c.setFillColor(backgroundColor.cgColor)
|
c.setFillColor(backgroundColor.cgColor)
|
||||||
c.fillEllipse(in: CGRect(origin: CGPoint(), size: arguments.drawingRect.size))
|
c.fillEllipse(in: CGRect(origin: CGPoint(), size: arguments.drawingRect.size))
|
||||||
}
|
}
|
||||||
|
let boundsSize = CGSize(width: arguments.drawingRect.size.width - 4.0 * 2.0, height: arguments.drawingRect.size.height - 4.0 * 2.0)
|
||||||
if let image = iconImage, let cgImage = generateTintedImage(image: image, color: foregroundColor)?.cgImage {
|
if let image = iconImage, let cgImage = generateTintedImage(image: image, color: foregroundColor)?.cgImage {
|
||||||
let boundsSize = CGSize(width: arguments.drawingRect.size.width - 4.0 * 2.0, height: arguments.drawingRect.size.height - 4.0 * 2.0)
|
|
||||||
let fittedSize = image.size.aspectFitted(boundsSize)
|
let fittedSize = image.size.aspectFitted(boundsSize)
|
||||||
c.draw(cgImage, in: CGRect(origin: CGPoint(x: floor((arguments.drawingRect.width - fittedSize.width) / 2.0), y: floor((arguments.drawingRect.height - fittedSize.height) / 2.0)), size: fittedSize))
|
c.draw(cgImage, in: CGRect(origin: CGPoint(x: floor((arguments.drawingRect.width - fittedSize.width) / 2.0), y: floor((arguments.drawingRect.height - fittedSize.height) / 2.0)), size: fittedSize))
|
||||||
} else if isBuiltinIcon {
|
} else if isBuiltinIcon {
|
||||||
@ -191,7 +191,8 @@ public func venueIcon(postbox: Postbox, type: String, background: Bool) -> Signa
|
|||||||
image = nil
|
image = nil
|
||||||
}
|
}
|
||||||
if let image = image, let pinImage = generateTintedImage(image: image, color: foregroundColor), let cgImage = pinImage.cgImage {
|
if let image = image, let pinImage = generateTintedImage(image: image, color: foregroundColor), let cgImage = pinImage.cgImage {
|
||||||
c.draw(cgImage, in: CGRect(origin: CGPoint(x: floor((arguments.drawingRect.width - pinImage.size.width) / 2.0), y: floor((arguments.drawingRect.height - pinImage.size.height) / 2.0)), size: pinImage.size))
|
let fittedSize = image.size.aspectFitted(boundsSize)
|
||||||
|
c.draw(cgImage, in: CGRect(origin: CGPoint(x: floor((arguments.drawingRect.width - fittedSize.width) / 2.0), y: floor((arguments.drawingRect.height - fittedSize.height) / 2.0)), size: fittedSize))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,6 +32,8 @@ static_library(
|
|||||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||||
"//submodules/DeviceAccess:DeviceAccess",
|
"//submodules/DeviceAccess:DeviceAccess",
|
||||||
"//submodules/ChatListSearchItemHeader:ChatListSearchItemHeader",
|
"//submodules/ChatListSearchItemHeader:ChatListSearchItemHeader",
|
||||||
|
"//submodules/PhoneNumberFormat:PhoneNumberFormat",
|
||||||
|
"//submodules/PersistentStringHash:PersistentStringHash",
|
||||||
],
|
],
|
||||||
frameworks = [
|
frameworks = [
|
||||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||||
|
|||||||
73
submodules/LocationUI/Sources/CachedGeocodes.swift
Normal file
73
submodules/LocationUI/Sources/CachedGeocodes.swift
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import SwiftSignalKit
|
||||||
|
import Postbox
|
||||||
|
import TelegramCore
|
||||||
|
import SyncCore
|
||||||
|
import TelegramUIPreferences
|
||||||
|
import PersistentStringHash
|
||||||
|
import AccountContext
|
||||||
|
import Geocoding
|
||||||
|
|
||||||
|
public final class CachedGeocode: PostboxCoding {
|
||||||
|
public let latitude: Double
|
||||||
|
public let longitude: Double
|
||||||
|
|
||||||
|
public init(latitude: Double, longitude: Double) {
|
||||||
|
self.latitude = latitude
|
||||||
|
self.longitude = longitude
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(decoder: PostboxDecoder) {
|
||||||
|
self.latitude = decoder.decodeDoubleForKey("lat", orElse: 0.0)
|
||||||
|
self.longitude = decoder.decodeDoubleForKey("lon", orElse: 0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encode(_ encoder: PostboxEncoder) {
|
||||||
|
encoder.encodeDouble(self.latitude, forKey: "lat")
|
||||||
|
encoder.encodeDouble(self.longitude, forKey: "lon")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func cachedGeocode(postbox: Postbox, address: DeviceContactAddressData) -> Signal<CachedGeocode?, NoError> {
|
||||||
|
return postbox.transaction { transaction -> CachedGeocode? in
|
||||||
|
let key = ValueBoxKey(length: 8)
|
||||||
|
key.setInt64(0, value: Int64(bitPattern: address.string.persistentHashValue))
|
||||||
|
if let entry = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: ApplicationSpecificItemCacheCollectionId.cachedGeocodes, key: key)) as? CachedGeocode {
|
||||||
|
return entry
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let collectionSpec = ItemCacheCollectionSpec(lowWaterItemCount: 10, highWaterItemCount: 20)
|
||||||
|
|
||||||
|
private func updateCachedGeocode(postbox: Postbox, address: DeviceContactAddressData, latitude: Double, longitude: Double) -> Signal<(Double, Double), NoError> {
|
||||||
|
return postbox.transaction { transaction -> (Double, Double) in
|
||||||
|
let key = ValueBoxKey(length: 8)
|
||||||
|
key.setInt64(0, value: Int64(bitPattern: address.string.persistentHashValue))
|
||||||
|
let id = ItemCacheEntryId(collectionId: ApplicationSpecificItemCacheCollectionId.cachedGeocodes, key: key)
|
||||||
|
transaction.putItemCacheEntry(id: id, entry: CachedGeocode(latitude: latitude, longitude: longitude), collectionSpec: collectionSpec)
|
||||||
|
return (latitude, longitude)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func geocodeAddress(postbox: Postbox, address: DeviceContactAddressData) -> Signal<(Double, Double)?, NoError> {
|
||||||
|
return cachedGeocode(postbox: postbox, address: address)
|
||||||
|
|> mapToSignal { cached -> Signal<(Double, Double)?, NoError> in
|
||||||
|
if let cached = cached {
|
||||||
|
return .single((cached.latitude, cached.longitude))
|
||||||
|
} else {
|
||||||
|
return geocodeLocation(dictionary: address.dictionary)
|
||||||
|
|> mapToSignal { coordinate in
|
||||||
|
if let (latitude, longitude) = coordinate {
|
||||||
|
return updateCachedGeocode(postbox: postbox, address: address, latitude: latitude, longitude: longitude)
|
||||||
|
|> map(Optional.init)
|
||||||
|
} else {
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -115,7 +115,7 @@ class LocationPinAnnotationView: MKAnnotationView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.iconNode = TransformImageNode()
|
self.iconNode = TransformImageNode()
|
||||||
self.iconNode.bounds = CGRect(origin: CGPoint(), size: CGSize(width: 64.0, height: 64.0))
|
self.iconNode.bounds = CGRect(origin: CGPoint(), size: CGSize(width: 60.0, height: 60.0))
|
||||||
|
|
||||||
self.smallIconNode = TransformImageNode()
|
self.smallIconNode = TransformImageNode()
|
||||||
self.smallIconNode.frame = CGRect(origin: CGPoint(x: 15.0, y: 15.0), size: CGSize(width: 26.0, height: 26.0))
|
self.smallIconNode.frame = CGRect(origin: CGPoint(x: 15.0, y: 15.0), size: CGSize(width: 26.0, height: 26.0))
|
||||||
@ -140,6 +140,14 @@ class LocationPinAnnotationView: MKAnnotationView {
|
|||||||
self.annotation = annotation
|
self.annotation = annotation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var defaultZPosition: CGFloat {
|
||||||
|
if let annotation = self.annotation as? LocationPinAnnotation, let venueType = annotation.location?.venue?.type, ["home", "work"].contains(venueType) {
|
||||||
|
return -0.5
|
||||||
|
} else {
|
||||||
|
return -1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
@ -386,46 +394,70 @@ class LocationPinAnnotationView: MKAnnotationView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func setCustom(_ custom: Bool, animated: Bool) {
|
func setCustom(_ custom: Bool, animated: Bool) {
|
||||||
|
if let annotation = self.annotation as? LocationPinAnnotation {
|
||||||
|
self.iconNode.setSignal(venueIcon(postbox: annotation.account.postbox, type: "", background: false))
|
||||||
|
}
|
||||||
|
|
||||||
|
if let avatarNode = self.avatarNode {
|
||||||
|
self.backgroundNode.addSubnode(avatarNode)
|
||||||
|
avatarNode.position = CGPoint(x: self.backgroundNode.frame.width / 2.0, y: self.backgroundNode.frame.height / 2.0 - 5.0)
|
||||||
|
}
|
||||||
|
self.shadowNode.position = CGPoint(x: UIScreenPixel, y: -36.0)
|
||||||
|
self.backgroundNode.position = CGPoint(x: self.shadowNode.frame.width / 2.0, y: self.shadowNode.frame.height / 2.0)
|
||||||
|
self.iconNode.position = CGPoint(x: self.shadowNode.frame.width / 2.0, y: self.shadowNode.frame.height / 2.0 - 5.0)
|
||||||
|
|
||||||
|
let transition = {
|
||||||
|
let color: UIColor
|
||||||
|
if custom, let annotation = self.annotation as? LocationPinAnnotation {
|
||||||
|
color = annotation.theme.list.itemAccentColor
|
||||||
|
} else {
|
||||||
|
color = .white
|
||||||
|
}
|
||||||
|
self.backgroundNode.image = generateTintedImage(image: UIImage(bundleImageName: "Location/PinBackground"), color: color)
|
||||||
|
self.avatarNode?.isHidden = custom
|
||||||
|
self.iconNode.isHidden = !custom
|
||||||
|
}
|
||||||
|
|
||||||
|
let completion = {
|
||||||
|
if !custom, let avatarNode = self.avatarNode {
|
||||||
|
self.addSubnode(avatarNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if animated {
|
if animated {
|
||||||
self.animating = true
|
self.animating = true
|
||||||
|
|
||||||
if let annotation = self.annotation as? LocationPinAnnotation {
|
|
||||||
self.iconNode.setSignal(venueIcon(postbox: annotation.account.postbox, type: "", background: false))
|
|
||||||
}
|
|
||||||
|
|
||||||
if let avatarNode = self.avatarNode {
|
|
||||||
self.backgroundNode.addSubnode(avatarNode)
|
|
||||||
avatarNode.position = CGPoint(x: self.backgroundNode.frame.width / 2.0, y: self.backgroundNode.frame.height / 2.0 - 5.0)
|
|
||||||
}
|
|
||||||
self.shadowNode.position = CGPoint(x: UIScreenPixel, y: -36.0)
|
|
||||||
self.backgroundNode.position = CGPoint(x: self.shadowNode.frame.width / 2.0, y: self.shadowNode.frame.height / 2.0)
|
|
||||||
self.iconNode.position = CGPoint(x: self.shadowNode.frame.width / 2.0, y: self.shadowNode.frame.height / 2.0 - 5.0)
|
|
||||||
|
|
||||||
Queue.mainQueue().after(0.01) {
|
Queue.mainQueue().after(0.01) {
|
||||||
UIView.transition(with: self.backgroundNode.view, duration: 0.2, options: [.transitionCrossDissolve, .allowAnimatedContent], animations: {
|
UIView.transition(with: self.backgroundNode.view, duration: 0.2, options: [.transitionCrossDissolve, .allowAnimatedContent], animations: {
|
||||||
let color: UIColor
|
transition()
|
||||||
if custom, let annotation = self.annotation as? LocationPinAnnotation {
|
|
||||||
color = annotation.theme.list.itemAccentColor
|
|
||||||
} else {
|
|
||||||
color = .white
|
|
||||||
}
|
|
||||||
self.backgroundNode.image = generateTintedImage(image: UIImage(bundleImageName: "Location/PinBackground"), color: color)
|
|
||||||
self.avatarNode?.isHidden = custom
|
|
||||||
self.iconNode.isHidden = !custom
|
|
||||||
}) { finished in
|
}) { finished in
|
||||||
if !custom, let avatarNode = self.avatarNode {
|
completion()
|
||||||
self.addSubnode(avatarNode)
|
|
||||||
}
|
|
||||||
self.animating = false
|
self.animating = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.setNeedsLayout()
|
} else {
|
||||||
|
transition()
|
||||||
|
completion()
|
||||||
}
|
}
|
||||||
|
self.setNeedsLayout()
|
||||||
|
|
||||||
self.dotNode.isHidden = !custom
|
self.dotNode.isHidden = !custom
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func animateAppearance() {
|
||||||
|
self.smallNode.transform = CATransform3DMakeScale(0.1, 0.1, 1.0)
|
||||||
|
|
||||||
|
let avatarNodeTransform = self.avatarNode?.transform
|
||||||
|
self.avatarNode?.transform = CATransform3DMakeScale(0.1, 0.1, 1.0)
|
||||||
|
UIView.animate(withDuration: 0.55, delay: 0.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.5, options: [], animations: {
|
||||||
|
self.smallNode.transform = CATransform3DIdentity
|
||||||
|
if let avatarNodeTransform = avatarNodeTransform {
|
||||||
|
self.avatarNode?.transform = avatarNodeTransform
|
||||||
|
}
|
||||||
|
}) { _ in
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override func layoutSubviews() {
|
override func layoutSubviews() {
|
||||||
super.layoutSubviews()
|
super.layoutSubviews()
|
||||||
|
|
||||||
@ -456,10 +488,8 @@ class LocationPinAnnotationView: MKAnnotationView {
|
|||||||
if !self.appeared {
|
if !self.appeared {
|
||||||
self.appeared = true
|
self.appeared = true
|
||||||
|
|
||||||
self.smallNode.transform = CATransform3DMakeScale(0.1, 0.1, 1.0)
|
if let annotation = annotation as? LocationPinAnnotation, annotation.location != nil {
|
||||||
UIView.animate(withDuration: 0.55, delay: 0.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.5, options: [], animations: {
|
self.animateAppearance()
|
||||||
self.smallNode.transform = CATransform3DIdentity
|
|
||||||
}) { _ in
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,6 +33,17 @@ private class PickerAnnotationContainerView: UIView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class LocationMapView: MKMapView, UIGestureRecognizerDelegate {
|
||||||
|
var customHitTest: ((CGPoint) -> Bool)?
|
||||||
|
|
||||||
|
@objc override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
|
if let customHitTest = self.customHitTest, customHitTest(gestureRecognizer.location(in: self)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
||||||
private let locationPromise = Promise<CLLocation?>(nil)
|
private let locationPromise = Promise<CLLocation?>(nil)
|
||||||
|
|
||||||
@ -41,8 +52,8 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
|||||||
|
|
||||||
private let pinDisposable = MetaDisposable()
|
private let pinDisposable = MetaDisposable()
|
||||||
|
|
||||||
private var mapView: MKMapView? {
|
private var mapView: LocationMapView? {
|
||||||
return self.view as? MKMapView
|
return self.view as? LocationMapView
|
||||||
}
|
}
|
||||||
|
|
||||||
var returnedToUserLocation = true
|
var returnedToUserLocation = true
|
||||||
@ -50,7 +61,9 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
|||||||
var isDragging = false
|
var isDragging = false
|
||||||
var beganInteractiveDragging: (() -> Void)?
|
var beganInteractiveDragging: (() -> Void)?
|
||||||
var endedInteractiveDragging: ((CLLocationCoordinate2D) -> Void)?
|
var endedInteractiveDragging: ((CLLocationCoordinate2D) -> Void)?
|
||||||
|
|
||||||
var annotationSelected: ((LocationPinAnnotation?) -> Void)?
|
var annotationSelected: ((LocationPinAnnotation?) -> Void)?
|
||||||
|
var userLocationAnnotationSelected: (() -> Void)?
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
self.pickerAnnotationContainerView = PickerAnnotationContainerView()
|
self.pickerAnnotationContainerView = PickerAnnotationContainerView()
|
||||||
@ -59,7 +72,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
|||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.setViewBlock({
|
self.setViewBlock({
|
||||||
return MKMapView()
|
return LocationMapView()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,6 +91,18 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
|||||||
self.mapView?.isRotateEnabled = self.isRotateEnabled
|
self.mapView?.isRotateEnabled = self.isRotateEnabled
|
||||||
self.mapView?.showsUserLocation = true
|
self.mapView?.showsUserLocation = true
|
||||||
self.mapView?.showsPointsOfInterest = false
|
self.mapView?.showsPointsOfInterest = false
|
||||||
|
self.mapView?.customHitTest = { [weak self] point in
|
||||||
|
guard let strongSelf = self, let annotationView = strongSelf.customUserLocationAnnotationView else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if let annotationRect = annotationView.superview?.convert(annotationView.frame.insetBy(dx: -16.0, dy: -16.0), to: strongSelf.mapView), annotationRect.contains(point) {
|
||||||
|
strongSelf.userLocationAnnotationSelected?()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
self.view.addSubview(self.pickerAnnotationContainerView)
|
self.view.addSubview(self.pickerAnnotationContainerView)
|
||||||
}
|
}
|
||||||
@ -105,9 +130,16 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
|||||||
}
|
}
|
||||||
self.ignoreRegionChanges = false
|
self.ignoreRegionChanges = false
|
||||||
|
|
||||||
if isUserLocation && !self.returnedToUserLocation {
|
if isUserLocation {
|
||||||
self.returnedToUserLocation = true
|
if !self.returnedToUserLocation {
|
||||||
self.pickerAnnotationView?.setRaised(true, animated: true)
|
self.returnedToUserLocation = true
|
||||||
|
self.pickerAnnotationView?.setRaised(true, animated: true)
|
||||||
|
}
|
||||||
|
} else if self.hasPickerAnnotation, let customUserLocationAnnotationView = self.customUserLocationAnnotationView, customUserLocationAnnotationView.isHidden {
|
||||||
|
self.pickerAnnotationContainerView.isHidden = true
|
||||||
|
customUserLocationAnnotationView.setSelected(false, animated: false)
|
||||||
|
customUserLocationAnnotationView.isHidden = false
|
||||||
|
customUserLocationAnnotationView.animateAppearance()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,16 +154,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
|||||||
self.returnedToUserLocation = false
|
self.returnedToUserLocation = false
|
||||||
self.beganInteractiveDragging?()
|
self.beganInteractiveDragging?()
|
||||||
|
|
||||||
if self.hasPickerAnnotation {
|
self.switchToPicking(raise: true, animated: true)
|
||||||
self.customUserLocationAnnotationView?.isHidden = true
|
|
||||||
self.pickerAnnotationContainerView.isHidden = false
|
|
||||||
if let pickerAnnotationView = self.pickerAnnotationView, !pickerAnnotationView.isRaised {
|
|
||||||
pickerAnnotationView.setCustom(true, animated: true)
|
|
||||||
pickerAnnotationView.setRaised(true, animated: true)
|
|
||||||
}
|
|
||||||
self.resetAnnotationSelection()
|
|
||||||
self.resetScheduledPin()
|
|
||||||
}
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -189,7 +212,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
|||||||
view.addSubview(annotationView)
|
view.addSubview(annotationView)
|
||||||
}
|
}
|
||||||
} else if let view = view as? LocationPinAnnotationView {
|
} else if let view = view as? LocationPinAnnotationView {
|
||||||
view.setZPosition(-1.0)
|
view.setZPosition(view.defaultZPosition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -213,7 +236,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
|||||||
func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) {
|
func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) {
|
||||||
if let view = view as? LocationPinAnnotationView {
|
if let view = view as? LocationPinAnnotationView {
|
||||||
Queue.mainQueue().after(0.2) {
|
Queue.mainQueue().after(0.2) {
|
||||||
view.setZPosition(-1.0)
|
view.setZPosition(view.defaultZPosition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,6 +287,23 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func switchToPicking(raise: Bool = false, animated: Bool) {
|
||||||
|
guard self.hasPickerAnnotation else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.customUserLocationAnnotationView?.isHidden = true
|
||||||
|
self.pickerAnnotationContainerView.isHidden = false
|
||||||
|
if let pickerAnnotationView = self.pickerAnnotationView, !pickerAnnotationView.isRaised {
|
||||||
|
pickerAnnotationView.setCustom(true, animated: animated)
|
||||||
|
if raise {
|
||||||
|
pickerAnnotationView.setRaised(true, animated: animated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.resetAnnotationSelection()
|
||||||
|
self.resetScheduledPin()
|
||||||
|
}
|
||||||
|
|
||||||
var customUserLocationAnnotationView: LocationPinAnnotationView? = nil
|
var customUserLocationAnnotationView: LocationPinAnnotationView? = nil
|
||||||
var userLocationAnnotation: LocationPinAnnotation? = nil {
|
var userLocationAnnotation: LocationPinAnnotation? = nil {
|
||||||
didSet {
|
didSet {
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import AccountContext
|
|||||||
import AppBundle
|
import AppBundle
|
||||||
import CoreLocation
|
import CoreLocation
|
||||||
import Geocoding
|
import Geocoding
|
||||||
|
import PhoneNumberFormat
|
||||||
|
|
||||||
private struct LocationPickerTransaction {
|
private struct LocationPickerTransaction {
|
||||||
let deletions: [ListViewDeleteItem]
|
let deletions: [ListViewDeleteItem]
|
||||||
@ -323,13 +324,106 @@ final class LocationPickerControllerNode: ViewControllerTracingNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let personalAddresses = self.context.account.postbox.peerView(id: self.context.account.peerId)
|
||||||
|
|> mapToSignal { view -> Signal<(DeviceContactAddressData?, DeviceContactAddressData?)?, NoError> in
|
||||||
|
if let user = peerViewMainPeer(view) as? TelegramUser, let phoneNumber = user.phone {
|
||||||
|
return ((context.sharedContext.contactDataManager?.basicData() ?? .single([:])) |> take(1))
|
||||||
|
|> mapToSignal { basicData -> Signal<DeviceContactExtendedData?, NoError> in
|
||||||
|
var stableId: String?
|
||||||
|
let queryPhoneNumber = formatPhoneNumber(phoneNumber)
|
||||||
|
outer: for (id, data) in basicData {
|
||||||
|
for phoneNumber in data.phoneNumbers {
|
||||||
|
if formatPhoneNumber(phoneNumber.value) == queryPhoneNumber {
|
||||||
|
stableId = id
|
||||||
|
break outer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let stableId = stableId {
|
||||||
|
return (context.sharedContext.contactDataManager?.extendedData(stableId: stableId) ?? .single(nil))
|
||||||
|
|> take(1)
|
||||||
|
|> map { extendedData -> DeviceContactExtendedData? in
|
||||||
|
return extendedData
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> map { extendedData -> (DeviceContactAddressData?, DeviceContactAddressData?)? in
|
||||||
|
if let extendedData = extendedData {
|
||||||
|
var homeAddress: DeviceContactAddressData?
|
||||||
|
var workAddress: DeviceContactAddressData?
|
||||||
|
for address in extendedData.addresses {
|
||||||
|
if address.label == "_$!<Home>!$_" {
|
||||||
|
homeAddress = address
|
||||||
|
} else if address.label == "_$!<Work>!$_" {
|
||||||
|
workAddress = address
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (homeAddress, workAddress)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let personalVenues: Signal<[TelegramMediaMap]?, NoError> = .single(nil)
|
||||||
|
|> then(
|
||||||
|
personalAddresses
|
||||||
|
|> mapToSignal { homeAndWorkAddresses -> Signal<[TelegramMediaMap]?, NoError> in
|
||||||
|
if let (homeAddress, workAddress) = homeAndWorkAddresses {
|
||||||
|
let home: Signal<(Double, Double)?, NoError>
|
||||||
|
let work: Signal<(Double, Double)?, NoError>
|
||||||
|
if let address = homeAddress {
|
||||||
|
home = geocodeAddress(postbox: context.account.postbox, address: address)
|
||||||
|
} else {
|
||||||
|
home = .single(nil)
|
||||||
|
}
|
||||||
|
if let address = workAddress {
|
||||||
|
work = geocodeAddress(postbox: context.account.postbox, address: address)
|
||||||
|
} else {
|
||||||
|
work = .single(nil)
|
||||||
|
}
|
||||||
|
return combineLatest(home, work)
|
||||||
|
|> map { homeCoordinate, workCoordinate -> [TelegramMediaMap]? in
|
||||||
|
var venues: [TelegramMediaMap] = []
|
||||||
|
if let (latitude, longitude) = homeCoordinate, let address = homeAddress {
|
||||||
|
venues.append(TelegramMediaMap(latitude: latitude, longitude: longitude, geoPlace: nil, venue: MapVenue(title: presentationData.strings.Map_Home, address: address.displayString, provider: nil, id: "home", type: "home"), liveBroadcastingTimeout: nil))
|
||||||
|
}
|
||||||
|
if let (latitude, longitude) = workCoordinate, let address = workAddress {
|
||||||
|
venues.append(TelegramMediaMap(latitude: latitude, longitude: longitude, geoPlace: nil, venue: MapVenue(title: presentationData.strings.Map_Work, address: address.displayString, provider: nil, id: "work", type: "work"), liveBroadcastingTimeout: nil))
|
||||||
|
}
|
||||||
|
return venues
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
let venues: Signal<[TelegramMediaMap]?, NoError> = .single(nil)
|
let venues: Signal<[TelegramMediaMap]?, NoError> = .single(nil)
|
||||||
|> then(
|
|> then(
|
||||||
filteredUserLocation
|
filteredUserLocation
|
||||||
|> mapToSignal { location -> Signal<[TelegramMediaMap]?, NoError> in
|
|> mapToSignal { location -> Signal<[TelegramMediaMap]?, NoError> in
|
||||||
if let location = location, location.horizontalAccuracy > 0 {
|
if let location = location, location.horizontalAccuracy > 0 {
|
||||||
return nearbyVenues(account: context.account, latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
|
return combineLatest(nearbyVenues(account: context.account, latitude: location.coordinate.latitude, longitude: location.coordinate.longitude), personalVenues)
|
||||||
|> map(Optional.init)
|
|> map { nearbyVenues, personalVenues -> [TelegramMediaMap]? in
|
||||||
|
var resultVenues: [TelegramMediaMap] = []
|
||||||
|
if let personalVenues = personalVenues {
|
||||||
|
for venue in personalVenues {
|
||||||
|
let venueLocation = CLLocation(latitude: venue.latitude, longitude: venue.longitude)
|
||||||
|
if venueLocation.distance(from: location) < 500 {
|
||||||
|
resultVenues.append(venue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resultVenues.append(contentsOf: nearbyVenues)
|
||||||
|
return resultVenues
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
}
|
}
|
||||||
@ -436,6 +530,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode {
|
|||||||
}
|
}
|
||||||
if updateMap {
|
if updateMap {
|
||||||
strongSelf.headerNode.mapNode.setMapCenter(coordinate: coordinate, isUserLocation: false, animated: true)
|
strongSelf.headerNode.mapNode.setMapCenter(coordinate: coordinate, isUserLocation: false, animated: true)
|
||||||
|
strongSelf.headerNode.mapNode.switchToPicking(animated: false)
|
||||||
}
|
}
|
||||||
case let .venue(venue):
|
case let .venue(venue):
|
||||||
strongSelf.headerNode.mapNode.setMapCenter(coordinate: venue.coordinate, animated: true)
|
strongSelf.headerNode.mapNode.setMapCenter(coordinate: venue.coordinate, animated: true)
|
||||||
@ -497,7 +592,6 @@ final class LocationPickerControllerNode: ViewControllerTracingNode {
|
|||||||
guard let strongSelf = self, let (layout, navigationBarHeight) = strongSelf.validLayout, strongSelf.listNode.scrollEnabled else {
|
guard let strongSelf = self, let (layout, navigationBarHeight) = strongSelf.validLayout, strongSelf.listNode.scrollEnabled else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let overlap: CGFloat = 6.0
|
let overlap: CGFloat = 6.0
|
||||||
strongSelf.listOffset = max(0.0, offset)
|
strongSelf.listOffset = max(0.0, offset)
|
||||||
let headerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: max(0.0, offset + overlap)))
|
let headerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: max(0.0, offset + overlap)))
|
||||||
@ -510,7 +604,6 @@ final class LocationPickerControllerNode: ViewControllerTracingNode {
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.updateState { state in
|
strongSelf.updateState { state in
|
||||||
var state = state
|
var state = state
|
||||||
state.displayingMapModeOptions = false
|
state.displayingMapModeOptions = false
|
||||||
@ -522,7 +615,6 @@ final class LocationPickerControllerNode: ViewControllerTracingNode {
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.updateState { state in
|
strongSelf.updateState { state in
|
||||||
var state = state
|
var state = state
|
||||||
state.displayingMapModeOptions = false
|
state.displayingMapModeOptions = false
|
||||||
@ -535,7 +627,6 @@ final class LocationPickerControllerNode: ViewControllerTracingNode {
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.updateState { state in
|
strongSelf.updateState { state in
|
||||||
var state = state
|
var state = state
|
||||||
if case .selecting = state.selectedLocation {
|
if case .selecting = state.selectedLocation {
|
||||||
@ -549,7 +640,6 @@ final class LocationPickerControllerNode: ViewControllerTracingNode {
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.updateState { state in
|
strongSelf.updateState { state in
|
||||||
var state = state
|
var state = state
|
||||||
state.displayingMapModeOptions = false
|
state.displayingMapModeOptions = false
|
||||||
@ -557,6 +647,18 @@ final class LocationPickerControllerNode: ViewControllerTracingNode {
|
|||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.headerNode.mapNode.userLocationAnnotationSelected = { [weak self] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.updateState { state in
|
||||||
|
var state = state
|
||||||
|
state.displayingMapModeOptions = false
|
||||||
|
state.selectedLocation = .none
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import WebSearchUI
|
|||||||
import InstantPageCache
|
import InstantPageCache
|
||||||
import SettingsUI
|
import SettingsUI
|
||||||
import WallpaperResources
|
import WallpaperResources
|
||||||
|
import LocationUI
|
||||||
|
|
||||||
private var telegramUIDeclaredEncodables: Void = {
|
private var telegramUIDeclaredEncodables: Void = {
|
||||||
declareEncodable(InAppNotificationSettings.self, f: { InAppNotificationSettings(decoder: $0) })
|
declareEncodable(InAppNotificationSettings.self, f: { InAppNotificationSettings(decoder: $0) })
|
||||||
@ -52,6 +53,7 @@ private var telegramUIDeclaredEncodables: Void = {
|
|||||||
declareEncodable(MediaPlaybackStoredState.self, f: { MediaPlaybackStoredState(decoder: $0) })
|
declareEncodable(MediaPlaybackStoredState.self, f: { MediaPlaybackStoredState(decoder: $0) })
|
||||||
declareEncodable(WebBrowserSettings.self, f: { WebBrowserSettings(decoder: $0) })
|
declareEncodable(WebBrowserSettings.self, f: { WebBrowserSettings(decoder: $0) })
|
||||||
declareEncodable(IntentsSettings.self, f: { IntentsSettings(decoder: $0) })
|
declareEncodable(IntentsSettings.self, f: { IntentsSettings(decoder: $0) })
|
||||||
|
declareEncodable(CachedGeocode.self, f: { CachedGeocode(decoder: $0) })
|
||||||
return
|
return
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|||||||
@ -60,6 +60,7 @@ private enum ApplicationSpecificItemCacheCollectionIdValues: Int8 {
|
|||||||
case cachedInstantPages = 1
|
case cachedInstantPages = 1
|
||||||
case cachedWallpapers = 2
|
case cachedWallpapers = 2
|
||||||
case mediaPlaybackStoredState = 3
|
case mediaPlaybackStoredState = 3
|
||||||
|
case cachedGeocodes = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct ApplicationSpecificItemCacheCollectionId {
|
public struct ApplicationSpecificItemCacheCollectionId {
|
||||||
@ -67,6 +68,7 @@ public struct ApplicationSpecificItemCacheCollectionId {
|
|||||||
public static let cachedInstantPages = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.cachedInstantPages.rawValue)
|
public static let cachedInstantPages = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.cachedInstantPages.rawValue)
|
||||||
public static let cachedWallpapers = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.cachedWallpapers.rawValue)
|
public static let cachedWallpapers = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.cachedWallpapers.rawValue)
|
||||||
public static let mediaPlaybackStoredState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.mediaPlaybackStoredState.rawValue)
|
public static let mediaPlaybackStoredState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.mediaPlaybackStoredState.rawValue)
|
||||||
|
public static let cachedGeocodes = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.cachedGeocodes.rawValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum ApplicationSpecificOrderedItemListCollectionIdValues: Int32 {
|
private enum ApplicationSpecificOrderedItemListCollectionIdValues: Int32 {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user