Merge commit '4903eafb12bbe1c5dab121ec30c42c4bc8e85241'

This commit is contained in:
Peter 2019-10-04 23:23:45 +04:00
commit 9da7eabff0
49 changed files with 221 additions and 219 deletions

View File

@ -52,7 +52,7 @@ final class ChatVideoGalleryItemScrubberView: UIView {
var seek: (Double) -> Void = { _ in } var seek: (Double) -> Void = { _ in }
override init(frame: CGRect) { override init(frame: CGRect) {
self.scrubberNode = MediaPlayerScrubbingNode(content: .standard(lineHeight: 5.0, lineCap: .round, scrubberHandle: .circle, backgroundColor: UIColor(white: 1.0, alpha: 0.42), foregroundColor: .white)) self.scrubberNode = MediaPlayerScrubbingNode(content: .standard(lineHeight: 5.0, lineCap: .round, scrubberHandle: .circle, backgroundColor: UIColor(white: 1.0, alpha: 0.42), foregroundColor: .white, bufferingColor: UIColor(rgb: 0xffffff, alpha: 0.5)))
self.leftTimestampNode = MediaPlayerTimeTextNode(textColor: .white) self.leftTimestampNode = MediaPlayerTimeTextNode(textColor: .white)
self.rightTimestampNode = MediaPlayerTimeTextNode(textColor: .white) self.rightTimestampNode = MediaPlayerTimeTextNode(textColor: .white)

View File

@ -98,7 +98,7 @@ final class InstantPageAudioNode: ASDisplayNode, InstantPageNode {
if brightness > 0.5 { if brightness > 0.5 {
backgroundAlpha = 0.4 backgroundAlpha = 0.4
} }
self.scrubbingNode = MediaPlayerScrubbingNode(content: .standard(lineHeight: 3.0, lineCap: .round, scrubberHandle: .line, backgroundColor: theme.textCategories.paragraph.color.withAlphaComponent(backgroundAlpha), foregroundColor: theme.textCategories.paragraph.color)) self.scrubbingNode = MediaPlayerScrubbingNode(content: .standard(lineHeight: 3.0, lineCap: .round, scrubberHandle: .line, backgroundColor: theme.textCategories.paragraph.color.withAlphaComponent(backgroundAlpha), foregroundColor: theme.textCategories.paragraph.color, bufferingColor: theme.textCategories.paragraph.color.withAlphaComponent(0.5)))
let playlistType: MediaManagerPlayerType let playlistType: MediaManagerPlayerType
if let file = self.media.media as? TelegramMediaFile { if let file = self.media.media as? TelegramMediaFile {

View File

@ -18,20 +18,40 @@ private func generateHandleBackground(color: UIColor) -> UIImage? {
})?.stretchableImage(withLeftCapWidth: 0, topCapHeight: 2) })?.stretchableImage(withLeftCapWidth: 0, topCapHeight: 2)
} }
private final class MediaPlayerScrubbingNodeButton: ASDisplayNode { private final class MediaPlayerScrubbingNodeButton: ASDisplayNode, UIGestureRecognizerDelegate {
var beginScrubbing: (() -> Void)? var beginScrubbing: (() -> Void)?
var endScrubbing: ((Bool) -> Void)? var endScrubbing: ((Bool) -> Void)?
var updateScrubbing: ((CGFloat) -> Void)? var updateScrubbing: ((CGFloat, Double) -> Void)?
var updateMultiplier: ((Double) -> Void)?
var highlighted: ((Bool) -> Void)? var highlighted: ((Bool) -> Void)?
var verticalPanEnabled = false
var hapticFeedback = HapticFeedback()
private var scrubbingMultiplier: Double = 1.0
private var scrubbingStartLocation: CGPoint? private var scrubbingStartLocation: CGPoint?
override func didLoad() { override func didLoad() {
super.didLoad() super.didLoad()
self.view.disablesInteractiveTransitionGestureRecognizer = true self.view.disablesInteractiveTransitionGestureRecognizer = true
self.view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))))
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
gestureRecognizer.delegate = self
self.view.addGestureRecognizer(gestureRecognizer)
}
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
guard let gestureRecognizer = gestureRecognizer as? UIPanGestureRecognizer else {
return !self.verticalPanEnabled
}
let translation = gestureRecognizer.translation(in: gestureRecognizer.view)
if self.verticalPanEnabled {
return abs(translation.x) > abs(translation.y) || translation.y > 0.0
} else {
return abs(translation.x) > abs(translation.y)
}
} }
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
@ -66,15 +86,38 @@ private final class MediaPlayerScrubbingNodeButton: ASDisplayNode {
case .changed: case .changed:
if let scrubbingStartLocation = self.scrubbingStartLocation { if let scrubbingStartLocation = self.scrubbingStartLocation {
let delta = location.x - scrubbingStartLocation.x let delta = location.x - scrubbingStartLocation.x
self.updateScrubbing?(delta / self.bounds.size.width) var multiplier: Double = 1.0
var skipUpdate = false
if self.verticalPanEnabled, location.y > scrubbingStartLocation.y {
let verticalDelta = abs(location.y - scrubbingStartLocation.y)
if verticalDelta > 150.0 {
multiplier = 0.01
} else if verticalDelta > 100.0 {
multiplier = 0.25
} else if verticalDelta > 50.0 {
multiplier = 0.5
}
if multiplier != self.scrubbingMultiplier {
skipUpdate = true
self.scrubbingMultiplier = multiplier
self.scrubbingStartLocation = CGPoint(x: location.x, y: scrubbingStartLocation.y)
self.updateMultiplier?(multiplier)
self.hapticFeedback.impact()
}
}
if !skipUpdate {
self.updateScrubbing?(delta / self.bounds.size.width, multiplier)
}
} }
case .ended, .cancelled: case .ended, .cancelled:
if let scrubbingStartLocation = self.scrubbingStartLocation { if let scrubbingStartLocation = self.scrubbingStartLocation {
self.scrubbingStartLocation = nil self.scrubbingStartLocation = nil
let delta = location.x - scrubbingStartLocation.x let delta = location.x - scrubbingStartLocation.x
self.updateScrubbing?(delta / self.bounds.size.width) self.updateScrubbing?(delta / self.bounds.size.width, self.scrubbingMultiplier)
self.endScrubbing?(recognizer.state == .ended) self.endScrubbing?(recognizer.state == .ended)
self.highlighted?(false) self.highlighted?(false)
self.scrubbingMultiplier = 1.0
} }
default: default:
break break
@ -106,7 +149,7 @@ public enum MediaPlayerScrubbingNodeHandle {
} }
public enum MediaPlayerScrubbingNodeContent { public enum MediaPlayerScrubbingNodeContent {
case standard(lineHeight: CGFloat, lineCap: MediaPlayerScrubbingNodeCap, scrubberHandle: MediaPlayerScrubbingNodeHandle, backgroundColor: UIColor, foregroundColor: UIColor) case standard(lineHeight: CGFloat, lineCap: MediaPlayerScrubbingNodeCap, scrubberHandle: MediaPlayerScrubbingNodeHandle, backgroundColor: UIColor, foregroundColor: UIColor, bufferingColor: UIColor)
case custom(backgroundNode: ASDisplayNode, foregroundContentNode: ASDisplayNode) case custom(backgroundNode: ASDisplayNode, foregroundContentNode: ASDisplayNode)
} }
@ -194,10 +237,12 @@ private final class MediaPlayerScrubbingBufferingNode: ASDisplayNode {
for range in ranges.0.rangeView { for range in ranges.0.rangeView {
let rangeWidth = min(size.width, (CGFloat(range.count) / CGFloat(ranges.1)) * size.width) let rangeWidth = min(size.width, (CGFloat(range.count) / CGFloat(ranges.1)) * size.width)
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: rangeWidth, height: size.height))) transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: rangeWidth, height: size.height)))
transition.updateAlpha(node: self.foregroundNode, alpha: abs(size.width - rangeWidth) < 1.0 ? 0.0 : 1.0)
break break
} }
} else { } else {
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: 0.0, height: size.height))) transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: 0.0, height: size.height)))
transition.updateAlpha(node: self.foregroundNode, alpha: 0.0)
} }
} }
} }
@ -240,6 +285,17 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
} }
} }
public var enableFineScrubbing: Bool = false {
didSet {
switch self.contentNodes {
case let .standard(node):
node.handleNodeContainer?.verticalPanEnabled = self.enableFineScrubbing
case let .custom(node):
node.handleNodeContainer?.verticalPanEnabled = self.enableFineScrubbing
}
}
}
private var _statusValue: MediaPlayerStatus? private var _statusValue: MediaPlayerStatus?
private var statusValue: MediaPlayerStatus? { private var statusValue: MediaPlayerStatus? {
get { get {
@ -293,13 +349,13 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
private static func contentNodesFromContent(_ content: MediaPlayerScrubbingNodeContent, enableScrubbing: Bool) -> MediaPlayerScrubbingNodeContentNodes { private static func contentNodesFromContent(_ content: MediaPlayerScrubbingNodeContent, enableScrubbing: Bool) -> MediaPlayerScrubbingNodeContentNodes {
switch content { switch content {
case let .standard(lineHeight, lineCap, scrubberHandle, backgroundColor, foregroundColor): case let .standard(lineHeight, lineCap, scrubberHandle, backgroundColor, foregroundColor, bufferingColor):
let backgroundNode = ASImageNode() let backgroundNode = ASImageNode()
backgroundNode.isLayerBacked = true backgroundNode.isLayerBacked = true
backgroundNode.displaysAsynchronously = false backgroundNode.displaysAsynchronously = false
backgroundNode.displayWithoutProcessing = true backgroundNode.displayWithoutProcessing = true
let bufferingNode = MediaPlayerScrubbingBufferingNode(color: foregroundColor.withAlphaComponent(0.5), lineCap: lineCap, lineHeight: lineHeight) let bufferingNode = MediaPlayerScrubbingBufferingNode(color: bufferingColor, lineCap: lineCap, lineHeight: lineHeight)
let foregroundContentNode = ASImageNode() let foregroundContentNode = ASImageNode()
foregroundContentNode.isLayerBacked = true foregroundContentNode.isLayerBacked = true
@ -421,7 +477,7 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
strongSelf.displayLink?.isPaused = true strongSelf.displayLink?.isPaused = true
var timestamp = statusValue.timestamp var timestamp = statusValue.timestamp
if statusValue.generationTimestamp > 0 { if statusValue.generationTimestamp > 0 && statusValue.status == .playing {
let currentTimestamp = CACurrentMediaTime() let currentTimestamp = CACurrentMediaTime()
timestamp = timestamp + (currentTimestamp - statusValue.generationTimestamp) * statusValue.baseRate timestamp = timestamp + (currentTimestamp - statusValue.generationTimestamp) * statusValue.baseRate
} }
@ -437,7 +493,6 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
strongSelf.updateProgressAnimations() strongSelf.updateProgressAnimations()
highlightedHandleNode.layer.animateSpring(from: 1.0 as NSNumber, to: 0.1875 as NSNumber, keyPath: "transform.scale", duration: 0.65, initialVelocity: 0.0, damping: 120.0, removeOnCompletion: false) highlightedHandleNode.layer.animateSpring(from: 1.0 as NSNumber, to: 0.1875 as NSNumber, keyPath: "transform.scale", duration: 0.65, initialVelocity: 0.0, damping: 120.0, removeOnCompletion: false)
} }
} }
} }
@ -453,10 +508,11 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
} }
} }
} }
handleNodeContainer.updateScrubbing = { [weak self] addedFraction in handleNodeContainer.updateScrubbing = { [weak self] addedFraction, multiplier in
if let strongSelf = self { if let strongSelf = self {
if let statusValue = strongSelf.statusValue, let scrubbingBeginTimestamp = strongSelf.scrubbingBeginTimestamp, Double(0.0).isLess(than: statusValue.duration) { if let statusValue = strongSelf.statusValue, let scrubbingBeginTimestamp = strongSelf.scrubbingBeginTimestamp, Double(0.0).isLess(than: statusValue.duration) {
let timestampValue = max(0.0, min(statusValue.duration, scrubbingBeginTimestamp + statusValue.duration * Double(addedFraction))) let delta: Double = (statusValue.duration * Double(addedFraction)) * multiplier
let timestampValue = max(0.0, min(statusValue.duration, scrubbingBeginTimestamp + delta))
strongSelf.scrubbingTimestampValue = timestampValue strongSelf.scrubbingTimestampValue = timestampValue
strongSelf._scrubbingTimestamp.set(.single(strongSelf.scrubbingTimestampValue)) strongSelf._scrubbingTimestamp.set(.single(strongSelf.scrubbingTimestampValue))
strongSelf._scrubbingPosition.set(.single(strongSelf.scrubbingTimestampValue.flatMap { $0 / statusValue.duration })) strongSelf._scrubbingPosition.set(.single(strongSelf.scrubbingTimestampValue.flatMap { $0 / statusValue.duration }))
@ -465,6 +521,13 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
} }
} }
} }
handleNodeContainer.updateMultiplier = { [weak self] multiplier in
if let strongSelf = self {
if let statusValue = strongSelf.statusValue, let scrubbingBeginTimestamp = strongSelf.scrubbingBeginTimestamp, Double(0.0).isLess(than: statusValue.duration) {
strongSelf.scrubbingBeginTimestamp = strongSelf.scrubbingTimestampValue
}
}
}
handleNodeContainer.endScrubbing = { [weak self] apply in handleNodeContainer.endScrubbing = { [weak self] apply in
if let strongSelf = self { if let strongSelf = self {
strongSelf.scrubbingBeginTimestamp = nil strongSelf.scrubbingBeginTimestamp = nil
@ -513,14 +576,21 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
} }
} }
} }
handleNodeContainer.updateScrubbing = { [weak self] addedFraction in handleNodeContainer.updateScrubbing = { [weak self] addedFraction, multiplier in
if let strongSelf = self { if let strongSelf = self {
if let statusValue = strongSelf.statusValue, let scrubbingBeginTimestamp = strongSelf.scrubbingBeginTimestamp, Double(0.0).isLess(than: statusValue.duration) { if let statusValue = strongSelf.statusValue, let scrubbingBeginTimestamp = strongSelf.scrubbingBeginTimestamp, Double(0.0).isLess(than: statusValue.duration) {
strongSelf.scrubbingTimestampValue = scrubbingBeginTimestamp + statusValue.duration * Double(addedFraction) strongSelf.scrubbingTimestampValue = scrubbingBeginTimestamp + (statusValue.duration * Double(addedFraction)) * multiplier
strongSelf.updateProgressAnimations() strongSelf.updateProgressAnimations()
} }
} }
} }
handleNodeContainer.updateMultiplier = { [weak self] multiplier in
if let strongSelf = self {
if let statusValue = strongSelf.statusValue, let scrubbingBeginTimestamp = strongSelf.scrubbingBeginTimestamp, Double(0.0).isLess(than: statusValue.duration) {
strongSelf.scrubbingBeginTimestamp = strongSelf.scrubbingTimestampValue
}
}
}
handleNodeContainer.endScrubbing = { [weak self] apply in handleNodeContainer.endScrubbing = { [weak self] apply in
if let strongSelf = self { if let strongSelf = self {
strongSelf.scrubbingBeginTimestamp = nil strongSelf.scrubbingBeginTimestamp = nil

View File

@ -35,6 +35,20 @@ private struct MediaPlayerTimeTextNodeState: Equatable {
} }
} }
private extension MediaPlayerTimeTextNodeState {
var string: String {
if let hours = self.hours, let minutes = self.minutes, let seconds = self.seconds {
if hours != 0 {
return String(format: "%d:%02d:%02d", hours, minutes, seconds)
} else {
return String(format: "%d:%02d", minutes, seconds)
}
} else {
return "-:--"
}
}
}
private final class MediaPlayerTimeTextNodeParameters: NSObject { private final class MediaPlayerTimeTextNodeParameters: NSObject {
let state: MediaPlayerTimeTextNodeState let state: MediaPlayerTimeTextNodeState
let alignment: NSTextAlignment let alignment: NSTextAlignment
@ -160,6 +174,14 @@ public final class MediaPlayerTimeTextNode: ASDisplayNode {
} }
} }
private let digitsSet = CharacterSet(charactersIn: "0123456789")
private func widthForString(_ string: String) -> CGFloat {
let convertedString = string.components(separatedBy: digitsSet).joined(separator: "8")
let text = NSAttributedString(string: convertedString, font: textFont, textColor: .black)
let size = text.boundingRect(with: CGSize(width: 200.0, height: 100.0), options: NSStringDrawingOptions.usesLineFragmentOrigin, context: nil).size
return size.width
}
override public func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? { override public func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
return MediaPlayerTimeTextNodeParameters(state: self.state, alignment: self.alignment, mode: self.mode, textColor: self.textColor) return MediaPlayerTimeTextNodeParameters(state: self.state, alignment: self.alignment, mode: self.mode, textColor: self.textColor)
} }
@ -174,19 +196,8 @@ public final class MediaPlayerTimeTextNode: ASDisplayNode {
} }
if let parameters = parameters as? MediaPlayerTimeTextNodeParameters { if let parameters = parameters as? MediaPlayerTimeTextNodeParameters {
let text: String let string = NSAttributedString(string: parameters.state.string, font: textFont, textColor: parameters.textColor)
if let hours = parameters.state.hours, let minutes = parameters.state.minutes, let seconds = parameters.state.seconds {
if hours != 0 {
text = String(format: "%d:%02d:%02d", hours, minutes, seconds)
} else {
text = String(format: "%d:%02d", minutes, seconds)
}
} else {
text = "-:--"
}
let string = NSAttributedString(string: text, font: textFont, textColor: parameters.textColor)
let size = string.boundingRect(with: CGSize(width: 200.0, height: 100.0), options: NSStringDrawingOptions.usesLineFragmentOrigin, context: nil).size let size = string.boundingRect(with: CGSize(width: 200.0, height: 100.0), options: NSStringDrawingOptions.usesLineFragmentOrigin, context: nil).size
if parameters.alignment == .left { if parameters.alignment == .left {
string.draw(at: CGPoint()) string.draw(at: CGPoint())
} else { } else {

View File

@ -258,7 +258,7 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollViewDeleg
self.actionPlayNode.image = PresentationResourcesRootController.navigationPlayerPlayIcon(self.theme) self.actionPlayNode.image = PresentationResourcesRootController.navigationPlayerPlayIcon(self.theme)
self.actionPlayNode.isHidden = true self.actionPlayNode.isHidden = true
self.scrubbingNode = MediaPlayerScrubbingNode(content: .standard(lineHeight: 2.0, lineCap: .square, scrubberHandle: .none, backgroundColor: .clear, foregroundColor: self.theme.rootController.navigationBar.accentTextColor)) self.scrubbingNode = MediaPlayerScrubbingNode(content: .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)))
self.separatorNode = ASDisplayNode() self.separatorNode = ASDisplayNode()
self.separatorNode.isLayerBacked = true self.separatorNode.isLayerBacked = true
@ -371,7 +371,7 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollViewDeleg
self.actionPlayNode.image = PresentationResourcesRootController.navigationPlayerPlayIcon(self.theme) self.actionPlayNode.image = PresentationResourcesRootController.navigationPlayerPlayIcon(self.theme)
self.actionPauseNode.image = PresentationResourcesRootController.navigationPlayerPauseIcon(self.theme) self.actionPauseNode.image = PresentationResourcesRootController.navigationPlayerPauseIcon(self.theme)
self.separatorNode.backgroundColor = self.theme.rootController.navigationBar.separatorColor self.separatorNode.backgroundColor = self.theme.rootController.navigationBar.separatorColor
self.scrubbingNode.updateContent(.standard(lineHeight: 2.0, lineCap: .square, scrubberHandle: .none, backgroundColor: .clear, foregroundColor: self.theme.rootController.navigationBar.accentTextColor)) 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)))
if let playbackBaseRate = self.playbackBaseRate { if let playbackBaseRate = self.playbackBaseRate {
switch playbackBaseRate { switch playbackBaseRate {

View File

@ -26,15 +26,10 @@ public enum PresentationResourceKey: Int32 {
case navigationPlayerPlayIcon case navigationPlayerPlayIcon
case navigationPlayerPauseIcon case navigationPlayerPauseIcon
case navigationPlayerMaximizedPlayIcon
case navigationPlayerMaximizedPauseIcon
case navigationPlayerMaximizedPreviousIcon
case navigationPlayerMaximizedNextIcon
case navigationPlayerMaximizedShuffleIcon
case navigationPlayerMaximizedRepeatIcon
case navigationPlayerHandleIcon
case navigationPlayerRateActiveIcon case navigationPlayerRateActiveIcon
case navigationPlayerRateInactiveIcon case navigationPlayerRateInactiveIcon
case navigationPlayerMaximizedRateActiveIcon
case navigationPlayerMaximizedRateInactiveIcon
case itemListDisclosureArrow case itemListDisclosureArrow
case itemListCheckIcon case itemListCheckIcon

View File

@ -120,6 +120,18 @@ public struct PresentationResourcesRootController {
}) })
} }
public static func navigationPlayerMaximizedRateActiveIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationPlayerMaximizedRateActiveIcon.rawValue, { theme in
return generatePlayerRateIcon(theme.list.itemAccentColor)
})
}
public static func navigationPlayerMaximizedRateInactiveIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationPlayerMaximizedRateInactiveIcon.rawValue, { theme in
return generatePlayerRateIcon(theme.list.itemSecondaryTextColor)
})
}
public static func navigationPlayerPauseIcon(_ theme: PresentationTheme) -> UIImage? { public static func navigationPlayerPauseIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationPlayerPauseIcon.rawValue, { theme in return theme.image(PresentationResourceKey.navigationPlayerPauseIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/MinimizedPause"), color: theme.rootController.navigationBar.accentTextColor) return generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/MinimizedPause"), color: theme.rootController.navigationBar.accentTextColor)
@ -132,48 +144,6 @@ public struct PresentationResourcesRootController {
}) })
} }
public static func navigationPlayerMaximizedPlayIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationPlayerMaximizedPlayIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/Play"), color: theme.rootController.navigationBar.primaryTextColor)
})
}
public static func navigationPlayerMaximizedPauseIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationPlayerMaximizedPauseIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/Pause"), color: theme.rootController.navigationBar.primaryTextColor)
})
}
public static func navigationPlayerMaximizedPreviousIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationPlayerMaximizedPreviousIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/Previous"), color: theme.rootController.navigationBar.primaryTextColor)
})
}
public static func navigationPlayerMaximizedNextIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationPlayerMaximizedNextIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/Next"), color: theme.rootController.navigationBar.primaryTextColor)
})
}
public static func navigationPlayerMaximizedShuffleIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationPlayerMaximizedShuffleIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/Shuffle"), color: theme.rootController.navigationBar.primaryTextColor)
})
}
public static func navigationPlayerMaximizedRepeatIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationPlayerMaximizedRepeatIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/Repeat"), color: theme.rootController.navigationBar.primaryTextColor)
})
}
public static func navigationPlayerHandleIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationPlayerHandleIcon.rawValue, { theme in
return generateStretchableFilledCircleImage(diameter: 7.0, color: theme.rootController.navigationBar.controlColor)
})
}
public static func inAppNotificationBackground(_ theme: PresentationTheme) -> UIImage? { public static func inAppNotificationBackground(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.inAppNotificationBackground.rawValue, { theme in return theme.image(PresentationResourceKey.inAppNotificationBackground.rawValue, { theme in
let inset: CGFloat = 16.0 let inset: CGFloat = 16.0

View File

@ -2,17 +2,7 @@
"images" : [ "images" : [
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "1x" "filename" : "ic_musicnext.pdf"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlForward@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlForward@3x.png",
"scale" : "3x"
} }
], ],
"info" : { "info" : {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -2,17 +2,7 @@
"images" : [ "images" : [
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "1x" "filename" : "ic_musicshuffle.pdf"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlShuffle@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlShuffle@3x.png",
"scale" : "3x"
} }
], ],
"info" : { "info" : {

View File

@ -2,17 +2,7 @@
"images" : [ "images" : [
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "1x" "filename" : "ic_musicflipover.pdf"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlReverse@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlReverse@3x.png",
"scale" : "3x"
} }
], ],
"info" : { "info" : {

View File

@ -2,17 +2,7 @@
"images" : [ "images" : [
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "1x" "filename" : "ic_musicpause.pdf"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlPause@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlPause@3x.png",
"scale" : "3x"
} }
], ],
"info" : { "info" : {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 371 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 273 B

View File

@ -2,17 +2,7 @@
"images" : [ "images" : [
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "1x" "filename" : "ic_musicplay.pdf"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlPlay@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlPlay@3x.png",
"scale" : "3x"
} }
], ],
"info" : { "info" : {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 565 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

View File

@ -2,17 +2,7 @@
"images" : [ "images" : [
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "1x" "filename" : "ic_musicprevious.pdf"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlBack@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlBack@3x.png",
"scale" : "3x"
} }
], ],
"info" : { "info" : {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 683 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 591 B

View File

@ -2,17 +2,7 @@
"images" : [ "images" : [
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "1x" "filename" : "ic_musicrepeat.pdf"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlRepeat@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlRepeat@3x.png",
"scale" : "3x"
} }
], ],
"info" : { "info" : {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 391 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 429 B

View File

@ -2,17 +2,7 @@
"images" : [ "images" : [
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "1x" "filename" : "ic_musicrepeatone.pdf"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlRepeatOne@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlRepeatOne@3x.png",
"scale" : "3x"
} }
], ],
"info" : { "info" : {

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "ic_musicshare.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@ -1,22 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlShuffle@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlShuffle@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 549 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 497 B

View File

@ -402,6 +402,8 @@ final class AuthorizedApplicationContext {
} }
}) })
if !isMuted { if !isMuted {
if firstMessage.id.peerId == context.account.peerId, !firstMessage.flags.contains(.WasScheduled) {
} else {
if inAppNotificationSettings.playSounds { if inAppNotificationSettings.playSounds {
serviceSoundManager.playIncomingMessageSound() serviceSoundManager.playIncomingMessageSound()
} }
@ -410,6 +412,7 @@ final class AuthorizedApplicationContext {
} }
} }
} }
}
if chatIsVisible { if chatIsVisible {
return return

View File

@ -352,6 +352,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
if let gestureRecognizers = view.gestureRecognizers, view != self.view { if let gestureRecognizers = view.gestureRecognizers, view != self.view {
for gestureRecognizer in gestureRecognizers { for gestureRecognizer in gestureRecognizers {
if let panGestureRecognizer = gestureRecognizer as? UIPanGestureRecognizer, gestureRecognizer.isEnabled { if let panGestureRecognizer = gestureRecognizer as? UIPanGestureRecognizer, gestureRecognizer.isEnabled {
if panGestureRecognizer.state != .began {
panGestureRecognizer.isEnabled = false panGestureRecognizer.isEnabled = false
panGestureRecognizer.isEnabled = true panGestureRecognizer.isEnabled = true
} }
@ -359,6 +360,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
} }
} }
} }
}
return true return true
} }

