Various improvements

This commit is contained in:
Ilya Laktyushin 2020-10-21 02:29:08 +04:00
parent 047a0085c7
commit 8ca5750cb9
35 changed files with 4773 additions and 4441 deletions

View File

@ -117,6 +117,10 @@
"PUSH_MESSAGES_1" = "%1$@|sent you a message"; "PUSH_MESSAGES_1" = "%1$@|sent you a message";
"PUSH_MESSAGES_any" = "%1$@|sent you %2$d messages"; "PUSH_MESSAGES_any" = "%1$@|sent you %2$d messages";
"PUSH_ALBUM" = "%1$@|sent you an album"; "PUSH_ALBUM" = "%1$@|sent you an album";
"PUSH_MESSAGE_DOCS" = "%1$@|sent you %2$d files";
"PUSH_MESSAGE_DOCS_1" = "%1$@|sent you a file";
"PUSH_MESSAGE_DOCS_any" = "%1$@|sent you %2$d files";
"PUSH_CHANNEL_MESSAGE_TEXT" = "%1$@|%2$@"; "PUSH_CHANNEL_MESSAGE_TEXT" = "%1$@|%2$@";
"PUSH_CHANNEL_MESSAGE_NOTEXT" = "%1$@|posted a message"; "PUSH_CHANNEL_MESSAGE_NOTEXT" = "%1$@|posted a message";
@ -149,6 +153,9 @@
"PUSH_CHANNEL_MESSAGES_1" = "%1$@|posted a message"; "PUSH_CHANNEL_MESSAGES_1" = "%1$@|posted a message";
"PUSH_CHANNEL_MESSAGES_any" = "%1$@|posted %2$d messages"; "PUSH_CHANNEL_MESSAGES_any" = "%1$@|posted %2$d messages";
"PUSH_CHANNEL_ALBUM" = "%1$@|posted an album"; "PUSH_CHANNEL_ALBUM" = "%1$@|posted an album";
"PUSH_CHANNEL_MESSAGE_DOCS" = "%1$@|posted %2$d files";
"PUSH_CHANNEL_MESSAGE_DOCS_1" = "%1$@|posted a file";
"PUSH_CHANNEL_MESSAGE_DOCS_any" = "%1$@|posted %2$d files";
"PUSH_CHAT_MESSAGE_TEXT" = "%2$@|%1$@:%3$@"; "PUSH_CHAT_MESSAGE_TEXT" = "%2$@|%1$@:%3$@";
"PUSH_CHAT_MESSAGE_NOTEXT" = "%2$@|%1$@ sent a message to the group"; "PUSH_CHAT_MESSAGE_NOTEXT" = "%2$@|%1$@ sent a message to the group";
@ -191,6 +198,9 @@
"PUSH_CHAT_MESSAGES_1" = "%2$@|%1$@ sent a message"; "PUSH_CHAT_MESSAGES_1" = "%2$@|%1$@ sent a message";
"PUSH_CHAT_MESSAGES_any" = "%2$@|%1$@ sent %3$d messages"; "PUSH_CHAT_MESSAGES_any" = "%2$@|%1$@ sent %3$d messages";
"PUSH_CHAT_ALBUM" = "%2$@|%1$@ sent an album"; "PUSH_CHAT_ALBUM" = "%2$@|%1$@ sent an album";
"PUSH_CHAT_MESSAGE_DOCS" = "%2$@|%1$@ sent %3$d files";
"PUSH_CHAT_MESSAGE_DOCS_1" = "%2$@|%1$@ sent a file";
"PUSH_CHAT_MESSAGE_DOCS_any" = "%2$@|%1$@ sent %3$d files";
"PUSH_PINNED_TEXT" = "%1$@|pinned \"%2$@\" "; "PUSH_PINNED_TEXT" = "%1$@|pinned \"%2$@\" ";
"PUSH_PINNED_NOTEXT" = "%1$@|pinned a message"; "PUSH_PINNED_NOTEXT" = "%1$@|pinned a message";
@ -5820,7 +5830,7 @@ Any member of this group will be able to see messages in the channel.";
"Conversation.ContextViewStats" = "View Statistics"; "Conversation.ContextViewStats" = "View Statistics";
"ChatList.MessageMusic_1" = "1 Music File"; "ChatList.MessageMusic_1" = "%@ Music File";
"ChatList.MessageMusic_any" = "%@ Music Files"; "ChatList.MessageMusic_any" = "%@ Music Files";
"Conversation.PinOlderMessageAlertTitle" = "Pin Message"; "Conversation.PinOlderMessageAlertTitle" = "Pin Message";
@ -5838,7 +5848,8 @@ Any member of this group will be able to see messages in the channel.";
"Conversation.Dice.u1F3B0" = "Send a slot machine emoji to try your luck."; "Conversation.Dice.u1F3B0" = "Send a slot machine emoji to try your luck.";
"Notification.ProximityReached" = "%1$@ is now within %2$@ from you"; "Notification.ProximityReached" = "%1$@ is now within %2$@ from %3$@";
"Notification.ProximityReachedYou" = "%1$@ is now within %2$@ from you";
"Location.ProximityNotification.Title" = "Notification"; "Location.ProximityNotification.Title" = "Notification";
"Location.ProximityNotification.Notify" = "Notify me within %@"; "Location.ProximityNotification.Notify" = "Notify me within %@";
@ -5852,3 +5863,6 @@ Any member of this group will be able to see messages in the channel.";
"Location.ProximityTip" = "Alert when %@ is close"; "Location.ProximityTip" = "Alert when %@ is close";
"Location.ProximityGroupTip" = "Alert when any group member is close"; "Location.ProximityGroupTip" = "Alert when any group member is close";
"ChatList.MessageFiles_1" = "%@ File";
"ChatList.MessageFiles_any" = "%@ Files";

View File

@ -743,6 +743,8 @@ public final class AnimatedStickerNode: ASDisplayNode {
public var started: () -> Void = {} public var started: () -> Void = {}
private var reportedStarted = false private var reportedStarted = false
public var completed: (Bool) -> Void = { _ in }
private let timer = Atomic<SwiftSignalKit.Timer?>(value: nil) private let timer = Atomic<SwiftSignalKit.Timer?>(value: nil)
private let frameSource = Atomic<QueueLocalObject<AnimatedStickerFrameSourceWrapper>?>(value: nil) private let frameSource = Atomic<QueueLocalObject<AnimatedStickerFrameSourceWrapper>?>(value: nil)
@ -760,6 +762,14 @@ public final class AnimatedStickerNode: ASDisplayNode {
return self.playbackStatus.get() return self.playbackStatus.get()
} }
public var autoplay = true {
didSet {
if self.autoplay != oldValue {
self.updateIsPlaying()
}
}
}
public var visibility = false { public var visibility = false {
didSet { didSet {
if self.visibility != oldValue { if self.visibility != oldValue {
@ -825,6 +835,7 @@ public final class AnimatedStickerNode: ASDisplayNode {
strongSelf.directData = (directData, path, width, height, cachePathPrefix, source.fitzModifier) strongSelf.directData = (directData, path, width, height, cachePathPrefix, source.fitzModifier)
} }
if case let .still(position) = playbackMode { if case let .still(position) = playbackMode {
strongSelf.play(firstFrame: true)
strongSelf.seekTo(position) strongSelf.seekTo(position)
} else if strongSelf.isPlaying { } else if strongSelf.isPlaying {
strongSelf.play() strongSelf.play()
@ -877,7 +888,7 @@ public final class AnimatedStickerNode: ASDisplayNode {
if self.isPlaying != isPlaying { if self.isPlaying != isPlaying {
self.isPlaying = isPlaying self.isPlaying = isPlaying
if isPlaying { if isPlaying {
self.play() self.play(firstFrame: !self.autoplay)
} else{ } else{
self.pause() self.pause()
} }
@ -950,11 +961,17 @@ public final class AnimatedStickerNode: ASDisplayNode {
} }
}) })
if case .once = strongSelf.playbackMode, frame.isLastFrame { if frame.isLastFrame {
strongSelf.stop() var stopped = false
strongSelf.isPlaying = false if case .once = strongSelf.playbackMode {
strongSelf.stop()
strongSelf.isPlaying = false
stopped = true
}
strongSelf.completed(stopped)
} }
let timestamp: Double = frameRate > 0 ? Double(frame.index) / Double(frameRate) : 0 let timestamp: Double = frameRate > 0 ? Double(frame.index) / Double(frameRate) : 0
strongSelf.playbackStatus.set(.single(AnimatedStickerStatus(playing: strongSelf.isPlaying, duration: duration, timestamp: timestamp))) strongSelf.playbackStatus.set(.single(AnimatedStickerStatus(playing: strongSelf.isPlaying, duration: duration, timestamp: timestamp)))
} }
@ -1021,11 +1038,17 @@ public final class AnimatedStickerNode: ASDisplayNode {
} }
}) })
if case .once = strongSelf.playbackMode, frame.isLastFrame { if frame.isLastFrame {
strongSelf.stop() var stopped = false
strongSelf.isPlaying = false if case .once = strongSelf.playbackMode {
strongSelf.stop()
strongSelf.isPlaying = false
stopped = true
}
strongSelf.completed(stopped)
} }
let timestamp: Double = frameRate > 0 ? Double(frame.index) / Double(frameRate) : 0 let timestamp: Double = frameRate > 0 ? Double(frame.index) / Double(frameRate) : 0
strongSelf.playbackStatus.set(.single(AnimatedStickerStatus(playing: strongSelf.isPlaying, duration: duration, timestamp: timestamp))) strongSelf.playbackStatus.set(.single(AnimatedStickerStatus(playing: strongSelf.isPlaying, duration: duration, timestamp: timestamp)))
} }

