mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-07-31 23:47:01 +00:00
Merge commit '0077a070f2e6a9cb831b6e017260edd0f29d91e8'
This commit is contained in:
commit
98dc32fe4c
@ -282,7 +282,7 @@ public enum ChatInterfaceMediaDraftState: Codable, Equatable {
|
||||
public struct Audio: Codable, Equatable {
|
||||
public let resource: LocalFileMediaResource
|
||||
public let fileSize: Int32
|
||||
public let duration: Int32
|
||||
public let duration: Double
|
||||
public let waveform: AudioWaveform
|
||||
public let trimRange: Range<Double>?
|
||||
public let resumeData: Data?
|
||||
@ -290,7 +290,7 @@ public enum ChatInterfaceMediaDraftState: Codable, Equatable {
|
||||
public init(
|
||||
resource: LocalFileMediaResource,
|
||||
fileSize: Int32,
|
||||
duration: Int32,
|
||||
duration: Double,
|
||||
waveform: AudioWaveform,
|
||||
trimRange: Range<Double>?,
|
||||
resumeData: Data?
|
||||
@ -310,7 +310,12 @@ public enum ChatInterfaceMediaDraftState: Codable, Equatable {
|
||||
self.resource = LocalFileMediaResource(decoder: PostboxDecoder(buffer: MemoryBuffer(data: resourceData.data)))
|
||||
|
||||
self.fileSize = try container.decode(Int32.self, forKey: "s")
|
||||
self.duration = try container.decode(Int32.self, forKey: "d")
|
||||
|
||||
if let doubleValue = try container.decodeIfPresent(Double.self, forKey: "dd") {
|
||||
self.duration = doubleValue
|
||||
} else {
|
||||
self.duration = Double(try container.decode(Int32.self, forKey: "d"))
|
||||
}
|
||||
|
||||
let waveformData = try container.decode(Data.self, forKey: "wd")
|
||||
let waveformPeak = try container.decode(Int32.self, forKey: "wp")
|
||||
@ -330,7 +335,7 @@ public enum ChatInterfaceMediaDraftState: Codable, Equatable {
|
||||
|
||||
try container.encode(PostboxEncoder().encodeObjectToRawData(self.resource), forKey: "r")
|
||||
try container.encode(self.fileSize, forKey: "s")
|
||||
try container.encode(self.duration, forKey: "d")
|
||||
try container.encode(self.duration, forKey: "dd")
|
||||
try container.encode(self.waveform.samples, forKey: "wd")
|
||||
try container.encode(self.waveform.peak, forKey: "wp")
|
||||
|
||||
@ -368,13 +373,13 @@ public enum ChatInterfaceMediaDraftState: Codable, Equatable {
|
||||
}
|
||||
|
||||
public struct Video: Codable, Equatable {
|
||||
public let duration: Int32
|
||||
public let duration: Double
|
||||
public let frames: [UIImage]
|
||||
public let framesUpdateTimestamp: Double
|
||||
public let trimRange: Range<Double>?
|
||||
|
||||
public init(
|
||||
duration: Int32,
|
||||
duration: Double,
|
||||
frames: [UIImage],
|
||||
framesUpdateTimestamp: Double,
|
||||
trimRange: Range<Double>?
|
||||
@ -388,7 +393,11 @@ public enum ChatInterfaceMediaDraftState: Codable, Equatable {
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: StringCodingKey.self)
|
||||
|
||||
self.duration = try container.decode(Int32.self, forKey: "d")
|
||||
if let doubleValue = try container.decodeIfPresent(Double.self, forKey: "dd") {
|
||||
self.duration = doubleValue
|
||||
} else {
|
||||
self.duration = Double(try container.decode(Int32.self, forKey: "d"))
|
||||
}
|
||||
self.frames = []
|
||||
self.framesUpdateTimestamp = try container.decode(Double.self, forKey: "fu")
|
||||
if let trimLowerBound = try container.decodeIfPresent(Double.self, forKey: "tl"), let trimUpperBound = try container.decodeIfPresent(Double.self, forKey: "tu") {
|
||||
@ -401,7 +410,7 @@ public enum ChatInterfaceMediaDraftState: Codable, Equatable {
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: StringCodingKey.self)
|
||||
|
||||
try container.encode(self.duration, forKey: "d")
|
||||
try container.encode(self.duration, forKey: "dd")
|
||||
try container.encode(self.framesUpdateTimestamp, forKey: "fu")
|
||||
if let trimRange = self.trimRange {
|
||||
try container.encode(trimRange.lowerBound, forKey: "tl")
|
||||
|
@ -271,13 +271,25 @@ public final class LegacyControllerContext: NSObject, LegacyComponentsContext {
|
||||
return
|
||||
}
|
||||
|
||||
var position: TooltipScreen.ArrowPosition = .bottom
|
||||
if let layout = self.controller?.currentlyAppliedLayout, let orientation = layout.metrics.orientation {
|
||||
switch orientation {
|
||||
case .landscapeLeft:
|
||||
position = .left
|
||||
case .landscapeRight:
|
||||
position = .right
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let controller = TooltipScreen(
|
||||
account: context.account,
|
||||
sharedContext: context.sharedContext,
|
||||
text: .plain(text: text),
|
||||
style: .customBlur(UIColor(rgb: 0x18181a), 0.0),
|
||||
icon: .image(icon),
|
||||
location: .point(sourceRect, .bottom),
|
||||
location: .point(sourceRect, position),
|
||||
displayDuration: .custom(2.0),
|
||||
shouldDismissOnTouch: { _, _ in
|
||||
return .dismiss(consume: false)
|
||||
|
@ -157,7 +157,7 @@ public final class MediaPlayerTimeTextNode: ASDisplayNode {
|
||||
|
||||
private func ensureHasTimer() {
|
||||
if self.updateTimer == nil {
|
||||
let timer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: { [weak self] in
|
||||
let timer = SwiftSignalKit.Timer(timeout: 0.2, repeat: true, completion: { [weak self] in
|
||||
self?.updateTimestamp()
|
||||
}, queue: Queue.mainQueue())
|
||||
self.updateTimer = timer
|
||||
@ -182,7 +182,12 @@ public final class MediaPlayerTimeTextNode: ASDisplayNode {
|
||||
duration = trimRange.upperBound - trimRange.lowerBound
|
||||
}
|
||||
|
||||
if self.showDurationIfNotStarted && (timestamp < .ulpOfOne || self.isScrubbing) {
|
||||
var isPlaying = false
|
||||
if case .playing = statusValue.status {
|
||||
isPlaying = true
|
||||
}
|
||||
|
||||
if self.showDurationIfNotStarted && (timestamp < .ulpOfOne || self.isScrubbing) && !isPlaying {
|
||||
let timestamp = Int32(duration)
|
||||
self.state = MediaPlayerTimeTextNodeState(hours: timestamp / (60 * 60), minutes: timestamp % (60 * 60) / 60, seconds: timestamp % 60)
|
||||
} else {
|
||||
|
@ -279,6 +279,14 @@ public extension Peer {
|
||||
}
|
||||
}
|
||||
|
||||
var displayForumAsTabs: Bool {
|
||||
if let channel = self as? TelegramChannel, isForum {
|
||||
return channel.flags.contains(.displayForumAsTabs)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var isForumOrMonoForum: Bool {
|
||||
if let channel = self as? TelegramChannel {
|
||||
return channel.flags.contains(.isForum) || channel.flags.contains(.isMonoforum)
|
||||
@ -460,7 +468,7 @@ public func peerViewMonoforumMainPeer(_ view: PeerView) -> Peer? {
|
||||
if let channel = peer as? TelegramChannel, channel.flags.contains(.isMonoforum), let linkedMonoforumId = channel.linkedMonoforumId {
|
||||
return view.peers[linkedMonoforumId]
|
||||
} else {
|
||||
return peer
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
|
@ -206,6 +206,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 {
|
||||
case multipleStoriesTooltip = 79
|
||||
case voiceMessagesPauseSuggestion = 80
|
||||
case videoMessagesPauseSuggestion = 81
|
||||
case voiceMessagesResumeTrimWarning = 82
|
||||
|
||||
var key: ValueBoxKey {
|
||||
let v = ValueBoxKey(length: 4)
|
||||
@ -579,6 +580,10 @@ private struct ApplicationSpecificNoticeKeys {
|
||||
static func videoMessagesPauseSuggestion() -> NoticeEntryKey {
|
||||
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.videoMessagesPauseSuggestion.key)
|
||||
}
|
||||
|
||||
static func voiceMessagesResumeTrimWarning() -> NoticeEntryKey {
|
||||
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.voiceMessagesResumeTrimWarning.key)
|
||||
}
|
||||
}
|
||||
|
||||
public struct ApplicationSpecificNotice {
|
||||
@ -2522,4 +2527,31 @@ public struct ApplicationSpecificNotice {
|
||||
return Int(previousValue)
|
||||
}
|
||||
}
|
||||
|
||||
public static func getVoiceMessagesResumeTrimWarning(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Int32, NoError> {
|
||||
return accountManager.transaction { transaction -> Int32 in
|
||||
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.voiceMessagesResumeTrimWarning())?.get(ApplicationSpecificCounterNotice.self) {
|
||||
return value.value
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func incrementVoiceMessagesResumeTrimWarning(accountManager: AccountManager<TelegramAccountManagerTypes>, count: Int = 1) -> Signal<Int, NoError> {
|
||||
return accountManager.transaction { transaction -> Int in
|
||||
var currentValue: Int32 = 0
|
||||
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.voiceMessagesResumeTrimWarning())?.get(ApplicationSpecificCounterNotice.self) {
|
||||
currentValue = value.value
|
||||
}
|
||||
let previousValue = currentValue
|
||||
currentValue += Int32(count)
|
||||
|
||||
if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) {
|
||||
transaction.setNotice(ApplicationSpecificNoticeKeys.voiceMessagesResumeTrimWarning(), entry)
|
||||
}
|
||||
|
||||
return Int(previousValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -325,7 +325,7 @@ extension ChatControllerImpl {
|
||||
ChatInterfaceMediaDraftState.Audio(
|
||||
resource: resource!,
|
||||
fileSize: Int32(data.compressedData.count),
|
||||
duration: Int32(data.duration),
|
||||
duration: data.duration,
|
||||
waveform: audioWaveform,
|
||||
trimRange: data.trimRange,
|
||||
resumeData: data.resumeData
|
||||
@ -439,7 +439,7 @@ extension ChatControllerImpl {
|
||||
$0.updatedInterfaceState {
|
||||
$0.withUpdatedMediaDraftState(.video(
|
||||
ChatInterfaceMediaDraftState.Video(
|
||||
duration: Int32(data.duration),
|
||||
duration: data.duration,
|
||||
frames: data.frames,
|
||||
framesUpdateTimestamp: data.framesUpdateTimestamp,
|
||||
trimRange: data.trimRange
|
||||
@ -494,7 +494,7 @@ extension ChatControllerImpl {
|
||||
})
|
||||
} else {
|
||||
let proceed = {
|
||||
self.withAudioRecorder({ audioRecorder in
|
||||
self.withAudioRecorder(resuming: true, { audioRecorder in
|
||||
audioRecorder.resume()
|
||||
|
||||
self.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||
@ -505,24 +505,34 @@ extension ChatControllerImpl {
|
||||
})
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
if let recordedMediaPreview = self.presentationInterfaceState.interfaceState.mediaDraftState, case let .audio(audio) = recordedMediaPreview, let _ = audio.trimRange {
|
||||
self.present(
|
||||
textAlertController(
|
||||
context: self.context,
|
||||
title: self.presentationData.strings.Chat_TrimVoiceMessageToResume_Title,
|
||||
text: self.presentationData.strings.Chat_TrimVoiceMessageToResume_Text,
|
||||
actions: [
|
||||
TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {}),
|
||||
TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Chat_TrimVoiceMessageToResume_Proceed, action: {
|
||||
proceed()
|
||||
})
|
||||
]
|
||||
), in: .window(.root)
|
||||
)
|
||||
} else {
|
||||
proceed()
|
||||
}
|
||||
let _ = (ApplicationSpecificNotice.getVoiceMessagesResumeTrimWarning(accountManager: self.context.sharedContext.accountManager)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] count in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if count > 0 {
|
||||
proceed()
|
||||
return
|
||||
}
|
||||
if let recordedMediaPreview = self.presentationInterfaceState.interfaceState.mediaDraftState, case let .audio(audio) = recordedMediaPreview, let trimRange = audio.trimRange, trimRange.lowerBound > 0.1 || trimRange.upperBound < audio.duration {
|
||||
self.present(
|
||||
textAlertController(
|
||||
context: self.context,
|
||||
title: self.presentationData.strings.Chat_TrimVoiceMessageToResume_Title,
|
||||
text: self.presentationData.strings.Chat_TrimVoiceMessageToResume_Text,
|
||||
actions: [
|
||||
TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {}),
|
||||
TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Chat_TrimVoiceMessageToResume_Proceed, action: {
|
||||
proceed()
|
||||
let _ = ApplicationSpecificNotice.incrementVoiceMessagesResumeTrimWarning(accountManager: self.context.sharedContext.accountManager).start()
|
||||
})
|
||||
]
|
||||
), in: .window(.root)
|
||||
)
|
||||
} else {
|
||||
proceed()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -607,13 +617,43 @@ extension ChatControllerImpl {
|
||||
self.present(tooltipController, in: .window(.root))
|
||||
}
|
||||
|
||||
private func withAudioRecorder(_ f: (ManagedAudioRecorder) -> Void) {
|
||||
private func withAudioRecorder(resuming: Bool, _ f: (ManagedAudioRecorder) -> Void) {
|
||||
if let audioRecorder = self.audioRecorderValue {
|
||||
f(audioRecorder)
|
||||
} else if let recordedMediaPreview = self.presentationInterfaceState.interfaceState.mediaDraftState, case let .audio(audio) = recordedMediaPreview {
|
||||
self.requestAudioRecorder(beginWithTone: false, existingDraft: audio)
|
||||
if let audioRecorder = self.audioRecorderValue {
|
||||
f(audioRecorder)
|
||||
|
||||
if !resuming {
|
||||
self.recorderDataDisposable.set(
|
||||
(audioRecorder.takenRecordedData()
|
||||
|> deliverOnMainQueue).startStrict(
|
||||
next: { [weak self] data in
|
||||
if let strongSelf = self, let data = data {
|
||||
let audioWaveform = audio.waveform
|
||||
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||
$0.updatedInterfaceState {
|
||||
$0.withUpdatedMediaDraftState(.audio(
|
||||
ChatInterfaceMediaDraftState.Audio(
|
||||
resource: audio.resource,
|
||||
fileSize: Int32(data.compressedData.count),
|
||||
duration: data.duration,
|
||||
waveform: audioWaveform,
|
||||
trimRange: data.trimRange,
|
||||
resumeData: data.resumeData
|
||||
)
|
||||
))
|
||||
}.updatedInputTextPanelState { panelState in
|
||||
return panelState.withUpdatedMediaRecordingState(nil)
|
||||
}
|
||||
})
|
||||
strongSelf.updateDownButtonVisibility()
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -622,7 +662,7 @@ extension ChatControllerImpl {
|
||||
if let videoRecorder = self.videoRecorderValue {
|
||||
videoRecorder.updateTrimRange(start: start, end: end, updatedEnd: updatedEnd, apply: apply)
|
||||
} else {
|
||||
self.withAudioRecorder({ audioRecorder in
|
||||
self.withAudioRecorder(resuming: false, { audioRecorder in
|
||||
audioRecorder.updateTrimRange(start: start, end: end, updatedEnd: updatedEnd, apply: apply)
|
||||
})
|
||||
}
|
||||
|
@ -119,7 +119,7 @@ final class PlayButtonNode: ASDisplayNode {
|
||||
|
||||
transition.updateFrame(node: self.backgroundNode, frame: buttonSize.centered(in: CGRect(origin: .zero, size: size)))
|
||||
|
||||
self.playPauseIconNode.frame = CGRect(origin: CGPoint(x: 4.0, y: 1.0 - UIScreenPixel), size: CGSize(width: 21.0, height: 21.0))
|
||||
self.playPauseIconNode.frame = CGRect(origin: CGPoint(x: 3.0, y: 1.0 - UIScreenPixel), size: CGSize(width: 21.0, height: 21.0))
|
||||
|
||||
transition.updateFrame(node: self.durationLabel, frame: CGRect(origin: CGPoint(x: 18.0, y: 3.0), size: CGSize(width: 35.0, height: 20.0)))
|
||||
transition.updateAlpha(node: self.durationLabel, alpha: buttonSize.width > 27.0 ? 1.0 : 0.0)
|
||||
@ -509,16 +509,17 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
|
||||
}))
|
||||
}
|
||||
|
||||
let minDuration = max(1.0, 56.0 * audio.duration / waveformBackgroundFrame.size.width)
|
||||
let (leftHandleFrame, rightHandleFrame) = self.trimView.update(
|
||||
style: .voiceMessage,
|
||||
theme: interfaceState.theme,
|
||||
visualInsets: .zero,
|
||||
scrubberSize: waveformBackgroundFrame.size,
|
||||
duration: Double(audio.duration),
|
||||
duration: audio.duration,
|
||||
startPosition: audio.trimRange?.lowerBound ?? 0.0,
|
||||
endPosition: audio.trimRange?.upperBound ?? Double(audio.duration),
|
||||
position: 0.0,
|
||||
minDuration: 2.0,
|
||||
minDuration: minDuration,
|
||||
maxDuration: Double(audio.duration),
|
||||
transition: .immediate
|
||||
)
|
||||
@ -530,7 +531,7 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
|
||||
if !updatedEnd {
|
||||
self.mediaPlayer?.seek(timestamp: start, play: true)
|
||||
} else {
|
||||
self.mediaPlayer?.seek(timestamp: end - 1.0, play: true)
|
||||
self.mediaPlayer?.seek(timestamp: max(0.0, end - 1.0), play: true)
|
||||
}
|
||||
self.playButtonNode.durationLabel.isScrubbing = false
|
||||
Queue.mainQueue().after(0.1) {
|
||||
@ -547,6 +548,7 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
|
||||
}
|
||||
}
|
||||
self.trimView.frame = waveformBackgroundFrame
|
||||
self.trimView.isHidden = audio.duration < 2.0
|
||||
|
||||
let playButtonSize = CGSize(width: max(0.0, rightHandleFrame.minX - leftHandleFrame.maxX), height: waveformBackgroundFrame.height)
|
||||
self.playButtonNode.update(size: playButtonSize, transition: transition)
|
||||
@ -823,18 +825,23 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode {
|
||||
}
|
||||
if let recordedMediaPreview = self.presentationInterfaceState?.interfaceState.mediaDraftState, case let .audio(audio) = recordedMediaPreview, let trimRange = audio.trimRange {
|
||||
let _ = (mediaPlayer.status
|
||||
|> map(Optional.init)
|
||||
|> timeout(0.3, queue: Queue.mainQueue(), alternate: .single(nil))
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
guard let self, let mediaPlayer = self.mediaPlayer else {
|
||||
return
|
||||
}
|
||||
|
||||
if case .playing = status.status {
|
||||
mediaPlayer.pause()
|
||||
} else if status.timestamp <= trimRange.lowerBound {
|
||||
mediaPlayer.seek(timestamp: trimRange.lowerBound, play: true)
|
||||
if let status {
|
||||
if case .playing = status.status {
|
||||
mediaPlayer.pause()
|
||||
} else if status.timestamp <= trimRange.lowerBound {
|
||||
mediaPlayer.seek(timestamp: trimRange.lowerBound, play: true)
|
||||
} else {
|
||||
mediaPlayer.play()
|
||||
}
|
||||
} else {
|
||||
mediaPlayer.play()
|
||||
mediaPlayer.seek(timestamp: trimRange.lowerBound, play: true)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
|
@ -927,6 +927,15 @@ func openResolvedUrlImpl(
|
||||
}
|
||||
}
|
||||
|
||||
if let navigationController = context.sharedContext.mainWindow?.viewController as? NavigationController {
|
||||
for controller in navigationController.overlayControllers {
|
||||
controller.dismiss()
|
||||
}
|
||||
for controller in navigationController.globalOverlayControllers {
|
||||
controller.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
let _ = (context.engine.messages.checkStoriesUploadAvailability(target: .myStories)
|
||||
|> deliverOnMainQueue).start(next: { availability in
|
||||
if case let .available(remainingCount) = availability {
|
||||
|
@ -738,6 +738,9 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
||||
backgroundFrame = CGRect(origin: CGPoint(x: rect.midX - backgroundWidth / 2.0, y: rect.minY - bottomInset - backgroundHeight), size: CGSize(width: backgroundWidth, height: backgroundHeight))
|
||||
case .right:
|
||||
backgroundFrame = CGRect(origin: CGPoint(x: rect.minX - backgroundWidth - bottomInset, y: rect.midY - backgroundHeight / 2.0), size: CGSize(width: backgroundWidth, height: backgroundHeight))
|
||||
case .left:
|
||||
backgroundFrame = CGRect(origin: CGPoint(x: rect.maxX + bottomInset, y: rect.midY - backgroundHeight / 2.0), size: CGSize(width: backgroundWidth, height: backgroundHeight))
|
||||
|
||||
}
|
||||
|
||||
if backgroundFrame.minX < sideInset {
|
||||
@ -808,6 +811,17 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
||||
|
||||
transition.updateFrame(node: self.arrowContainer, frame: arrowFrame.offsetBy(dx: 8.0 - UIScreenPixel, dy: 0.0))
|
||||
|
||||
let arrowBounds = CGRect(origin: .zero, size: arrowSize)
|
||||
self.arrowNode.frame = arrowBounds
|
||||
self.arrowGradientNode?.frame = arrowBounds
|
||||
case .left:
|
||||
let arrowCenterY = floorToScreenPixels(rect.midY - arrowSize.height / 2.0)
|
||||
arrowFrame = CGRect(origin: CGPoint(x: -arrowSize.height, y: self.view.convert(CGPoint(x: 0.0, y: arrowCenterY), to: self.arrowContainer.supernode?.view).y), size: CGSize(width: arrowSize.height, height: arrowSize.width))
|
||||
|
||||
ContainedViewLayoutTransition.immediate.updateTransformRotation(node: self.arrowContainer, angle: CGFloat.pi / 2.0)
|
||||
|
||||
transition.updateFrame(node: self.arrowContainer, frame: arrowFrame.offsetBy(dx: 3.0 - UIScreenPixel, dy: -19.0))
|
||||
|
||||
let arrowBounds = CGRect(origin: .zero, size: arrowSize)
|
||||
self.arrowNode.frame = arrowBounds
|
||||
self.arrowGradientNode?.frame = arrowBounds
|
||||
@ -1073,6 +1087,8 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
||||
startPoint = CGPoint(x: self.arrowContainer.frame.midX - self.containerNode.bounds.width / 2.0, y: arrowY - self.containerNode.bounds.height / 2.0)
|
||||
case .right:
|
||||
startPoint = CGPoint(x: self.arrowContainer.frame.maxX - self.containerNode.bounds.width / 2.0, y: self.arrowContainer.frame.minY - self.containerNode.bounds.height / 2.0)
|
||||
case .left:
|
||||
startPoint = CGPoint(x: self.arrowContainer.frame.minX - self.containerNode.bounds.width / 2.0, y: self.arrowContainer.frame.minY - self.containerNode.bounds.height / 2.0)
|
||||
}
|
||||
|
||||
self.containerNode.layer.animateSpring(from: NSValue(cgPoint: startPoint), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.4, damping: 105.0, additive: true)
|
||||
@ -1123,6 +1139,8 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
||||
targetPoint = CGPoint(x: self.arrowContainer.frame.midX - self.containerNode.bounds.width / 2.0, y: arrowY - self.containerNode.bounds.height / 2.0)
|
||||
case .right:
|
||||
targetPoint = CGPoint(x: self.arrowContainer.frame.maxX - self.containerNode.bounds.width / 2.0, y: self.arrowContainer.frame.minY - self.containerNode.bounds.height / 2.0)
|
||||
case .left:
|
||||
targetPoint = CGPoint(x: self.arrowContainer.frame.minX - self.containerNode.bounds.width / 2.0, y: self.arrowContainer.frame.minY - self.containerNode.bounds.height / 2.0)
|
||||
}
|
||||
|
||||
self.containerNode.layer.animatePosition(from: CGPoint(), to: targetPoint, duration: 0.2, removeOnCompletion: false, additive: true)
|
||||
@ -1179,6 +1197,7 @@ public final class TooltipScreen: ViewController {
|
||||
case top
|
||||
case right
|
||||
case bottom
|
||||
case left
|
||||
}
|
||||
|
||||
public enum ArrowStyle {
|
||||
|
Loading…
x
Reference in New Issue
Block a user