View File

@ -21,16 +21,41 @@ private func generateBackground(theme: PresentationTheme) -> UIImage? {
})?.stretchableImage(withLeftCapWidth: 10, topCapHeight: 10 + 8) })?.stretchableImage(withLeftCapWidth: 10, topCapHeight: 10 + 8)
} }
private func generateShareIcon(theme: PresentationTheme) -> UIImage? { private func generateCollapseIcon(theme: PresentationTheme) -> UIImage? {
return generateImage(CGSize(width: 19.0, height: 5.0), rotatedContext: { size, context in return generateImage(CGSize(width: 38.0, height: 5.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size)) let bounds = CGRect(origin: CGPoint(), size: size)
context.setFillColor(theme.list.itemAccentColor.cgColor) context.clear(bounds)
for i in 0 ..< 3 {
context.fillEllipse(in: CGRect(origin: CGPoint(x: CGFloat(i) * (5.0 + 2.0), y: 0.0), size: CGSize(width: 5.0, height: 5.0))) let path = UIBezierPath(roundedRect: bounds, cornerRadius: 2.5)
} context.setFillColor(theme.list.controlSecondaryColor.cgColor)
context.addPath(path.cgPath)
context.fillPath()
}) })
} }
private let digitsSet = CharacterSet(charactersIn: "0123456789")
private func timestampLabelWidthForDuration(_ timestamp: Double) -> CGFloat {
let text: String
if timestamp > 0 {
let timestamp = Int32(timestamp)
let hours = timestamp / (60 * 60)
let minutes = timestamp % (60 * 60) / 60
let seconds = timestamp % 60
if hours != 0 {
text = String(format: "%d:%02d:%02d", hours, minutes, seconds)
} else {
text = String(format: "%d:%02d", minutes, seconds)
}
} else {
text = "-:--"
}
let convertedString = text.components(separatedBy: digitsSet).joined(separator: "8")
let string = NSAttributedString(string: convertedString, font: Font.regular(13.0), textColor: .black)
let size = string.boundingRect(with: CGSize(width: 200.0, height: 100.0), options: NSStringDrawingOptions.usesLineFragmentOrigin, context: nil).size
return size.width
}
private let titleFont = Font.semibold(17.0) private let titleFont = Font.semibold(17.0)
private let descriptionFont = Font.regular(17.0) private let descriptionFont = Font.regular(17.0)
@ -112,6 +137,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
private var scrubbingDisposable: Disposable? private var scrubbingDisposable: Disposable?
private var leftDurationLabelPushed = false private var leftDurationLabelPushed = false
private var rightDurationLabelPushed = false private var rightDurationLabelPushed = false
private var currentDuration: Double = 0.0
private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, maxHeight: CGFloat)? private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, maxHeight: CGFloat)?
@ -128,7 +154,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
self.collapseNode = HighlightableButtonNode() self.collapseNode = HighlightableButtonNode()
self.collapseNode.displaysAsynchronously = false self.collapseNode.displaysAsynchronously = false
self.collapseNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/CollapseArrow"), color: theme.list.controlSecondaryColor), for: []) self.collapseNode.setImage(generateCollapseIcon(theme: theme), for: [])
self.albumArtNode = TransformImageNode() self.albumArtNode = TransformImageNode()
@ -141,9 +167,9 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
self.descriptionNode.displaysAsynchronously = false self.descriptionNode.displaysAsynchronously = false
self.shareNode = HighlightableButtonNode() self.shareNode = HighlightableButtonNode()
self.shareNode.setImage(generateShareIcon(theme: theme), for: []) self.shareNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/Share"), color: theme.list.itemAccentColor), for: [])
self.scrubberNode = MediaPlayerScrubbingNode(content: .standard(lineHeight: 3.0, lineCap: .round, scrubberHandle: .circle, backgroundColor: theme.list.controlSecondaryColor, foregroundColor: theme.list.itemAccentColor)) self.scrubberNode = MediaPlayerScrubbingNode(content: .standard(lineHeight: 3.0, lineCap: .round, scrubberHandle: .circle, backgroundColor: theme.list.controlSecondaryColor, foregroundColor: theme.list.itemAccentColor, bufferingColor: theme.list.itemAccentColor.withAlphaComponent(0.4)))
self.leftDurationLabel = MediaPlayerTimeTextNode(textColor: theme.list.itemSecondaryTextColor) self.leftDurationLabel = MediaPlayerTimeTextNode(textColor: theme.list.itemSecondaryTextColor)
self.leftDurationLabel.displaysAsynchronously = false self.leftDurationLabel.displaysAsynchronously = false
self.rightDurationLabel = MediaPlayerTimeTextNode(textColor: theme.list.itemSecondaryTextColor) self.rightDurationLabel = MediaPlayerTimeTextNode(textColor: theme.list.itemSecondaryTextColor)
@ -264,6 +290,8 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
strongSelf.currentItemId = valueItemId strongSelf.currentItemId = valueItemId
strongSelf.scrubberNode.ignoreSeekId = nil strongSelf.scrubberNode.ignoreSeekId = nil
} }
var rateButtonIsHidden = true
strongSelf.shareNode.isHidden = false strongSelf.shareNode.isHidden = false
var displayData: SharedMediaPlaybackDisplayData? var displayData: SharedMediaPlaybackDisplayData?
if let (_, valueOrLoading) = value, case let .state(value) = valueOrLoading { if let (_, valueOrLoading) = value, case let .state(value) = valueOrLoading {
@ -310,10 +338,22 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
} }
if let displayData = displayData, case let .music(_, _, _, long) = displayData, long { if let displayData = displayData, case let .music(_, _, _, long) = displayData, long {
strongSelf.rateButton.isHidden = false strongSelf.scrubberNode.enableFineScrubbing = true
rateButtonIsHidden = false
} else { } else {
strongSelf.rateButton.isHidden = true strongSelf.scrubberNode.enableFineScrubbing = false
rateButtonIsHidden = true
} }
let duration = value.status.duration
if duration != strongSelf.currentDuration {
strongSelf.currentDuration = duration
if let layout = strongSelf.validLayout {
strongSelf.updateLayout(width: layout.0, leftInset: layout.1, rightInset: layout.2, maxHeight: layout.3, transition: .immediate)
}
}
strongSelf.rateButton.isHidden = rateButtonIsHidden && strongSelf.currentDuration.isZero
} else { } else {
strongSelf.playPauseButton.isEnabled = false strongSelf.playPauseButton.isEnabled = false
strongSelf.backwardButton.isEnabled = false strongSelf.backwardButton.isEnabled = false
@ -377,8 +417,8 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
self.theme = theme self.theme = theme
self.backgroundNode.image = generateBackground(theme: theme) self.backgroundNode.image = generateBackground(theme: theme)
self.collapseNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/CollapseArrow"), color: theme.list.controlSecondaryColor), for: []) self.collapseNode.setImage(generateCollapseIcon(theme: theme), for: [])
self.shareNode.setImage(generateShareIcon(theme: theme), for: []) self.shareNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/Share"), color: theme.list.itemAccentColor), for: [])
self.scrubberNode.updateColors(backgroundColor: theme.list.controlSecondaryColor, foregroundColor: theme.list.itemAccentColor) self.scrubberNode.updateColors(backgroundColor: theme.list.controlSecondaryColor, foregroundColor: theme.list.itemAccentColor)
self.leftDurationLabel.textColor = theme.list.itemSecondaryTextColor self.leftDurationLabel.textColor = theme.list.itemSecondaryTextColor
self.rightDurationLabel.textColor = theme.list.itemSecondaryTextColor self.rightDurationLabel.textColor = theme.list.itemSecondaryTextColor
@ -479,9 +519,9 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
private func updateRateButton(_ baseRate: AudioPlaybackRate) { private func updateRateButton(_ baseRate: AudioPlaybackRate) {
switch baseRate { switch baseRate {
case .x2: case .x2:
self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateActiveIcon(self.theme), for: []) self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerMaximizedRateActiveIcon(self.theme), for: [])
default: default:
self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateInactiveIcon(self.theme), for: []) self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerMaximizedRateInactiveIcon(self.theme), for: [])
} }
} }
@ -506,7 +546,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
transition.updateFrame(node: self.collapseNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 2.0), size: CGSize(width: width, height: 30.0))) transition.updateFrame(node: self.collapseNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 2.0), size: CGSize(width: width, height: 30.0)))
let sideInset: CGFloat = 20.0 let sideInset: CGFloat = 20.0
let sideButtonsInset: CGFloat = sideInset + 30.0 let sideButtonsInset: CGFloat = sideInset + 36.0
let infoVerticalOrigin: CGFloat = panelHeight - OverlayPlayerControlsNode.basePanelHeight + 36.0 let infoVerticalOrigin: CGFloat = panelHeight - OverlayPlayerControlsNode.basePanelHeight + 36.0
@ -598,7 +638,8 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
var rightLabelVerticalOffset: CGFloat = self.rightDurationLabelPushed ? 6.0 : 0.0 var rightLabelVerticalOffset: CGFloat = self.rightDurationLabelPushed ? 6.0 : 0.0
transition.updateFrame(node: self.rightDurationLabel, frame: CGRect(origin: CGPoint(x: width - sideInset - rightInset - 100.0, y: scrubberVerticalOrigin + 14.0 + rightLabelVerticalOffset), size: CGSize(width: 100.0, height: 20.0))) transition.updateFrame(node: self.rightDurationLabel, frame: CGRect(origin: CGPoint(x: width - sideInset - rightInset - 100.0, y: scrubberVerticalOrigin + 14.0 + rightLabelVerticalOffset), size: CGSize(width: 100.0, height: 20.0)))
transition.updateFrame(node: self.rateButton, frame: CGRect(origin: CGPoint(x: width - sideInset - rightInset - 100.0 + 24.0, y: scrubberVerticalOrigin + 10.0 + rightLabelVerticalOffset), size: CGSize(width: 24.0, height: 24.0))) let rateRightOffset = timestampLabelWidthForDuration(self.currentDuration)
transition.updateFrame(node: self.rateButton, frame: CGRect(origin: CGPoint(x: width - sideInset - rightInset - rateRightOffset - 28.0, y: scrubberVerticalOrigin + 10.0 + rightLabelVerticalOffset), size: CGSize(width: 24.0, height: 24.0)))
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -8.0), size: CGSize(width: width, height: panelHeight + 8.0))) transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -8.0), size: CGSize(width: width, height: panelHeight + 8.0)))

