mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 22:55:00 +00:00
Improve media playback rate controls
This commit is contained in:
@@ -11,6 +11,9 @@ import AccountContext
|
||||
import TelegramStringFormatting
|
||||
import ManagedAnimationNode
|
||||
import ContextUI
|
||||
import TelegramNotices
|
||||
import TooltipUI
|
||||
import SliderContextItem
|
||||
|
||||
private let titleFont = Font.regular(12.0)
|
||||
private let subtitleFont = Font.regular(10.0)
|
||||
@@ -186,28 +189,9 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
|
||||
}
|
||||
self.rateButton.accessibilityLabel = self.strings.VoiceOver_Media_PlaybackRate
|
||||
self.rateButton.accessibilityHint = self.strings.VoiceOver_Media_PlaybackRateChange
|
||||
switch playbackBaseRate {
|
||||
case .x0_5:
|
||||
self.rateButton.setContent(.image(optionsRateImage(rate: "0.5X", color: self.theme.rootController.navigationBar.accentTextColor)))
|
||||
self.rateButton.accessibilityValue = self.strings.VoiceOver_Media_PlaybackRate05X
|
||||
case .x1:
|
||||
self.rateButton.setContent(.image(optionsRateImage(rate: "1X", color: self.theme.rootController.navigationBar.controlColor)))
|
||||
self.rateButton.accessibilityValue = self.strings.VoiceOver_Media_PlaybackRateNormal
|
||||
case .x1_25:
|
||||
self.rateButton.setContent(.image(optionsRateImage(rate: "1.25X", color: self.theme.rootController.navigationBar.accentTextColor)))
|
||||
self.rateButton.accessibilityValue = self.strings.VoiceOver_Media_PlaybackRate125X
|
||||
case .x1_5:
|
||||
self.rateButton.setContent(.image(optionsRateImage(rate: "1.5X", color: self.theme.rootController.navigationBar.accentTextColor)))
|
||||
self.rateButton.accessibilityValue = self.strings.VoiceOver_Media_PlaybackRate15X
|
||||
case .x1_75:
|
||||
self.rateButton.setContent(.image(optionsRateImage(rate: "1.75X", color: self.theme.rootController.navigationBar.accentTextColor)))
|
||||
self.rateButton.accessibilityValue = self.strings.VoiceOver_Media_PlaybackRate175X
|
||||
case .x2:
|
||||
self.rateButton.setContent(.image(optionsRateImage(rate: "2X", color: self.theme.rootController.navigationBar.accentTextColor)))
|
||||
self.rateButton.accessibilityValue = self.strings.VoiceOver_Media_PlaybackRate2X
|
||||
default:
|
||||
break
|
||||
}
|
||||
self.rateButton.accessibilityValue = playbackBaseRate.stringValue
|
||||
|
||||
self.rateButton.setContent(.image(optionsRateImage(rate: playbackBaseRate.stringValue.uppercased(), color: self.theme.rootController.navigationBar.controlColor)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,22 +364,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
|
||||
self.scrubbingNode.updateContent(.standard(lineHeight: 2.0, lineCap: .square, scrubberHandle: .none, backgroundColor: .clear, foregroundColor: self.theme.rootController.navigationBar.accentTextColor, bufferingColor: self.theme.rootController.navigationBar.accentTextColor.withAlphaComponent(0.5), chapters: []))
|
||||
|
||||
if let playbackBaseRate = self.playbackBaseRate {
|
||||
switch playbackBaseRate {
|
||||
case .x0_5:
|
||||
self.rateButton.setContent(.image(optionsRateImage(rate: "0.5X", color: self.theme.rootController.navigationBar.accentTextColor)))
|
||||
case .x1:
|
||||
self.rateButton.setContent(.image(optionsRateImage(rate: "1X", color: self.theme.rootController.navigationBar.controlColor)))
|
||||
case .x1_25:
|
||||
self.rateButton.setContent(.image(optionsRateImage(rate: "1.25X", color: self.theme.rootController.navigationBar.controlColor)))
|
||||
case .x1_5:
|
||||
self.rateButton.setContent(.image(optionsRateImage(rate: "1.5X", color: self.theme.rootController.navigationBar.accentTextColor)))
|
||||
case .x1_75:
|
||||
self.rateButton.setContent(.image(optionsRateImage(rate: "1.75X", color: self.theme.rootController.navigationBar.controlColor)))
|
||||
case .x2:
|
||||
self.rateButton.setContent(.image(optionsRateImage(rate: "2X", color: self.theme.rootController.navigationBar.accentTextColor)))
|
||||
default:
|
||||
break
|
||||
}
|
||||
self.rateButton.setContent(.image(optionsRateImage(rate: playbackBaseRate.stringValue.uppercased(), color: self.theme.rootController.navigationBar.controlColor)))
|
||||
}
|
||||
if let (size, leftInset, rightInset) = self.validLayout {
|
||||
self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
|
||||
@@ -532,23 +501,41 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
|
||||
nextRate = .x2
|
||||
}
|
||||
self.setRate?(nextRate, false)
|
||||
|
||||
let frame = self.rateButton.view.convert(self.rateButton.bounds, to: nil)
|
||||
|
||||
let _ = (ApplicationSpecificNotice.incrementAudioRateOptionsTip(accountManager: self.context.sharedContext.accountManager)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] value in
|
||||
if let strongSelf = self, let controller = strongSelf.getController?(), value == 4 {
|
||||
controller.present(TooltipScreen(account: strongSelf.context.account, text: strongSelf.strings.Conversation_AudioRateOptionsTooltip, style: .default, icon: nil, location: .point(frame.offsetBy(dx: 0.0, dy: 4.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in
|
||||
return .dismiss(consume: false)
|
||||
}), in: .window(.root))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func speedList(strings: PresentationStrings) -> [(String, String, AudioPlaybackRate)] {
|
||||
let speedList: [(String, String, AudioPlaybackRate)] = [
|
||||
("0.5x", "0.5x", .x0_5),
|
||||
(strings.PlaybackSpeed_Normal, "1x", .x1),
|
||||
("1.25x", "1.25x", .x1_25),
|
||||
("1.5x", "1.5x", .x1_5),
|
||||
("1.75x", "1.75x", .x1_75),
|
||||
("2x", "2x", .x2)
|
||||
]
|
||||
return speedList
|
||||
}
|
||||
|
||||
private func contextMenuSpeedItems() -> Signal<[ContextMenuItem], NoError> {
|
||||
private func contextMenuSpeedItems(dismiss: @escaping () -> Void) -> Signal<[ContextMenuItem], NoError> {
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
items.append(.custom(SliderContextItem(minValue: 0.05, maxValue: 2.5, value: self.playbackBaseRate?.doubleValue ?? 1.0, valueChanged: { [weak self] newValue, finished in
|
||||
self?.setRate?(AudioPlaybackRate(newValue), true)
|
||||
if finished {
|
||||
dismiss()
|
||||
}
|
||||
}), true))
|
||||
|
||||
items.append(.separator)
|
||||
|
||||
for (text, _, rate) in self.speedList(strings: self.strings) {
|
||||
let isSelected = self.playbackBaseRate == rate
|
||||
items.append(.action(ContextMenuActionItem(text: text, icon: { theme in
|
||||
@@ -571,9 +558,14 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
|
||||
guard let controller = self.getController?() else {
|
||||
return
|
||||
}
|
||||
let items: Signal<[ContextMenuItem], NoError> = self.contextMenuSpeedItems()
|
||||
var dismissImpl: (() -> Void)?
|
||||
let items: Signal<[ContextMenuItem], NoError> = self.contextMenuSpeedItems(dismiss: {
|
||||
dismissImpl?()
|
||||
})
|
||||
let contextController = ContextController(account: self.context.account, presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: self.rateButton.referenceNode, shouldBeDismissed: self.dismissedPromise.get())), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
|
||||
|
||||
dismissImpl = { [weak contextController] in
|
||||
contextController?.dismiss()
|
||||
}
|
||||
self.presentInGlobalOverlay?(contextController)
|
||||
}
|
||||
|
||||
@@ -639,45 +631,38 @@ private final class PlayPauseIconNode: ManagedAnimationNode {
|
||||
}
|
||||
|
||||
private func optionsRateImage(rate: String, color: UIColor = .white) -> UIImage? {
|
||||
return generateImage(CGSize(width: 36.0, height: 16.0), rotatedContext: { size, context in
|
||||
let isLarge = "".isEmpty
|
||||
return generateImage(isLarge ? CGSize(width: 30.0, height: 30.0) : CGSize(width: 24.0, height: 24.0), rotatedContext: { size, context in
|
||||
UIGraphicsPushContext(context)
|
||||
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
let lineWidth = 1.0 + UIScreenPixel
|
||||
context.setLineWidth(lineWidth)
|
||||
context.setStrokeColor(color.cgColor)
|
||||
|
||||
if let image = generateTintedImage(image: UIImage(bundleImageName: isLarge ? "Chat/Context Menu/Playspeed30" : "Chat/Context Menu/Playspeed24"), color: color) {
|
||||
image.draw(at: CGPoint(x: 0.0, y: 0.0))
|
||||
}
|
||||
|
||||
let string = NSMutableAttributedString(string: rate, font: Font.with(size: 11.0, design: .round, weight: .bold), textColor: color)
|
||||
let string = NSMutableAttributedString(string: rate, font: Font.with(size: isLarge ? 11.0 : 10.0, design: .round, weight: .semibold), textColor: color)
|
||||
|
||||
var offset = CGPoint(x: 1.0, y: 0.0)
|
||||
var width: CGFloat
|
||||
if rate.count >= 5 {
|
||||
string.addAttribute(.kern, value: -0.8 as NSNumber, range: NSRange(string.string.startIndex ..< string.string.endIndex, in: string.string))
|
||||
offset.x += -0.5
|
||||
width = 34.0
|
||||
} else if rate.count >= 3 {
|
||||
if rate == "0.5X" {
|
||||
if rate.count >= 3 {
|
||||
if rate == "0.5x" {
|
||||
string.addAttribute(.kern, value: -0.8 as NSNumber, range: NSRange(string.string.startIndex ..< string.string.endIndex, in: string.string))
|
||||
offset.x += -0.5
|
||||
} else {
|
||||
string.addAttribute(.kern, value: -0.5 as NSNumber, range: NSRange(string.string.startIndex ..< string.string.endIndex, in: string.string))
|
||||
offset.x += -0.3
|
||||
}
|
||||
width = 29.0
|
||||
} else {
|
||||
string.addAttribute(.kern, value: -0.5 as NSNumber, range: NSRange(string.string.startIndex ..< string.string.endIndex, in: string.string))
|
||||
width = 19.0
|
||||
offset.x += -0.3
|
||||
}
|
||||
|
||||
let path = UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - width) / 2.0), y: 0.0, width: width, height: 16.0).insetBy(dx: lineWidth / 2.0, dy: lineWidth / 2.0), byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 2.0, height: 2.0))
|
||||
context.addPath(path.cgPath)
|
||||
context.strokePath()
|
||||
|
||||
|
||||
if !isLarge {
|
||||
offset.x *= 0.5
|
||||
offset.y *= 0.5
|
||||
}
|
||||
|
||||
let boundingRect = string.boundingRect(with: size, options: [], context: nil)
|
||||
string.draw(at: CGPoint(x: offset.x + floor((size.width - boundingRect.width) / 2.0), y: offset.y + UIScreenPixel + floor((size.height - boundingRect.height) / 2.0)))
|
||||
string.draw(at: CGPoint(x: offset.x + floor((size.width - boundingRect.width) / 2.0), y: offset.y + floor((size.height - boundingRect.height) / 2.0)))
|
||||
|
||||
UIGraphicsPopContext()
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user