Story locations

This commit is contained in:
Ilya Laktyushin
2023-07-31 20:19:11 +02:00
parent 487c01f448
commit 8805c0b7bd
38 changed files with 956 additions and 204 deletions

View File

@@ -35,10 +35,10 @@ private enum LocationPickerEntryId: Hashable {
}
private enum LocationPickerEntry: Comparable, Identifiable {
case location(PresentationTheme, String, String, TelegramMediaMap?, CLLocationCoordinate2D?)
case location(PresentationTheme, String, String, TelegramMediaMap?, Int64?, String?, CLLocationCoordinate2D?, String?)
case liveLocation(PresentationTheme, String, String, CLLocationCoordinate2D?)
case header(PresentationTheme, String)
case venue(PresentationTheme, TelegramMediaMap?, Int)
case venue(PresentationTheme, TelegramMediaMap?, Int64?, String?, Int)
case attribution(PresentationTheme, LocationAttribution)
var stableId: LocationPickerEntryId {
@@ -49,7 +49,7 @@ private enum LocationPickerEntry: Comparable, Identifiable {
return .liveLocation
case .header:
return .header
case let .venue(_, venue, index):
case let .venue(_, venue, _, _, index):
return .venue(venue?.venue?.id ?? "\(index)")
case .attribution:
return .attribution
@@ -58,8 +58,8 @@ private enum LocationPickerEntry: Comparable, Identifiable {
static func ==(lhs: LocationPickerEntry, rhs: LocationPickerEntry) -> Bool {
switch lhs {
case let .location(lhsTheme, lhsTitle, lhsSubtitle, lhsVenue, lhsCoordinate):
if case let .location(rhsTheme, rhsTitle, rhsSubtitle, rhsVenue, rhsCoordinate) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsVenue?.venue?.id == rhsVenue?.venue?.id, lhsCoordinate == rhsCoordinate {
case let .location(lhsTheme, lhsTitle, lhsSubtitle, lhsVenue, lhsQueryId, lhsResultId, lhsCoordinate, lhsName):
if case let .location(rhsTheme, rhsTitle, rhsSubtitle, rhsVenue, rhsQueryId, rhsResultId, rhsCoordinate, rhsName) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsVenue?.venue?.id == rhsVenue?.venue?.id, lhsQueryId == rhsQueryId && lhsResultId == rhsResultId, lhsCoordinate == rhsCoordinate, lhsName == rhsName {
return true
} else {
return false
@@ -76,8 +76,8 @@ private enum LocationPickerEntry: Comparable, Identifiable {
} else {
return false
}
case let .venue(lhsTheme, lhsVenue, lhsIndex):
if case let .venue(rhsTheme, rhsVenue, rhsIndex) = rhs, lhsTheme === rhsTheme, lhsVenue?.venue?.id == rhsVenue?.venue?.id, lhsIndex == rhsIndex {
case let .venue(lhsTheme, lhsVenue, lhsQueryId, lhsResultId, lhsIndex):
if case let .venue(rhsTheme, rhsVenue, rhsQueryId, rhsResultId, rhsIndex) = rhs, lhsTheme === rhsTheme, lhsVenue?.venue?.id == rhsVenue?.venue?.id, lhsQueryId == rhsQueryId && lhsResultId == rhsResultId, lhsIndex == rhsIndex {
return true
} else {
return false
@@ -114,11 +114,11 @@ private enum LocationPickerEntry: Comparable, Identifiable {
case .venue, .attribution:
return true
}
case let .venue(_, _, lhsIndex):
case let .venue(_, _, _, _, lhsIndex):
switch rhs {
case .location, .liveLocation, .header:
return false
case let .venue(_, _, rhsIndex):
case let .venue(_, _, _, _, rhsIndex):
return lhsIndex < rhsIndex
case .attribution:
return true
@@ -130,7 +130,7 @@ private enum LocationPickerEntry: Comparable, Identifiable {
func item(engine: TelegramEngine, presentationData: PresentationData, interaction: LocationPickerInteraction?) -> ListViewItem {
switch self {
case let .location(_, title, subtitle, venue, coordinate):
case let .location(_, title, subtitle, venue, queryId, resultId, coordinate, name):
let icon: LocationActionListItemIcon
if let venue = venue {
icon = .venue(venue)
@@ -139,9 +139,9 @@ private enum LocationPickerEntry: Comparable, Identifiable {
}
return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), engine: engine, title: title, subtitle: subtitle, icon: icon, beginTimeAndTimeout: nil, action: {
if let venue = venue {
interaction?.sendVenue(venue)
interaction?.sendVenue(venue, queryId, resultId)
} else if let coordinate = coordinate {
interaction?.sendLocation(coordinate)
interaction?.sendLocation(coordinate, name)
}
}, highlighted: { highlighted in
interaction?.updateSendActionHighlight(highlighted)
@@ -154,10 +154,10 @@ private enum LocationPickerEntry: Comparable, Identifiable {
})
case let .header(_, title):
return LocationSectionHeaderItem(presentationData: ItemListPresentationData(presentationData), title: title)
case let .venue(_, venue, _):
case let .venue(_, venue, queryId, resultId, _):
let venueType = venue?.venue?.type ?? ""
return ItemListVenueItem(presentationData: ItemListPresentationData(presentationData), engine: engine, venue: venue, style: .plain, action: venue.flatMap { venue in
return { interaction?.sendVenue(venue) }
return { interaction?.sendVenue(venue, queryId, resultId) }
}, infoAction: ["home", "work"].contains(venueType) ? {
interaction?.openHomeWorkInfo()
} : nil)
@@ -181,7 +181,7 @@ enum LocationPickerLocation: Equatable {
case none
case selecting
case location(CLLocationCoordinate2D, String?)
case venue(TelegramMediaMap)
case venue(TelegramMediaMap, Int64?, String?)
var isCustom: Bool {
switch self {
@@ -212,8 +212,8 @@ enum LocationPickerLocation: Equatable {
} else {
return false
}
case let .venue(lhsVenue):
if case let .venue(rhsVenue) = rhs, lhsVenue.venue?.id == rhsVenue.venue?.id {
case let .venue(lhsVenue, lhsQueryId, lhsResultId):
if case let .venue(rhsVenue, rhsQueryId, rhsResultId) = rhs, lhsVenue.venue?.id == rhsVenue.venue?.id, lhsQueryId == rhsQueryId, lhsResultId == rhsResultId {
return true
} else {
return false
@@ -227,6 +227,7 @@ struct LocationPickerState {
var mapMode: LocationMapMode
var displayingMapModeOptions: Bool
var selectedLocation: LocationPickerLocation
var address: String?
var forceSelection: Bool
var searchingVenuesAround: Bool
@@ -234,6 +235,7 @@ struct LocationPickerState {
self.mapMode = .map
self.displayingMapModeOptions = false
self.selectedLocation = .none
self.address = nil
self.forceSelection = false
self.searchingVenuesAround = false
}
@@ -281,6 +283,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
private var presentationData: PresentationData
private let presentationDataPromise: Promise<PresentationData>
private let mode: LocationPickerMode
private let source: LocationPickerController.Source
private let interaction: LocationPickerInteraction
private let locationManager: LocationManager
@@ -314,12 +317,13 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
var beganInteractiveDragging: () -> Void = {}
var locationAccessDeniedUpdated: (Bool) -> Void = { _ in }
init(controller: LocationPickerController, context: AccountContext, presentationData: PresentationData, mode: LocationPickerMode, interaction: LocationPickerInteraction, locationManager: LocationManager) {
init(controller: LocationPickerController, context: AccountContext, presentationData: PresentationData, mode: LocationPickerMode, source: LocationPickerController.Source, interaction: LocationPickerInteraction, locationManager: LocationManager) {
self.controller = controller
self.context = context
self.presentationData = presentationData
self.presentationDataPromise = Promise(presentationData)
self.mode = mode
self.source = source
self.interaction = interaction
self.locationManager = locationManager
@@ -445,24 +449,37 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
}
)
let venues: Signal<[TelegramMediaMap]?, NoError> = .single(nil)
let venues: Signal<([(TelegramMediaMap, String)], Int64)?, NoError> = .single(nil)
|> then(
throttledUserLocation(userLocation)
|> mapToSignal { location -> Signal<[TelegramMediaMap]?, NoError> in
|> mapToSignal { location -> Signal<([(TelegramMediaMap, String)], Int64)?, NoError> in
if let location = location, location.horizontalAccuracy > 0 {
return combineLatest(nearbyVenues(context: context, latitude: location.coordinate.latitude, longitude: location.coordinate.longitude), personalVenues)
|> map { nearbyVenues, personalVenues -> [TelegramMediaMap]? in
var resultVenues: [TelegramMediaMap] = []
return combineLatest(nearbyVenues(context: context, story: source == .story, latitude: location.coordinate.latitude, longitude: location.coordinate.longitude), personalVenues)
|> map { contextResult, personalVenues -> ([(TelegramMediaMap, String)], Int64)? in
var resultVenues: [(TelegramMediaMap, String)] = []
if let personalVenues = personalVenues {
for venue in personalVenues {
let venueLocation = CLLocation(latitude: venue.latitude, longitude: venue.longitude)
if venueLocation.distance(from: location) <= 1000 {
resultVenues.append(venue)
resultVenues.append((venue, ""))
}
}
}
resultVenues.append(contentsOf: nearbyVenues)
return resultVenues
if let contextResult {
for result in contextResult.results {
switch result.message {
case let .mapLocation(mapMedia, _):
if let _ = mapMedia.venue {
resultVenues.append((mapMedia, result.id))
}
default:
break
}
}
return (resultVenues, contextResult.queryId)
} else {
return (resultVenues, 0)
}
}
} else {
return .single(nil)
@@ -470,17 +487,32 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
}
)
let foundVenues: Signal<([TelegramMediaMap], CLLocation)?, NoError> = .single(nil)
let foundVenues: Signal<([(TelegramMediaMap, String)], Int64, CLLocation)?, NoError> = .single(nil)
|> then(
self.searchVenuesPromise.get()
|> distinctUntilChanged
|> mapToSignal { coordinate -> Signal<([TelegramMediaMap], CLLocation)?, NoError> in
|> mapToSignal { coordinate -> Signal<([(TelegramMediaMap, String)], Int64, CLLocation)?, NoError> in
if let coordinate = coordinate {
return (.single(nil)
|> then(
nearbyVenues(context: context, latitude: coordinate.latitude, longitude: coordinate.longitude)
|> map { venues -> ([TelegramMediaMap], CLLocation)? in
return (venues, CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude))
nearbyVenues(context: context, story: source == .story, latitude: coordinate.latitude, longitude: coordinate.longitude)
|> map { contextResult -> ([(TelegramMediaMap, String)], Int64, CLLocation)? in
if let contextResult {
var resultVenues: [(TelegramMediaMap, String)] = []
for result in contextResult.results {
switch result.message {
case let .mapLocation(mapMedia, _):
if let _ = mapMedia.venue {
resultVenues.append((mapMedia, result.id))
}
default:
break
}
}
return (resultVenues, contextResult.queryId, CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude))
} else {
return nil
}
}
))
} else {
@@ -497,29 +529,37 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
self.disposable = (combineLatest(self.presentationDataPromise.get(), self.statePromise.get(), userLocation, venues, foundVenues, self.locationContext.locationAccess())
|> deliverOnMainQueue).start(next: { [weak self] presentationData, state, userLocation, venues, foundVenuesAndLocation, access in
if let strongSelf = self {
let (foundVenues, foundVenuesLocation) = foundVenuesAndLocation ?? (nil, nil)
let (foundVenues, _, foundVenuesLocation) = foundVenuesAndLocation ?? (nil, nil, nil)
var entries: [LocationPickerEntry] = []
switch state.selectedLocation {
case let .location(coordinate, address):
let title: String
switch strongSelf.mode {
case .share:
if source == .story {
title = "Add This Location"
} else {
title = presentationData.strings.Map_SendThisLocation
}
case .pick:
title = presentationData.strings.Map_SetThisLocation
}
entries.append(.location(presentationData.theme, title, address ?? presentationData.strings.Map_Locating, nil, coordinate))
entries.append(.location(presentationData.theme, title, address ?? presentationData.strings.Map_Locating, nil, nil, nil, coordinate, state.address))
case .selecting:
let title: String
switch strongSelf.mode {
case .share:
if source == .story {
title = "Add This Location"
} else {
title = presentationData.strings.Map_SendThisLocation
}
case .pick:
title = presentationData.strings.Map_SetThisLocation
}
entries.append(.location(presentationData.theme, title, presentationData.strings.Map_Locating, nil, nil))
case let .venue(venue):
entries.append(.location(presentationData.theme, title, presentationData.strings.Map_Locating, nil, nil, nil, nil, nil))
case let .venue(venue, queryId, resultId):
let title: String
switch strongSelf.mode {
case .share:
@@ -527,16 +567,20 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
case .pick:
title = presentationData.strings.Map_SetThisPlace
}
entries.append(.location(presentationData.theme, title, venue.venue?.title ?? "", venue, venue.coordinate))
entries.append(.location(presentationData.theme, title, venue.venue?.title ?? "", venue, queryId, resultId, venue.coordinate, nil))
case .none:
let title: String
switch strongSelf.mode {
case .share:
if source == .story {
title = "Add My Current Location"
} else {
title = presentationData.strings.Map_SendMyCurrentLocation
}
case .pick:
title = presentationData.strings.Map_SetThisLocation
}
entries.append(.location(presentationData.theme, title, (userLocation?.horizontalAccuracy).flatMap { presentationData.strings.Map_AccurateTo(stringForDistance(strings: presentationData.strings, distance: $0)).string } ?? presentationData.strings.Map_Locating, nil, userLocation?.coordinate))
entries.append(.location(presentationData.theme, title, (userLocation?.horizontalAccuracy).flatMap { presentationData.strings.Map_AccurateTo(stringForDistance(strings: presentationData.strings, distance: $0)).string } ?? presentationData.strings.Map_Locating, nil, nil, nil, userLocation?.coordinate, state.address))
}
if case .share(_, _, true) = mode {
@@ -545,17 +589,26 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
entries.append(.header(presentationData.theme, presentationData.strings.Map_ChooseAPlace.uppercased()))
let displayedVenues = foundVenues != nil || state.searchingVenuesAround ? foundVenues : venues
let displayedVenues: [(TelegramMediaMap, String)]?
let queryId: Int64?
if foundVenues != nil || state.searchingVenuesAround {
displayedVenues = foundVenues
queryId = foundVenuesAndLocation?.1
} else {
displayedVenues = venues?.0
queryId = venues?.1
}
var index: Int = 0
if let venues = displayedVenues {
if let venues = displayedVenues, let queryId {
var attribution: LocationAttribution?
for venue in venues {
for (venue, resultId) in venues {
if venue.venue?.provider == "foursquare" {
attribution = .foursquare
} else if venue.venue?.provider == "gplaces" {
attribution = .google
}
entries.append(.venue(presentationData.theme, venue, index))
entries.append(.venue(presentationData.theme, venue, queryId, resultId, index))
index += 1
}
if let attribution = attribution {
@@ -563,7 +616,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
}
} else {
for _ in 0 ..< 8 {
entries.append(.venue(presentationData.theme, nil, index))
entries.append(.venue(presentationData.theme, nil, nil, nil, index))
index += 1
}
}
@@ -615,15 +668,15 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
}
}
}
case let .venue(venue):
case let .venue(venue, _, _):
strongSelf.headerNode.mapNode.setMapCenter(coordinate: venue.coordinate, hidePicker: true, animated: true)
}
strongSelf.headerNode.updateState(mapMode: state.mapMode, trackingMode: .none, displayingMapModeOptions: state.displayingMapModeOptions, displayingPlacesButton: displayingPlacesButton, proximityNotification: nil, animated: true)
let annotations: [LocationPinAnnotation]
if let venues = displayedVenues {
annotations = venues.compactMap { LocationPinAnnotation(context: context, theme: presentationData.theme, location: $0) }
if let venues = displayedVenues, let queryId {
annotations = venues.compactMap { LocationPinAnnotation(context: context, theme: presentationData.theme, location: $0.0, queryId: queryId, resultId: $0.1) }
} else {
annotations = []
}
@@ -671,21 +724,43 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
if address.isEmpty {
address = presentationData.strings.Map_Unknown
}
let name = placemark?.city ?? ""
strongSelf.updateState { state in
var state = state
state.selectedLocation = .location(coordinate, address)
state.address = name
return state
}
}
}))
} else {
strongSelf.geocodingDisposable.set(nil)
if case .none = state.selectedLocation, let location = userLocation, state.address == nil {
strongSelf.geocodingDisposable.set((reverseGeocodeLocation(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
|> deliverOnMainQueue).start(next: { [weak self] placemark in
if let strongSelf = self {
var address = placemark?.fullAddress ?? ""
if address.isEmpty {
address = presentationData.strings.Map_Unknown
}
let name = placemark?.city ?? ""
strongSelf.updateState { state in
var state = state
state.address = name
return state
}
}
}))
} else {
strongSelf.geocodingDisposable.set(nil)
}
}
}
})
if case let .share(_, selfPeer, _) = self.mode, let peer = selfPeer {
self.headerNode.mapNode.userLocationAnnotation = LocationPinAnnotation(context: context, theme: self.presentationData.theme, peer: peer)
if case let .share(_, selfPeer, _) = self.mode {
if let selfPeer {
self.headerNode.mapNode.userLocationAnnotation = LocationPinAnnotation(context: context, theme: self.presentationData.theme, peer: selfPeer)
}
self.headerNode.mapNode.hasPickerAnnotation = true
}
@@ -747,7 +822,9 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
strongSelf.updateState { state in
var state = state
state.displayingMapModeOptions = false
state.selectedLocation = annotation?.location.flatMap { .venue($0) } ?? .none
if let annotation, let location = annotation.location {
state.selectedLocation = .venue(location, annotation.queryId, annotation.resultId)
}
if annotation == nil {
state.searchingVenuesAround = false
}
@@ -831,7 +908,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
return .complete()
}
let searchContainerNode = LocationSearchContainerNode(context: self.context, coordinate: coordinate, interaction: self.interaction)
let searchContainerNode = LocationSearchContainerNode(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, coordinate: coordinate, interaction: self.interaction, story: self.source == .story)
self.insertSubnode(searchContainerNode, belowSubnode: navigationBar)
self.searchContainerNode = searchContainerNode