View File

@ -11,6 +11,7 @@ private enum MessageGroupType {
case photos case photos
case videos case videos
case music case music
case files
case generic case generic
} }
@ -25,6 +26,7 @@ private func singleMessageType(message: Message) -> MessageGroupType {
if file.isVideo && !file.isInstantVideo { if file.isVideo && !file.isInstantVideo {
return .videos return .videos
} }
return .files
} }
} }
return .generic return .generic
@ -91,6 +93,13 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
messageText = strings.ChatList_MessageMusic(Int32(messages.count)) messageText = strings.ChatList_MessageMusic(Int32(messages.count))
textIsReady = true textIsReady = true
} }
case .files:
if !messageText.isEmpty {
textIsReady = true
} else {
messageText = strings.ChatList_MessageFiles(Int32(messages.count))
textIsReady = true
}
case .generic: case .generic:
break break
} }

View File

@ -18,6 +18,7 @@ import PresentationDataUtils
enum ChatMediaGalleryThumbnail: Equatable { enum ChatMediaGalleryThumbnail: Equatable {
case image(ImageMediaReference) case image(ImageMediaReference)
case video(FileMediaReference) case video(FileMediaReference)
case file(FileMediaReference)
static func ==(lhs: ChatMediaGalleryThumbnail, rhs: ChatMediaGalleryThumbnail) -> Bool { static func ==(lhs: ChatMediaGalleryThumbnail, rhs: ChatMediaGalleryThumbnail) -> Bool {
switch lhs { switch lhs {
@ -33,6 +34,12 @@ enum ChatMediaGalleryThumbnail: Equatable {
} else { } else {
return false return false
} }
case let .file(lhsFile):
if case let .file(rhsFile) = rhs, lhsFile.media.isEqual(to: rhsFile.media) {
return true
} else {
return false
}
} }
} }
} }
@ -45,8 +52,12 @@ final class ChatMediaGalleryThumbnailItem: GalleryThumbnailItem {
self.account = account self.account = account
if let imageReference = mediaReference.concrete(TelegramMediaImage.self) { if let imageReference = mediaReference.concrete(TelegramMediaImage.self) {
self.thumbnail = .image(imageReference) self.thumbnail = .image(imageReference)
} else if let fileReference = mediaReference.concrete(TelegramMediaFile.self), fileReference.media.isVideo { } else if let fileReference = mediaReference.concrete(TelegramMediaFile.self) {
self.thumbnail = .video(fileReference) if fileReference.media.isVideo {
self.thumbnail = .video(fileReference)
} else {
self.thumbnail = .file(fileReference)
}
} else { } else {
return nil return nil
} }
@ -74,6 +85,12 @@ final class ChatMediaGalleryThumbnailItem: GalleryThumbnailItem {
} else { } else {
return (.single({ _ in return nil }), CGSize(width: 128.0, height: 128.0)) return (.single({ _ in return nil }), CGSize(width: 128.0, height: 128.0))
} }
case let .file(fileReference):
if let representation = smallestImageRepresentation(fileReference.media.previewRepresentations) {
return (chatWebpageSnippetFile(account: self.account, fileReference: fileReference, representation: representation), representation.dimensions.cgSize)
} else {
return (.single({ _ in return nil }), CGSize(width: 128.0, height: 128.0))
}
} }
} }
} }
@ -153,7 +170,7 @@ class ChatImageGalleryItem: GalleryItem {
for m in self.message.media { for m in self.message.media {
if let m = m as? TelegramMediaImage { if let m = m as? TelegramMediaImage {
mediaReference = .message(message: MessageReference(self.message), media: m) mediaReference = .message(message: MessageReference(self.message), media: m)
} else if let m = m as? TelegramMediaFile, m.isVideo { } else if let m = m as? TelegramMediaFile {
mediaReference = .message(message: MessageReference(self.message), media: m) mediaReference = .message(message: MessageReference(self.message), media: m)
} }
} }

View File

@ -720,6 +720,23 @@
- (void)updateEditorButtonsForItem:(id<TGModernGalleryItem>)item animated:(bool)animated - (void)updateEditorButtonsForItem:(id<TGModernGalleryItem>)item animated:(bool)animated
{ {
__weak TGMediaPickerGalleryInterfaceView *weakSelf = self;
id<TGModernGalleryEditableItem> galleryEditableItem = (id<TGModernGalleryEditableItem>)item;
if ([item conformsToProtocol:@protocol(TGModernGalleryEditableItem)])
{
id<TGMediaEditableItem> editableMediaItem = [galleryEditableItem editableMediaItem];
[_captionDisposable setDisposable:[[galleryEditableItem.editingContext captionSignalForItem:editableMediaItem] startWithNext:^(NSDictionary *captionWithEntities)
{
__strong TGMediaPickerGalleryInterfaceView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
NSString *caption = captionWithEntities[@"caption"];
NSArray *entities = captionWithEntities[@"entities"];
[strongSelf->_captionMixin setCaption:caption entities:entities animated:animated];
}]];
}
if (_editingContext == nil || _editingContext.inhibitEditing) if (_editingContext == nil || _editingContext.inhibitEditing)
{ {
[_portraitToolbarView setEditButtonsHidden:true animated:false]; [_portraitToolbarView setEditButtonsHidden:true animated:false];
@ -757,12 +774,10 @@
return; return;
} }
id<TGModernGalleryEditableItem> galleryEditableItem = (id<TGModernGalleryEditableItem>)item;
if ([item conformsToProtocol:@protocol(TGModernGalleryEditableItem)]) if ([item conformsToProtocol:@protocol(TGModernGalleryEditableItem)])
{ {
id<TGMediaEditableItem> editableMediaItem = [galleryEditableItem editableMediaItem]; id<TGMediaEditableItem> editableMediaItem = [galleryEditableItem editableMediaItem];
__weak TGMediaPickerGalleryInterfaceView *weakSelf = self;
__weak id<TGModernGalleryEditableItem> weakGalleryEditableItem = galleryEditableItem; __weak id<TGModernGalleryEditableItem> weakGalleryEditableItem = galleryEditableItem;
[_adjustmentsDisposable setDisposable:[[[[galleryEditableItem.editingContext adjustmentsSignalForItem:editableMediaItem] mapToSignal:^SSignal *(id<TGMediaEditAdjustments> adjustments) { [_adjustmentsDisposable setDisposable:[[[[galleryEditableItem.editingContext adjustmentsSignalForItem:editableMediaItem] mapToSignal:^SSignal *(id<TGMediaEditAdjustments> adjustments) {
__strong id<TGModernGalleryEditableItem> strongGalleryEditableItem = weakGalleryEditableItem; __strong id<TGModernGalleryEditableItem> strongGalleryEditableItem = weakGalleryEditableItem;
@ -803,17 +818,6 @@
[strongSelf updateEditorButtonsForAdjustments:adjustments dimensions:originalSize timer:timer]; [strongSelf updateEditorButtonsForAdjustments:adjustments dimensions:originalSize timer:timer];
}]]; }]];
[_captionDisposable setDisposable:[[galleryEditableItem.editingContext captionSignalForItem:editableMediaItem] startWithNext:^(NSDictionary *captionWithEntities)
{
__strong TGMediaPickerGalleryInterfaceView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
NSString *caption = captionWithEntities[@"caption"];
NSArray *entities = captionWithEntities[@"entities"];
[strongSelf->_captionMixin setCaption:caption entities:entities animated:animated];
}]];
} }
else else
{ {

View File

@ -314,9 +314,9 @@ class LocationDistancePickerScreenNode: ViewControllerTracingNode, UIScrollViewD
pickerView.selectRow(0, inComponent: 0, animated: false) pickerView.selectRow(0, inComponent: 0, animated: false)
if self.usesMetricSystem() { if self.usesMetricSystem() {
pickerView.selectRow(30, inComponent: 1, animated: false) pickerView.selectRow(50, inComponent: 1, animated: false)
} else { } else {
pickerView.selectRow(20, inComponent: 1, animated: false) pickerView.selectRow(30, inComponent: 1, animated: false)
} }
self.contentContainerNode.view.addSubview(pickerView) self.contentContainerNode.view.addSubview(pickerView)
self.pickerView = pickerView self.pickerView = pickerView

View File

@ -168,7 +168,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
context.setAlpha(alpha) context.setAlpha(alpha)
let path = UIBezierPath(rect: CGRect(x: mapRect.origin.x, y: mapRect.origin.y, width: mapRect.size.width, height: mapRect.size.height)) let path = UIBezierPath(rect: CGRect(x: mapRect.origin.x, y: mapRect.origin.y, width: mapRect.size.width, height: mapRect.size.height))
let radiusInMap = overlay.radius * MKMapPointsPerMeterAtLatitude(overlay.coordinate.latitude) let radiusInMap = overlay.radius * MKMapPointsPerMeterAtLatitude(overlay.coordinate.latitude) * 2.0
let mapSize: MKMapSize = MKMapSize(width: radiusInMap, height: radiusInMap) let mapSize: MKMapSize = MKMapSize(width: radiusInMap, height: radiusInMap)
let regionOrigin = MKMapPoint(overlay.coordinate) let regionOrigin = MKMapPoint(overlay.coordinate)
var regionRect: MKMapRect = MKMapRect(origin: regionOrigin, size: mapSize) var regionRect: MKMapRect = MKMapRect(origin: regionOrigin, size: mapSize)
@ -724,15 +724,21 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
} }
annotations.append(contentsOf: self.annotations) annotations.append(contentsOf: self.annotations)
var zoomRect: MKMapRect = MKMapRect() var zoomRect: MKMapRect?
for annotation in annotations { for annotation in annotations {
let pointRegionRect = MKMapRect(region: MKCoordinateRegion(center: annotation.coordinate, latitudinalMeters: 100, longitudinalMeters: 100)) let pointRegionRect = MKMapRect(region: MKCoordinateRegion(center: annotation.coordinate, latitudinalMeters: 100, longitudinalMeters: 100))
zoomRect = zoomRect.union(pointRegionRect) if let currentZoomRect = zoomRect {
zoomRect = currentZoomRect.union(pointRegionRect)
} else {
zoomRect = pointRegionRect
}
} }
let insets = UIEdgeInsets() if let zoomRect = zoomRect {
zoomRect = mapView.mapRectThatFits(zoomRect, edgePadding: insets) let insets = UIEdgeInsets(top: 0.0, left: 80.0, bottom: 0.0, right: 80.0)
mapView.setVisibleMapRect(zoomRect, animated: animated) let fittedZoomRect = mapView.mapRectThatFits(zoomRect, edgePadding: insets)
mapView.setVisibleMapRect(fittedZoomRect, animated: animated)
}
} }
func updateLayout(size: CGSize) { func updateLayout(size: CGSize) {

View File

@ -65,8 +65,6 @@ class LocationViewInteraction {
} }
} }
var CURRENT_DISTANCE: Double? = nil
public final class LocationViewController: ViewController { public final class LocationViewController: ViewController {
private var controllerNode: LocationViewControllerNode { private var controllerNode: LocationViewControllerNode {
return self.displayNode as! LocationViewControllerNode return self.displayNode as! LocationViewControllerNode
@ -174,13 +172,9 @@ public final class LocationViewController: ViewController {
} }
if reset { if reset {
strongSelf.controllerNode.updateState { state -> LocationViewState in if let messageId = messageId {
var state = state let _ = cancelProximityNotification(postbox: context.account.postbox, network: context.account.network, messageId: messageId).start()
state.proximityRadius = nil
return state
} }
CURRENT_DISTANCE = nil
} else { } else {
strongSelf.controllerNode.setProximityIndicator(radius: 0) strongSelf.controllerNode.setProximityIndicator(radius: 0)
@ -196,15 +190,8 @@ public final class LocationViewController: ViewController {
if let messageId = messageId { if let messageId = messageId {
completion() completion()
strongSelf.controllerNode.updateState { state -> LocationViewState in
var state = state
state.proximityRadius = Double(distance)
return state
}
let _ = requestProximityNotification(postbox: context.account.postbox, network: context.account.network, messageId: messageId, distance: distance).start() let _ = requestProximityNotification(postbox: context.account.postbox, network: context.account.network, messageId: messageId, distance: distance).start()
CURRENT_DISTANCE = Double(distance)
} else if let coordinate = coordinate { } else if let coordinate = coordinate {
strongSelf.present(textAlertController(context: strongSelf.context, 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: { [weak self] in strongSelf.present(textAlertController(context: strongSelf.context, 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: { [weak self] in
completion() completion()
@ -250,12 +237,6 @@ public final class LocationViewController: ViewController {
params.sendLiveLocation(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: period)) params.sendLiveLocation(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: period))
if let distance = distance { if let distance = distance {
strongSelf.controllerNode.updateState { state -> LocationViewState in
var state = state
state.proximityRadius = Double(distance)
return state
}
strongSelf.controllerNode.ownLiveLocationStartedAction = { messageId in strongSelf.controllerNode.ownLiveLocationStartedAction = { messageId in
let _ = requestProximityNotification(postbox: context.account.postbox, network: context.account.network, messageId: messageId, distance: distance).start() let _ = requestProximityNotification(postbox: context.account.postbox, network: context.account.network, messageId: messageId, distance: distance).start()
} }
@ -330,12 +311,6 @@ public final class LocationViewController: ViewController {
self.displayNode = LocationViewControllerNode(context: self.context, presentationData: self.presentationData, subject: self.subject, interaction: interaction, locationManager: self.locationManager) self.displayNode = LocationViewControllerNode(context: self.context, presentationData: self.presentationData, subject: self.subject, interaction: interaction, locationManager: self.locationManager)
self.displayNodeDidLoad() self.displayNodeDidLoad()
self.controllerNode.updateState { state -> LocationViewState in
var state = state
state.proximityRadius = CURRENT_DISTANCE
return state
}
} }
private func updateRightBarButton() { private func updateRightBarButton() {

View File

@ -180,13 +180,11 @@ struct LocationViewState {
var mapMode: LocationMapMode var mapMode: LocationMapMode
var displayingMapModeOptions: Bool var displayingMapModeOptions: Bool
var selectedLocation: LocationViewLocation var selectedLocation: LocationViewLocation
var proximityRadius: Double?
init() { init() {
self.mapMode = .map self.mapMode = .map
self.displayingMapModeOptions = false self.displayingMapModeOptions = false
self.selectedLocation = .initial self.selectedLocation = .initial
self.proximityRadius = nil
} }
} }
@ -305,8 +303,10 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
return transaction.getPeer(context.account.peerId) return transaction.getPeer(context.account.peerId)
} }
self.disposable = (combineLatest(self.presentationDataPromise.get(), self.statePromise.get(), selfPeer, liveLocations, self.headerNode.mapNode.userLocation, userLocation, address, eta) let proximityNotificationState = proximityNotificationStoredState(account: context.account, peerId: subject.id.peerId)
|> 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, proximityNotificationState)
|> deliverOnMainQueue).start(next: { [weak self] presentationData, state, selfPeer, liveLocations, userLocation, distance, address, eta, proximityNotificationState in
if let strongSelf = self, let location = getLocation(from: subject) { if let strongSelf = self, let location = getLocation(from: subject) {
var entries: [LocationViewEntry] = [] var entries: [LocationViewEntry] = []
var annotations: [LocationPinAnnotation] = [] var annotations: [LocationPinAnnotation] = []
@ -388,7 +388,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
} }
effectiveLiveLocations = sortedLiveLocations effectiveLiveLocations = sortedLiveLocations
} }
for message in effectiveLiveLocations { for message in effectiveLiveLocations {
var liveBroadcastingTimeout: Int32 = 0 var liveBroadcastingTimeout: Int32 = 0
if let location = getLocation(from: message), let timeout = location.liveBroadcastingTimeout { if let location = getLocation(from: message), let timeout = location.liveBroadcastingTimeout {
@ -396,7 +396,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
} }
let remainingTime = max(0, message.timestamp + liveBroadcastingTimeout - currentTime) let remainingTime = max(0, message.timestamp + liveBroadcastingTimeout - currentTime)
if message.flags.contains(.Incoming) && remainingTime != 0 { if message.flags.contains(.Incoming) && remainingTime != 0 {
proximityNotification = state.proximityRadius != nil proximityNotification = proximityNotificationState?.distance != nil
} }
let subjectLocation = CLLocation(latitude: location.latitude, longitude: location.longitude) let subjectLocation = CLLocation(latitude: location.latitude, longitude: location.longitude)
@ -412,7 +412,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
} }
if subject.id.peerId.namespace != Namespaces.Peer.CloudUser { if subject.id.peerId.namespace != Namespaces.Peer.CloudUser {
proximityNotification = state.proximityRadius != nil proximityNotification = proximityNotificationState?.distance != nil
} }
let previousEntries = previousEntries.swap(entries) let previousEntries = previousEntries.swap(entries)
@ -428,7 +428,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
let _ = (ApplicationSpecificNotice.getLocationProximityAlertTip(accountManager: context.sharedContext.accountManager) let _ = (ApplicationSpecificNotice.getLocationProximityAlertTip(accountManager: context.sharedContext.accountManager)
|> deliverOnMainQueue).start(next: { [weak self] counter in |> deliverOnMainQueue).start(next: { [weak self] counter in
if let strongSelf = self, counter < 3 { if let strongSelf = self, counter < 3000 {
let _ = ApplicationSpecificNotice.incrementLocationProximityAlertTip(accountManager: context.sharedContext.accountManager).start() let _ = ApplicationSpecificNotice.incrementLocationProximityAlertTip(accountManager: context.sharedContext.accountManager).start()
strongSelf.displayProximityAlertTooltip() strongSelf.displayProximityAlertTooltip()
} }
@ -462,8 +462,11 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
strongSelf.headerNode.mapNode.annotations = annotations strongSelf.headerNode.mapNode.annotations = annotations
} }
strongSelf.headerNode.mapNode.activeProximityRadius = state.proximityRadius if let _ = proximityNotification {
strongSelf.headerNode.mapNode.activeProximityRadius = (proximityNotificationState?.distance).flatMap { Double($0) }
} else {
strongSelf.headerNode.mapNode.activeProximityRadius = nil
}
let rightBarButtonAction: LocationViewRightBarButton let rightBarButtonAction: LocationViewRightBarButton
if location.liveBroadcastingTimeout != nil { if location.liveBroadcastingTimeout != nil {
if liveLocations.count > 1 { if liveLocations.count > 1 {

View File

@ -10,6 +10,7 @@ static_library(
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared",
"//submodules/AsyncDisplayKit:AsyncDisplayKit#shared", "//submodules/AsyncDisplayKit:AsyncDisplayKit#shared",
"//submodules/Postbox:Postbox#shared", "//submodules/Postbox:Postbox#shared",
"//submodules/TelegramCore:TelegramCore#shared",
"//submodules/GZip:GZip", "//submodules/GZip:GZip",
"//submodules/rlottie:RLottieBinding", "//submodules/rlottie:RLottieBinding",
"//submodules/AppBundle:AppBundle", "//submodules/AppBundle:AppBundle",

View File

@ -11,6 +11,7 @@ swift_library(
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Postbox:Postbox", "//submodules/Postbox:Postbox",
"//submodules/TelegramCore:TelegramCore",
"//submodules/GZip:GZip", "//submodules/GZip:GZip",
"//submodules/rlottie:RLottieBinding", "//submodules/rlottie:RLottieBinding",
"//submodules/AppBundle:AppBundle", "//submodules/AppBundle:AppBundle",

View File

@ -3,6 +3,7 @@ import UIKit
import Display import Display
import AsyncDisplayKit import AsyncDisplayKit
import Postbox import Postbox
import TelegramCore
import RLottieBinding import RLottieBinding
import AppBundle import AppBundle
import GZip import GZip
@ -72,7 +73,7 @@ public enum ManagedAnimationFrameRange: Equatable {
public enum ManagedAnimationSource: Equatable { public enum ManagedAnimationSource: Equatable {
case local(String) case local(String)
case resource(MediaBox, MediaResource) case resource(Account, MediaResource)
var cacheKey: String { var cacheKey: String {
switch self { switch self {
@ -87,8 +88,8 @@ public enum ManagedAnimationSource: Equatable {
switch self { switch self {
case let .local(name): case let .local(name):
return getAppBundle().path(forResource: name, ofType: "tgs") return getAppBundle().path(forResource: name, ofType: "tgs")
case let .resource(mediaBox, resource): case let .resource(account, resource):
return mediaBox.completedResourcePath(resource) return account.postbox.mediaBox.completedResourcePath(resource)
} }
} }
@ -100,8 +101,8 @@ public enum ManagedAnimationSource: Equatable {
} else { } else {
return false return false
} }
case let .resource(lhsMediaBox, lhsResource): case let .resource(lhsAccount, lhsResource):
if case let .resource(rhsMediaBox, rhsResource) = rhs, lhsMediaBox === rhsMediaBox, lhsResource.isEqual(to: rhsResource) { if case let .resource(rhsAccount, rhsResource) = rhs, lhsAccount === rhsAccount, lhsResource.isEqual(to: rhsResource) {
return true return true
} else { } else {
return false return false
@ -112,9 +113,9 @@ public enum ManagedAnimationSource: Equatable {
public struct ManagedAnimationItem { public struct ManagedAnimationItem {
public let source: ManagedAnimationSource public let source: ManagedAnimationSource
var frames: ManagedAnimationFrameRange? public var frames: ManagedAnimationFrameRange?
var duration: Double? public var duration: Double?
var loop: Bool public var loop: Bool
var callbacks: [(Int, () -> Void)] var callbacks: [(Int, () -> Void)]
public init(source: ManagedAnimationSource, frames: ManagedAnimationFrameRange? = nil, duration: Double? = nil, loop: Bool = false, callbacks: [(Int, () -> Void)] = []) { public init(source: ManagedAnimationSource, frames: ManagedAnimationFrameRange? = nil, duration: Double? = nil, loop: Bool = false, callbacks: [(Int, () -> Void)] = []) {

View File

@ -2250,6 +2250,7 @@ public final class Postbox {
fileprivate func removeItemCacheEntry(id: ItemCacheEntryId) { fileprivate func removeItemCacheEntry(id: ItemCacheEntryId) {
self.itemCacheTable.remove(id: id, metaTable: self.itemCacheMetaTable) self.itemCacheTable.remove(id: id, metaTable: self.itemCacheMetaTable)
self.currentUpdatedCacheEntryKeys.insert(id)
} }
fileprivate func replaceGlobalMessageTagsHole(transaction: Transaction, globalTags: GlobalMessageTags, index: MessageIndex, with updatedIndex: MessageIndex?, messages: [StoreMessage]) { fileprivate func replaceGlobalMessageTagsHole(transaction: Transaction, globalTags: GlobalMessageTags, index: MessageIndex, with updatedIndex: MessageIndex?, messages: [StoreMessage]) {

View File

@ -153,22 +153,22 @@ final class PostboxTransaction {
if !self.currentUpdatedPendingPeerNotificationSettings.isEmpty { if !self.currentUpdatedPendingPeerNotificationSettings.isEmpty {
return false return false
} }
if replacedAdditionalChatListItems != nil { if self.replacedAdditionalChatListItems != nil {
return false return false
} }
if !updatedNoticeEntryKeys.isEmpty { if !self.updatedNoticeEntryKeys.isEmpty {
return false return false
} }
if !updatedCacheEntryKeys.isEmpty { if !self.updatedCacheEntryKeys.isEmpty {
return false return false
} }
if !updatedFailedMessagePeerIds.isEmpty { if !self.updatedFailedMessagePeerIds.isEmpty {
return false return false
} }
if !updatedFailedMessageIds.isEmpty { if !self.updatedFailedMessageIds.isEmpty {
return false return false
} }
if updatedGlobalNotificationSettings { if self.updatedGlobalNotificationSettings {
return false return false
} }
return true return true

View File

@ -0,0 +1,26 @@
load("//Config:buck_rule_macros.bzl", "static_library")
static_library(
name = "SlotMachineAnimationNode",
srcs = glob([
"Sources/**/*.swift",
]),
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared",
"//submodules/AsyncDisplayKit:AsyncDisplayKit#shared",
"//submodules/Display:Display#shared",
"//submodules/Postbox:Postbox#shared",
"//submodules/SyncCore:SyncCore#shared",
"//submodules/TelegramCore:TelegramCore#shared",
"//submodules/AccountContext:AccountContext",
"//submodules/StickerResources:StickerResources",
"//submodules/ManagedAnimationNode:ManagedAnimationNode",
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
"//submodules/AppBundle:AppBundle",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
],
)

View File

@ -0,0 +1,26 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "SlotMachineAnimationNode",
module_name = "SlotMachineAnimationNode",
srcs = glob([
"Sources/**/*.swift",
]),
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display:Display",
"//submodules/Postbox:Postbox",
"//submodules/SyncCore:SyncCore",
"//submodules/TelegramCore:TelegramCore",
"//submodules/AccountContext:AccountContext",
"//submodules/StickerResources:StickerResources",
"//submodules/ManagedAnimationNode:ManagedAnimationNode",
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
"//submodules/AppBundle:AppBundle",
],
visibility = [
"//visibility:public",
],
)

View File

@ -5,9 +5,11 @@ import Postbox
import SyncCore import SyncCore
import TelegramCore import TelegramCore
import SwiftSignalKit import SwiftSignalKit
import AccountContext
import StickerResources import StickerResources
import ManagedAnimationNode import ManagedAnimationNode
import AnimatedStickerNode
import TelegramAnimatedStickerNode
import AppBundle
private struct SlotMachineValue { private struct SlotMachineValue {
enum ReelValue { enum ReelValue {
@ -137,27 +139,29 @@ private func rightReelAnimationItem(value: SlotMachineValue.ReelValue, immediate
} }
} }
final class SlotMachineAnimationNode: ASDisplayNode, GenericAnimatedStickerNode { public enum ManagedSlotMachineAnimationState: Equatable {
private let context: AccountContext case rolling
case value(Int32, Bool)
}
public final class SlotMachineAnimationNode: ASDisplayNode {
private let backNode: ManagedAnimationNode private let backNode: ManagedAnimationNode
private let leftReelNode: ManagedAnimationNode private let leftReelNode: ManagedAnimationNode
private let centerReelNode: ManagedAnimationNode private let centerReelNode: ManagedAnimationNode
private let rightReelNode: ManagedAnimationNode private let rightReelNode: ManagedAnimationNode
private let frontNode: ManagedAnimationNode private let frontNode: ManagedAnimationNode
private var diceState: ManagedDiceAnimationState? = nil private var diceState: ManagedSlotMachineAnimationState? = nil
private let disposables = DisposableSet() private let disposables = DisposableSet()
init(context: AccountContext) {
self.context = context
let size = CGSize(width: 184.0, height: 184.0) private let animationSize = CGSize(width: 184.0, height: 184.0)
self.backNode = ManagedAnimationNode(size: size)
self.leftReelNode = ManagedAnimationNode(size: size) public override init() {
self.centerReelNode = ManagedAnimationNode(size: size) self.backNode = ManagedAnimationNode(size: self.animationSize)
self.rightReelNode = ManagedAnimationNode(size: size) self.leftReelNode = ManagedAnimationNode(size: self.animationSize)
self.frontNode = ManagedAnimationNode(size: size) self.centerReelNode = ManagedAnimationNode(size: self.animationSize)
self.rightReelNode = ManagedAnimationNode(size: self.animationSize)
self.frontNode = ManagedAnimationNode(size: self.animationSize)
super.init() super.init()
@ -172,7 +176,7 @@ final class SlotMachineAnimationNode: ASDisplayNode, GenericAnimatedStickerNode
self.disposables.dispose() self.disposables.dispose()
} }
override func layout() { public override func layout() {
super.layout() super.layout()
self.backNode.frame = self.bounds self.backNode.frame = self.bounds
@ -182,7 +186,7 @@ final class SlotMachineAnimationNode: ASDisplayNode, GenericAnimatedStickerNode
self.frontNode.frame = self.bounds self.frontNode.frame = self.bounds
} }
func setState(_ diceState: ManagedDiceAnimationState) { public func setState(_ diceState: ManagedSlotMachineAnimationState) {
let previousState = self.diceState let previousState = self.diceState
self.diceState = diceState self.diceState = diceState
@ -219,10 +223,10 @@ final class SlotMachineAnimationNode: ASDisplayNode, GenericAnimatedStickerNode
} }
} }
} else { } else {
self.backNode.trackTo(item: ManagedAnimationItem(source: .local("Slot_Back_Win"), frames: .still(.start), loop: false))
switch diceState { switch diceState {
case let .value(value, immediate): case let .value(value, immediate):
self.backNode.trackTo(item: ManagedAnimationItem(source: .local("Slot_Back_Win"), frames: .still(.start), loop: false))
let slotValue = SlotMachineValue(rawValue: value) let slotValue = SlotMachineValue(rawValue: value)
self.leftReelNode.trackTo(item: leftReelAnimationItem(value: slotValue.left, immediate: immediate)) self.leftReelNode.trackTo(item: leftReelAnimationItem(value: slotValue.left, immediate: immediate))
self.centerReelNode.trackTo(item: centerReelAnimationItem(value: slotValue.center, immediate: immediate)) self.centerReelNode.trackTo(item: centerReelAnimationItem(value: slotValue.center, immediate: immediate))
@ -231,7 +235,6 @@ final class SlotMachineAnimationNode: ASDisplayNode, GenericAnimatedStickerNode
let frames: ManagedAnimationFrameRange? = immediate ? .still(.end) : nil let frames: ManagedAnimationFrameRange? = immediate ? .still(.end) : nil
self.frontNode.trackTo(item: ManagedAnimationItem(source: .local("Slot_Front_Pull"), frames: frames, loop: false)) self.frontNode.trackTo(item: ManagedAnimationItem(source: .local("Slot_Front_Pull"), frames: frames, loop: false))
case .rolling: case .rolling:
self.backNode.trackTo(item: ManagedAnimationItem(source: .local("Slot_Back_Win"), frames: .still(.start), loop: false))
self.leftReelNode.trackTo(item: leftReelAnimationItem(value: .rolling)) self.leftReelNode.trackTo(item: leftReelAnimationItem(value: .rolling))
self.centerReelNode.trackTo(item: centerReelAnimationItem(value: .rolling)) self.centerReelNode.trackTo(item: centerReelAnimationItem(value: .rolling))
self.rightReelNode.trackTo(item: rightReelAnimationItem(value: .rolling)) self.rightReelNode.trackTo(item: rightReelAnimationItem(value: .rolling))
@ -240,3 +243,97 @@ final class SlotMachineAnimationNode: ASDisplayNode, GenericAnimatedStickerNode
} }
} }
} }
class DiceAnimatedStickerNode: ASDisplayNode {
public let intrinsicSize: CGSize
private let animationNode: AnimatedStickerNode
public var state: ManagedAnimationState?
public var trackStack: [ManagedAnimationItem] = []
public var didTryAdvancingState = false
init(size: CGSize) {
self.intrinsicSize = size
self.animationNode = AnimatedStickerNode()
self.animationNode.visibility = true
super.init()
self.addSubnode(self.animationNode)
self.animationNode.completed = { [weak self] willStop in
guard let strongSelf = self, !strongSelf.didTryAdvancingState, let state = strongSelf.state else {
return
}
if state.item.loop && strongSelf.trackStack.isEmpty {
} else {
strongSelf.didTryAdvancingState = true
strongSelf.advanceState()
}
}
}
private func advanceState() {
guard !self.trackStack.isEmpty else {
return
}
let item = self.trackStack.removeFirst()
if let state = self.state, state.item.source == item.source {
self.state = ManagedAnimationState(displaySize: self.intrinsicSize, item: item, current: state)
} else {
self.state = ManagedAnimationState(displaySize: self.intrinsicSize, item: item, current: nil)
}
var source: AnimatedStickerNodeSource?
switch item.source {
case let .local(animationName):
if let path = getAppBundle().path(forResource: animationName, ofType: "tgs") {
source = AnimatedStickerNodeLocalFileSource(path: path)
}
case let .resource(account, resource):
source = AnimatedStickerResourceSource(account: account, resource: resource)
}
let playbackMode: AnimatedStickerPlaybackMode
if item.loop {
playbackMode = .loop
} else if let frames = item.frames, case let .still(position) = frames {
playbackMode = .still(position == .start ? .start : .end)
} else {
playbackMode = .once
}
if let source = source {
self.animationNode.setup(source: source, width: Int(self.intrinsicSize.width), height: Int(self.intrinsicSize.height), playbackMode: playbackMode, mode: .direct(cachePathPrefix: nil))
}
self.didTryAdvancingState = false
}
func trackTo(item: ManagedAnimationItem) {
if let currentItem = self.state?.item {
if currentItem.source == item.source && currentItem.frames == item.frames && currentItem.loop == item.loop {
return
}
}
self.trackStack.append(item)
self.didTryAdvancingState = false
if !self.animationNode.isPlaying {
self.advanceState()
}
}
override func layout() {
super.layout()
self.animationNode.updateLayout(size: self.bounds.size)
self.animationNode.frame = self.bounds
}
}

View File

@ -72,6 +72,7 @@ public struct Namespaces {
public static let cachedThemesConfiguration: Int8 = 8 public static let cachedThemesConfiguration: Int8 = 8
public static let cachedPollResults: Int8 = 9 public static let cachedPollResults: Int8 = 9
public static let cachedContextResults: Int8 = 10 public static let cachedContextResults: Int8 = 10
public static let proximityNotificationStoredState: Int8 = 11
} }
public struct UnorderedItemList { public struct UnorderedItemList {

View File

@ -45,7 +45,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case botSentSecureValues(types: [SentSecureValueType]) case botSentSecureValues(types: [SentSecureValueType])
case peerJoined case peerJoined
case phoneNumberRequest case phoneNumberRequest
case geoProximityReached(distance: Int32) case geoProximityReached(from: PeerId, to: PeerId, distance: Int32)
public init(decoder: PostboxDecoder) { public init(decoder: PostboxDecoder) {
let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0) let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0)
@ -97,7 +97,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case 20: case 20:
self = .phoneNumberRequest self = .phoneNumberRequest
case 21: case 21:
self = .geoProximityReached(distance: (decoder.decodeInt32ForKey("dst", orElse: 0))) self = .geoProximityReached(from: PeerId(decoder.decodeInt64ForKey("fromId", orElse: 0)), to: PeerId(decoder.decodeInt64ForKey("toId", orElse: 0)), distance: (decoder.decodeInt32ForKey("dst", orElse: 0)))
default: default:
self = .unknown self = .unknown
} }
@ -183,8 +183,10 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
encoder.encodeInt32(19, forKey: "_rawValue") encoder.encodeInt32(19, forKey: "_rawValue")
case .phoneNumberRequest: case .phoneNumberRequest:
encoder.encodeInt32(20, forKey: "_rawValue") encoder.encodeInt32(20, forKey: "_rawValue")
case let .geoProximityReached(distance): case let .geoProximityReached(from, to, distance):
encoder.encodeInt32(21, forKey: "_rawValue") encoder.encodeInt32(21, forKey: "_rawValue")
encoder.encodeInt64(from.toInt64(), forKey: "fromId")
encoder.encodeInt64(to.toInt64(), forKey: "toId")
encoder.encodeInt32(distance, forKey: "dst") encoder.encodeInt32(distance, forKey: "dst")
} }
} }
@ -201,6 +203,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
return [groupId] return [groupId]
case let .groupMigratedToChannel(channelId): case let .groupMigratedToChannel(channelId):
return [channelId] return [channelId]
case let .geoProximityReached(from, to, _):
return [from, to]
default: default:
return [] return []
} }

View File

@ -168,6 +168,7 @@ private var declaredEncodables: Void = {
declareEncodable(Country.CountryCode.self, f: { Country.CountryCode(decoder: $0) }) declareEncodable(Country.CountryCode.self, f: { Country.CountryCode(decoder: $0) })
declareEncodable(CountriesList.self, f: { CountriesList(decoder: $0) }) declareEncodable(CountriesList.self, f: { CountriesList(decoder: $0) })
declareEncodable(ValidationMessageAttribute.self, f: { ValidationMessageAttribute(decoder: $0) }) declareEncodable(ValidationMessageAttribute.self, f: { ValidationMessageAttribute(decoder: $0) })
declareEncodable(ProximityNotificationStoredState.self, f: { ProximityNotificationStoredState(decoder: $0) })
return return
}() }()

View File

@ -2327,6 +2327,14 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
} }
} }
} }
for media in message.media {
if let action = media as? TelegramMediaAction, case .geoProximityReached = action.action {
if id.peerId.namespace == Namespaces.Peer.CloudUser {
let _ = updateProximityNotificationStoredStateInteractively(postbox: postbox, peerId: id.peerId, state: nil).start()
}
}
break
}
} }
} }
} }

View File

@ -1,6 +1,7 @@
import Foundation import Foundation
import Postbox import Postbox
import SwiftSignalKit import SwiftSignalKit
import TelegramApi
import SyncCore import SyncCore
@ -9,7 +10,7 @@ public func topPeerActiveLiveLocationMessages(viewTracker: AccountViewTracker, a
|> map { (view, _, _) -> (Peer?, [Message]) in |> map { (view, _, _) -> (Peer?, [Message]) in
var accountPeer: Peer? var accountPeer: Peer?
for entry in view.additionalData { for entry in view.additionalData {
if case let .peer(id, peer) = entry { if case let .peer(_, peer) = entry {
accountPeer = peer accountPeer = peer
break break
} }
@ -31,3 +32,93 @@ public func topPeerActiveLiveLocationMessages(viewTracker: AccountViewTracker, a
return (accountPeer, result) return (accountPeer, result)
} }
} }
public func requestProximityNotification(postbox: Postbox, network: Network, messageId: MessageId, distance: Int32) -> Signal<Void, NoError> {
return postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer)
}
|> mapToSignal { inputPeer -> Signal<Void, NoError> in
guard let inputPeer = inputPeer else {
return .complete()
}
let flags: Int32 = 1 << 0
return network.request(Api.functions.messages.requestProximityNotification(flags: flags, peer: inputPeer, msgId: messageId.id, maxDistance: distance))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Bool?, NoError> in
return .single(nil)
}
|> mapToSignal { _ -> Signal<Void, NoError> in
return updateProximityNotificationStoredStateInteractively(postbox: postbox, peerId: messageId.peerId, state: ProximityNotificationStoredState(messageId: messageId, distance: distance))
}
}
}
public func cancelProximityNotification(postbox: Postbox, network: Network, messageId: MessageId) -> Signal<Void, NoError> {
return postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer)
}
|> mapToSignal { inputPeer -> Signal<Void, NoError> in
guard let inputPeer = inputPeer else {
return .complete()
}
return network.request(Api.functions.messages.requestProximityNotification(flags: 0, peer: inputPeer, msgId: messageId.id, maxDistance: nil))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Bool?, NoError> in
return .single(nil)
}
|> mapToSignal { _ -> Signal<Void, NoError> in
return updateProximityNotificationStoredStateInteractively(postbox: postbox, peerId: messageId.peerId, state: nil)
}
}
}
public final class ProximityNotificationStoredState: PostboxCoding {
public let messageId: MessageId
public let distance: Int32
public init(messageId: MessageId, distance: Int32) {
self.messageId = messageId
self.distance = distance
}
public init(decoder: PostboxDecoder) {
self.messageId = MessageId(peerId: PeerId(decoder.decodeInt64ForKey("id.peerId", orElse: 0)), namespace: decoder.decodeInt32ForKey("id.namespace", orElse: 0), id: decoder.decodeInt32ForKey("id.id", orElse: 0))
self.distance = decoder.decodeInt32ForKey("distance", orElse: 0)
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt64(self.messageId.peerId.toInt64(), forKey: "id.peerId")
encoder.encodeInt32(self.messageId.namespace, forKey: "id.namespace")
encoder.encodeInt32(self.messageId.id, forKey: "id.id")
encoder.encodeInt32(self.distance, forKey: "distance")
}
}
private let collectionSpec = ItemCacheCollectionSpec(lowWaterItemCount: 25, highWaterItemCount: 50)
public func updateProximityNotificationStoredStateInteractively(postbox: Postbox, peerId: PeerId, state: ProximityNotificationStoredState?) -> Signal<Void, NoError> {
return postbox.transaction { transaction -> Void in
let key = ValueBoxKey(length: 8)
key.setInt64(0, value: peerId.toInt64())
let id = ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.proximityNotificationStoredState, key: key)
if let state = state {
transaction.putItemCacheEntry(id: id, entry: state, collectionSpec: collectionSpec)
} else {
transaction.removeItemCacheEntry(id: id)
}
}
}
public func proximityNotificationStoredState(account: Account, peerId: PeerId) -> Signal<ProximityNotificationStoredState?, NoError> {
let key = ValueBoxKey(length: 8)
key.setInt64(0, value: peerId.toInt64())
let id = ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.proximityNotificationStoredState, key: key)
let viewKey = PostboxViewKey.cachedItem(id)
return account.postbox.combinedView(keys: [viewKey])
|> map { views -> ProximityNotificationStoredState? in
if let value = (views.views[viewKey] as? CachedItemView)?.value as? ProximityNotificationStoredState {
return value
} else {
return nil
}
}
}

View File

@ -311,42 +311,3 @@ public func requestEditLiveLocation(postbox: Postbox, network: Network, stateMan
} }
} }
} }
public func requestProximityNotification(postbox: Postbox, network: Network, messageId: MessageId, distance: Int32) -> Signal<Void, NoError> {
return postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer)
}
|> mapToSignal { inputPeer -> Signal<Void, NoError> in
guard let inputPeer = inputPeer else {
return .complete()
}
let flags: Int32 = 1 << 0
return network.request(Api.functions.messages.requestProximityNotification(flags: flags, peer: inputPeer, msgId: messageId.id, maxDistance: distance))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Bool?, NoError> in
return .single(nil)
}
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
}
}
}
public func cancelProximityNotification(postbox: Postbox, network: Network, messageId: MessageId) -> Signal<Void, NoError> {
return postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer)
}
|> mapToSignal { inputPeer -> Signal<Void, NoError> in
guard let inputPeer = inputPeer else {
return .complete()
}
return network.request(Api.functions.messages.requestProximityNotification(flags: 1 << 1, peer: inputPeer, msgId: messageId.id, maxDistance: nil))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Bool?, NoError> in
return .single(nil)
}
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
}
}
}