View File

@ -297,9 +297,9 @@ private final class WalletQrScanScreenNode: ViewControllerTracingNode, UIScrollV
if case .tablet = layout.deviceMetrics.type { if case .tablet = layout.deviceMetrics.type {
if UIDevice.current.orientation == .landscapeLeft { if UIDevice.current.orientation == .landscapeLeft {
self.previewNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
} else if UIDevice.current.orientation == .landscapeRight {
self.previewNode.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0) self.previewNode.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
} else if UIDevice.current.orientation == .landscapeRight {
self.previewNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
} else { } else {
self.previewNode.transform = CATransform3DIdentity self.previewNode.transform = CATransform3DIdentity
} }

View File

@ -129,7 +129,7 @@ private final class WalletQrViewScreenNode: ViewControllerTracingNode {
self.iconNode = AnimatedStickerNode() self.iconNode = AnimatedStickerNode()
if let path = getAppBundle().path(forResource: "WalletIntroStatic", ofType: "tgs") { if let path = getAppBundle().path(forResource: "WalletIntroStatic", ofType: "tgs") {
self.iconNode.setup(account: context.account, resource: .localFile(path), width: 120, height: 120, mode: .direct) self.iconNode.setup(account: context.account, resource: .localFile(path), width: 240, height: 240, mode: .direct)
self.iconNode.visibility = true self.iconNode.visibility = true
} }