Live location improvements

This commit is contained in:
Ilya Laktyushin 2024-04-16 18:42:21 +04:00
parent a736b22baa
commit 7357c59707
13 changed files with 258 additions and 98 deletions

View File

@ -12085,4 +12085,7 @@ Sorry for the inconvenience.";
"Map.LiveLocationIndefinite" = "Until I turn it off";
"Map.TapToAddTime" = "tap to add time";
"Map.SharingLocation" = "Sharing Location...";
"Channel.AdminLog.Settings" = "Settings";

View File

@ -4,11 +4,18 @@ import AsyncDisplayKit
import Markdown
public class ActionSheetTextItem: ActionSheetItem {
public enum Font {
case `default`
case large
}
public let title: String
public let font: Font
public let parseMarkdown: Bool
public init(title: String, parseMarkdown: Bool = true) {
public init(title: String, font: Font = .default, parseMarkdown: Bool = true) {
self.title = title
self.font = font
self.parseMarkdown = parseMarkdown
}
@ -30,8 +37,6 @@ public class ActionSheetTextItem: ActionSheetItem {
}
public class ActionSheetTextNode: ActionSheetItemNode {
private let defaultFont: UIFont
private let theme: ActionSheetControllerTheme
private var item: ActionSheetTextItem?
@ -42,7 +47,6 @@ public class ActionSheetTextNode: ActionSheetItemNode {
override public init(theme: ActionSheetControllerTheme) {
self.theme = theme
self.defaultFont = Font.regular(floor(theme.baseFontSize * 13.0 / 17.0))
self.label = ImmediateTextNode()
self.label.isUserInteractionEnabled = false
@ -66,8 +70,16 @@ public class ActionSheetTextNode: ActionSheetItemNode {
func setItem(_ item: ActionSheetTextItem) {
self.item = item
let defaultFont = Font.regular(floor(theme.baseFontSize * 13.0 / 17.0))
let boldFont = Font.semibold(floor(theme.baseFontSize * 13.0 / 17.0))
let fontSize: CGFloat
switch item.font {
case .default:
fontSize = 13.0
case .large:
fontSize = 15.0
}
let defaultFont = Font.regular(floor(self.theme.baseFontSize * fontSize / 17.0))
let boldFont = Font.semibold(floor(self.theme.baseFontSize * fontSize / 17.0))
if item.parseMarkdown {
let body = MarkdownAttributeSet(font: defaultFont, textColor: self.theme.secondaryTextColor)

View File

@ -68,14 +68,18 @@ public final class LiveLocationManagerImpl: LiveLocationManager {
for media in message.media {
if let telegramMap = media as? TelegramMediaMap {
if let liveBroadcastingTimeout = telegramMap.liveBroadcastingTimeout {
if message.timestamp + liveBroadcastingTimeout > timestamp {
if liveBroadcastingTimeout == liveLocationIndefinitePeriod || message.timestamp + liveBroadcastingTimeout > timestamp {
activeLiveBroadcastingTimeout = liveBroadcastingTimeout
}
}
}
}
if let activeLiveBroadcastingTimeout = activeLiveBroadcastingTimeout {
broadcastToMessageIds[message.id] = message.timestamp + activeLiveBroadcastingTimeout
if activeLiveBroadcastingTimeout == liveLocationIndefinitePeriod {
broadcastToMessageIds[message.id] = activeLiveBroadcastingTimeout
} else {
broadcastToMessageIds[message.id] = message.timestamp + activeLiveBroadcastingTimeout
}
} else {
stopMessageIds.insert(message.id)
}

View File

@ -4,6 +4,7 @@ import AsyncDisplayKit
import Display
import TelegramPresentationData
private let infinityFont = Font.with(size: 15.0, design: .round, weight: .bold)
private let textFont = Font.with(size: 13.0, design: .round, weight: .bold)
private let smallTextFont = Font.with(size: 11.0, design: .round, weight: .bold)
@ -72,14 +73,19 @@ public final class ChatMessageLiveLocationTimerNode: ASDisplayNode {
let remaining = beginTimestamp + timeout - timestamp
value = CGFloat(max(0.0, 1.0 - min(1.0, remaining / timeout)))
let intRemaining = Int32(remaining)
let string: String
if intRemaining > 60 * 60 {
let hours = Int32(round(remaining / (60.0 * 60.0)))
string = strings.Map_LiveLocationShortHour("\(hours)").string
if timeout < 0.0 {
value = 0.0
string = ""
} else {
let minutes = Int32(round(remaining / (60.0)))
string = "\(minutes)"
let intRemaining = Int32(remaining)
if intRemaining > 60 * 60 {
let hours = Int32(round(remaining / (60.0 * 60.0)))
string = strings.Map_LiveLocationShortHour("\(hours)").string
} else {
let minutes = Int32(round(remaining / (60.0)))
string = "\(minutes)"
}
}
return ChatMessageLiveLocationTimerNodeParams(backgroundColor: backgroundColor, foregroundColor: foregroundColor, textColor: textColor, value: value, string: string)
@ -120,16 +126,27 @@ public final class ChatMessageLiveLocationTimerNode: ASDisplayNode {
path.lineCapStyle = .round
path.stroke()
let attributes: [NSAttributedString.Key: Any] = [.font: parameters.string.count > 2 ? smallTextFont : textFont, .foregroundColor: parameters.foregroundColor]
let font: UIFont
if parameters.string == "" {
font = infinityFont
} else if parameters.string.count > 2 {
font = smallTextFont
} else {
font = textFont
}
let attributes: [NSAttributedString.Key: Any] = [.font: font, .foregroundColor: parameters.foregroundColor]
let nsString = parameters.string as NSString
let size = nsString.size(withAttributes: attributes)
var offset: CGFloat = 0.0
if parameters.string.count > 2 {
offset = UIScreenPixel
var offset = CGPoint()
if parameters.string == "" {
offset = CGPoint(x: 1.0, y: -1.0)
} else if parameters.string.count > 2 {
offset = CGPoint(x: 0.0, y: UIScreenPixel)
}
nsString.draw(at: CGPoint(x: floor((bounds.size.width - size.width) / 2.0), y: floor((bounds.size.height - size.height) / 2.0) + offset), withAttributes: attributes)
nsString.draw(at: CGPoint(x: floor((bounds.size.width - size.width) / 2.0) + offset.x, y: floor((bounds.size.height - size.height) / 2.0) + offset.y), withAttributes: attributes)
}
}
}

View File

@ -5,10 +5,12 @@ import AsyncDisplayKit
private final class LiveLocationWavesNodeParams: NSObject {
let color: UIColor
let extend: Bool
let progress: CGFloat
init(color: UIColor, progress: CGFloat) {
init(color: UIColor, extend: Bool, progress: CGFloat) {
self.color = color
self.extend = extend
self.progress = progress
super.init()
@ -26,6 +28,12 @@ public final class LiveLocationWavesNode: ASDisplayNode {
}
}
public var extend: Bool {
didSet {
self.setNeedsDisplay()
}
}
private var effectiveProgress: CGFloat = 0.0 {
didSet {
self.setNeedsDisplay()
@ -34,8 +42,9 @@ public final class LiveLocationWavesNode: ASDisplayNode {
var animator: ConstantDisplayLinkAnimator?
public init(color: UIColor) {
public init(color: UIColor, extend: Bool = false) {
self.color = color
self.extend = extend
super.init()
@ -105,7 +114,7 @@ public final class LiveLocationWavesNode: ASDisplayNode {
public override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
let t = CACurrentMediaTime()
let value: CGFloat = CGFloat(t.truncatingRemainder(dividingBy: 2.0)) / 2.0
return LiveLocationWavesNodeParams(color: self.color, progress: value)
return LiveLocationWavesNodeParams(color: self.color, extend: self.extend, progress: value)
}
@objc public override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
@ -142,7 +151,9 @@ public final class LiveLocationWavesNode: ASDisplayNode {
}
context.setAlpha(alpha * 0.7)
draw(context, position, false)
if !parameters.extend {
draw(context, position, false)
}
draw(context, position, true)
var progress = parameters.progress + 0.5
@ -157,7 +168,9 @@ public final class LiveLocationWavesNode: ASDisplayNode {
}
context.setAlpha(largerAlpha * 0.7)
draw(context, largerPos, false)
if !parameters.extend {
draw(context, largerPos, false)
}
draw(context, largerPos, true)
}
}

View File

@ -15,6 +15,7 @@ public enum LocationActionListItemIcon: Equatable {
case location
case liveLocation
case stopLiveLocation
case extendLiveLocation
case venue(TelegramMediaMap)
public static func ==(lhs: LocationActionListItemIcon, rhs: LocationActionListItemIcon) -> Bool {
@ -37,6 +38,12 @@ public enum LocationActionListItemIcon: Equatable {
} else {
return false
}
case .extendLiveLocation:
if case .extendLiveLocation = rhs {
return true
} else {
return false
}
case let .venue(lhsVenue):
if case let .venue(rhsVenue) = rhs, lhsVenue.venue?.id == rhsVenue.venue?.id {
return true
@ -63,18 +70,67 @@ private func generateLocationIcon(theme: PresentationTheme) -> UIImage {
})!
}
private func generateLiveLocationIcon(theme: PresentationTheme, stop: Bool) -> UIImage {
private enum LiveLocationIconType {
case start
case stop
case extend
}
private func generateLiveLocationIcon(theme: PresentationTheme, type: LiveLocationIconType) -> UIImage {
return generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in
let imageName: String
let color: UIColor
switch type {
case .start:
imageName = "Location/SendLiveLocationIcon"
color = UIColor(rgb: 0x6cc139)
case .stop:
imageName = "Location/SendLocationIcon"
color = UIColor(rgb: 0xff6464)
case .extend:
imageName = "Location/SendLocationIcon"
color = UIColor(rgb: 0x6cc139)
}
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor(rgb: stop ? 0xff6464 : 0x6cc139).cgColor)
context.setFillColor(color.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
if let image = generateTintedImage(image: UIImage(bundleImageName: stop ? "Location/SendLocationIcon" : "Location/SendLiveLocationIcon"), color: theme.chat.inputPanel.actionControlForegroundColor) {
if let image = generateTintedImage(image: UIImage(bundleImageName: imageName), color: theme.chat.inputPanel.actionControlForegroundColor) {
context.draw(image.cgImage!, in: CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size))
if case .extend = type {
context.setStrokeColor(theme.chat.inputPanel.actionControlForegroundColor.cgColor)
context.setLineWidth(2.0 - UIScreenPixel)
context.setLineCap(.round)
let length: CGFloat = 6.0 + UIScreenPixel
context.move(to: CGPoint(x: 8.0 + 0.0, y: image.size.height / 2.0))
context.addLine(to: CGPoint(x: 8.0 + length, y: image.size.height / 2.0))
context.strokePath()
context.move(to: CGPoint(x: 8.0 + length / 2.0, y: image.size.height / 2.0 - length / 2.0))
context.addLine(to: CGPoint(x: 8.0 + length / 2.0, y: image.size.height / 2.0 + length / 2.0))
context.strokePath()
} else if case .stop = type {
context.setStrokeColor(color.cgColor)
context.setLineWidth(5.0)
context.move(to: CGPoint(x: 10.0, y: size.height - 10.0))
context.addLine(to: CGPoint(x: size.width - 10.0, y: 10.0))
context.strokePath()
context.setStrokeColor(theme.chat.inputPanel.actionControlForegroundColor.cgColor)
context.setLineWidth(2.0 - UIScreenPixel)
context.setLineCap(.round)
context.move(to: CGPoint(x: 12.0, y: size.height - 12.0))
context.addLine(to: CGPoint(x: size.width - 12.0, y: 12.0))
context.strokePath()
}
}
})!
}
@ -273,10 +329,18 @@ final class LocationActionListItemNode: ListViewItemNode {
strongSelf.iconNode.isHidden = false
strongSelf.venueIconNode.isHidden = true
strongSelf.iconNode.image = generateLocationIcon(theme: item.presentationData.theme)
case .liveLocation, .stopLiveLocation:
case .liveLocation:
strongSelf.iconNode.isHidden = false
strongSelf.venueIconNode.isHidden = true
strongSelf.iconNode.image = generateLiveLocationIcon(theme: item.presentationData.theme, stop: updatedIcon == .stopLiveLocation)
strongSelf.iconNode.image = generateLiveLocationIcon(theme: item.presentationData.theme, type: .start)
case .stopLiveLocation:
strongSelf.iconNode.isHidden = false
strongSelf.venueIconNode.isHidden = true
strongSelf.iconNode.image = generateLiveLocationIcon(theme: item.presentationData.theme, type: .stop)
case .extendLiveLocation:
strongSelf.iconNode.isHidden = false
strongSelf.venueIconNode.isHidden = true
strongSelf.iconNode.image = generateLiveLocationIcon(theme: item.presentationData.theme, type: .extend)
case let .venue(venue):
strongSelf.iconNode.isHidden = true
strongSelf.venueIconNode.isHidden = false
@ -293,13 +357,14 @@ final class LocationActionListItemNode: ListViewItemNode {
strongSelf.venueIconNode.setSignal(venueIcon(engine: item.engine, type: type ?? "", flag: flag, background: true))
}
if updatedIcon == .stopLiveLocation {
let wavesNode = LiveLocationWavesNode(color: item.presentationData.theme.chat.inputPanel.actionControlForegroundColor)
switch updatedIcon {
case .stopLiveLocation, .extendLiveLocation:
let wavesNode = LiveLocationWavesNode(color: item.presentationData.theme.chat.inputPanel.actionControlForegroundColor, extend: updatedIcon == .extendLiveLocation)
strongSelf.addSubnode(wavesNode)
strongSelf.wavesNode = wavesNode
} else if let wavesNode = strongSelf.wavesNode {
default:
strongSelf.wavesNode?.removeFromSupernode()
strongSelf.wavesNode = nil
wavesNode.removeFromSupernode()
}
strongSelf.wavesNode?.color = item.presentationData.theme.chat.inputPanel.actionControlForegroundColor
}
@ -349,7 +414,7 @@ final class LocationActionListItemNode: ListViewItemNode {
strongSelf.timerNode = timerNode
}
let timerSize = CGSize(width: 28.0, height: 28.0)
timerNode.update(backgroundColor: item.presentationData.theme.list.itemAccentColor.withAlphaComponent(0.4), foregroundColor: item.presentationData.theme.list.itemAccentColor, textColor: item.presentationData.theme.list.itemAccentColor, beginTimestamp: beginTimestamp, timeout: timeout, strings: item.presentationData.strings)
timerNode.update(backgroundColor: item.presentationData.theme.list.itemAccentColor.withAlphaComponent(0.4), foregroundColor: item.presentationData.theme.list.itemAccentColor, textColor: item.presentationData.theme.list.itemAccentColor, beginTimestamp: beginTimestamp, timeout: Int32(timeout) == liveLocationIndefinitePeriod ? -1.0 : timeout, strings: item.presentationData.strings)
timerNode.frame = CGRect(origin: CGPoint(x: contentSize.width - 16.0 - timerSize.width, y: floorToScreenPixels((contentSize.height - timerSize.height) / 2.0) - 2.0), size: timerSize)
} else if let timerNode = strongSelf.timerNode {
strongSelf.timerNode = nil

View File

@ -142,33 +142,34 @@ public final class LocationPickerController: ViewController, AttachmentContainab
return
}
let controller = ActionSheetController(presentationData: strongSelf.presentationData)
var title = strongSelf.presentationData.strings.Map_LiveLocationGroupDescription
var title = strongSelf.presentationData.strings.Map_LiveLocationGroupNewDescription
if case let .share(peer, _, _) = strongSelf.mode, let peer = peer, case .user = peer {
title = strongSelf.presentationData.strings.Map_LiveLocationPrivateDescription(peer.compactDisplayTitle).string
title = strongSelf.presentationData.strings.Map_LiveLocationPrivateNewDescription(peer.compactDisplayTitle).string
}
let sendLiveLocationImpl: (Int32) -> Void = { [weak self, weak controller] period in
controller?.dismissAnimated()
guard let self, let controller else {
return
}
controller.dismissAnimated()
self.completion(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: period), nil, nil, nil, nil)
self.dismiss()
}
controller.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetTextItem(title: title),
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor15Minutes, color: .accent, action: { [weak self, weak controller] in
controller?.dismissAnimated()
if let strongSelf = self {
strongSelf.completion(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: 15 * 60), nil, nil, nil, nil)
strongSelf.dismiss()
}
ActionSheetTextItem(title: title, font: .large, parseMarkdown: true),
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationForMinutes(15), color: .accent, action: { sendLiveLocationImpl(15 * 60)
}),
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor1Hour, color: .accent, action: { [weak self, weak controller] in
controller?.dismissAnimated()
if let strongSelf = self {
strongSelf.completion(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: 60 * 60 - 1), nil, nil, nil, nil)
strongSelf.dismiss()
}
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationForHours(1), color: .accent, action: {
sendLiveLocationImpl(60 * 60 - 1)
}),
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor8Hours, color: .accent, action: { [weak self, weak controller] in
controller?.dismissAnimated()
if let strongSelf = self {
strongSelf.completion(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: 8 * 60 * 60), nil, nil, nil, nil)
strongSelf.dismiss()
}
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationForHours(8), color: .accent, action: {
sendLiveLocationImpl(8 * 60 * 60)
}),
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationIndefinite, color: .accent, action: {
sendLiveLocationImpl(liveLocationIndefinitePeriod)
})
]),
ActionSheetItemGroup(items: [

View File

@ -47,12 +47,12 @@ class LocationViewInteraction {
let share: () -> Void
let setupProximityNotification: (Bool, EngineMessage.Id?) -> Void
let updateSendActionHighlight: (Bool) -> Void
let sendLiveLocation: (Int32?) -> Void
let sendLiveLocation: (Int32?, Bool) -> Void
let stopLiveLocation: () -> Void
let updateRightBarButton: (LocationViewRightBarButton) -> Void
let present: (ViewController) -> Void
init(toggleMapModeSelection: @escaping () -> Void, updateMapMode: @escaping (LocationMapMode) -> Void, toggleTrackingMode: @escaping () -> Void, goToCoordinate: @escaping (CLLocationCoordinate2D) -> Void, requestDirections: @escaping (TelegramMediaMap, String?, OpenInLocationDirections) -> Void, share: @escaping () -> Void, setupProximityNotification: @escaping (Bool, EngineMessage.Id?) -> Void, updateSendActionHighlight: @escaping (Bool) -> Void, sendLiveLocation: @escaping (Int32?) -> Void, stopLiveLocation: @escaping () -> Void, updateRightBarButton: @escaping (LocationViewRightBarButton) -> Void, present: @escaping (ViewController) -> Void) {
init(toggleMapModeSelection: @escaping () -> Void, updateMapMode: @escaping (LocationMapMode) -> Void, toggleTrackingMode: @escaping () -> Void, goToCoordinate: @escaping (CLLocationCoordinate2D) -> Void, requestDirections: @escaping (TelegramMediaMap, String?, OpenInLocationDirections) -> Void, share: @escaping () -> Void, setupProximityNotification: @escaping (Bool, EngineMessage.Id?) -> Void, updateSendActionHighlight: @escaping (Bool) -> Void, sendLiveLocation: @escaping (Int32?, Bool) -> Void, stopLiveLocation: @escaping () -> Void, updateRightBarButton: @escaping (LocationViewRightBarButton) -> Void, present: @escaping (ViewController) -> Void) {
self.toggleMapModeSelection = toggleMapModeSelection
self.updateMapMode = updateMapMode
self.toggleTrackingMode = toggleTrackingMode
@ -323,7 +323,7 @@ public final class LocationViewController: ViewController {
} else {
strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: updatedPresentationData, title: strongSelf.presentationData.strings.Location_LiveLocationRequired_Title, text: strongSelf.presentationData.strings.Location_LiveLocationRequired_Description, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Location_LiveLocationRequired_ShareLocation, action: {
completion()
strongSelf.interaction?.sendLiveLocation(distance)
strongSelf.interaction?.sendLiveLocation(distance, false)
}), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {})], actionLayout: .vertical), in: .window(.root))
}
completion()
@ -341,7 +341,7 @@ public final class LocationViewController: ViewController {
return
}
strongSelf.controllerNode.updateSendActionHighlight(highlighted)
}, sendLiveLocation: { [weak self] distance in
}, sendLiveLocation: { [weak self] distance, extend in
guard let strongSelf = self else {
return
}
@ -400,9 +400,14 @@ public final class LocationViewController: ViewController {
let _ = (context.account.postbox.loadedPeerWithId(subject.id.peerId)
|> deliverOnMainQueue).start(next: { peer in
let controller = ActionSheetController(presentationData: strongSelf.presentationData)
var title = strongSelf.presentationData.strings.Map_LiveLocationGroupDescription
if let user = peer as? TelegramUser {
title = strongSelf.presentationData.strings.Map_LiveLocationPrivateDescription(EnginePeer(user).compactDisplayTitle).string
var title: String
if extend {
title = strongSelf.presentationData.strings.Map_LiveLocationExtendDescription
} else {
title = strongSelf.presentationData.strings.Map_LiveLocationGroupNewDescription
if let user = peer as? TelegramUser {
title = strongSelf.presentationData.strings.Map_LiveLocationPrivateNewDescription(EnginePeer(user).compactDisplayTitle).string
}
}
let sendLiveLocationImpl: (Int32) -> Void = { [weak controller] period in
@ -418,15 +423,18 @@ public final class LocationViewController: ViewController {
controller.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetTextItem(title: title),
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor15Minutes, color: .accent, action: {
ActionSheetTextItem(title: title, font: .large, parseMarkdown: true),
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationForMinutes(15), color: .accent, action: {
sendLiveLocationImpl(15 * 60)
}),
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor1Hour, color: .accent, action: {
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationForHours(1), color: .accent, action: {
sendLiveLocationImpl(60 * 60 - 1)
}),
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor8Hours, color: .accent, action: {
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationForHours(8), color: .accent, action: {
sendLiveLocationImpl(8 * 60 * 60)
}),
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationIndefinite, color: .accent, action: {
sendLiveLocationImpl(liveLocationIndefinitePeriod)
})
]),
ActionSheetItemGroup(items: [

View File

@ -42,21 +42,21 @@ private struct LocationViewTransaction {
private enum LocationViewEntryId: Hashable {
case info
case toggleLiveLocation
case toggleLiveLocation(Bool)
case liveLocation(UInt32)
}
private enum LocationViewEntry: Comparable, Identifiable {
case info(PresentationTheme, TelegramMediaMap, String?, Double?, ExpectedTravelTime, ExpectedTravelTime, ExpectedTravelTime, Bool)
case toggleLiveLocation(PresentationTheme, String, String, Double?, Double?)
case toggleLiveLocation(PresentationTheme, String, String, Double?, Double?, Bool)
case liveLocation(PresentationTheme, PresentationDateTimeFormat, PresentationPersonNameOrder, EngineMessage, Double?, ExpectedTravelTime, ExpectedTravelTime, ExpectedTravelTime, Int)
var stableId: LocationViewEntryId {
switch self {
case .info:
return .info
case .toggleLiveLocation:
return .toggleLiveLocation
case let .toggleLiveLocation(_, _, _, _, _, additional):
return .toggleLiveLocation(additional)
case let .liveLocation(_, _, _, message, _, _, _, _, _):
return .liveLocation(message.stableId)
}
@ -70,8 +70,8 @@ private enum LocationViewEntry: Comparable, Identifiable {
} else {
return false
}
case let .toggleLiveLocation(lhsTheme, lhsTitle, lhsSubtitle, lhsBeginTimestamp, lhsTimeout):
if case let .toggleLiveLocation(rhsTheme, rhsTitle, rhsSubtitle, rhsBeginTimestamp, rhsTimeout) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsBeginTimestamp == rhsBeginTimestamp, lhsTimeout == rhsTimeout {
case let .toggleLiveLocation(lhsTheme, lhsTitle, lhsSubtitle, lhsBeginTimestamp, lhsTimeout, lhsAdditional):
if case let .toggleLiveLocation(rhsTheme, rhsTitle, rhsSubtitle, rhsBeginTimestamp, rhsTimeout, rhsAdditional) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsBeginTimestamp == rhsBeginTimestamp, lhsTimeout == rhsTimeout, lhsAdditional == rhsAdditional {
return true
} else {
return false
@ -94,10 +94,12 @@ private enum LocationViewEntry: Comparable, Identifiable {
case .toggleLiveLocation, .liveLocation:
return true
}
case .toggleLiveLocation:
case let .toggleLiveLocation(_, _, _, _, _, lhsAdditional):
switch rhs {
case .info, .toggleLiveLocation:
case .info:
return false
case let .toggleLiveLocation(_, _, _, _, _, rhsAdditional):
return !lhsAdditional && rhsAdditional
case .liveLocation:
return true
}
@ -135,18 +137,36 @@ private enum LocationViewEntry: Comparable, Identifiable {
}, walkingAction: {
interaction?.requestDirections(location, nil, .walking)
})
case let .toggleLiveLocation(_, title, subtitle, beginTimstamp, timeout):
let beginTimeAndTimeout: (Double, Double)?
case let .toggleLiveLocation(_, title, subtitle, beginTimstamp, timeout, additional):
var beginTimeAndTimeout: (Double, Double)?
if let beginTimstamp = beginTimstamp, let timeout = timeout {
beginTimeAndTimeout = (beginTimstamp, timeout)
} else {
beginTimeAndTimeout = nil
}
return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), engine: context.engine, title: title, subtitle: subtitle, icon: beginTimeAndTimeout != nil ? .stopLiveLocation : .liveLocation, beginTimeAndTimeout: beginTimeAndTimeout, action: {
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 {
interaction?.stopLiveLocation()
if let timeout, Int32(timeout) != liveLocationIndefinitePeriod {
if additional {
interaction?.stopLiveLocation()
} else {
interaction?.sendLiveLocation(nil, true)
}
} else {
interaction?.stopLiveLocation()
}
} else {
interaction?.sendLiveLocation(nil)
interaction?.sendLiveLocation(nil, false)
}
}, highlighted: { highlight in
interaction?.updateSendActionHighlight(highlight)
@ -421,7 +441,12 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
if case let .channel(channel) = subject.author, case .broadcast = channel.info, activeOwnLiveLocation == nil {
} else {
entries.append(.toggleLiveLocation(presentationData.theme, title, subtitle, beginTime, timeout))
if let timeout, Int32(timeout) != liveLocationIndefinitePeriod {
entries.append(.toggleLiveLocation(presentationData.theme, presentationData.strings.Map_SharingLocation, presentationData.strings.Map_TapToAddTime, beginTime, timeout, false))
entries.append(.toggleLiveLocation(presentationData.theme, title, subtitle, beginTime, timeout, true))
} else {
entries.append(.toggleLiveLocation(presentationData.theme, title, subtitle, beginTime, timeout, false))
}
}
var sortedLiveLocations: [EngineMessage] = []
@ -452,7 +477,12 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
if let timeout = location.liveBroadcastingTimeout {
liveBroadcastingTimeout = timeout
}
let remainingTime = max(0, message.timestamp + liveBroadcastingTimeout - currentTime)
let remainingTime: Int32
if liveBroadcastingTimeout == liveLocationIndefinitePeriod {
remainingTime = liveLocationIndefinitePeriod
} else {
remainingTime = max(0, message.timestamp + liveBroadcastingTimeout - currentTime)
}
if message.flags.contains(.Incoming) && remainingTime != 0 && proximityNotification == nil {
proximityNotification = false
}

View File

@ -1,5 +1,7 @@
import Postbox
public let liveLocationIndefinitePeriod: Int32 = 0x7fffffff
public final class NamedGeoPlace: PostboxCoding, Equatable {
public let country: String?
public let state: String?

View File

@ -19,7 +19,7 @@ func _internal_topPeerActiveLiveLocationMessages(viewTracker: AccountViewTracker
for entry in view.entries {
for media in entry.message.media {
if let location = media as? TelegramMediaMap, let liveBroadcastingTimeout = location.liveBroadcastingTimeout {
if entry.message.timestamp + liveBroadcastingTimeout > timestamp {
if liveBroadcastingTimeout == liveLocationIndefinitePeriod || entry.message.timestamp + liveBroadcastingTimeout > timestamp {
result.append(entry.message)
}
} else {

View File

@ -85,7 +85,7 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
selectedMedia = telegramMap
if let liveBroadcastingTimeout = telegramMap.liveBroadcastingTimeout {
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
if item.message.timestamp != scheduleWhenOnlineTimestamp && item.message.timestamp + liveBroadcastingTimeout > timestamp {
if item.message.timestamp != scheduleWhenOnlineTimestamp && (liveBroadcastingTimeout == liveLocationIndefinitePeriod || item.message.timestamp + liveBroadcastingTimeout > timestamp) {
activeLiveBroadcastingTimeout = liveBroadcastingTimeout
}
}
@ -402,7 +402,7 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
let timerForegroundColor: UIColor = incoming ? item.presentationData.theme.theme.chat.message.incoming.accentControlColor : item.presentationData.theme.theme.chat.message.outgoing.accentControlColor
let timerTextColor: UIColor = incoming ? item.presentationData.theme.theme.chat.message.incoming.secondaryTextColor : item.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor
strongSelf.liveTimerNode?.update(backgroundColor: timerForegroundColor.withAlphaComponent(0.4), foregroundColor: timerForegroundColor, textColor: timerTextColor, beginTimestamp: Double(item.message.timestamp), timeout: Double(activeLiveBroadcastingTimeout), strings: item.presentationData.strings)
strongSelf.liveTimerNode?.update(backgroundColor: timerForegroundColor.withAlphaComponent(0.4), foregroundColor: timerForegroundColor, textColor: timerTextColor, beginTimestamp: Double(item.message.timestamp), timeout: activeLiveBroadcastingTimeout == liveLocationIndefinitePeriod ? -1.0 : Double(activeLiveBroadcastingTimeout), strings: item.presentationData.strings)
if strongSelf.liveTextNode == nil {
let liveTextNode = ChatMessageLiveLocationTextNode()
@ -421,20 +421,25 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.liveTextNode?.update(color: timerTextColor, timestamp: Double(updateTimestamp), strings: item.presentationData.strings, dateTimeFormat: item.presentationData.dateTimeFormat)
let timeoutDeadline = item.message.timestamp + activeLiveBroadcastingTimeout
if strongSelf.timeoutTimer?.1 != timeoutDeadline {
if activeLiveBroadcastingTimeout != liveLocationIndefinitePeriod {
let timeoutDeadline = item.message.timestamp + activeLiveBroadcastingTimeout
if strongSelf.timeoutTimer?.1 != timeoutDeadline {
strongSelf.timeoutTimer?.0.invalidate()
let currentTimestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
let timer = SwiftSignalKit.Timer(timeout: Double(max(0, timeoutDeadline - currentTimestamp)), repeat: false, completion: {
if let strongSelf = self {
strongSelf.timeoutTimer?.0.invalidate()
strongSelf.timeoutTimer = nil
item.controllerInteraction.requestMessageUpdate(item.message.id, false)
}
}, queue: Queue.mainQueue())
strongSelf.timeoutTimer = (timer, timeoutDeadline)
timer.start()
}
} else {
strongSelf.timeoutTimer?.0.invalidate()
let currentTimestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
let timer = SwiftSignalKit.Timer(timeout: Double(max(0, timeoutDeadline - currentTimestamp)), repeat: false, completion: {
if let strongSelf = self {
strongSelf.timeoutTimer?.0.invalidate()
strongSelf.timeoutTimer = nil
item.controllerInteraction.requestMessageUpdate(item.message.id, false)
}
}, queue: Queue.mainQueue())
strongSelf.timeoutTimer = (timer, timeoutDeadline)
timer.start()
strongSelf.timeoutTimer = nil
}
} else {
if let liveTimerNode = strongSelf.liveTimerNode {

View File

@ -2547,7 +2547,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
contentRequiredValidation = true
} else if message.flags.contains(.Incoming), let media = media as? TelegramMediaMap, let liveBroadcastingTimeout = media.liveBroadcastingTimeout {
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
if message.timestamp + liveBroadcastingTimeout > timestamp {
if liveBroadcastingTimeout == liveLocationIndefinitePeriod || message.timestamp + liveBroadcastingTimeout > timestamp {
messageIdsWithLiveLocation.append(message.id)
}
} else if let telegramFile = media as? TelegramMediaFile {