mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Live location improvements
This commit is contained in:
parent
a736b22baa
commit
7357c59707
@ -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";
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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: [
|
||||
|
@ -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: [
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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?
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user