View File

@ -58,7 +58,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
case .messageActionContactSignUp: case .messageActionContactSignUp:
return TelegramMediaAction(action: .peerJoined) return TelegramMediaAction(action: .peerJoined)
case let .messageActionGeoProximityReached(fromId, toId, distance): case let .messageActionGeoProximityReached(fromId, toId, distance):
return TelegramMediaAction(action: .geoProximityReached(distance: distance)) return TelegramMediaAction(action: .geoProximityReached(from: fromId.peerId, to: toId.peerId, distance: distance))
} }
} }

View File

@ -442,9 +442,13 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
attributedString = addAttributesToStringWithRanges(strings.Notification_Joined(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)])) attributedString = addAttributesToStringWithRanges(strings.Notification_Joined(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
case .phoneNumberRequest: case .phoneNumberRequest:
attributedString = nil attributedString = nil
case let .geoProximityReached(distance): case let .geoProximityReached(_, toId, distance):
let distanceString = stringForDistance(strings: strings, distance: Double(distance)) let distanceString = stringForDistance(strings: strings, distance: Double(distance))
attributedString = addAttributesToStringWithRanges(strings.Notification_ProximityReached(authorName, distanceString), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)])) if toId == accountPeerId {
attributedString = addAttributesToStringWithRanges(strings.Notification_ProximityReachedYou(authorName, distanceString), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
} else {
attributedString = addAttributesToStringWithRanges(strings.Notification_ProximityReached(authorName, distanceString, message.peers[toId]?.displayTitle(strings: strings, displayOrder: nameDisplayOrder) ?? ""), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id), (2, toId)]))
}
case .unknown: case .unknown:
attributedString = nil attributedString = nil
} }

