Initial invite requests implementation

This commit is contained in:
Ilya Laktyushin
2021-10-06 00:03:40 +04:00
parent da5e87c515
commit 4a12dcbb22
52 changed files with 2332 additions and 336 deletions

View File

@@ -37,6 +37,7 @@ private struct LocationViewTransaction {
let deletions: [ListViewDeleteItem]
let insertions: [ListViewInsertItem]
let updates: [ListViewUpdateItem]
let gotTravelTimes: Bool
}
private enum LocationViewEntryId: Hashable {
@@ -48,7 +49,7 @@ private enum LocationViewEntryId: Hashable {
private enum LocationViewEntry: Comparable, Identifiable {
case info(PresentationTheme, TelegramMediaMap, String?, Double?, Double?)
case toggleLiveLocation(PresentationTheme, String, String, Double?, Double?)
case liveLocation(PresentationTheme, PresentationDateTimeFormat, PresentationPersonNameOrder, Message, Double?, Int)
case liveLocation(PresentationTheme, PresentationDateTimeFormat, PresentationPersonNameOrder, Message, Double?, Double?, Double?, Double?, Int)
var stableId: LocationViewEntryId {
switch self {
@@ -56,7 +57,7 @@ private enum LocationViewEntry: Comparable, Identifiable {
return .info
case .toggleLiveLocation:
return .toggleLiveLocation
case let .liveLocation(_, _, _, message, _, _):
case let .liveLocation(_, _, _, message, _, _, _, _, _):
return .liveLocation(message.stableId)
}
}
@@ -75,8 +76,8 @@ private enum LocationViewEntry: Comparable, Identifiable {
} else {
return false
}
case let .liveLocation(lhsTheme, lhsDateTimeFormat, lhsNameDisplayOrder, lhsMessage, lhsDistance, lhsIndex):
if case let .liveLocation(rhsTheme, rhsDateTimeFormat, rhsNameDisplayOrder, rhsMessage, rhsDistance, rhsIndex) = rhs, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsNameDisplayOrder == rhsNameDisplayOrder, areMessagesEqual(lhsMessage, rhsMessage), lhsDistance == rhsDistance, lhsIndex == rhsIndex {
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
@@ -100,11 +101,11 @@ private enum LocationViewEntry: Comparable, Identifiable {
case .liveLocation:
return true
}
case let .liveLocation(_, _, _, _, _, lhsIndex):
case let .liveLocation(_, _, _, _, _, _, _, _, lhsIndex):
switch rhs {
case .info, .toggleLiveLocation:
return false
case let .liveLocation(_, _, _, _, _, rhsIndex):
case let .liveLocation(_, _, _, _, _, _, _, _, rhsIndex):
return lhsIndex < rhsIndex
}
}
@@ -125,11 +126,14 @@ private enum LocationViewEntry: Comparable, Identifiable {
} else {
distanceString = nil
}
let eta = time.flatMap { stringForEstimatedDuration(strings: presentationData.strings, eta: $0) }
var eta: String?
if let time = time, time < 60.0 * 60.0 * 10.0 {
eta = stringForEstimatedDuration(strings: presentationData.strings, time: time, format: { presentationData.strings.Map_DirectionsDriveEta($0).string })
}
return LocationInfoListItem(presentationData: ItemListPresentationData(presentationData), engine: context.engine, location: location, address: addressString, distance: distanceString, eta: eta, action: {
interaction?.goToCoordinate(location.coordinate)
}, getDirections: {
interaction?.requestDirections()
interaction?.requestDirections(location, nil, .driving)
})
case let .toggleLiveLocation(_, title, subtitle, beginTimstamp, timeout):
let beginTimeAndTimeout: (Double, Double)?
@@ -147,24 +151,40 @@ private enum LocationViewEntry: Comparable, Identifiable {
}, highlighted: { highlight in
interaction?.updateSendActionHighlight(highlight)
})
case let .liveLocation(_, dateTimeFormat, nameDisplayOrder, message, distance, _):
return LocationLiveListItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: context, message: message, distance: distance, action: {
case let .liveLocation(_, dateTimeFormat, nameDisplayOrder, message, distance, drivingTime, transitTime, walkingTime, _):
var title: String?
if let author = message.author {
title = EnginePeer(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: {})
}, 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)
}
})
}
}
}
private func preparedTransition(from fromEntries: [LocationViewEntry], to toEntries: [LocationViewEntry], context: AccountContext, presentationData: PresentationData, interaction: LocationViewInteraction?) -> LocationViewTransaction {
private func preparedTransition(from fromEntries: [LocationViewEntry], to toEntries: [LocationViewEntry], context: AccountContext, presentationData: PresentationData, interaction: LocationViewInteraction?, gotTravelTimes: Bool) -> LocationViewTransaction {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction), directionHint: nil) }
return LocationViewTransaction(deletions: deletions, insertions: insertions, updates: updates)
return LocationViewTransaction(deletions: deletions, insertions: insertions, updates: updates, gotTravelTimes: gotTravelTimes)
}
enum LocationViewLocation: Equatable {
@@ -217,6 +237,14 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
var reportedAnnotationsReady = false
var onAnnotationsReady: (() -> Void)?
private let travelDisposables = DisposableSet()
private var travelTimes: [EngineMessage.Id: (Double, Double?, Double?, Double?)] = [:] {
didSet {
self.travelTimesPromise.set(.single(self.travelTimes))
}
}
private let travelTimesPromise = Promise<[EngineMessage.Id: (Double, Double?, Double?, Double?)]>([:])
init(context: AccountContext, presentationData: PresentationData, subject: Message, interaction: LocationViewInteraction, locationManager: LocationManager) {
self.context = context
@@ -263,7 +291,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
if let location = getLocation(from: subject), location.liveBroadcastingTimeout == nil {
eta = .single(nil)
|> then(driveEta(coordinate: location.coordinate))
|> then(getExpectedTravelTime(coordinate: location.coordinate, transportType: .automobile))
if let venue = location.venue, let venueAddress = venue.address, !venueAddress.isEmpty {
address = .single(venueAddress)
@@ -304,13 +332,14 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
let previousUserAnnotation = Atomic<LocationPinAnnotation?>(value: nil)
let previousAnnotations = Atomic<[LocationPinAnnotation]>(value: [])
let previousEntries = Atomic<[LocationViewEntry]?>(value: nil)
let previousHadTravelTimes = Atomic<Bool>(value: false)
let selfPeer = context.account.postbox.transaction { transaction -> Peer? in
return transaction.getPeer(context.account.peerId)
}
self.disposable = (combineLatest(self.presentationDataPromise.get(), self.statePromise.get(), selfPeer, liveLocations, self.headerNode.mapNode.userLocation, userLocation, address, eta)
|> deliverOnMainQueue).start(next: { [weak self] presentationData, state, selfPeer, liveLocations, userLocation, distance, address, eta in
self.disposable = (combineLatest(self.presentationDataPromise.get(), self.statePromise.get(), selfPeer, liveLocations, self.headerNode.mapNode.userLocation, userLocation, address, eta, self.travelTimesPromise.get())
|> deliverOnMainQueue).start(next: { [weak self] presentationData, state, selfPeer, liveLocations, userLocation, distance, address, eta, travelTimes in
if let strongSelf = self, let location = getLocation(from: subject) {
var entries: [LocationViewEntry] = []
var annotations: [LocationPinAnnotation] = []
@@ -323,7 +352,10 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
var proximityNotificationRadius: Int32?
var index: Int = 0
var isLocationView = false
if location.liveBroadcastingTimeout == nil {
isLocationView = true
let subjectLocation = CLLocation(latitude: location.latitude, longitude: location.longitude)
let distance = userLocation.flatMap { subjectLocation.distance(from: $0) }
@@ -414,11 +446,45 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
let subjectLocation = CLLocation(latitude: location.latitude, longitude: location.longitude)
let distance = userLocation.flatMap { subjectLocation.distance(from: $0) }
let timestamp = CACurrentMediaTime()
if message.localTags.contains(.OutgoingLiveLocation), let selfPeer = selfPeer {
userAnnotation = LocationPinAnnotation(context: context, theme: presentationData.theme, message: message, selfPeer: selfPeer, isSelf: true, heading: location.heading)
} else {
var drivingTime: Double?
var transitTime: Double?
var walkingTime: Double?
if !isLocationView {
if let (previousTimestamp, maybeDrivingTime, maybeTransitTime, maybeWalkingTime) = travelTimes[message.id] {
drivingTime = maybeDrivingTime
transitTime = maybeTransitTime
walkingTime = maybeWalkingTime
if timestamp > previousTimestamp + 60.0 {
strongSelf.travelDisposables.add(combineLatest(queue: Queue.mainQueue(), getExpectedTravelTime(coordinate: location.coordinate, transportType: .automobile), getExpectedTravelTime(coordinate: location.coordinate, transportType: .transit), getExpectedTravelTime(coordinate: location.coordinate, transportType: .walking)).start(next: { [weak self] drivingTime, transitTime, walkingTime in
guard let strongSelf = self else {
return
}
let timestamp = CACurrentMediaTime()
var travelTimes = strongSelf.travelTimes
travelTimes[message.id] = (timestamp, drivingTime, transitTime, walkingTime)
strongSelf.travelTimes = travelTimes
}))
}
} else {
strongSelf.travelDisposables.add(combineLatest(queue: Queue.mainQueue(), getExpectedTravelTime(coordinate: location.coordinate, transportType: .automobile), getExpectedTravelTime(coordinate: location.coordinate, transportType: .transit), getExpectedTravelTime(coordinate: location.coordinate, transportType: .walking)).start(next: { [weak self] drivingTime, transitTime, walkingTime in
guard let strongSelf = self else {
return
}
let timestamp = CACurrentMediaTime()
var travelTimes = strongSelf.travelTimes
travelTimes[message.id] = (timestamp, drivingTime, transitTime, walkingTime)
strongSelf.travelTimes = travelTimes
}))
}
}
annotations.append(LocationPinAnnotation(context: context, theme: presentationData.theme, message: message, selfPeer: selfPeer, isSelf: message.author?.id == context.account.peerId, heading: location.heading))
entries.append(.liveLocation(presentationData.theme, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, message, distance, index))
entries.append(.liveLocation(presentationData.theme, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, message, distance, drivingTime, transitTime, walkingTime, index))
}
index += 1
}
@@ -441,8 +507,9 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
let previousEntries = previousEntries.swap(entries)
let previousState = previousState.swap(state)
let transition = preparedTransition(from: previousEntries ?? [], to: entries, context: context, presentationData: presentationData, interaction: strongSelf.interaction)
let previousHadTravelTimes = previousHadTravelTimes.swap(!travelTimes.isEmpty)
let transition = preparedTransition(from: previousEntries ?? [], to: entries, context: context, presentationData: presentationData, interaction: strongSelf.interaction, gotTravelTimes: !travelTimes.isEmpty && !previousHadTravelTimes)
strongSelf.enqueueTransition(transition)
strongSelf.headerNode.updateState(mapMode: state.mapMode, trackingMode: state.trackingMode, displayingMapModeOptions: state.displayingMapModeOptions, displayingPlacesButton: false, proximityNotification: proximityNotification, animated: false)
@@ -581,7 +648,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
deinit {
self.disposable?.dispose()
self.travelDisposables.dispose()
self.locationManager.manager.stopUpdatingHeading()
}
@@ -633,17 +700,20 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
self.enqueuedTransitions.remove(at: 0)
let scrollToItem: ListViewScrollToItem?
if !self.initialized, transition.insertions.count > 0 {
if (!self.initialized && transition.insertions.count > 0) || transition.gotTravelTimes {
var index: Int = 0
var offset: CGFloat = 0.0
if transition.insertions.count > 2 {
if transition.gotTravelTimes {
index = 1
offset = 0.0
} else if transition.insertions.count > 2 {
index = 2
offset = 40.0
} else if transition.insertions.count == 2 {
index = 1
}
scrollToItem = ListViewScrollToItem(index: index, position: .bottom(offset), animated: false, curve: .Default(duration: nil), directionHint: .Up)
scrollToItem = ListViewScrollToItem(index: index, position: .bottom(offset), animated: transition.gotTravelTimes, curve: .Default(duration: 0.3), directionHint: .Up)
self.initialized = true
} else {
scrollToItem = nil