diff --git a/submodules/DeviceLocationManager/Sources/DeviceLocationManager.swift b/submodules/DeviceLocationManager/Sources/DeviceLocationManager.swift index 60b1bd5df2..1a20f4e2c1 100644 --- a/submodules/DeviceLocationManager/Sources/DeviceLocationManager.swift +++ b/submodules/DeviceLocationManager/Sources/DeviceLocationManager.swift @@ -119,6 +119,19 @@ public final class DeviceLocationManager: NSObject { } } +extension CLHeading { + var effectiveHeading: Double? { + if self.headingAccuracy < 0.0 { + return nil + } + if self.trueHeading > 0.0 { + return self.trueHeading + } else { + return self.magneticHeading + } + } +} + extension DeviceLocationManager: CLLocationManagerDelegate { public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { assert(self.queue.isCurrent()) @@ -127,7 +140,7 @@ extension DeviceLocationManager: CLLocationManagerDelegate { if self.currentTopMode != nil { self.currentLocation = location for subscriber in self.subscribers { - subscriber.update(location, self.currentHeading?.magneticHeading) + subscriber.update(location, self.currentHeading?.effectiveHeading) } } } @@ -140,7 +153,7 @@ extension DeviceLocationManager: CLLocationManagerDelegate { self.currentHeading = newHeading if let currentLocation = self.currentLocation { for subscriber in self.subscribers { - subscriber.update(currentLocation, newHeading.magneticHeading) + subscriber.update(currentLocation, newHeading.effectiveHeading) } } } diff --git a/submodules/LocationUI/Sources/LocationViewControllerNode.swift b/submodules/LocationUI/Sources/LocationViewControllerNode.swift index 1968bfa591..6c063fccc9 100644 --- a/submodules/LocationUI/Sources/LocationViewControllerNode.swift +++ b/submodules/LocationUI/Sources/LocationViewControllerNode.swift @@ -544,7 +544,14 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan } func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) { - self.headerNode.mapNode.userHeading = CGFloat(newHeading.magneticHeading) + if newHeading.headingAccuracy < 0.0 { + self.headerNode.mapNode.userHeading = nil + } + if newHeading.trueHeading > 0.0 { + self.headerNode.mapNode.userHeading = CGFloat(newHeading.trueHeading) + } else { + self.headerNode.mapNode.userHeading = CGFloat(newHeading.magneticHeading) + } } func updatePresentationData(_ presentationData: PresentationData) { diff --git a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift index 8af7550cab..f1f856a7ad 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -176,6 +176,8 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { private var highlightedState: Bool = false private var haptic: EmojiHaptic? + private var mediaPlayer: MediaPlayer? + private let mediaStatusDisposable = MetaDisposable() private var currentSwipeToReplyTranslation: CGFloat = 0.0 @@ -246,6 +248,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { deinit { self.disposable.dispose() + self.mediaStatusDisposable.set(nil) } required init?(coder aDecoder: NSCoder) { @@ -1231,6 +1234,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { let beatingHearts: [UInt32] = [0x2764, 0x1F90E, 0x1F9E1, 0x1F499, 0x1F49A, 0x1F49C, 0x1F49B, 0x1F5A4, 0x1F90D] let peach = 0x1F351 + let coffin = 0x26B0 let appConfiguration = item.context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) |> take(1) @@ -1271,16 +1275,46 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { if shouldPlay { let _ = (appConfiguration |> deliverOnMainQueue).start(next: { [weak self] appConfiguration in + guard let strongSelf = self else { + return + } let emojiSounds = AnimatedEmojiSoundsConfiguration.with(appConfiguration: appConfiguration, account: item.context.account) for (emoji, file) in emojiSounds.sounds { if emoji.unicodeScalars.first == firstScalar { let mediaManager = item.context.sharedContext.mediaManager let mediaPlayer = MediaPlayer(audioSessionManager: mediaManager.audioSession, postbox: item.context.account.postbox, resourceReference: .standalone(resource: file.resource), streamable: .none, video: false, preferSoftwareDecoding: false, enableSound: true, fetchAutomatically: true) mediaPlayer.togglePlayPause() - self?.mediaPlayer = mediaPlayer - - animationNode.play() + mediaPlayer.actionAtEnd = .action({ [weak self] in + self?.mediaPlayer = nil + }) + strongSelf.mediaPlayer = mediaPlayer + strongSelf.mediaStatusDisposable.set((mediaPlayer.status + |> deliverOnMainQueue).start(next: { [weak self, weak animationNode] status in + if let strongSelf = self { + if firstScalar.value == coffin { + var haptic: EmojiHaptic + if let current = strongSelf.haptic { + haptic = current + } else { + haptic = CoffinHaptic() + haptic.enabled = true + strongSelf.haptic = haptic + } + if !haptic.active { + haptic.start(time: 0.0) + } + } + + switch status.status { + case .playing: + animationNode?.play() + strongSelf.mediaStatusDisposable.set(nil) + default: + break + } + } + })) break } } @@ -1303,8 +1337,6 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { return nil } - private var mediaPlayer: MediaPlayer? - @objc private func shareButtonPressed() { if let item = self.item { if case .pinnedMessages = item.associatedData.subject { diff --git a/submodules/TelegramUI/Sources/CoffinHaptic.swift b/submodules/TelegramUI/Sources/CoffinHaptic.swift new file mode 100644 index 0000000000..b1e0713ec3 --- /dev/null +++ b/submodules/TelegramUI/Sources/CoffinHaptic.swift @@ -0,0 +1,74 @@ +import Foundation +import Display +import SwiftSignalKit + +private let firstImpactTime: Double = 0.4 +private let secondImpactTime: Double = 0.6 + +final class CoffinHaptic: EmojiHaptic { + private var hapticFeedback = HapticFeedback() + private var timer: SwiftSignalKit.Timer? + private var time: Double = 0.0 + var enabled: Bool = false { + didSet { + if !self.enabled { + self.reset() + } + } + } + + var active: Bool { + return self.timer != nil + } + + private func reset() { + if let timer = self.timer { + self.time = 0.0 + timer.invalidate() + self.timer = nil + } + } + + private func beat(time: Double) { + let epsilon = 0.1 + if fabs(firstImpactTime - time) < epsilon || fabs(secondImpactTime - time) < epsilon { + self.hapticFeedback.impact(.heavy) + } + } + + func start(time: Double) { + self.hapticFeedback.prepareImpact() + + if time > firstImpactTime { + return + } + + let startTime: Double = 0.0 + + let block = { [weak self] in + guard let strongSelf = self, strongSelf.enabled else { + return + } + + strongSelf.time = startTime + strongSelf.beat(time: startTime) + strongSelf.timer = SwiftSignalKit.Timer(timeout: 0.2, repeat: true, completion: { [weak self] in + guard let strongSelf = self, strongSelf.enabled else { + return + } + strongSelf.time += 0.2 + strongSelf.beat(time: strongSelf.time) + + if strongSelf.time > secondImpactTime { + strongSelf.reset() + strongSelf.time = 0.0 + strongSelf.timer?.invalidate() + strongSelf.timer = nil + } + }, queue: Queue.mainQueue()) + strongSelf.timer?.start() + } + + block() + } +}