View File

@ -211,6 +211,7 @@ framework(
"//submodules/ChatInterfaceState:ChatInterfaceState", "//submodules/ChatInterfaceState:ChatInterfaceState",
"//submodules/AnimatedCountLabelNode:AnimatedCountLabelNode", "//submodules/AnimatedCountLabelNode:AnimatedCountLabelNode",
"//submodules/AnimatedAvatarSetNode:AnimatedAvatarSetNode", "//submodules/AnimatedAvatarSetNode:AnimatedAvatarSetNode",
"//submodules/SlotMachineAnimationNode:SlotMachineAnimationNode",
], ],
frameworks = [ frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework", "$SDKROOT/System/Library/Frameworks/Foundation.framework",

View File

@ -208,6 +208,7 @@ swift_library(
"//submodules/ChatInterfaceState:ChatInterfaceState", "//submodules/ChatInterfaceState:ChatInterfaceState",
"//submodules/AnimatedCountLabelNode:AnimatedCountLabelNode", "//submodules/AnimatedCountLabelNode:AnimatedCountLabelNode",
"//submodules/AnimatedAvatarSetNode:AnimatedAvatarSetNode", "//submodules/AnimatedAvatarSetNode:AnimatedAvatarSetNode",
"//submodules/SlotMachineAnimationNode:SlotMachineAnimationNode",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -361,7 +361,7 @@ final class AuthorizedApplicationContext {
var processed = false var processed = false
for media in firstMessage.media { for media in firstMessage.media {
if let action = media as? TelegramMediaAction, case let .geoProximityReached(distance) = action.action { if let action = media as? TelegramMediaAction, case let .geoProximityReached(fromId, toId, distance) = action.action {
strongSelf.context.sharedContext.openLocationScreen(context: strongSelf.context, messageId: firstMessage.id, navigationController: strongSelf.rootController) strongSelf.context.sharedContext.openLocationScreen(context: strongSelf.context, messageId: firstMessage.id, navigationController: strongSelf.rootController)
processed = true processed = true
break break

View File

@ -19,6 +19,7 @@ import TelegramAnimatedStickerNode
import Emoji import Emoji
import Markdown import Markdown
import ManagedAnimationNode import ManagedAnimationNode
import SlotMachineAnimationNode
private let nameFont = Font.medium(14.0) private let nameFont = Font.medium(14.0)
private let inlineBotPrefixFont = Font.regular(14.0) private let inlineBotPrefixFont = Font.regular(14.0)
@ -32,6 +33,10 @@ extension AnimatedStickerNode: GenericAnimatedStickerNode {
} }
extension SlotMachineAnimationNode: GenericAnimatedStickerNode {
}
class ChatMessageShareButton: HighlightableButtonNode { class ChatMessageShareButton: HighlightableButtonNode {
private let backgroundNode: ASImageNode private let backgroundNode: ASImageNode
private let iconNode: ASImageNode private let iconNode: ASImageNode
@ -328,7 +333,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
if let telegramDice = self.telegramDice { if let telegramDice = self.telegramDice {
if telegramDice.emoji == "🎰" { if telegramDice.emoji == "🎰" {
let animationNode = SlotMachineAnimationNode(context: item.context) let animationNode = SlotMachineAnimationNode()
self.animationNode = animationNode self.animationNode = animationNode
} else { } else {
let animationNode = ManagedDiceAnimationNode(context: item.context, emoji: telegramDice.emoji.strippedEmoji) let animationNode = ManagedDiceAnimationNode(context: item.context, emoji: telegramDice.emoji.strippedEmoji)
@ -1458,10 +1463,15 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
if let telegramDice = self.telegramDice, let diceNode = self.animationNode as? ManagedDiceAnimationNode, let item = self.item, item.message.effectivelyIncoming(item.context.account.peerId) { if let telegramDice = self.telegramDice, let item = self.item, item.message.effectivelyIncoming(item.context.account.peerId) {
if let value = telegramDice.value, value != 0 { if let value = telegramDice.value, value != 0 {
diceNode.setState(.rolling) if let diceNode = self.animationNode as? ManagedDiceAnimationNode {
diceNode.setState(.value(value, false)) diceNode.setState(.rolling)
diceNode.setState(.value(value, false))
} else if let diceNode = self.animationNode as? SlotMachineAnimationNode {
diceNode.setState(.rolling)
diceNode.setState(.value(value, false))
}
} }
} }
} }

View File

@ -30,9 +30,9 @@ private func animationItem(account: Account, emojis: Signal<[TelegramMediaFile],
if let _ = account.postbox.mediaBox.completedResourcePath(file.resource) { if let _ = account.postbox.mediaBox.completedResourcePath(file.resource) {
if immediate { if immediate {
return .single(ManagedAnimationItem(source: .resource(account.postbox.mediaBox, file.resource), frames: .still(.end), duration: 0)) return .single(ManagedAnimationItem(source: .resource(account, file.resource), frames: .still(.end), duration: 0))
} else { } else {
return .single(ManagedAnimationItem(source: .resource(account.postbox.mediaBox, file.resource), loop: loop, callbacks: callbacks)) return .single(ManagedAnimationItem(source: .resource(account, file.resource), loop: loop, callbacks: callbacks))
} }
} else { } else {
let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512) let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512)
@ -45,7 +45,7 @@ private func animationItem(account: Account, emojis: Signal<[TelegramMediaFile],
|> filter { data in |> filter { data in
return data.complete return data.complete
}).start(next: { next in }).start(next: { next in
subscriber.putNext(ManagedAnimationItem(source: .resource(account.postbox.mediaBox, file.resource), loop: loop, callbacks: callbacks)) subscriber.putNext(ManagedAnimationItem(source: .resource(account, file.resource), loop: loop, callbacks: callbacks))
subscriber.putCompletion() subscriber.putCompletion()
}) })

View File

@ -21,6 +21,7 @@ static_library(
"//submodules/AppBundle:AppBundle", "//submodules/AppBundle:AppBundle",
"//submodules/StickerResources:StickerResources", "//submodules/StickerResources:StickerResources",
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode", "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
"//submodules/SlotMachineAnimationNode:SlotMachineAnimationNode",
], ],
frameworks = [ frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework", "$SDKROOT/System/Library/Frameworks/Foundation.framework",

View File

@ -22,6 +22,7 @@ swift_library(
"//submodules/AppBundle:AppBundle", "//submodules/AppBundle:AppBundle",
"//submodules/StickerResources:StickerResources", "//submodules/StickerResources:StickerResources",
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode", "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
"//submodules/SlotMachineAnimationNode:SlotMachineAnimationNode",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -10,6 +10,7 @@ import RadialStatusNode
import AppBundle import AppBundle
import AnimatedStickerNode import AnimatedStickerNode
import TelegramAnimatedStickerNode import TelegramAnimatedStickerNode
import SlotMachineAnimationNode
import AnimationUI import AnimationUI
import SyncCore import SyncCore
import Postbox import Postbox
@ -24,6 +25,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
private let iconCheckNode: RadialStatusNode? private let iconCheckNode: RadialStatusNode?
private let animationNode: AnimationNode? private let animationNode: AnimationNode?
private var animatedStickerNode: AnimatedStickerNode? private var animatedStickerNode: AnimatedStickerNode?
private var slotMachineNode: SlotMachineAnimationNode?
private var stillStickerNode: TransformImageNode? private var stillStickerNode: TransformImageNode?
private var stickerImageSize: CGSize? private var stickerImageSize: CGSize?
private var stickerOffset: CGPoint? private var stickerOffset: CGPoint?
@ -339,23 +341,31 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
break break
} }
let animatedStickerNode = AnimatedStickerNode() if dice.emoji == "🎰" {
self.animatedStickerNode = animatedStickerNode let slotMachineNode = SlotMachineAnimationNode()
self.slotMachineNode = slotMachineNode
let _ = (loadedStickerPack(postbox: account.postbox, network: account.network, reference: .dice(dice.emoji), forceActualized: false)
|> deliverOnMainQueue).start(next: { stickerPack in // slotMachineNode.setState(.rolling)
if let value = dice.value { // slotMachineNode.setState(.value(value, true))
switch stickerPack { } else {
case let .result(_, items, _): let animatedStickerNode = AnimatedStickerNode()
let item = items[Int(value)] self.animatedStickerNode = animatedStickerNode
if let item = item as? StickerPackItem {
animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: account, resource: item.file.resource), width: 120, height: 120, playbackMode: .once, mode: .direct(cachePathPrefix: nil)) let _ = (loadedStickerPack(postbox: account.postbox, network: account.network, reference: .dice(dice.emoji), forceActualized: false)
} |> deliverOnMainQueue).start(next: { stickerPack in
default: if let value = dice.value {
break switch stickerPack {
case let .result(_, items, _):
let item = items[Int(value)]
if let item = item as? StickerPackItem {
animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: account, resource: item.file.resource), width: 120, height: 120, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
}
default:
break
}
} }
} })
}) }
} }
self.remainingSeconds = self.originalRemainingSeconds self.remainingSeconds = self.originalRemainingSeconds
@ -397,6 +407,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.animationNode.flatMap(self.panelWrapperNode.addSubnode) self.animationNode.flatMap(self.panelWrapperNode.addSubnode)
self.stillStickerNode.flatMap(self.panelWrapperNode.addSubnode) self.stillStickerNode.flatMap(self.panelWrapperNode.addSubnode)
self.animatedStickerNode.flatMap(self.panelWrapperNode.addSubnode) self.animatedStickerNode.flatMap(self.panelWrapperNode.addSubnode)
self.slotMachineNode.flatMap(self.panelWrapperNode.addSubnode)
self.panelWrapperNode.addSubnode(self.titleNode) self.panelWrapperNode.addSubnode(self.titleNode)
self.panelWrapperNode.addSubnode(self.textNode) self.panelWrapperNode.addSubnode(self.textNode)
self.panelWrapperNode.addSubnode(self.buttonNode) self.panelWrapperNode.addSubnode(self.buttonNode)
@ -593,6 +604,10 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
let iconFrame = CGRect(origin: CGPoint(x: floor((leftInset - iconSize.width) / 2.0), y: floor((contentHeight - iconSize.height) / 2.0)), size: iconSize) let iconFrame = CGRect(origin: CGPoint(x: floor((leftInset - iconSize.width) / 2.0), y: floor((contentHeight - iconSize.height) / 2.0)), size: iconSize)
animatedStickerNode.updateLayout(size: iconFrame.size) animatedStickerNode.updateLayout(size: iconFrame.size)
transition.updateFrame(node: animatedStickerNode, frame: iconFrame) transition.updateFrame(node: animatedStickerNode, frame: iconFrame)
} else if let slotMachineNode = self.slotMachineNode {
let iconSize = CGSize(width: 32.0, height: 32.0)
let iconFrame = CGRect(origin: CGPoint(x: floor((leftInset - iconSize.width) / 2.0), y: floor((contentHeight - iconSize.height) / 2.0)), size: iconSize)
transition.updateFrame(node: slotMachineNode, frame: iconFrame)
} }
let timerTextSize = self.timerTextNode.updateLayout(CGSize(width: 100.0, height: 100.0)) let timerTextSize = self.timerTextNode.updateLayout(CGSize(width: 100.0, height: 100.0))