Story search

This commit is contained in:
Isaac 2024-06-07 15:22:11 +04:00
parent 9094bb23b8
commit 571e2164b2
28 changed files with 1236 additions and 447 deletions

View file

@ -884,6 +884,11 @@ public enum CollectibleItemInfoScreenSubject {
case username(String)
}
public enum StorySearchControllerScope {
case query(String)
case location(coordinates: MediaArea.Coordinates, venue: MediaArea.Venue, address: MediaArea.Address?)
}
public protocol SharedAccountContext: AnyObject {
var sharedContainerPath: String { get }
var basePath: String { get }
@ -970,7 +975,7 @@ public protocol SharedAccountContext: AnyObject {
func makeAttachmentFileController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, bannedSendMedia: (Int32, Bool)?, presentGallery: @escaping () -> Void, presentFiles: @escaping () -> Void, send: @escaping (AnyMediaReference) -> Void) -> AttachmentFileController
func makeGalleryCaptionPanelView(context: AccountContext, chatLocation: ChatLocation, isScheduledMessages: Bool, isFile: Bool, customEmojiAvailable: Bool, present: @escaping (ViewController) -> Void, presentInGlobalOverlay: @escaping (ViewController) -> Void) -> NSObject?
func makeHashtagSearchController(context: AccountContext, peer: EnginePeer?, query: String, all: Bool) -> ViewController
func makeStorySearchController(context: AccountContext, query: String) -> ViewController
func makeStorySearchController(context: AccountContext, scope: StorySearchControllerScope) -> ViewController
func makeMyStoriesController(context: AccountContext, isArchive: Bool) -> ViewController
func makeArchiveSettingsController(context: AccountContext) -> ViewController
func makeFilterSettingsController(context: AccountContext, modal: Bool, scrollToTags: Bool, dismissed: (() -> Void)?) -> ViewController

View file

@ -26,10 +26,10 @@ private func generateSmallBackgroundImage(color: UIColor) -> UIImage? {
})
}
class LocationPinAnnotation: NSObject, MKAnnotation {
public class LocationPinAnnotation: NSObject, MKAnnotation {
let context: AccountContext
let theme: PresentationTheme
var coordinate: CLLocationCoordinate2D {
public var coordinate: CLLocationCoordinate2D {
willSet {
self.willChangeValue(forKey: "coordinate")
}
@ -55,10 +55,10 @@ class LocationPinAnnotation: NSObject, MKAnnotation {
var isSelf = false
var selfPeer: EnginePeer?
var title: String? = ""
var subtitle: String? = ""
public var title: String? = ""
public var subtitle: String? = ""
init(context: AccountContext, theme: PresentationTheme, peer: EnginePeer?) {
public init(context: AccountContext, theme: PresentationTheme, peer: EnginePeer?) {
self.context = context
self.theme = theme
self.location = nil
@ -71,7 +71,7 @@ class LocationPinAnnotation: NSObject, MKAnnotation {
super.init()
}
init(context: AccountContext, theme: PresentationTheme, location: TelegramMediaMap, queryId: Int64?, resultId: String?, forcedSelection: Bool = false) {
public init(context: AccountContext, theme: PresentationTheme, location: TelegramMediaMap, queryId: Int64?, resultId: String?, forcedSelection: Bool = false) {
self.context = context
self.theme = theme
self.location = location
@ -84,7 +84,7 @@ class LocationPinAnnotation: NSObject, MKAnnotation {
super.init()
}
init(context: AccountContext, theme: PresentationTheme, message: EngineMessage, selfPeer: EnginePeer?, isSelf: Bool, heading: Int32?) {
public init(context: AccountContext, theme: PresentationTheme, message: EngineMessage, selfPeer: EnginePeer?, isSelf: Bool, heading: Int32?) {
self.context = context
self.theme = theme
self.location = nil
@ -104,7 +104,7 @@ class LocationPinAnnotation: NSObject, MKAnnotation {
super.init()
}
var id: String {
public var id: String {
if let message = self.message {
return "\(message.id.id)"
} else if let peer = self.peer {
@ -157,7 +157,7 @@ private func removePulseAnimations(layer: CALayer) {
layer.removeAnimation(forKey: "pulse-opacity")
}
class LocationPinAnnotationView: MKAnnotationView {
public class LocationPinAnnotationView: MKAnnotationView {
let shadowNode: ASImageNode
let pulseNode: ASImageNode
let backgroundNode: ASImageNode
@ -178,17 +178,17 @@ class LocationPinAnnotationView: MKAnnotationView {
var headingKvoToken: NSKeyValueObservation?
override class var layerClass: AnyClass {
override public class var layerClass: AnyClass {
return LocationPinAnnotationLayer.self
}
func setZPosition(_ zPosition: CGFloat?) {
public func setZPosition(_ zPosition: CGFloat?) {
if let layer = self.layer as? LocationPinAnnotationLayer {
layer.customZPosition = zPosition
}
}
init(annotation: LocationPinAnnotation) {
public init(annotation: LocationPinAnnotation) {
self.shadowNode = ASImageNode()
self.shadowNode.image = UIImage(bundleImageName: "Location/PinShadow")
if let image = self.shadowNode.image {
@ -244,7 +244,7 @@ class LocationPinAnnotationView: MKAnnotationView {
self.annotation = annotation
}
required init?(coder aDecoder: NSCoder) {
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@ -252,7 +252,7 @@ class LocationPinAnnotationView: MKAnnotationView {
self.headingKvoToken?.invalidate()
}
var defaultZPosition: CGFloat {
public var defaultZPosition: CGFloat {
if let annotation = self.annotation as? LocationPinAnnotation {
if annotation.forcedSelection {
return 0.0
@ -266,7 +266,7 @@ class LocationPinAnnotationView: MKAnnotationView {
}
}
override var annotation: MKAnnotation? {
override public var annotation: MKAnnotation? {
didSet {
if let annotation = self.annotation as? LocationPinAnnotation {
if let message = annotation.message {
@ -363,14 +363,14 @@ class LocationPinAnnotationView: MKAnnotationView {
}
}
override func prepareForReuse() {
override public func prepareForReuse() {
self.previousPeerId = nil
self.smallNode.isHidden = true
self.backgroundNode.isHidden = false
self.appeared = false
}
override func setSelected(_ selected: Bool, animated: Bool) {
override public func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if let annotation = self.annotation as? LocationPinAnnotation {
@ -547,7 +547,7 @@ class LocationPinAnnotationView: MKAnnotationView {
}
var previousPeerId: EnginePeer.Id?
func setPeer(context: AccountContext, theme: PresentationTheme, peer: EnginePeer) {
public func setPeer(context: AccountContext, theme: PresentationTheme, peer: EnginePeer) {
let avatarNode: AvatarNode
if let currentAvatarNode = self.avatarNode {
avatarNode = currentAvatarNode
@ -566,7 +566,7 @@ class LocationPinAnnotationView: MKAnnotationView {
}
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
override public func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
if let labelNode = self.labelNode {
@ -589,7 +589,7 @@ class LocationPinAnnotationView: MKAnnotationView {
}
var isRaised = false
func setRaised(_ raised: Bool, animated: Bool, completion: @escaping () -> Void = {}) {
public func setRaised(_ raised: Bool, animated: Bool, completion: @escaping () -> Void = {}) {
guard raised != self.isRaised else {
return
}
@ -625,7 +625,7 @@ class LocationPinAnnotationView: MKAnnotationView {
}
}
func setCustom(_ custom: Bool, animated: Bool) {
public func setCustom(_ custom: Bool, animated: Bool) {
if let annotation = self.annotation as? LocationPinAnnotation {
self.iconNode.setSignal(venueIcon(engine: annotation.context.engine, type: "", background: false))
}
@ -676,7 +676,7 @@ class LocationPinAnnotationView: MKAnnotationView {
self.dotNode.isHidden = !custom
}
func animateAppearance() {
public func animateAppearance() {
guard let annotation = self.annotation as? LocationPinAnnotation, annotation.location != nil && !annotation.forcedSelection else {
return
}
@ -694,7 +694,7 @@ class LocationPinAnnotationView: MKAnnotationView {
}
}
override func layoutSubviews() {
override public func layoutSubviews() {
super.layoutSubviews()
guard !self.animating else {

View file

@ -11,7 +11,7 @@ import AppBundle
import SolidRoundedButtonNode
import ShimmerEffect
final class LocationInfoListItem: ListViewItem {
public final class LocationInfoListItem: ListViewItem {
let presentationData: ItemListPresentationData
let engine: TelegramEngine
let location: TelegramMediaMap
@ -75,7 +75,7 @@ final class LocationInfoListItem: ListViewItem {
}
}
final class LocationInfoListItemNode: ListViewItemNode {
public final class LocationInfoListItemNode: ListViewItemNode {
private let backgroundNode: ASDisplayNode
private var titleNode: TextNode?
private var subtitleNode: TextNode?
@ -91,7 +91,7 @@ final class LocationInfoListItemNode: ListViewItemNode {
private var layoutParams: ListViewItemLayoutParams?
private var absoluteLocation: (CGRect, CGSize)?
required init() {
required public init() {
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true
self.buttonNode = HighlightableButtonNode()
@ -127,7 +127,7 @@ final class LocationInfoListItemNode: ListViewItemNode {
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
}
override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
override public func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
if let item = self.item {
let makeLayout = self.asyncLayout()
let (nodeLayout, nodeApply) = makeLayout(item, params)
@ -137,7 +137,7 @@ final class LocationInfoListItemNode: ListViewItemNode {
}
}
func asyncLayout() -> (_ item: LocationInfoListItem, _ params: ListViewItemLayoutParams) -> (ListViewItemNodeLayout, () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) {
public func asyncLayout() -> (_ item: LocationInfoListItem, _ params: ListViewItemLayoutParams) -> (ListViewItemNodeLayout, () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) {
let currentItem = self.item
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
@ -205,6 +205,8 @@ final class LocationInfoListItemNode: ListViewItemNode {
strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.plainBackgroundColor
}
strongSelf.backgroundNode.isHidden = params.isStandalone
let arguments = VenueIconArguments(defaultBackgroundColor: item.presentationData.theme.chat.inputPanel.actionControlFillColor, defaultForegroundColor: item.presentationData.theme.chat.inputPanel.actionControlForegroundColor)
if let updatedLocation = updatedLocation {
strongSelf.venueIconNode.setSignal(venueIcon(engine: item.engine, type: updatedLocation.venue?.type ?? "", background: true))
@ -384,11 +386,11 @@ final class LocationInfoListItemNode: ListViewItemNode {
}
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) {
override public func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration * 0.5)
}
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
override public func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration * 0.5, removeOnCompletion: false)
}

View file

@ -34,7 +34,7 @@ private func generateShadowImage(theme: PresentationTheme, highlighted: Bool) ->
})?.stretchableImage(withLeftCapWidth: 13, topCapHeight: 0)
}
final class LocationMapHeaderNode: ASDisplayNode {
public final class LocationMapHeaderNode: ASDisplayNode {
private var presentationData: PresentationData
private let toggleMapModeSelection: () -> Void
private let goToUserLocation: () -> Void
@ -44,8 +44,8 @@ final class LocationMapHeaderNode: ASDisplayNode {
private var displayingPlacesButton = false
private var proximityNotification: Bool?
let mapNode: LocationMapNode
var trackingMode: LocationTrackingMode = .none
public let mapNode: LocationMapNode
public var trackingMode: LocationTrackingMode = .none
private let optionsBackgroundNode: ASImageNode
private let optionsSeparatorNode: ASDisplayNode
@ -59,7 +59,7 @@ final class LocationMapHeaderNode: ASDisplayNode {
private var validLayout: (ContainerViewLayout, CGFloat, CGFloat, CGFloat, CGSize)?
init(presentationData: PresentationData, toggleMapModeSelection: @escaping () -> Void, goToUserLocation: @escaping () -> Void, setupProximityNotification: @escaping (Bool) -> Void = { _ in }, showPlacesInThisArea: @escaping () -> Void = {}) {
public 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
@ -131,7 +131,7 @@ final class LocationMapHeaderNode: ASDisplayNode {
self.placesButtonNode.addTarget(self, action: #selector(self.placesPressed), forControlEvents: .touchUpInside)
}
func updateState(mapMode: LocationMapMode, trackingMode: LocationTrackingMode, displayingMapModeOptions: Bool, displayingPlacesButton: Bool, proximityNotification: Bool?, animated: Bool) {
public func updateState(mapMode: LocationMapMode, trackingMode: LocationTrackingMode, displayingMapModeOptions: Bool, displayingPlacesButton: Bool, proximityNotification: Bool?, animated: Bool) {
self.mapNode.mapMode = mapMode
self.trackingMode = trackingMode
self.infoButtonNode.isSelected = displayingMapModeOptions
@ -149,7 +149,7 @@ final class LocationMapHeaderNode: ASDisplayNode {
}
}
func updatePresentationData(_ presentationData: PresentationData) {
public func updatePresentationData(_ presentationData: PresentationData) {
self.presentationData = presentationData
self.optionsBackgroundNode.image = generateBackgroundImage(theme: presentationData.theme)
@ -177,13 +177,13 @@ final class LocationMapHeaderNode: ASDisplayNode {
}
}
func updateLayout(layout: ContainerViewLayout, navigationBarHeight: CGFloat, topPadding: CGFloat, offset: CGFloat, size: CGSize, transition: ContainedViewLayoutTransition) {
public func updateLayout(layout: ContainerViewLayout, navigationBarHeight: CGFloat, topPadding: CGFloat, offset: CGFloat, size: CGSize, transition: ContainedViewLayoutTransition) {
self.validLayout = (layout, navigationBarHeight, topPadding, offset, size)
let mapHeight: CGFloat = floor(layout.size.height * 1.3)
let mapFrame = CGRect(x: 0.0, y: floorToScreenPixels((size.height - mapHeight + navigationBarHeight) / 2.0) + offset, width: size.width, height: mapHeight)
transition.updateFrame(node: self.mapNode, frame: mapFrame)
self.mapNode.updateLayout(size: mapFrame.size)
self.mapNode.updateLayout(size: mapFrame.size, topPadding: layout.intrinsicInsets.top)
let inset: CGFloat = 6.0
@ -191,6 +191,8 @@ final class LocationMapHeaderNode: ASDisplayNode {
let placesButtonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - placesButtonSize.width) / 2.0), y: self.displayingPlacesButton ? navigationBarHeight + topPadding + inset : 0.0), size: placesButtonSize)
transition.updateFrame(node: self.placesBackgroundNode, frame: placesButtonFrame)
transition.updateFrame(node: self.placesButtonNode, frame: CGRect(origin: CGPoint(), size: placesButtonSize))
transition.updateAlpha(node: self.placesBackgroundNode, alpha: self.displayingPlacesButton ? 1.0 : 0.0)
transition.updateAlpha(node: self.placesButtonNode, alpha: self.displayingPlacesButton ? 1.0 : 0.0)
transition.updateFrame(node: self.shadowNode, frame: CGRect(x: 0.0, y: size.height - 14.0, width: size.width, height: 14.0))
@ -214,7 +216,7 @@ final class LocationMapHeaderNode: ASDisplayNode {
alphaTransition.updateAlpha(node: self.optionsBackgroundNode, alpha: optionsAlpha)
}
var forceIsHidden: Bool = false {
public var forceIsHidden: Bool = false {
didSet {
if let (layout, navigationBarHeight, topPadding, offset, size) = self.validLayout {
self.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, topPadding: topPadding, offset: offset, size: size, transition: .immediate)
@ -222,11 +224,11 @@ final class LocationMapHeaderNode: ASDisplayNode {
}
}
func updateHighlight(_ highlighted: Bool) {
public func updateHighlight(_ highlighted: Bool) {
self.shadowNode.image = generateShadowImage(theme: self.presentationData.theme, highlighted: highlighted)
}
func proximityButtonFrame() -> CGRect? {
public func proximityButtonFrame() -> CGRect? {
if self.notificationButtonNode.alpha > 0.0 {
return self.optionsBackgroundNode.view.convert(self.notificationButtonNode.frame, to: self.view)
} else {

View file

@ -5,8 +5,6 @@ import Display
import SwiftSignalKit
import MapKit
let defaultMapSpan = MKCoordinateSpan(latitudeDelta: 0.016, longitudeDelta: 0.016)
let viewMapSpan = MKCoordinateSpan(latitudeDelta: 0.008, longitudeDelta: 0.008)
private let pinOffset = CGPoint(x: 0.0, y: 33.0)
public enum LocationMapMode {
@ -128,7 +126,10 @@ private func generateProximityDim(size: CGSize) -> UIImage {
})!
}
final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
public final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
public static let defaultMapSpan = MKCoordinateSpan(latitudeDelta: 0.016, longitudeDelta: 0.016)
public static let viewMapSpan = MKCoordinateSpan(latitudeDelta: 0.008, longitudeDelta: 0.008)
class ProximityCircleRenderer: MKCircleRenderer {
override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {
super.draw(mapRect, zoomScale: zoomScale, in: context)
@ -204,7 +205,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
}
private var circleOverlay: MKCircle?
var activeProximityRadius: Double? {
public var activeProximityRadius: Double? {
didSet {
if let activeProximityRadius = self.activeProximityRadius {
if let circleOverlay = self.circleOverlay {
@ -225,7 +226,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
}
}
override init() {
override public init() {
self.pickerAnnotationContainerView = PickerAnnotationContainerView()
self.pickerAnnotationContainerView.isHidden = true
@ -236,7 +237,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
})
}
override func didLoad() {
override public func didLoad() {
super.didLoad()
self.headingArrowView = UIImageView()
@ -292,7 +293,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
}
}
var trackingMode: LocationTrackingMode = .none {
public var trackingMode: LocationTrackingMode = .none {
didSet {
self.mapView?.userTrackingMode = self.trackingMode.userTrackingMode
if self.trackingMode == .followWithHeading && self.headingArrowView?.image != nil {
@ -303,11 +304,18 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
}
}
var topPadding: CGFloat = 0.0
var mapOffset: CGFloat = 0.0
func setMapCenter(coordinate: CLLocationCoordinate2D, radius: Double, insets: UIEdgeInsets, offset: CGFloat, animated: Bool = false) {
var hasValidLayout: Bool = false
var pendingSetMapCenter: (coordinate: CLLocationCoordinate2D, span: MKCoordinateSpan, offset: CGPoint, isUserLocation: Bool, hidePicker: Bool, animated: Bool)?
public func setMapCenter(coordinate: CLLocationCoordinate2D, radius: Double, insets: UIEdgeInsets, offset: CGFloat, animated: Bool = false) {
self.mapOffset = offset
self.ignoreRegionChanges = true
var insets = insets
insets.top += self.topPadding
let mapRect = MKMapRect(region: MKCoordinateRegion(center: coordinate, latitudinalMeters: radius * 2.0, longitudinalMeters: radius * 2.0))
self.mapView?.setVisibleMapRect(mapRect, edgePadding: insets, animated: animated)
self.ignoreRegionChanges = false
@ -315,16 +323,34 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
self.proximityDimView.center = CGPoint(x: self.bounds.midX, y: self.bounds.midY + offset)
}
func setMapCenter(coordinate: CLLocationCoordinate2D, span: MKCoordinateSpan = defaultMapSpan, offset: CGPoint = CGPoint(), isUserLocation: Bool = false, hidePicker: Bool = false, animated: Bool = false) {
public func setMapCenter(coordinate: CLLocationCoordinate2D, span: MKCoordinateSpan = defaultMapSpan, offset: CGPoint = CGPoint(), isUserLocation: Bool = false, hidePicker: Bool = false, animated: Bool = false) {
self.pendingSetMapCenter = (
coordinate, span, offset, isUserLocation, hidePicker, animated
)
if self.hasValidLayout {
self.applyPendingSetMapCenter()
}
}
private func applyPendingSetMapCenter() {
if !self.hasValidLayout {
return
}
guard let (coordinate, span, offset, isUserLocation, hidePicker, animated) = self.pendingSetMapCenter else {
return
}
self.pendingSetMapCenter = nil
let region = MKCoordinateRegion(center: coordinate, span: span)
self.ignoreRegionChanges = true
if offset == CGPoint() {
if offset == CGPoint() && self.topPadding == 0.0 {
self.mapView?.setRegion(region, animated: animated)
} else {
let mapRect = MKMapRect(region: region)
self.mapView?.setVisibleMapRect(mapRect, edgePadding: UIEdgeInsets(top: offset.y, left: offset.x, bottom: 0.0, right: 0.0), animated: animated)
self.mapView?.setVisibleMapRect(mapRect, edgePadding: UIEdgeInsets(top: offset.y + self.topPadding, left: offset.x, bottom: 0.0, right: 0.0), animated: animated)
}
self.ignoreRegionChanges = false
self.ignoreRegionChanges = false
if isUserLocation {
if !self.returnedToUserLocation {
@ -339,7 +365,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
}
}
func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
public func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
guard !self.ignoreRegionChanges, let scrollView = mapView.subviews.first, let gestureRecognizers = scrollView.gestureRecognizers else {
return
}
@ -356,7 +382,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
}
}
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
public func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
let wasDragging = self.isDragging
if self.isDragging {
self.isDragging = false
@ -372,7 +398,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
}
}
func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) {
public func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) {
guard let location = userLocation.location else {
return
}
@ -380,11 +406,11 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
self.locationPromise.set(.single(location))
}
func mapView(_ mapView: MKMapView, didFailToLocateUserWithError error: Error) {
public func mapView(_ mapView: MKMapView, didFailToLocateUserWithError error: Error) {
self.locationPromise.set(.single(nil))
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
public func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation === mapView.userLocation {
return nil
}
@ -400,7 +426,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
return nil
}
func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) {
public func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) {
for view in views {
if view.annotation is MKUserLocation {
self.defaultUserLocationAnnotation = view.annotation
@ -424,7 +450,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
}
}
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
public func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
guard let annotation = view.annotation as? LocationPinAnnotation else {
return
}
@ -440,7 +466,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
}
}
func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) {
public func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) {
if let view = view as? LocationPinAnnotationView {
Queue.mainQueue().after(0.2) {
view.setZPosition(view.defaultZPosition)
@ -459,7 +485,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
}
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
public func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if let circle = overlay as? MKCircle {
let renderer = ProximityCircleRenderer(circle: circle)
renderer.fillColor = .clear
@ -472,7 +498,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
}
}
var distancesToAllAnnotations: Signal<[Double], NoError> {
public var distancesToAllAnnotations: Signal<[Double], NoError> {
let poll = Signal<[LocationPinAnnotation], NoError> { [weak self] subscriber in
if let strongSelf = self {
subscriber.putNext(strongSelf.annotations)
@ -497,30 +523,30 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
}
}
var currentUserLocation: CLLocation? {
public var currentUserLocation: CLLocation? {
return self.mapView?.userLocation.location
}
var userLocation: Signal<CLLocation?, NoError> {
public var userLocation: Signal<CLLocation?, NoError> {
return .single(self.currentUserLocation)
|> then (self.locationPromise.get())
}
var mapCenterCoordinate: CLLocationCoordinate2D? {
public var mapCenterCoordinate: CLLocationCoordinate2D? {
guard let mapView = self.mapView else {
return nil
}
return mapView.convert(CGPoint(x: (mapView.frame.width + pinOffset.x) / 2.0, y: (mapView.frame.height + pinOffset.y) / 2.0), toCoordinateFrom: mapView)
}
var mapSpan: MKCoordinateSpan? {
public var mapSpan: MKCoordinateSpan? {
guard let mapView = self.mapView else {
return nil
}
return mapView.region.span
}
func resetAnnotationSelection() {
public func resetAnnotationSelection() {
guard let mapView = self.mapView else {
return
}
@ -529,8 +555,8 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
}
}
var pickerAnnotationView: LocationPinAnnotationView? = nil
var hasPickerAnnotation: Bool = false {
public var pickerAnnotationView: LocationPinAnnotationView? = nil
public var hasPickerAnnotation: Bool = false {
didSet {
if self.hasPickerAnnotation, let annotation = self.userLocationAnnotation {
let pickerAnnotationView = LocationPinAnnotationView(annotation: annotation)
@ -544,7 +570,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
}
}
func switchToPicking(raise: Bool = false, animated: Bool) {
public func switchToPicking(raise: Bool = false, animated: Bool) {
guard self.hasPickerAnnotation else {
return
}
@ -561,8 +587,8 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
self.resetScheduledPin()
}
var customUserLocationAnnotationView: LocationPinAnnotationView? = nil
var userLocationAnnotation: LocationPinAnnotation? = nil {
public var customUserLocationAnnotationView: LocationPinAnnotationView? = nil
public var userLocationAnnotation: LocationPinAnnotation? = nil {
didSet {
if let annotation = self.userLocationAnnotation {
self.customUserLocationAnnotationView?.removeFromSuperview()
@ -582,7 +608,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
}
}
var userHeading: CGFloat? = nil {
public var userHeading: CGFloat? = nil {
didSet {
if let heading = self.userHeading {
self.headingArrowView?.isHidden = false
@ -594,7 +620,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
}
}
var annotations: [LocationPinAnnotation] = [] {
public var annotations: [LocationPinAnnotation] = [] {
didSet {
guard let mapView = self.mapView else {
return
@ -709,7 +735,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
self.pinDisposable.set(nil)
}
func showAll(animated: Bool = true) {
public func showAll(animated: Bool = true) {
guard let mapView = self.mapView else {
return
}
@ -736,11 +762,17 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
}
}
func updateLayout(size: CGSize) {
self.proximityDimView.frame = CGRect(origin: CGPoint(x: 0.0, y: self.mapOffset), size: size)
public func updateLayout(size: CGSize, topPadding: CGFloat) {
self.hasValidLayout = true
self.topPadding = topPadding
self.proximityDimView.frame = CGRect(origin: CGPoint(x: 0.0, y: self.topPadding + self.mapOffset), 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)
}
self.applyPendingSetMapCenter()
}
}

View file

@ -6,14 +6,14 @@ import TelegramCore
import TelegramPresentationData
import SegmentedControlNode
final class LocationOptionsNode: ASDisplayNode {
public final class LocationOptionsNode: ASDisplayNode {
private var presentationData: PresentationData
private let backgroundNode: NavigationBackgroundNode
private let separatorNode: ASDisplayNode
private let segmentedControlNode: SegmentedControlNode
init(presentationData: PresentationData, updateMapMode: @escaping (LocationMapMode) -> Void) {
public init(presentationData: PresentationData, hasBackground: Bool = true, updateMapMode: @escaping (LocationMapMode) -> Void) {
self.presentationData = presentationData
self.backgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.rootController.navigationBar.blurredBackgroundColor)
@ -24,8 +24,11 @@ final class LocationOptionsNode: ASDisplayNode {
super.init()
self.addSubnode(self.backgroundNode)
self.addSubnode(self.separatorNode)
if hasBackground {
self.addSubnode(self.backgroundNode)
self.addSubnode(self.separatorNode)
}
self.addSubnode(self.segmentedControlNode)
self.segmentedControlNode.selectedIndexChanged = { index in
@ -42,14 +45,14 @@ final class LocationOptionsNode: ASDisplayNode {
}
}
func updatePresentationData(_ presentationData: PresentationData) {
public func updatePresentationData(_ presentationData: PresentationData) {
self.presentationData = presentationData
self.backgroundNode.updateColor(color: self.presentationData.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate)
self.separatorNode.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor
self.segmentedControlNode.updateTheme(SegmentedControlTheme(theme: self.presentationData.theme))
}
func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size))
self.backgroundNode.update(size: size, transition: transition)

View file

@ -101,7 +101,7 @@ func stringForEstimatedDuration(strings: PresentationStrings, time: Double, form
}
}
func throttledUserLocation(_ userLocation: Signal<CLLocation?, NoError>) -> Signal<CLLocation?, NoError> {
public func throttledUserLocation(_ userLocation: Signal<CLLocation?, NoError>) -> Signal<CLLocation?, NoError> {
return userLocation
|> reduceLeft(value: nil) { current, updated, emit -> CLLocation? in
if let current = current {
@ -126,13 +126,13 @@ func throttledUserLocation(_ userLocation: Signal<CLLocation?, NoError>) -> Sign
}
}
enum ExpectedTravelTime: Equatable {
public enum ExpectedTravelTime: Equatable {
case unknown
case calculating
case ready(Double)
}
func getExpectedTravelTime(coordinate: CLLocationCoordinate2D, transportType: MKDirectionsTransportType) -> Signal<ExpectedTravelTime, NoError> {
public func getExpectedTravelTime(coordinate: CLLocationCoordinate2D, transportType: MKDirectionsTransportType) -> Signal<ExpectedTravelTime, NoError> {
return Signal { subscriber in
subscriber.putNext(.calculating)

View file

@ -41,159 +41,159 @@ private struct LocationViewTransaction {
let animated: Bool
}
private enum LocationViewEntryId: Hashable {
public enum LocationViewEntryId: Hashable {
case info
case toggleLiveLocation(Bool)
case liveLocation(UInt32)
}
private enum LocationViewEntry: Comparable, Identifiable {
public enum LocationViewEntry: Comparable, Identifiable {
case info(PresentationTheme, TelegramMediaMap, String?, Double?, ExpectedTravelTime, ExpectedTravelTime, ExpectedTravelTime, Bool)
case toggleLiveLocation(PresentationTheme, String, String, Double?, Double?, Bool, EngineMessage.Id?)
case liveLocation(PresentationTheme, PresentationDateTimeFormat, PresentationPersonNameOrder, EngineMessage, Double?, ExpectedTravelTime, ExpectedTravelTime, ExpectedTravelTime, Int)
var stableId: LocationViewEntryId {
public var stableId: LocationViewEntryId {
switch self {
case .info:
return .info
case let .toggleLiveLocation(_, _, _, _, _, additional, _):
return .toggleLiveLocation(additional)
case let .liveLocation(_, _, _, message, _, _, _, _, _):
return .liveLocation(message.stableId)
case .info:
return .info
case let .toggleLiveLocation(_, _, _, _, _, additional, _):
return .toggleLiveLocation(additional)
case let .liveLocation(_, _, _, message, _, _, _, _, _):
return .liveLocation(message.stableId)
}
}
static func ==(lhs: LocationViewEntry, rhs: LocationViewEntry) -> Bool {
public static func ==(lhs: LocationViewEntry, rhs: LocationViewEntry) -> Bool {
switch lhs {
case let .info(lhsTheme, lhsLocation, lhsAddress, lhsDistance, lhsDrivingTime, lhsTransitTime, lhsWalkingTime, lhsHasEta):
if case let .info(rhsTheme, rhsLocation, rhsAddress, rhsDistance, rhsDrivingTime, rhsTransitTime, rhsWalkingTime, rhsHasEta) = rhs, lhsTheme === rhsTheme, lhsLocation.venue?.id == rhsLocation.venue?.id, lhsAddress == rhsAddress, lhsDistance == rhsDistance, lhsDrivingTime == rhsDrivingTime, lhsTransitTime == rhsTransitTime, lhsWalkingTime == rhsWalkingTime, lhsHasEta == rhsHasEta {
return true
} else {
return false
}
case let .toggleLiveLocation(lhsTheme, lhsTitle, lhsSubtitle, lhsBeginTimestamp, lhsTimeout, lhsAdditional, lhsMessageId):
if case let .toggleLiveLocation(rhsTheme, rhsTitle, rhsSubtitle, rhsBeginTimestamp, rhsTimeout, rhsAdditional, rhsMessageId) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsBeginTimestamp == rhsBeginTimestamp, lhsTimeout == rhsTimeout, lhsAdditional == rhsAdditional, lhsMessageId == rhsMessageId {
return true
} else {
return false
}
case let .liveLocation(lhsTheme, lhsDateTimeFormat, lhsNameDisplayOrder, lhsMessage, lhsDistance, lhsDrivingTime, lhsTransitTime, lhsWalkingTime, lhsIndex):
if case let .liveLocation(rhsTheme, rhsDateTimeFormat, rhsNameDisplayOrder, rhsMessage, rhsDistance, rhsDrivingTime, rhsTransitTime, rhsWalkingTime, rhsIndex) = rhs, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsNameDisplayOrder == rhsNameDisplayOrder, areMessagesEqual(lhsMessage, rhsMessage), lhsDistance == rhsDistance, lhsDrivingTime == rhsDrivingTime, lhsTransitTime == rhsTransitTime, lhsWalkingTime == rhsWalkingTime, lhsIndex == rhsIndex {
return true
} else {
return false
}
}
}
static func <(lhs: LocationViewEntry, rhs: LocationViewEntry) -> Bool {
switch lhs {
case .info:
switch rhs {
case .info:
return false
case .toggleLiveLocation, .liveLocation:
return true
}
case let .toggleLiveLocation(_, _, _, _, _, lhsAdditional, _):
switch rhs {
case .info:
return false
case let .toggleLiveLocation(_, _, _, _, _, rhsAdditional, _):
return !lhsAdditional && rhsAdditional
case .liveLocation:
return true
case let .info(lhsTheme, lhsLocation, lhsAddress, lhsDistance, lhsDrivingTime, lhsTransitTime, lhsWalkingTime, lhsHasEta):
if case let .info(rhsTheme, rhsLocation, rhsAddress, rhsDistance, rhsDrivingTime, rhsTransitTime, rhsWalkingTime, rhsHasEta) = rhs, lhsTheme === rhsTheme, lhsLocation.venue?.id == rhsLocation.venue?.id, lhsAddress == rhsAddress, lhsDistance == rhsDistance, lhsDrivingTime == rhsDrivingTime, lhsTransitTime == rhsTransitTime, lhsWalkingTime == rhsWalkingTime, lhsHasEta == rhsHasEta {
return true
} else {
return false
}
case let .toggleLiveLocation(lhsTheme, lhsTitle, lhsSubtitle, lhsBeginTimestamp, lhsTimeout, lhsAdditional, lhsMessageId):
if case let .toggleLiveLocation(rhsTheme, rhsTitle, rhsSubtitle, rhsBeginTimestamp, rhsTimeout, rhsAdditional, rhsMessageId) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsBeginTimestamp == rhsBeginTimestamp, lhsTimeout == rhsTimeout, lhsAdditional == rhsAdditional, lhsMessageId == rhsMessageId {
return true
} else {
return false
}
case let .liveLocation(lhsTheme, lhsDateTimeFormat, lhsNameDisplayOrder, lhsMessage, lhsDistance, lhsDrivingTime, lhsTransitTime, lhsWalkingTime, lhsIndex):
if case let .liveLocation(rhsTheme, rhsDateTimeFormat, rhsNameDisplayOrder, rhsMessage, rhsDistance, rhsDrivingTime, rhsTransitTime, rhsWalkingTime, rhsIndex) = rhs, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsNameDisplayOrder == rhsNameDisplayOrder, areMessagesEqual(lhsMessage, rhsMessage), lhsDistance == rhsDistance, lhsDrivingTime == rhsDrivingTime, lhsTransitTime == rhsTransitTime, lhsWalkingTime == rhsWalkingTime, lhsIndex == rhsIndex {
return true
} else {
return false
}
}
}
public static func <(lhs: LocationViewEntry, rhs: LocationViewEntry) -> Bool {
switch lhs {
case .info:
switch rhs {
case .info:
return false
case .toggleLiveLocation, .liveLocation:
return true
}
case let .toggleLiveLocation(_, _, _, _, _, lhsAdditional, _):
switch rhs {
case .info:
return false
case let .toggleLiveLocation(_, _, _, _, _, rhsAdditional, _):
return !lhsAdditional && rhsAdditional
case .liveLocation:
return true
}
case let .liveLocation(_, _, _, _, _, _, _, _, lhsIndex):
switch rhs {
case .info, .toggleLiveLocation:
return false
case let .liveLocation(_, _, _, _, _, _, _, _, rhsIndex):
return lhsIndex < rhsIndex
}
case let .liveLocation(_, _, _, _, _, _, _, _, lhsIndex):
switch rhs {
case .info, .toggleLiveLocation:
return false
case let .liveLocation(_, _, _, _, _, _, _, _, rhsIndex):
return lhsIndex < rhsIndex
}
}
}
func item(context: AccountContext, presentationData: PresentationData, interaction: LocationViewInteraction?) -> ListViewItem {
switch self {
case let .info(_, location, address, distance, drivingTime, transitTime, walkingTime, hasEta):
let addressString: String?
if let address = address {
addressString = address
} else {
addressString = presentationData.strings.Map_Locating
}
let distanceString: String?
if let distance = distance {
distanceString = distance < 10 ? presentationData.strings.Map_YouAreHere : presentationData.strings.Map_DistanceAway(stringForDistance(strings: presentationData.strings, distance: distance)).string
} else {
distanceString = nil
}
return LocationInfoListItem(presentationData: ItemListPresentationData(presentationData), engine: context.engine, location: location, address: addressString, distance: distanceString, drivingTime: drivingTime, transitTime: transitTime, walkingTime: walkingTime, hasEta: hasEta, action: {
interaction?.goToCoordinate(location.coordinate)
}, drivingAction: {
interaction?.requestDirections(location, nil, .driving)
}, transitAction: {
interaction?.requestDirections(location, nil, .transit)
}, walkingAction: {
interaction?.requestDirections(location, nil, .walking)
})
case let .toggleLiveLocation(_, title, subtitle, beginTimstamp, timeout, additional, messageId):
var beginTimeAndTimeout: (Double, Double)?
if let beginTimstamp = beginTimstamp, let timeout = timeout {
beginTimeAndTimeout = (beginTimstamp, timeout)
} else {
beginTimeAndTimeout = nil
}
case let .info(_, location, address, distance, drivingTime, transitTime, walkingTime, hasEta):
let addressString: String?
if let address = address {
addressString = address
} else {
addressString = presentationData.strings.Map_Locating
}
let distanceString: String?
if let distance = distance {
distanceString = distance < 10 ? presentationData.strings.Map_YouAreHere : presentationData.strings.Map_DistanceAway(stringForDistance(strings: presentationData.strings, distance: distance)).string
} else {
distanceString = nil
}
return LocationInfoListItem(presentationData: ItemListPresentationData(presentationData), engine: context.engine, location: location, address: addressString, distance: distanceString, drivingTime: drivingTime, transitTime: transitTime, walkingTime: walkingTime, hasEta: hasEta, action: {
interaction?.goToCoordinate(location.coordinate)
}, drivingAction: {
interaction?.requestDirections(location, nil, .driving)
}, transitAction: {
interaction?.requestDirections(location, nil, .transit)
}, walkingAction: {
interaction?.requestDirections(location, nil, .walking)
})
case let .toggleLiveLocation(_, title, subtitle, beginTimstamp, timeout, additional, messageId):
var beginTimeAndTimeout: (Double, Double)?
if let beginTimstamp = beginTimstamp, let timeout = timeout {
beginTimeAndTimeout = (beginTimstamp, timeout)
} else {
beginTimeAndTimeout = nil
}
let icon: LocationActionListItemIcon
if let timeout, Int32(timeout) != liveLocationIndefinitePeriod, !additional {
icon = .extendLiveLocation
} else if beginTimeAndTimeout != nil {
icon = .stopLiveLocation
} else {
icon = .liveLocation
}
let icon: LocationActionListItemIcon
if let timeout, Int32(timeout) != liveLocationIndefinitePeriod, !additional {
icon = .extendLiveLocation
} else if beginTimeAndTimeout != nil {
icon = .stopLiveLocation
} else {
icon = .liveLocation
}
return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), engine: context.engine, title: title, subtitle: subtitle, icon: icon, beginTimeAndTimeout: !additional ? beginTimeAndTimeout : nil, action: {
if beginTimeAndTimeout != nil {
if let timeout, Int32(timeout) != liveLocationIndefinitePeriod {
if additional {
interaction?.stopLiveLocation()
} else {
interaction?.sendLiveLocation(nil, true, messageId)
}
} else {
return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), engine: context.engine, title: title, subtitle: subtitle, icon: icon, beginTimeAndTimeout: !additional ? beginTimeAndTimeout : nil, action: {
if beginTimeAndTimeout != nil {
if let timeout, Int32(timeout) != liveLocationIndefinitePeriod {
if additional {
interaction?.stopLiveLocation()
} else {
interaction?.sendLiveLocation(nil, true, messageId)
}
} else {
interaction?.sendLiveLocation(nil, false, nil)
interaction?.stopLiveLocation()
}
}, highlighted: { highlight in
interaction?.updateSendActionHighlight(highlight)
})
case let .liveLocation(_, dateTimeFormat, nameDisplayOrder, message, distance, drivingTime, transitTime, walkingTime, _):
var title: String?
if let author = message.author {
title = author.displayTitle(strings: presentationData.strings, displayOrder: nameDisplayOrder)
} else {
interaction?.sendLiveLocation(nil, false, nil)
}
return LocationLiveListItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: context, message: message, distance: distance, drivingTime: drivingTime, transitTime: transitTime, walkingTime: walkingTime, action: {
if let location = getLocation(from: message) {
interaction?.goToCoordinate(location.coordinate)
}
}, longTapAction: {}, drivingAction: {
if let location = getLocation(from: message) {
interaction?.requestDirections(location, title, .driving)
}
}, transitAction: {
if let location = getLocation(from: message) {
interaction?.requestDirections(location, title, .transit)
}
}, walkingAction: {
if let location = getLocation(from: message) {
interaction?.requestDirections(location, title, .walking)
}
})
}, highlighted: { highlight in
interaction?.updateSendActionHighlight(highlight)
})
case let .liveLocation(_, dateTimeFormat, nameDisplayOrder, message, distance, drivingTime, transitTime, walkingTime, _):
var title: String?
if let author = message.author {
title = author.displayTitle(strings: presentationData.strings, displayOrder: nameDisplayOrder)
}
return LocationLiveListItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: context, message: message, distance: distance, drivingTime: drivingTime, transitTime: transitTime, walkingTime: walkingTime, action: {
if let location = getLocation(from: message) {
interaction?.goToCoordinate(location.coordinate)
}
}, longTapAction: {}, drivingAction: {
if let location = getLocation(from: message) {
interaction?.requestDirections(location, title, .driving)
}
}, transitAction: {
if let location = getLocation(from: message) {
interaction?.requestDirections(location, title, .transit)
}
}, walkingAction: {
if let location = getLocation(from: message) {
interaction?.requestDirections(location, title, .walking)
}
})
}
}
}
@ -208,22 +208,22 @@ private func preparedTransition(from fromEntries: [LocationViewEntry], to toEntr
return LocationViewTransaction(deletions: deletions, insertions: insertions, updates: updates, gotTravelTimes: gotTravelTimes, count: toEntries.count, animated: animated)
}
enum LocationViewLocation: Equatable {
public enum LocationViewLocation: Equatable {
case initial
case user
case coordinate(CLLocationCoordinate2D, Bool)
case custom
}
struct LocationViewState {
var mapMode: LocationMapMode
var displayingMapModeOptions: Bool
var selectedLocation: LocationViewLocation
var trackingMode: LocationTrackingMode
var updatingProximityRadius: Int32?
var cancellingProximityRadius: Bool
public struct LocationViewState {
public var mapMode: LocationMapMode
public var displayingMapModeOptions: Bool
public var selectedLocation: LocationViewLocation
public var trackingMode: LocationTrackingMode
public var updatingProximityRadius: Int32?
public var cancellingProximityRadius: Bool
init() {
public init() {
self.mapMode = .map
self.displayingMapModeOptions = false
self.selectedLocation = .initial
@ -614,12 +614,12 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
switch state.selectedLocation {
case .initial:
if previousState?.selectedLocation != .initial {
strongSelf.headerNode.mapNode.setMapCenter(coordinate: location.coordinate, span: viewMapSpan, animated: previousState != nil)
strongSelf.headerNode.mapNode.setMapCenter(coordinate: location.coordinate, span: LocationMapNode.viewMapSpan, animated: previousState != nil)
}
case let .coordinate(coordinate, defaultSpan):
if let previousState = previousState, case let .coordinate(previousCoordinate, _) = previousState.selectedLocation, previousCoordinate == coordinate {
} else {
strongSelf.headerNode.mapNode.setMapCenter(coordinate: coordinate, span: defaultSpan ? defaultMapSpan : viewMapSpan, animated: true)
strongSelf.headerNode.mapNode.setMapCenter(coordinate: coordinate, span: defaultSpan ? LocationMapNode.defaultMapSpan : LocationMapNode.viewMapSpan, animated: true)
}
case .user:
if previousState?.selectedLocation != .user, let userLocation = userLocation {

View file

@ -42,6 +42,7 @@ public protocol SparseItemGridBinding: AnyObject {
func onTagTap()
func didScroll()
func coveringInsetOffsetUpdated(transition: ContainedViewLayoutTransition)
func scrollingOffsetUpdated(transition: ContainedViewLayoutTransition)
func onBeginFastScrolling()
func getShimmerColors() -> SparseItemGrid.ShimmerColors
}
@ -1442,6 +1443,16 @@ public final class SparseItemGrid: ASDisplayNode {
return 0.0
}
}
public var scrollingOffset: CGFloat {
if let currentViewportTransition = self.currentViewportTransition {
return currentViewportTransition.offset
} else if let currentViewport = self.currentViewport {
return currentViewport.offset
} else {
return 0.0
}
}
public var cancelExternalContentGestures: (() -> Void)?
public var zoomLevelUpdated: ((ZoomLevel) -> Void)?
@ -1886,20 +1897,32 @@ public final class SparseItemGrid: ASDisplayNode {
}
private func offsetUpdated(viewport: Viewport, transition: ContainedViewLayoutTransition) {
guard let items = self.items else {
return
}
if self.currentViewportTransition != nil {
return
}
items.itemBinding.scrollingOffsetUpdated(transition: transition)
if let headerTextView = self.headerText?.view {
headerTextView.layer.transform = CATransform3DMakeTranslation(0.0, -viewport.offset, 0.0)
}
}
private func transitionOffsetUpdated(transition: ContainedViewLayoutTransition) {
guard let items = self.items else {
return
}
guard let currentViewportTransition = self.currentViewportTransition else {
return
}
items.itemBinding.scrollingOffsetUpdated(transition: transition)
if let headerTextView = self.headerText?.view {
headerTextView.layer.transform = CATransform3DMakeTranslation(0.0, -currentViewportTransition.offset, 0.0)
}

View file

@ -290,6 +290,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1107729093] = { return Api.Game.parse_game($0) }
dict[-1297942941] = { return Api.GeoPoint.parse_geoPoint($0) }
dict[286776671] = { return Api.GeoPoint.parse_geoPointEmpty($0) }
dict[-565420653] = { return Api.GeoPointAddress.parse_geoPointAddress($0) }
dict[1934380235] = { return Api.GlobalPrivacySettings.parse_globalPrivacySettings($0) }
dict[-711498484] = { return Api.GroupCall.parse_groupCall($0) }
dict[2004925620] = { return Api.GroupCall.parse_groupCallDiscarded($0) }
@ -513,7 +514,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[577893055] = { return Api.MediaArea.parse_inputMediaAreaChannelPost($0) }
dict[-1300094593] = { return Api.MediaArea.parse_inputMediaAreaVenue($0) }
dict[1996756655] = { return Api.MediaArea.parse_mediaAreaChannelPost($0) }
dict[-544523486] = { return Api.MediaArea.parse_mediaAreaGeoPoint($0) }
dict[-891992787] = { return Api.MediaArea.parse_mediaAreaGeoPoint($0) }
dict[340088945] = { return Api.MediaArea.parse_mediaAreaSuggestedReaction($0) }
dict[926421125] = { return Api.MediaArea.parse_mediaAreaUrl($0) }
dict[-1098720356] = { return Api.MediaArea.parse_mediaAreaVenue($0) }
@ -1627,6 +1628,8 @@ public extension Api {
_1.serialize(buffer, boxed)
case let _1 as Api.GeoPoint:
_1.serialize(buffer, boxed)
case let _1 as Api.GeoPointAddress:
_1.serialize(buffer, boxed)
case let _1 as Api.GlobalPrivacySettings:
_1.serialize(buffer, boxed)
case let _1 as Api.GroupCall:

View file

@ -51,7 +51,7 @@ public extension Api {
case inputMediaAreaChannelPost(coordinates: Api.MediaAreaCoordinates, channel: Api.InputChannel, msgId: Int32)
case inputMediaAreaVenue(coordinates: Api.MediaAreaCoordinates, queryId: Int64, resultId: String)
case mediaAreaChannelPost(coordinates: Api.MediaAreaCoordinates, channelId: Int64, msgId: Int32)
case mediaAreaGeoPoint(coordinates: Api.MediaAreaCoordinates, geo: Api.GeoPoint)
case mediaAreaGeoPoint(flags: Int32, coordinates: Api.MediaAreaCoordinates, geo: Api.GeoPoint, address: Api.GeoPointAddress?)
case mediaAreaSuggestedReaction(flags: Int32, coordinates: Api.MediaAreaCoordinates, reaction: Api.Reaction)
case mediaAreaUrl(coordinates: Api.MediaAreaCoordinates, url: String)
case mediaAreaVenue(coordinates: Api.MediaAreaCoordinates, geo: Api.GeoPoint, title: String, address: String, provider: String, venueId: String, venueType: String)
@ -82,12 +82,14 @@ public extension Api {
serializeInt64(channelId, buffer: buffer, boxed: false)
serializeInt32(msgId, buffer: buffer, boxed: false)
break
case .mediaAreaGeoPoint(let coordinates, let geo):
case .mediaAreaGeoPoint(let flags, let coordinates, let geo, let address):
if boxed {
buffer.appendInt32(-544523486)
buffer.appendInt32(-891992787)
}
serializeInt32(flags, buffer: buffer, boxed: false)
coordinates.serialize(buffer, true)
geo.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {address!.serialize(buffer, true)}
break
case .mediaAreaSuggestedReaction(let flags, let coordinates, let reaction):
if boxed {
@ -127,8 +129,8 @@ public extension Api {
return ("inputMediaAreaVenue", [("coordinates", coordinates as Any), ("queryId", queryId as Any), ("resultId", resultId as Any)])
case .mediaAreaChannelPost(let coordinates, let channelId, let msgId):
return ("mediaAreaChannelPost", [("coordinates", coordinates as Any), ("channelId", channelId as Any), ("msgId", msgId as Any)])
case .mediaAreaGeoPoint(let coordinates, let geo):
return ("mediaAreaGeoPoint", [("coordinates", coordinates as Any), ("geo", geo as Any)])
case .mediaAreaGeoPoint(let flags, let coordinates, let geo, let address):
return ("mediaAreaGeoPoint", [("flags", flags as Any), ("coordinates", coordinates as Any), ("geo", geo as Any), ("address", address as Any)])
case .mediaAreaSuggestedReaction(let flags, let coordinates, let reaction):
return ("mediaAreaSuggestedReaction", [("flags", flags as Any), ("coordinates", coordinates as Any), ("reaction", reaction as Any)])
case .mediaAreaUrl(let coordinates, let url):
@ -198,18 +200,26 @@ public extension Api {
}
}
public static func parse_mediaAreaGeoPoint(_ reader: BufferReader) -> MediaArea? {
var _1: Api.MediaAreaCoordinates?
var _1: Int32?
_1 = reader.readInt32()
var _2: Api.MediaAreaCoordinates?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.MediaAreaCoordinates
_2 = Api.parse(reader, signature: signature) as? Api.MediaAreaCoordinates
}
var _2: Api.GeoPoint?
var _3: Api.GeoPoint?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.GeoPoint
_3 = Api.parse(reader, signature: signature) as? Api.GeoPoint
}
var _4: Api.GeoPointAddress?
if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() {
_4 = Api.parse(reader, signature: signature) as? Api.GeoPointAddress
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.MediaArea.mediaAreaGeoPoint(coordinates: _1!, geo: _2!)
let _c3 = _3 != nil
let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.MediaArea.mediaAreaGeoPoint(flags: _1!, coordinates: _2!, geo: _3!, address: _4)
}
else {
return nil

View file

@ -10394,16 +10394,15 @@ public extension Api.functions.stories {
}
}
public extension Api.functions.stories {
static func searchPosts(flags: Int32, hashtag: String?, venueProvider: String?, venueId: String?, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.stories.FoundStories>) {
static func searchPosts(flags: Int32, hashtag: String?, area: Api.MediaArea?, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.stories.FoundStories>) {
let buffer = Buffer()
buffer.appendInt32(1391183841)
buffer.appendInt32(1827279210)
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeString(hashtag!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 1) != 0 {serializeString(venueProvider!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 1) != 0 {serializeString(venueId!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 1) != 0 {area!.serialize(buffer, true)}
serializeString(offset, buffer: buffer, boxed: false)
serializeInt32(limit, buffer: buffer, boxed: false)
return (FunctionDescription(name: "stories.searchPosts", parameters: [("flags", String(describing: flags)), ("hashtag", String(describing: hashtag)), ("venueProvider", String(describing: venueProvider)), ("venueId", String(describing: venueId)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.FoundStories? in
return (FunctionDescription(name: "stories.searchPosts", parameters: [("flags", String(describing: flags)), ("hashtag", String(describing: hashtag)), ("area", String(describing: area)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.FoundStories? in
let reader = BufferReader(buffer)
var result: Api.stories.FoundStories?
if let signature = reader.readInt32() {

View file

@ -648,6 +648,58 @@ public extension Api {
}
}
public extension Api {
enum GeoPointAddress: TypeConstructorDescription {
case geoPointAddress(flags: Int32, countryIso2: String, state: String?, city: String?, street: String?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .geoPointAddress(let flags, let countryIso2, let state, let city, let street):
if boxed {
buffer.appendInt32(-565420653)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(countryIso2, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeString(state!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 1) != 0 {serializeString(city!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 2) != 0 {serializeString(street!, buffer: buffer, boxed: false)}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .geoPointAddress(let flags, let countryIso2, let state, let city, let street):
return ("geoPointAddress", [("flags", flags as Any), ("countryIso2", countryIso2 as Any), ("state", state as Any), ("city", city as Any), ("street", street as Any)])
}
}
public static func parse_geoPointAddress(_ reader: BufferReader) -> GeoPointAddress? {
var _1: Int32?
_1 = reader.readInt32()
var _2: String?
_2 = parseString(reader)
var _3: String?
if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) }
var _4: String?
if Int(_1!) & Int(1 << 1) != 0 {_4 = parseString(reader) }
var _5: String?
if Int(_1!) & Int(1 << 2) != 0 {_5 = parseString(reader) }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil
let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.GeoPointAddress.geoPointAddress(flags: _1!, countryIso2: _2!, state: _3, city: _4, street: _5)
}
else {
return nil
}
}
}
}
public extension Api {
enum GlobalPrivacySettings: TypeConstructorDescription {
case globalPrivacySettings(flags: Int32)
@ -1262,53 +1314,3 @@ public extension Api {
}
}
public extension Api {
enum InputAppEvent: TypeConstructorDescription {
case inputAppEvent(time: Double, type: String, peer: Int64, data: Api.JSONValue)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .inputAppEvent(let time, let type, let peer, let data):
if boxed {
buffer.appendInt32(488313413)
}
serializeDouble(time, buffer: buffer, boxed: false)
serializeString(type, buffer: buffer, boxed: false)
serializeInt64(peer, buffer: buffer, boxed: false)
data.serialize(buffer, true)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .inputAppEvent(let time, let type, let peer, let data):
return ("inputAppEvent", [("time", time as Any), ("type", type as Any), ("peer", peer as Any), ("data", data as Any)])
}
}
public static func parse_inputAppEvent(_ reader: BufferReader) -> InputAppEvent? {
var _1: Double?
_1 = reader.readDouble()
var _2: String?
_2 = parseString(reader)
var _3: Int64?
_3 = reader.readInt64()
var _4: Api.JSONValue?
if let signature = reader.readInt32() {
_4 = Api.parse(reader, signature: signature) as? Api.JSONValue
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.InputAppEvent.inputAppEvent(time: _1!, type: _2!, peer: _3!, data: _4!)
}
else {
return nil
}
}
}
}

View file

@ -1,3 +1,53 @@
public extension Api {
enum InputAppEvent: TypeConstructorDescription {
case inputAppEvent(time: Double, type: String, peer: Int64, data: Api.JSONValue)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .inputAppEvent(let time, let type, let peer, let data):
if boxed {
buffer.appendInt32(488313413)
}
serializeDouble(time, buffer: buffer, boxed: false)
serializeString(type, buffer: buffer, boxed: false)
serializeInt64(peer, buffer: buffer, boxed: false)
data.serialize(buffer, true)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .inputAppEvent(let time, let type, let peer, let data):
return ("inputAppEvent", [("time", time as Any), ("type", type as Any), ("peer", peer as Any), ("data", data as Any)])
}
}
public static func parse_inputAppEvent(_ reader: BufferReader) -> InputAppEvent? {
var _1: Double?
_1 = reader.readDouble()
var _2: String?
_2 = parseString(reader)
var _3: Int64?
_3 = reader.readInt64()
var _4: Api.JSONValue?
if let signature = reader.readInt32() {
_4 = Api.parse(reader, signature: signature) as? Api.JSONValue
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.InputAppEvent.inputAppEvent(time: _1!, type: _2!, peer: _3!, data: _4!)
}
else {
return nil
}
}
}
}
public extension Api {
indirect enum InputBotApp: TypeConstructorDescription {
case inputBotAppID(id: Int64, accessHash: Int64)
@ -1262,43 +1312,3 @@ public extension Api {
}
}
public extension Api {
enum InputClientProxy: TypeConstructorDescription {
case inputClientProxy(address: String, port: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .inputClientProxy(let address, let port):
if boxed {
buffer.appendInt32(1968737087)
}
serializeString(address, buffer: buffer, boxed: false)
serializeInt32(port, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .inputClientProxy(let address, let port):
return ("inputClientProxy", [("address", address as Any), ("port", port as Any)])
}
}
public static func parse_inputClientProxy(_ reader: BufferReader) -> InputClientProxy? {
var _1: String?
_1 = parseString(reader)
var _2: Int32?
_2 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.InputClientProxy.inputClientProxy(address: _1!, port: _2!)
}
else {
return nil
}
}
}
}

View file

@ -1,3 +1,43 @@
public extension Api {
enum InputClientProxy: TypeConstructorDescription {
case inputClientProxy(address: String, port: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .inputClientProxy(let address, let port):
if boxed {
buffer.appendInt32(1968737087)
}
serializeString(address, buffer: buffer, boxed: false)
serializeInt32(port, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .inputClientProxy(let address, let port):
return ("inputClientProxy", [("address", address as Any), ("port", port as Any)])
}
}
public static func parse_inputClientProxy(_ reader: BufferReader) -> InputClientProxy? {
var _1: String?
_1 = parseString(reader)
var _2: Int32?
_2 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.InputClientProxy.inputClientProxy(address: _1!, port: _2!)
}
else {
return nil
}
}
}
}
public extension Api {
enum InputCollectible: TypeConstructorDescription {
case inputCollectiblePhone(phone: String)

View file

@ -482,7 +482,7 @@ func mediaAreaFromApiMediaArea(_ mediaArea: Api.MediaArea) -> MediaArea? {
return nil
case .inputMediaAreaVenue:
return nil
case let .mediaAreaGeoPoint(coordinates, geo):
case let .mediaAreaGeoPoint(_, coordinates, geo, address):
let latitude: Double
let longitude: Double
switch geo {
@ -493,7 +493,21 @@ func mediaAreaFromApiMediaArea(_ mediaArea: Api.MediaArea) -> MediaArea? {
latitude = 0.0
longitude = 0.0
}
return .venue(coordinates: coodinatesFromApiMediaAreaCoordinates(coordinates), venue: MediaArea.Venue(latitude: latitude, longitude: longitude, venue: nil, queryId: nil, resultId: nil))
var mappedAddress: MediaArea.Address?
if let address {
switch address {
case let .geoPointAddress(_, countryIso2, state, city, street):
mappedAddress = MediaArea.Address(
countryIso2: countryIso2,
state: state,
city: city,
street: street
)
}
}
return .venue(coordinates: coodinatesFromApiMediaAreaCoordinates(coordinates), venue: MediaArea.Venue(latitude: latitude, longitude: longitude, venue: nil, queryId: nil, resultId: nil), address: mappedAddress)
case let .mediaAreaVenue(coordinates, geo, title, address, provider, venueId, venueType):
let latitude: Double
let longitude: Double
@ -505,7 +519,7 @@ func mediaAreaFromApiMediaArea(_ mediaArea: Api.MediaArea) -> MediaArea? {
latitude = 0.0
longitude = 0.0
}
return .venue(coordinates: coodinatesFromApiMediaAreaCoordinates(coordinates), venue: MediaArea.Venue(latitude: latitude, longitude: longitude, venue: MapVenue(title: title, address: address, provider: provider, id: venueId, type: venueType), queryId: nil, resultId: nil))
return .venue(coordinates: coodinatesFromApiMediaAreaCoordinates(coordinates), venue: MediaArea.Venue(latitude: latitude, longitude: longitude, venue: MapVenue(title: title, address: address, provider: provider, id: venueId, type: venueType), queryId: nil, resultId: nil), address: nil)
case let .mediaAreaSuggestedReaction(flags, coordinates, reaction):
if let reaction = MessageReaction.Reaction(apiReaction: reaction) {
var parsedFlags = MediaArea.ReactionFlags()
@ -526,19 +540,80 @@ func mediaAreaFromApiMediaArea(_ mediaArea: Api.MediaArea) -> MediaArea? {
}
}
func apiMediaAreaFromVenue(coordinates: MediaArea.Coordinates, venue: MediaArea.Venue, address: MediaArea.Address?) -> Api.MediaArea {
let inputCoordinates = Api.MediaAreaCoordinates.mediaAreaCoordinates(x: coordinates.x, y: coordinates.y, w: coordinates.width, h: coordinates.height, rotation: coordinates.rotation)
if let queryId = venue.queryId, let resultId = venue.resultId {
return .inputMediaAreaVenue(coordinates: inputCoordinates, queryId: queryId, resultId: resultId)
} else if let venueInfo = venue.venue {
return .mediaAreaVenue(coordinates: inputCoordinates, geo: .geoPoint(flags: 0, long: venue.longitude, lat: venue.latitude, accessHash: 0, accuracyRadius: nil), title: venueInfo.title, address: venueInfo.address ?? "", provider: venueInfo.provider ?? "", venueId: venueInfo.id ?? "", venueType: venueInfo.type ?? "")
} else {
var flags: Int32 = 0
var mappedAddress: Api.GeoPointAddress?
if let address {
flags |= 1 << 0
var addressFlags: Int32 = 0
if address.state != nil {
addressFlags |= 1 << 0
}
if address.city != nil {
addressFlags |= 1 << 1
}
if address.street != nil {
addressFlags |= 1 << 2
}
mappedAddress = .geoPointAddress(
flags: addressFlags,
countryIso2: address.countryIso2,
state: address.state,
city: address.city,
street: address.street
)
}
return .mediaAreaGeoPoint(flags: 0, coordinates: inputCoordinates, geo: .geoPoint(flags: flags, long: venue.longitude, lat: venue.latitude, accessHash: 0, accuracyRadius: nil), address: mappedAddress)
}
}
func apiMediaAreasFromMediaAreas(_ mediaAreas: [MediaArea], transaction: Transaction) -> [Api.MediaArea] {
var apiMediaAreas: [Api.MediaArea] = []
for area in mediaAreas {
let coordinates = area.coordinates
let inputCoordinates = Api.MediaAreaCoordinates.mediaAreaCoordinates(x: coordinates.x, y: coordinates.y, w: coordinates.width, h: coordinates.height, rotation: coordinates.rotation)
switch area {
case let .venue(_, venue):
case let .venue(_, venue, address):
if let queryId = venue.queryId, let resultId = venue.resultId {
apiMediaAreas.append(.inputMediaAreaVenue(coordinates: inputCoordinates, queryId: queryId, resultId: resultId))
} else if let venueInfo = venue.venue {
apiMediaAreas.append(.mediaAreaVenue(coordinates: inputCoordinates, geo: .geoPoint(flags: 0, long: venue.longitude, lat: venue.latitude, accessHash: 0, accuracyRadius: nil), title: venueInfo.title, address: venueInfo.address ?? "", provider: venueInfo.provider ?? "", venueId: venueInfo.id ?? "", venueType: venueInfo.type ?? ""))
} else {
apiMediaAreas.append(.mediaAreaGeoPoint(coordinates: inputCoordinates, geo: .geoPoint(flags: 0, long: venue.longitude, lat: venue.latitude, accessHash: 0, accuracyRadius: nil)))
var flags: Int32 = 0
var mappedAddress: Api.GeoPointAddress?
if let address {
flags |= 1 << 0
var addressFlags: Int32 = 0
if address.state != nil {
addressFlags |= 1 << 0
}
if address.city != nil {
addressFlags |= 1 << 1
}
if address.street != nil {
addressFlags |= 1 << 2
}
mappedAddress = .geoPointAddress(
flags: addressFlags,
countryIso2: address.countryIso2,
state: address.state,
city: address.city,
street: address.street
)
}
apiMediaAreas.append(.mediaAreaGeoPoint(flags: flags, coordinates: inputCoordinates, geo: .geoPoint(flags: 0, long: venue.longitude, lat: venue.latitude, accessHash: 0, accuracyRadius: nil), address: mappedAddress))
}
case let .reaction(_, reaction, flags):
var apiFlags: Int32 = 0

View file

@ -7,6 +7,7 @@ public enum MediaArea: Codable, Equatable {
case coordinates
case value
case flags
case address
}
public struct Coordinates: Codable, Equatable {
@ -122,10 +123,25 @@ public enum MediaArea: Codable, Equatable {
}
}
case venue(coordinates: Coordinates, venue: Venue)
public struct Address: Codable, Equatable {
public var countryIso2: String
public var state: String?
public var city: String?
public var street: String?
public init(countryIso2: String, state: String?, city: String?, street: String?) {
self.countryIso2 = countryIso2
self.state = state
self.city = city
self.street = street
}
}
case venue(coordinates: Coordinates, venue: Venue, address: Address?)
case reaction(coordinates: Coordinates, reaction: MessageReaction.Reaction, flags: ReactionFlags)
case channelMessage(coordinates: Coordinates, messageId: EngineMessage.Id)
case url(coordinates: Coordinates, url: String)
public struct ReactionFlags: OptionSet {
public var rawValue: Int32
@ -163,7 +179,8 @@ public enum MediaArea: Codable, Equatable {
case .venue:
let coordinates = try container.decode(MediaArea.Coordinates.self, forKey: .coordinates)
let venue = try container.decode(MediaArea.Venue.self, forKey: .value)
self = .venue(coordinates: coordinates, venue: venue)
let address = try container.decodeIfPresent(MediaArea.Address.self, forKey: .address)
self = .venue(coordinates: coordinates, venue: venue, address: address)
case .reaction:
let coordinates = try container.decode(MediaArea.Coordinates.self, forKey: .coordinates)
let reaction = try container.decode(MessageReaction.Reaction.self, forKey: .value)
@ -184,10 +201,11 @@ public enum MediaArea: Codable, Equatable {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case let .venue(coordinates, venue):
case let .venue(coordinates, venue, address):
try container.encode(MediaAreaType.venue.rawValue, forKey: .type)
try container.encode(coordinates, forKey: .coordinates)
try container.encode(venue, forKey: .value)
try container.encodeIfPresent(address, forKey: .address)
case let .reaction(coordinates, reaction, flags):
try container.encode(MediaAreaType.reaction.rawValue, forKey: .type)
try container.encode(coordinates, forKey: .coordinates)
@ -208,7 +226,7 @@ public enum MediaArea: Codable, Equatable {
public extension MediaArea {
var coordinates: Coordinates {
switch self {
case let .venue(coordinates, _):
case let .venue(coordinates, _, _):
return coordinates
case let .reaction(coordinates, _, _):
return coordinates

View file

@ -1263,7 +1263,7 @@ public final class SearchStoryListContext: StoryListContext {
public enum Source {
case hashtag(String)
case venue(provider: String, id: String)
case location(coordinates: MediaArea.Coordinates, venue: MediaArea.Venue, address: MediaArea.Address?)
}
private final class Impl {
@ -1327,8 +1327,7 @@ public final class SearchStoryListContext: StoryListContext {
let accountPeerId = account.peerId
var searchHashtag: String? = nil
var venueProvider: String? = nil
var venueId: String? = nil
var searchMediaArea: Api.MediaArea?
var flags: Int32 = 0
switch source {
@ -1339,13 +1338,12 @@ public final class SearchStoryListContext: StoryListContext {
searchHashtag = query
}
flags |= (1 << 0)
case .venue(let provider, let id):
venueProvider = provider
venueId = id
case let .location(coordinates, venue, address):
searchMediaArea = apiMediaAreaFromVenue(coordinates: coordinates, venue: venue, address: address)
flags |= (1 << 1)
}
self.requestDisposable = (account.network.request(Api.functions.stories.searchPosts(flags: flags, hashtag: searchHashtag, venueProvider: venueProvider, venueId: venueId, offset: "", limit: Int32(limit)))
self.requestDisposable = (account.network.request(Api.functions.stories.searchPosts(flags: flags, hashtag: searchHashtag, area: searchMediaArea, offset: loadMoreToken, limit: Int32(limit)))
|> map { result -> Api.stories.FoundStories? in
return result
}

View file

@ -118,7 +118,8 @@ public enum CodableDrawingEntity: Equatable {
venue: entity.location.venue,
queryId: entity.queryId,
resultId: entity.resultId
)
),
address: nil
)
case let .sticker(entity):
if case let .file(_, type) = entity.content, case let .reaction(reaction, style) = type {

View file

@ -419,7 +419,7 @@ private final class PeerInfoPendingPane {
}
}
let visualPaneNode = PeerInfoStoryPaneNode(context: context, peerId: peerId, contentType: .photoOrVideo, captureProtected: captureProtected, isSaved: false, isArchive: key == .storyArchive, isProfileEmbedded: true, canManageStories: canManage, navigationController: chatControllerInteraction.navigationController, listContext: key == .storyArchive ? data.storyArchiveListContext : data.storyListContext)
let visualPaneNode = PeerInfoStoryPaneNode(context: context, scope: .peer(id: peerId, isSaved: false, isArchived: key == .storyArchive), captureProtected: captureProtected, isProfileEmbedded: true, canManageStories: canManage, navigationController: chatControllerInteraction.navigationController, listContext: key == .storyArchive ? data.storyArchiveListContext : data.storyListContext)
paneNode = visualPaneNode
visualPaneNode.openCurrentDate = {
openMediaCalendar()

View file

@ -28,6 +28,8 @@ swift_library(
"//submodules/TelegramUI/Components/MoreHeaderButton",
"//submodules/TelegramUI/Components/MediaEditorScreen",
"//submodules/SaveToCameraRoll",
"//submodules/ShareController",
"//submodules/OpenInExternalAppUI",
],
visibility = [
"//visibility:public",

View file

@ -455,11 +455,8 @@ final class PeerInfoStoryGridScreenComponent: Component {
} else {
paneNode = PeerInfoStoryPaneNode(
context: component.context,
peerId: component.peerId,
contentType: .photoOrVideo,
scope: .peer(id: component.peerId, isSaved: true, isArchived: component.scope == .archive),
captureProtected: false,
isSaved: true,
isArchive: component.scope == .archive,
isProfileEmbedded: false,
canManageStories: true,
navigationController: { [weak self] in

View file

@ -16,29 +16,24 @@ import UndoUI
import MoreHeaderButton
import MediaEditorScreen
import SaveToCameraRoll
import ShareController
import OpenInExternalAppUI
final class StorySearchGridScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let searchQuery: String
let scope: StorySearchControllerScope
init(
context: AccountContext,
searchQuery: String
scope: StorySearchControllerScope
) {
self.context = context
self.searchQuery = searchQuery
self.scope = scope
}
static func ==(lhs: StorySearchGridScreenComponent, rhs: StorySearchGridScreenComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.searchQuery != rhs.searchQuery {
return false
}
return true
}
@ -99,14 +94,18 @@ final class StorySearchGridScreenComponent: Component {
if let current = self.paneNode {
paneNode = current
} else {
let paneNodeScope: PeerInfoStoryPaneNode.Scope
switch component.scope {
case let .query(query):
paneNodeScope = .search(query: query)
case let .location(coordinates, venue, address):
paneNodeScope = .location(coordinates: coordinates, venue: venue, address: address)
}
paneNode = PeerInfoStoryPaneNode(
context: component.context,
peerId: nil,
searchQuery: component.searchQuery,
contentType: .photoOrVideo,
scope: paneNodeScope,
captureProtected: false,
isSaved: false,
isArchive: false,
isProfileEmbedded: false,
canManageStories: false,
navigationController: { [weak self] in
@ -117,6 +116,7 @@ final class StorySearchGridScreenComponent: Component {
},
listContext: nil
)
paneNode.parentController = environment.controller()
paneNode.isEmptyUpdated = { [weak self] _ in
guard let self else {
return
@ -169,23 +169,31 @@ final class StorySearchGridScreenComponent: Component {
}
}
public class StorySearchGridScreen: ViewControllerComponentContainer {
public final class StorySearchGridScreen: ViewControllerComponentContainer {
private let context: AccountContext
private let searchQuery: String
private let scope: StorySearchControllerScope
private var isDismissed: Bool = false
private var titleView: ChatTitleView?
override public var additionalNavigationBarHeight: CGFloat {
if let componentView = self.node.hostView.componentView as? StorySearchGridScreenComponent.View, let paneNode = componentView.paneNode {
return paneNode.additionalNavigationHeight
} else {
return 0.0
}
}
public init(
context: AccountContext,
searchQuery: String
scope: StorySearchControllerScope
) {
self.context = context
self.searchQuery = searchQuery
self.scope = scope
super.init(context: context, component: StorySearchGridScreenComponent(
context: context,
searchQuery: searchQuery
scope: scope
), navigationBarAppearance: .default, theme: .default)
let presentationData = context.sharedContext.currentPresentationData.with({ $0 })
@ -203,6 +211,10 @@ public class StorySearchGridScreen: ViewControllerComponentContainer {
self.navigationItem.titleView = self.titleView
if case .location = scope {
self.navigationItem.setRightBarButton(UIBarButtonItem(image: PresentationResourcesRootController.navigationShareIcon(presentationData.theme), style: .plain, target: self, action: #selector(self.sharePressed)), animated: true)
}
self.updateTitle()
self.scrollToTop = { [weak self] in
@ -220,6 +232,28 @@ public class StorySearchGridScreen: ViewControllerComponentContainer {
deinit {
}
@objc private func sharePressed() {
guard case let .location(_, venue, _) = self.scope else {
return
}
let locationMap = TelegramMediaMap(latitude: venue.latitude, longitude: venue.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil, liveProximityNotificationRadius: nil)
let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 })
let shareAction = OpenInControllerAction(title: presentationData.strings.Conversation_ContextMenuShare, action: { [weak self] in
guard let self else {
return
}
self.present(ShareController(context: self.context, subject: .mapMedia(locationMap), externalShare: true), in: .window(.root), with: nil)
})
self.present(OpenInActionSheetController(context: self.context, updatedPresentationData: nil, item: .location(location: locationMap, directions: nil), additionalAction: shareAction, openUrl: { [weak self] url in
guard let self else {
return
}
self.context.sharedContext.applicationBindings.openUrl(url)
}), in: .window(.root), with: nil)
}
func updateTitle() {
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let _ = presentationData
@ -236,7 +270,12 @@ public class StorySearchGridScreen: ViewControllerComponentContainer {
title = nil
}
//TODO:localize
self.titleView?.titleContent = .custom("\(self.searchQuery)", title, false)
switch self.scope {
case let .query(query):
self.titleView?.titleContent = .custom("\(query)", title, false)
case .location:
self.titleView?.titleContent = .custom("Location", nil, false)
}
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {

View file

@ -44,6 +44,8 @@ swift_library(
"//submodules/TelegramUI/Components/PlainButtonComponent",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/TelegramUI/Components/MediaEditorScreen",
"//submodules/LocationUI",
"//submodules/Components/MultilineTextComponent",
],
visibility = [
"//visibility:public",

View file

@ -35,6 +35,11 @@ import PlainButtonComponent
import ComponentDisplayAdapters
import MediaEditorScreen
import AvatarNode
import LocationUI
import CoreLocation
import Geocoding
import ItemListUI
import MultilineTextComponent
private let mediaBadgeBackgroundColor = UIColor(white: 0.0, alpha: 0.6)
private let mediaBadgeTextColor = UIColor.white
@ -1064,6 +1069,7 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding {
var onTagTapImpl: (() -> Void)?
var didScrollImpl: (() -> Void)?
var coveringInsetOffsetUpdatedImpl: ((ContainedViewLayoutTransition) -> Void)?
var scrollingOffsetUpdatedImpl: ((ContainedViewLayoutTransition) -> Void)?
var onBeginFastScrollingImpl: (() -> Void)?
var getShimmerColorsImpl: (() -> SparseItemGrid.ShimmerColors)?
var updateShimmerLayersImpl: ((SparseItemGridDisplayItem) -> Void)?
@ -1333,6 +1339,10 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding {
func coveringInsetOffsetUpdated(transition: ContainedViewLayoutTransition) {
self.coveringInsetOffsetUpdatedImpl?(transition)
}
func scrollingOffsetUpdated(transition: ContainedViewLayoutTransition) {
self.scrollingOffsetUpdatedImpl?(transition)
}
func onBeginFastScrolling() {
self.onBeginFastScrollingImpl?()
@ -1347,13 +1357,97 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding {
}
}
public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScrollViewDelegate, ASGestureRecognizerDelegate {
public enum ContentType {
case photoOrVideo
case photo
case video
private final class StorySearchHeaderComponent: Component {
let theme: PresentationTheme
let strings: PresentationStrings
let count: Int
init(
theme: PresentationTheme,
strings: PresentationStrings,
count: Int
) {
self.theme = theme
self.strings = strings
self.count = count
}
static func ==(lhs: StorySearchHeaderComponent, rhs: StorySearchHeaderComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.strings != rhs.strings {
return false
}
if lhs.count != rhs.count {
return false
}
return true
}
final class View: UIView {
private let title = ComponentView<Empty>()
private var component: StorySearchHeaderComponent?
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(component: StorySearchHeaderComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
if self.component?.theme !== component.theme {
self.backgroundColor = component.theme.chatList.sectionHeaderFillColor
}
let insets = UIEdgeInsets(top: 7.0, left: 16.0, bottom: 7.0, right: 16.0)
//TODO:localize
let titleString: String
if component.count == 1 {
titleString = "1 STORY FROM THIS LOCATION"
} else {
titleString = "\(component.count) STORIES FROM THIS LOCATION"
}
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: titleString, font: Font.regular(13.0), textColor: component.theme.chatList.sectionHeaderTextColor))
)),
environment: {},
containerSize: CGSize(width: availableSize.width - insets.left - insets.right, height: 100.0)
)
if let titleView = self.title.view {
if titleView.superview == nil {
self.addSubview(titleView)
}
titleView.frame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: titleSize)
}
return CGSize(width: availableSize.width, height: titleSize.height + insets.top + insets.bottom)
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScrollViewDelegate, ASGestureRecognizerDelegate {
public enum Scope {
case peer(id: EnginePeer.Id, isSaved: Bool, isArchived: Bool)
case search(query: String)
case location(coordinates: MediaArea.Coordinates, venue: MediaArea.Venue, address: MediaArea.Address?)
}
public struct ZoomLevel {
fileprivate var value: SparseItemGrid.ZoomLevel
@ -1370,26 +1464,66 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
}
}
private struct MapInfoData: Equatable {
var location: TelegramMediaMap
var address: String?
var distance: Double?
var drivingTime: ExpectedTravelTime
var transitTime: ExpectedTravelTime
var walkingTime: ExpectedTravelTime
var hasEta: Bool
init(
location: TelegramMediaMap,
address: String?,
distance: Double?,
drivingTime: ExpectedTravelTime,
transitTime: ExpectedTravelTime,
walkingTime: ExpectedTravelTime,
hasEta: Bool
) {
self.location = location
self.address = address
self.distance = distance
self.drivingTime = drivingTime
self.transitTime = transitTime
self.walkingTime = walkingTime
self.hasEta = hasEta
}
}
private let context: AccountContext
private let peerId: PeerId?
private let searchQuery: String?
private let isSaved: Bool
private let isArchive: Bool
private let scope: Scope
private let isProfileEmbedded: Bool
private let canManageStories: Bool
public private(set) var contentType: ContentType
private var contentTypePromise: ValuePromise<ContentType>
private let navigationController: () -> NavigationController?
public weak var parentController: ViewController?
private let contextGestureContainerNode: ContextControllerSourceNode
private var mapOptionsNode: LocationOptionsNode?
private var mapNode: LocationMapHeaderNode?
private var mapDisposable: Disposable?
private var locationViewState: LocationViewState = LocationViewState() {
didSet {
self.locationViewStatePromise.set(.single(self.locationViewState))
}
}
private let locationViewStatePromise = Promise<LocationViewState>(LocationViewState())
private var mapInfoData: MapInfoData?
private var mapInfoNode: LocationInfoListItemNode?
private var searchHeader: ComponentView<Empty>?
private let itemGrid: SparseItemGrid
private let itemGridBinding: SparseItemGridBindingImpl
private let directMediaImageCache: DirectMediaImageCache
private var items: SparseItemGrid.Items?
private var pinnedIds: Set<Int32> = Set()
private var itemCount: Int?
private var didUpdateItemsOnce: Bool = false
private var selectionPanel: ComponentView<Empty>?
@ -1490,20 +1624,27 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
private weak var contextControllerToDismissOnSelection: ContextControllerProtocol?
private weak var tempContextContentItemNode: TempExtractedItemNode?
public var additionalNavigationHeight: CGFloat {
if self.locationViewState.displayingMapModeOptions {
return 38.0
}
return 0.0
}
public init(context: AccountContext, peerId: PeerId?, searchQuery: String? = nil, contentType: ContentType, captureProtected: Bool, isSaved: Bool, isArchive: Bool, isProfileEmbedded: Bool, canManageStories: Bool, navigationController: @escaping () -> NavigationController?, listContext: StoryListContext?) {
public init(context: AccountContext, scope: Scope, captureProtected: Bool, isProfileEmbedded: Bool, canManageStories: Bool, navigationController: @escaping () -> NavigationController?, listContext: StoryListContext?) {
self.context = context
self.peerId = peerId
self.searchQuery = searchQuery
self.contentType = contentType
self.contentTypePromise = ValuePromise<ContentType>(contentType)
self.scope = scope
self.navigationController = navigationController
self.isSaved = isSaved
self.isArchive = isArchive
self.isProfileEmbedded = isProfileEmbedded
self.canManageStories = canManageStories
self.isSelectionModeActive = !isProfileEmbedded && isArchive
switch scope {
case let .peer(_, _, isArchived):
self.isSelectionModeActive = !isProfileEmbedded && isArchived
default:
self.isSelectionModeActive = false
}
self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
@ -1520,16 +1661,21 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
if let listContext {
self.listSource = listContext
} else if let searchQuery {
self.listSource = SearchStoryListContext(account: context.account, source: .hashtag(searchQuery))
} else {
self.listSource = PeerStoryListContext(account: context.account, peerId: peerId ?? context.account.peerId, isArchived: self.isArchive)
switch self.scope {
case let .peer(id, _, isArchived):
self.listSource = PeerStoryListContext(account: context.account, peerId: id, isArchived: isArchived)
case let .search(query):
self.listSource = SearchStoryListContext(account: context.account, source: .hashtag(query))
case let .location(coordinates, venue, address):
self.listSource = SearchStoryListContext(account: context.account, source: .location(coordinates: coordinates, venue: venue, address: address))
}
}
self.calendarSource = nil
super.init()
if self.peerId != nil {
if case .peer = self.scope {
let _ = (ApplicationSpecificNotice.getSharedMediaScrollingTooltip(accountManager: context.sharedContext.accountManager)
|> deliverOnMainQueue).start(next: { [weak self] count in
guard let strongSelf = self else {
@ -1577,9 +1723,16 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
}
//TODO:localize
let listPeerId: EnginePeer.Id
switch self.scope {
case let .peer(id, _, _):
listPeerId = id
default:
listPeerId = self.context.account.peerId
}
let listContext = PeerStoryListContentContextImpl(
context: self.context,
peerId: self.peerId ?? self.context.account.peerId,
peerId: listPeerId,
listContext: self.listSource,
initialId: item.story.id
)
@ -1732,15 +1885,26 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
return
}
strongSelf.paneDidScroll?()
strongSelf.cancelPreviewGestures()
if strongSelf.locationViewState.displayingMapModeOptions {
strongSelf.locationViewState.displayingMapModeOptions = false
strongSelf.parentController?.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
}
}
self.itemGridBinding.coveringInsetOffsetUpdatedImpl = { [weak self] transition in
guard let strongSelf = self else {
guard let self else {
return
}
strongSelf.tabBarOffsetUpdated?(transition)
self.tabBarOffsetUpdated?(transition)
}
self.itemGridBinding.scrollingOffsetUpdatedImpl = { [weak self] transition in
guard let self else {
return
}
self.gridScrollingOffsetUpdated(transition: transition)
}
var processedOnBeginFastScrolling = false
@ -1841,7 +2005,6 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
}
}
)
//TODO:selection
if self.isSelectionModeActive {
self._itemInteraction?.selectedIds = Set()
}
@ -1850,6 +2013,41 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
self.contextGestureContainerNode.isGestureEnabled = self.isProfileEmbedded
self.contextGestureContainerNode.addSubnode(self.itemGrid)
self.addSubnode(self.contextGestureContainerNode)
if case .location = scope {
let mapNode = LocationMapHeaderNode(
presentationData: self.presentationData,
toggleMapModeSelection: { [weak self] in
guard let self else {
return
}
var state = self.locationViewState
state.displayingMapModeOptions = !state.displayingMapModeOptions
self.locationViewState = state
},
goToUserLocation: { [weak self] in
guard let self else {
return
}
var state = self.locationViewState
state.displayingMapModeOptions = false
state.selectedLocation = .user
switch state.trackingMode {
case .none:
state.trackingMode = .follow
case .follow:
state.trackingMode = .followWithHeading
case .followWithHeading:
state.trackingMode = .none
}
self.locationViewState = state
}
)
self.mapNode = mapNode
self.addSubnode(mapNode)
}
self.contextGestureContainerNode.shouldBegin = { [weak self] point in
guard let strongSelf = self else {
@ -1939,7 +2137,14 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
strongSelf.openContextMenu(item: story, itemLayer: itemLayer, rect: rect, gesture: gesture)
}
self.statusPromise.set(.single(PeerInfoStatusData(text: "", isActivity: false, key: self.isArchive ? .storyArchive : .stories)))
let paneKey: PeerInfoPaneKey
switch self.scope {
case let .peer(_, _, isArchived):
paneKey = isArchived ? .storyArchive : .stories
default:
paneKey = .stories
}
self.statusPromise.set(.single(PeerInfoStatusData(text: "", isActivity: false, key: paneKey)))
self.presentationDataDisposable = (self.context.sharedContext.presentationData
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
@ -1948,15 +2153,147 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
}
strongSelf.itemGridBinding.updatePresentationData(presentationData: presentationData)
strongSelf.itemGrid.updatePresentationData(theme: presentationData.theme)
strongSelf.mapOptionsNode?.updatePresentationData(presentationData)
})
self.requestHistoryAroundVisiblePosition(synchronous: false, reloadAtTop: false)
if peerId == context.account.peerId && !isArchive {
if case let .peer(id, _, isArchived) = self.scope, id == context.account.peerId, !isArchived {
self.preloadArchiveListContext = PeerStoryListContext(account: context.account, peerId: context.account.peerId, isArchived: true)
}
if case let .location(_, venue, _) = scope, let mapNode = self.mapNode {
let locationCoordinate = CLLocationCoordinate2D(latitude: venue.latitude, longitude: venue.longitude)
var initialMapState = LocationViewState()
initialMapState.selectedLocation = .coordinate(locationCoordinate, true)
self.locationViewStatePromise.set(.single(initialMapState))
let userLocation: Signal<CLLocation?, NoError> = .single(nil)
|> then(
throttledUserLocation(mapNode.mapNode.userLocation)
)
var eta: Signal<(ExpectedTravelTime, ExpectedTravelTime, ExpectedTravelTime), NoError> = .single((.calculating, .calculating, .calculating))
var address: Signal<String?, NoError> = .single(nil)
let locale = localeWithStrings(self.presentationData.strings)
eta = .single((.calculating, .calculating, .calculating))
|> then(combineLatest(queue: Queue.mainQueue(), getExpectedTravelTime(coordinate: locationCoordinate, transportType: .automobile), getExpectedTravelTime(coordinate: locationCoordinate, transportType: .transit), getExpectedTravelTime(coordinate: locationCoordinate, transportType: .walking))
|> mapToSignal { drivingTime, transitTime, walkingTime -> Signal<(ExpectedTravelTime, ExpectedTravelTime, ExpectedTravelTime), NoError> in
if case .calculating = drivingTime {
return .complete()
}
if case .calculating = transitTime {
return .complete()
}
if case .calculating = walkingTime {
return .complete()
}
return .single((drivingTime, transitTime, walkingTime))
})
/*if let venue = location.venue, let venueAddress = venue.address, !venueAddress.isEmpty {
address = .single(venueAddress)
} else*/ do {
address = .single(nil)
|> then(
reverseGeocodeLocation(latitude: locationCoordinate.latitude, longitude: locationCoordinate.longitude, locale: locale)
|> map { placemark -> String? in
return placemark?.compactDisplayAddress ?? ""
}
)
}
let previousState = Atomic<LocationViewState?>(value: nil)
let previousAnnotations = Atomic<[LocationPinAnnotation]>(value: [])
self.mapDisposable = (combineLatest(
context.sharedContext.presentationData,
self.locationViewStatePromise.get(),
mapNode.mapNode.userLocation,
userLocation,
address,
eta
)
|> deliverOnMainQueue).start(next: { [weak self] presentationData, state, userLocation, distance, address, eta in
guard let self, let mapNode = self.mapNode else {
return
}
let previousState = previousState.swap(state)
var annotations: [LocationPinAnnotation] = []
let subjectLocation = CLLocation(latitude: locationCoordinate.latitude, longitude: locationCoordinate.longitude)
let distance = userLocation.flatMap { subjectLocation.distance(from: $0) }
let locationMap = TelegramMediaMap(latitude: locationCoordinate.latitude, longitude: locationCoordinate.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil, liveProximityNotificationRadius: nil)
let mapInfoData = MapInfoData(
location: locationMap,
address: address,
distance: distance,
drivingTime: eta.0,
transitTime: eta.1,
walkingTime: eta.2,
hasEta: false
)
annotations.append(LocationPinAnnotation(context: context, theme: presentationData.theme, location: locationMap, queryId: nil, resultId: nil, forcedSelection: true))
mapNode.updateState(
mapMode: state.mapMode,
trackingMode: state.trackingMode,
displayingMapModeOptions: state.displayingMapModeOptions,
displayingPlacesButton: false,
proximityNotification: nil,
animated: false
)
mapNode.mapNode.trackingMode = state.trackingMode
let previousAnnotations = previousAnnotations.swap(annotations)
if annotations != previousAnnotations {
mapNode.mapNode.annotations = annotations
}
switch state.selectedLocation {
case .initial:
if previousState?.selectedLocation != .initial {
mapNode.mapNode.setMapCenter(coordinate: locationCoordinate, span: LocationMapNode.viewMapSpan, animated: previousState != nil)
}
case let .coordinate(coordinate, defaultSpan):
if let previousState = previousState, case let .coordinate(previousCoordinate, _) = previousState.selectedLocation, previousCoordinate == coordinate {
} else {
mapNode.mapNode.setMapCenter(
coordinate: coordinate,
span: defaultSpan ? LocationMapNode.defaultMapSpan : LocationMapNode.viewMapSpan,
animated: true
)
}
case .user:
if previousState?.selectedLocation != .user, let userLocation = userLocation {
mapNode.mapNode.setMapCenter(
coordinate: userLocation.coordinate,
isUserLocation: true,
animated: true
)
}
case .custom:
break
}
if self.mapInfoData != mapInfoData {
self.mapInfoData = mapInfoData
self.update(transition: .immediate)
} else if let previousState, previousState.displayingMapModeOptions != state.displayingMapModeOptions {
self.parentController?.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
}
})
}
}
deinit {
@ -1965,6 +2302,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
self.animationTimer?.invalidate()
self.presentationDataDisposable?.dispose()
self.updateDisposable.dispose()
self.mapDisposable?.dispose()
}
public func loadHole(anchor: SparseItemGrid.HoleAnchor, at location: SparseItemGrid.HoleLocation) -> Signal<Never, NoError> {
@ -1991,24 +2329,24 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
var items: [ContextMenuItem] = []
if canManage, let peerId = self.peerId {
items.append(.action(ContextMenuActionItem(text: !self.isArchive ? self.presentationData.strings.StoryList_ItemAction_Archive : self.presentationData.strings.StoryList_ItemAction_Unarchive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: self.isArchive ? "Chat/Context Menu/Archive" : "Chat/Context Menu/Unarchive"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
if canManage, case let .peer(peerId, _, isArchived) = self.scope {
items.append(.action(ContextMenuActionItem(text: !isArchived ? self.presentationData.strings.StoryList_ItemAction_Archive : self.presentationData.strings.StoryList_ItemAction_Unarchive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isArchived ? "Chat/Context Menu/Archive" : "Chat/Context Menu/Unarchive"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
guard let self else {
f(.default)
return
}
if self.isArchive {
if isArchived {
f(.default)
} else {
f(.dismissWithoutContent)
}
let _ = self.context.engine.messages.updateStoriesArePinned(peerId: peerId, ids: [item.id: item], isPinned: self.isArchive ? true : false).startStandalone()
self.parentController?.present(UndoOverlayController(presentationData: self.presentationData, content: .actionSucceeded(title: nil, text: self.isArchive ? self.presentationData.strings.StoryList_ToastUnarchived_Text(1) : self.presentationData.strings.StoryList_ToastArchived_Text(1), cancel: nil, destructive: false), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
let _ = self.context.engine.messages.updateStoriesArePinned(peerId: peerId, ids: [item.id: item], isPinned: isArchived ? true : false).startStandalone()
self.parentController?.present(UndoOverlayController(presentationData: self.presentationData, content: .actionSucceeded(title: nil, text: isArchived ? self.presentationData.strings.StoryList_ToastUnarchived_Text(1) : self.presentationData.strings.StoryList_ToastArchived_Text(1), cancel: nil, destructive: false), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
})))
if !self.isArchive {
if !isArchived {
let isPinned = self.pinnedIds.contains(item.id)
items.append(.action(ContextMenuActionItem(text: isPinned ? self.presentationData.strings.StoryList_ItemAction_Unpin : self.presentationData.strings.StoryList_ItemAction_Pin, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isPinned ? "Chat/Context Menu/Unpin" : "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { [weak self, weak itemLayer] _, f in
itemLayer?.isHidden = false
@ -2104,7 +2442,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
if !item.isForwardingDisabled, case .everyone = item.privacy?.base {
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.StoryList_ItemAction_Forward, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
c?.dismiss(completion: {
guard let self, let peerId = self.peerId else {
guard let self, case let .peer(peerId, _, _) = self.scope else {
return
}
@ -2206,9 +2544,6 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
parentController.presentInGlobalOverlay(contextController)
}
public func updateContentType(contentType: ContentType) {
}
public func updateZoomLevel(level: ZoomLevel) {
self.itemGrid.setZoomLevel(level: level.value)
@ -2257,16 +2592,25 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
let title: String
if state.totalCount == 0 {
title = ""
} else {
if self.isSaved {
} else if case let .peer(_, isSaved, isArchived) = self.scope {
if isSaved {
title = self.presentationData.strings.StoryList_SubtitleSaved(Int32(state.totalCount))
} else if self.isArchive {
} else if isArchived {
title = self.presentationData.strings.StoryList_SubtitleArchived(Int32(state.totalCount))
} else {
title = self.presentationData.strings.StoryList_SubtitleCount(Int32(state.totalCount))
}
} else {
title = ""
}
self.statusPromise.set(.single(PeerInfoStatusData(text: title, isActivity: false, key: self.isArchive ? .storyArchive : .stories)))
let paneKey: PeerInfoPaneKey
switch self.scope {
case let .peer(_, _, isArchived):
paneKey = isArchived ? .storyArchive : .stories
default:
paneKey = .stories
}
self.statusPromise.set(.single(PeerInfoStatusData(text: title, isActivity: false, key: paneKey)))
let timezoneOffset = Int32(TimeZone.current.secondsFromGMT())
@ -2309,8 +2653,10 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
}
var headerText: String?
if strongSelf.isArchive && !mappedItems.isEmpty && strongSelf.peerId == strongSelf.context.account.peerId {
headerText = strongSelf.presentationData.strings.StoryList_ArchiveDescription
if case let .peer(peerId, _, isArchived) = strongSelf.scope {
if isArchived && !mappedItems.isEmpty && peerId == strongSelf.context.account.peerId {
headerText = strongSelf.presentationData.strings.StoryList_ArchiveDescription
}
}
let items = SparseItemGrid.Items(
@ -2321,6 +2667,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
headerText: headerText,
snapTopInset: false
)
strongSelf.itemCount = state.totalCount
let currentSynchronous = synchronous && firstTime
let currentReloadAtTop = reloadAtTop && firstTime
@ -2649,7 +2997,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
}
private func presentDeleteConfirmation(ids: Set<Int32>) {
guard let peerId = self.peerId else {
guard case let .peer(peerId, _, _) = self.scope else {
return
}
@ -2683,11 +3031,187 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
self.parentController?.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}
private func update(transition: ContainedViewLayoutTransition) {
if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) = self.currentParams {
self.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: false, transition: transition)
}
}
private func gridScrollingOffsetUpdated(transition: ContainedViewLayoutTransition) {
if let _ = self.mapNode, let currentParams = self.currentParams {
self.updateMapLayout(size: currentParams.size, topInset: currentParams.topInset, deviceMetrics: currentParams.deviceMetrics, transition: transition)
}
}
private var effectiveMapHeight: CGFloat = 0.0
private func updateMapLayout(size: CGSize, topInset: CGFloat, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition) {
guard let mapNode = self.mapNode else {
return
}
let mapOverscrollInset: CGFloat = 200.0
var mapHeight = min(size.width, size.height)
mapHeight = min(mapHeight, floor(size.height * 0.389))
self.effectiveMapHeight = mapHeight - self.additionalNavigationHeight
let mapSize = CGSize(width: size.width, height: mapHeight + mapOverscrollInset)
let mapFrame = CGRect(origin: CGPoint(x: 0.0, y: topInset - mapOverscrollInset - self.itemGrid.scrollingOffset - self.additionalNavigationHeight), size: mapSize)
transition.updateFrame(node: mapNode, frame: mapFrame)
mapNode.updateLayout(
layout: ContainerViewLayout(
size: mapSize,
metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact, orientation: nil),
deviceMetrics: deviceMetrics,
intrinsicInsets: UIEdgeInsets(top: mapOverscrollInset, left: 0.0, bottom: 0.0, right: 0.0),
safeInsets: UIEdgeInsets(),
additionalInsets: UIEdgeInsets(),
statusBarHeight: nil,
inputHeight: nil,
inputHeightIsInteractivellyChanging: false,
inVoiceOver: false
),
navigationBarHeight: 0.0,
topPadding: mapOverscrollInset + self.additionalNavigationHeight,
offset: min(floorToScreenPixels(self.itemGrid.scrollingOffset * 0.5), mapSize.height),
size: mapSize,
transition: transition
)
if let mapInfoData = self.mapInfoData {
let mapInfoNode: LocationInfoListItemNode
if let current = self.mapInfoNode {
mapInfoNode = current
} else {
mapInfoNode = LocationInfoListItemNode()
mapInfoNode.isUserInteractionEnabled = false
self.mapInfoNode = mapInfoNode
mapNode.supernode?.insertSubnode(mapInfoNode, aboveSubnode: mapNode)
}
let addressString: String?
if let address = mapInfoData.address {
addressString = address
} else {
addressString = self.presentationData.strings.Map_Locating
}
let distanceString: String?
if let distance = mapInfoData.distance {
distanceString = distance < 10 ? self.presentationData.strings.Map_YouAreHere : self.presentationData.strings.Map_DistanceAway(stringForDistance(strings: self.presentationData.strings, distance: distance)).string
} else {
distanceString = nil
}
let item = LocationInfoListItem(
presentationData: ItemListPresentationData(self.presentationData),
engine: self.context.engine,
location: mapInfoData.location,
address: addressString,
distance: distanceString,
drivingTime: mapInfoData.drivingTime,
transitTime: mapInfoData.transitTime,
walkingTime: mapInfoData.walkingTime,
hasEta: mapInfoData.hasEta,
action: {},
drivingAction: {},
transitAction: {},
walkingAction: {}
)
let (mapInfoLayout, mapInfoReadyAndApply) = mapInfoNode.asyncLayout()(
item,
ListViewItemLayoutParams(width: size.width, leftInset: 0.0, rightInset: 0.0, availableHeight: 1000.0, isStandalone: true)
)
let mapInfoTopInset: CGFloat = -6.0
let mapInfoFrame = CGRect(origin: CGPoint(x: 0.0, y: mapFrame.maxY + mapInfoTopInset), size: mapInfoLayout.contentSize)
mapInfoNode.frame = mapInfoFrame
mapInfoReadyAndApply().1(ListViewItemApply(isOnScreen: true))
self.effectiveMapHeight += mapInfoLayout.contentSize.height + mapInfoTopInset
if let itemCount = self.itemCount, itemCount != 0 {
let searchHeader: ComponentView<Empty>
if let current = self.searchHeader {
searchHeader = current
} else {
searchHeader = ComponentView()
self.searchHeader = searchHeader
}
let searchHeaderSize = searchHeader.update(
transition: Transition(transition),
component: AnyComponent(StorySearchHeaderComponent(
theme: self.presentationData.theme,
strings: self.presentationData.strings,
count: itemCount
)),
environment: {},
containerSize: CGSize(width: size.width, height: 1000.0)
)
let searchHeaderFrame = CGRect(origin: CGPoint(x: 0.0, y: max(topInset, mapInfoFrame.maxY)), size: searchHeaderSize)
if let searchHeaderView = searchHeader.view {
if searchHeaderView.superview == nil {
self.view.addSubview(searchHeaderView)
}
transition.updateFrame(view: searchHeaderView, frame: searchHeaderFrame)
}
self.effectiveMapHeight += searchHeaderSize.height
} else {
if let searchHeader = self.searchHeader {
self.searchHeader = nil
searchHeader.view?.removeFromSuperview()
}
}
} else {
if let mapInfoNode = self.mapInfoNode {
self.mapInfoNode = nil
mapInfoNode.removeFromSupernode()
}
if let searchHeader = self.searchHeader {
self.searchHeader = nil
searchHeader.view?.removeFromSuperview()
}
}
}
public func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) {
self.currentParams = (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData)
var gridTopInset = topInset
if self.mapNode != nil {
self.updateMapLayout(size: size, topInset: topInset, deviceMetrics: deviceMetrics, transition: transition)
gridTopInset += self.effectiveMapHeight
let mapOptionsNode: LocationOptionsNode
if let current = self.mapOptionsNode {
mapOptionsNode = current
} else {
mapOptionsNode = LocationOptionsNode(presentationData: self.presentationData, hasBackground: false, updateMapMode: { [weak self] mode in
guard let self else {
return
}
var state = self.locationViewState
state.mapMode = mode
state.displayingMapModeOptions = false
self.locationViewState = state
})
mapOptionsNode.clipsToBounds = true
self.mapOptionsNode = mapOptionsNode
self.parentController?.navigationBar?.additionalContentNode.addSubnode(mapOptionsNode)
}
let mapOptionsFrame = CGRect(origin: CGPoint(x: 0.0, y: topInset - self.additionalNavigationHeight), size: CGSize(width: size.width, height: self.additionalNavigationHeight))
transition.updatePosition(node: mapOptionsNode, position: mapOptionsFrame.center)
transition.updateBounds(node: mapOptionsNode, bounds: CGRect(origin: CGPoint(x: 0.0, y: 38.0 - self.additionalNavigationHeight), size: mapOptionsFrame.size))
mapOptionsNode.updateLayout(size: mapOptionsFrame.size, leftInset: sideInset, rightInset: sideInset, transition: transition)
}
var bottomInset = bottomInset
if self.isProfileEmbedded, let selectedIds = self.itemInteraction.selectedIds, self.canManageStories, let peerId = self.peerId {
if self.isProfileEmbedded, let selectedIds = self.itemInteraction.selectedIds, self.canManageStories, case let .peer(peerId, _, isArchived) = self.scope {
let selectionPanel: ComponentView<Empty>
var selectionPanelTransition = Transition(transition)
if let current = self.selectionPanel {
@ -2757,7 +3281,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
selectionItems.append(BottomActionsPanelComponent.Item(
id: "archive",
color: .accent,
title: self.isArchive ? presentationData.strings.StoryList_ActionPanel_Unarchive : presentationData.strings.StoryList_ActionPanel_Archive,
title: isArchived ? presentationData.strings.StoryList_ActionPanel_Unarchive : presentationData.strings.StoryList_ActionPanel_Archive,
isEnabled: !selectedIds.isEmpty,
action: { [weak self] in
guard let self, let _ = self.itemInteraction.selectedIds else {
@ -2770,10 +3294,10 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
parentController.cancelItemSelection()
}
let _ = self.context.engine.messages.updateStoriesArePinned(peerId: peerId, ids: items, isPinned: self.isArchive ? true : false).startStandalone()
let _ = self.context.engine.messages.updateStoriesArePinned(peerId: peerId, ids: items, isPinned: isArchived ? true : false).startStandalone()
let text: String
if self.isArchive {
if isArchived {
text = presentationData.strings.StoryList_ToastUnarchived_Text(Int32(items.count))
} else {
text = presentationData.strings.StoryList_ToastArchived_Text(Int32(items.count))
@ -2823,7 +3347,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
transition.updateFrame(node: self.contextGestureContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
if self.searchQuery == nil, let items = self.items, items.items.isEmpty, items.count == 0 {
if case let .peer(_, _, isArchived) = self.scope, let items = self.items, items.items.isEmpty, items.count == 0 {
let emptyStateView: ComponentView<Empty>
var emptyStateTransition = Transition(transition)
if let current = self.emptyStateView {
@ -2840,16 +3364,16 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
theme: presentationData.theme,
fitToHeight: self.isProfileEmbedded,
animationName: "StoryListEmpty",
title: self.isArchive ? presentationData.strings.StoryList_ArchivedEmptyState_Title : presentationData.strings.StoryList_SavedEmptyPosts_Title,
text: self.isArchive ? presentationData.strings.StoryList_ArchivedEmptyState_Text : presentationData.strings.StoryList_SavedEmptyPosts_Text,
actionTitle: self.isArchive ? nil : presentationData.strings.StoryList_SavedAddAction,
title: isArchived ? presentationData.strings.StoryList_ArchivedEmptyState_Title : presentationData.strings.StoryList_SavedEmptyPosts_Title,
text: isArchived ? presentationData.strings.StoryList_ArchivedEmptyState_Text : presentationData.strings.StoryList_SavedEmptyPosts_Text,
actionTitle: isArchived ? nil : presentationData.strings.StoryList_SavedAddAction,
action: { [weak self] in
guard let self else {
return
}
self.emptyAction?()
},
additionalActionTitle: (self.isArchive || self.isProfileEmbedded) ? nil : presentationData.strings.StoryList_SavedEmptyAction,
additionalActionTitle: (isArchived || self.isProfileEmbedded) ? nil : presentationData.strings.StoryList_SavedEmptyAction,
additionalAction: { [weak self] in
guard let self else {
return
@ -2858,14 +3382,14 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
}
)),
environment: {},
containerSize: CGSize(width: size.width, height: size.height - topInset - bottomInset)
containerSize: CGSize(width: size.width, height: size.height - gridTopInset - bottomInset)
)
let emptyStateFrame: CGRect
if self.isProfileEmbedded {
emptyStateFrame = CGRect(origin: CGPoint(x: floor((size.width - emptyStateSize.width) * 0.5), y: max(topInset, floor((visibleHeight - topInset - bottomInset - emptyStateSize.height) * 0.5))), size: emptyStateSize)
emptyStateFrame = CGRect(origin: CGPoint(x: floor((size.width - emptyStateSize.width) * 0.5), y: max(gridTopInset, floor((visibleHeight - gridTopInset - bottomInset - emptyStateSize.height) * 0.5))), size: emptyStateSize)
} else {
emptyStateFrame = CGRect(origin: CGPoint(x: floor((size.width - emptyStateSize.width) * 0.5), y: topInset), size: emptyStateSize)
emptyStateFrame = CGRect(origin: CGPoint(x: floor((size.width - emptyStateSize.width) * 0.5), y: gridTopInset), size: emptyStateSize)
}
if let emptyStateComponentView = emptyStateView.view {
@ -2917,14 +3441,9 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
self.didUpdateItemsOnce = true
let fixedItemHeight: CGFloat?
let isList = false
switch self.contentType {
default:
fixedItemHeight = nil
}
fixedItemHeight = nil
let fixedItemAspect: CGFloat? = 0.81
let gridTopInset = topInset
self.itemGrid.pinchEnabled = items.count > 2
self.itemGrid.update(size: size, insets: UIEdgeInsets(top: gridTopInset, left: sideInset, bottom: bottomInset, right: sideInset), useSideInsets: !isList, scrollIndicatorInsets: UIEdgeInsets(top: 0.0, left: sideInset, bottom: bottomInset, right: sideInset), lockScrollingAtTop: isScrollingLockedAtTop, fixedItemHeight: fixedItemHeight, fixedItemAspect: fixedItemAspect, items: items, theme: self.itemGridBinding.chatPresentationData.theme.theme, synchronous: wasFirstTime ? .full : .none)

View file

@ -1014,6 +1014,9 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding, ListShimme
func coveringInsetOffsetUpdated(transition: ContainedViewLayoutTransition) {
self.coveringInsetOffsetUpdatedImpl?(transition)
}
func scrollingOffsetUpdated(transition: ContainedViewLayoutTransition) {
}
func onBeginFastScrolling() {
self.onBeginFastScrollingImpl?()

View file

@ -2919,7 +2919,7 @@ final class StoryItemSetContainerSendMessage {
}
if !hashtag.isEmpty {
if peerName == nil {
let searchController = component.context.sharedContext.makeStorySearchController(context: component.context, query: hashtag)
let searchController = component.context.sharedContext.makeStorySearchController(context: component.context, scope: .query(hashtag))
navigationController.pushViewController(searchController)
} else {
let searchController = component.context.sharedContext.makeHashtagSearchController(context: component.context, peer: peer.flatMap(EnginePeer.init), query: hashtag, all: true)
@ -3346,9 +3346,10 @@ final class StoryItemSetContainerSendMessage {
var actions: [ContextMenuAction] = []
switch mediaArea {
case let .venue(_, venue):
case let .venue(coordinates, venue, address):
let action = { [weak controller, weak view] in
let subject = EngineMessage(stableId: 0, stableVersion: 0, id: EngineMessage.Id(peerId: PeerId(0), namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [.geo(TelegramMediaMap(latitude: venue.latitude, longitude: venue.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: venue.venue, liveBroadcastingTimeout: nil, liveProximityNotificationRadius: nil))], peers: [:], associatedMessages: [:], associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
let _ = view
/*let subject = EngineMessage(stableId: 0, stableVersion: 0, id: EngineMessage.Id(peerId: PeerId(0), namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [.geo(TelegramMediaMap(latitude: venue.latitude, longitude: venue.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: venue.venue, liveBroadcastingTimeout: nil, liveProximityNotificationRadius: nil))], peers: [:], associatedMessages: [:], associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
let locationController = LocationViewController(
context: context,
updatedPresentationData: updatedPresentationData,
@ -3370,7 +3371,10 @@ final class StoryItemSetContainerSendMessage {
view?.updateIsProgressPaused()
})
}
controller?.push(locationController)
controller?.push(locationController)*/
let searchController = context.sharedContext.makeStorySearchController(context: context, scope: .location(coordinates: coordinates, venue: venue, address: address))
controller?.push(searchController)
}
if immediate {
action()

View file

@ -1908,8 +1908,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return HashtagSearchController(context: context, peer: peer, query: query, all: all)
}
public func makeStorySearchController(context: AccountContext, query: String) -> ViewController {
return StorySearchGridScreen(context: context, searchQuery: query)
public func makeStorySearchController(context: AccountContext, scope: StorySearchControllerScope) -> ViewController {
return StorySearchGridScreen(context: context, scope: scope)
}
public func makeMyStoriesController(context: AccountContext, isArchive: Bool) -> ViewController {