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 }
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.rightTimestampNode = MediaPlayerTimeTextNode(textColor: .white)

View File

@ -98,7 +98,7 @@ final class InstantPageAudioNode: ASDisplayNode, InstantPageNode {
if brightness > 0.5 {
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
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)
}
private final class MediaPlayerScrubbingNodeButton: ASDisplayNode {
private final class MediaPlayerScrubbingNodeButton: ASDisplayNode, UIGestureRecognizerDelegate {
var beginScrubbing: (() -> Void)?
var endScrubbing: ((Bool) -> Void)?
var updateScrubbing: ((CGFloat) -> Void)?
var updateScrubbing: ((CGFloat, Double) -> Void)?
var updateMultiplier: ((Double) -> Void)?
var highlighted: ((Bool) -> Void)?
var verticalPanEnabled = false
var hapticFeedback = HapticFeedback()
private var scrubbingMultiplier: Double = 1.0
private var scrubbingStartLocation: CGPoint?
override func didLoad() {
super.didLoad()
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?) {
@ -66,15 +86,38 @@ private final class MediaPlayerScrubbingNodeButton: ASDisplayNode {
case .changed:
if let scrubbingStartLocation = self.scrubbingStartLocation {
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:
if let scrubbingStartLocation = self.scrubbingStartLocation {
self.scrubbingStartLocation = nil
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.highlighted?(false)
self.scrubbingMultiplier = 1.0
}
default:
break
@ -106,7 +149,7 @@ public enum MediaPlayerScrubbingNodeHandle {
}
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)
}
@ -194,10 +237,12 @@ private final class MediaPlayerScrubbingBufferingNode: ASDisplayNode {
for range in ranges.0.rangeView {
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.updateAlpha(node: self.foregroundNode, alpha: abs(size.width - rangeWidth) < 1.0 ? 0.0 : 1.0)
break
}
} else {
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? {
get {
@ -293,13 +349,13 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
private static func contentNodesFromContent(_ content: MediaPlayerScrubbingNodeContent, enableScrubbing: Bool) -> MediaPlayerScrubbingNodeContentNodes {
switch content {
case let .standard(lineHeight, lineCap, scrubberHandle, backgroundColor, foregroundColor):
case let .standard(lineHeight, lineCap, scrubberHandle, backgroundColor, foregroundColor, bufferingColor):
let backgroundNode = ASImageNode()
backgroundNode.isLayerBacked = true
backgroundNode.displaysAsynchronously = false
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()
foregroundContentNode.isLayerBacked = true
@ -421,7 +477,7 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
strongSelf.displayLink?.isPaused = true
var timestamp = statusValue.timestamp
if statusValue.generationTimestamp > 0 {
if statusValue.generationTimestamp > 0 && statusValue.status == .playing {
let currentTimestamp = CACurrentMediaTime()
timestamp = timestamp + (currentTimestamp - statusValue.generationTimestamp) * statusValue.baseRate
}
@ -437,7 +493,6 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
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)
}
}
}
@ -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 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._scrubbingTimestamp.set(.single(strongSelf.scrubbingTimestampValue))
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
if let strongSelf = self {
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 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()
}
}
}
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
if let strongSelf = self {
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 {
let state: MediaPlayerTimeTextNodeState
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? {
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 {
let text: String
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 string = NSAttributedString(string: parameters.state.string, font: textFont, textColor: parameters.textColor)
let size = string.boundingRect(with: CGSize(width: 200.0, height: 100.0), options: NSStringDrawingOptions.usesLineFragmentOrigin, context: nil).size
if parameters.alignment == .left {
string.draw(at: CGPoint())
} else {

View File

@ -258,7 +258,7 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollViewDeleg
self.actionPlayNode.image = PresentationResourcesRootController.navigationPlayerPlayIcon(self.theme)
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.isLayerBacked = true
@ -371,7 +371,7 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollViewDeleg
self.actionPlayNode.image = PresentationResourcesRootController.navigationPlayerPlayIcon(self.theme)
self.actionPauseNode.image = PresentationResourcesRootController.navigationPlayerPauseIcon(self.theme)
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 {
switch playbackBaseRate {

View File

@ -26,15 +26,10 @@ public enum PresentationResourceKey: Int32 {
case navigationPlayerPlayIcon
case navigationPlayerPauseIcon
case navigationPlayerMaximizedPlayIcon
case navigationPlayerMaximizedPauseIcon
case navigationPlayerMaximizedPreviousIcon
case navigationPlayerMaximizedNextIcon
case navigationPlayerMaximizedShuffleIcon
case navigationPlayerMaximizedRepeatIcon
case navigationPlayerHandleIcon
case navigationPlayerRateActiveIcon
case navigationPlayerRateInactiveIcon
case navigationPlayerMaximizedRateActiveIcon
case navigationPlayerMaximizedRateInactiveIcon
case itemListDisclosureArrow
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? {
return theme.image(PresentationResourceKey.navigationPlayerPauseIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/MinimizedPause"), color: theme.rootController.navigationBar.accentTextColor)
@ -131,49 +143,7 @@ public struct PresentationResourcesRootController {
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/LiveLocationPanelIcon"), color: theme.rootController.navigationBar.accentTextColor)
})
}
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? {
return theme.image(PresentationResourceKey.inAppNotificationBackground.rawValue, { theme in
let inset: CGFloat = 16.0

View File

@ -2,17 +2,7 @@
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlForward@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlForward@3x.png",
"scale" : "3x"
"filename" : "ic_musicnext.pdf"
}
],
"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" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlShuffle@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlShuffle@3x.png",
"scale" : "3x"
"filename" : "ic_musicshuffle.pdf"
}
],
"info" : {

View File

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

View File

@ -2,17 +2,7 @@
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlPause@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlPause@3x.png",
"scale" : "3x"
"filename" : "ic_musicpause.pdf"
}
],
"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" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlPlay@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlPlay@3x.png",
"scale" : "3x"
"filename" : "ic_musicplay.pdf"
}
],
"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" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlBack@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlBack@3x.png",
"scale" : "3x"
"filename" : "ic_musicprevious.pdf"
}
],
"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" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlRepeat@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlRepeat@3x.png",
"scale" : "3x"
"filename" : "ic_musicrepeat.pdf"
}
],
"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" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlRepeatOne@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "MusicPlayerControlRepeatOne@3x.png",
"scale" : "3x"
"filename" : "ic_musicrepeatone.pdf"
}
],
"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,11 +402,14 @@ final class AuthorizedApplicationContext {
}
})
if !isMuted {
if inAppNotificationSettings.playSounds {
serviceSoundManager.playIncomingMessageSound()
}
if inAppNotificationSettings.vibrate {
serviceSoundManager.playVibrationSound()
if firstMessage.id.peerId == context.account.peerId, !firstMessage.flags.contains(.WasScheduled) {
} else {
if inAppNotificationSettings.playSounds {
serviceSoundManager.playIncomingMessageSound()
}
if inAppNotificationSettings.vibrate {
serviceSoundManager.playVibrationSound()
}
}
}
}

View File

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

View File

@ -21,16 +21,41 @@ private func generateBackground(theme: PresentationTheme) -> UIImage? {
})?.stretchableImage(withLeftCapWidth: 10, topCapHeight: 10 + 8)
}
private func generateShareIcon(theme: PresentationTheme) -> UIImage? {
return generateImage(CGSize(width: 19.0, height: 5.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(theme.list.itemAccentColor.cgColor)
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)))
}
private func generateCollapseIcon(theme: PresentationTheme) -> UIImage? {
return generateImage(CGSize(width: 38.0, height: 5.0), rotatedContext: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
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 descriptionFont = Font.regular(17.0)
@ -112,6 +137,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
private var scrubbingDisposable: Disposable?
private var leftDurationLabelPushed = false
private var rightDurationLabelPushed = false
private var currentDuration: Double = 0.0
private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, maxHeight: CGFloat)?
@ -128,7 +154,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
self.collapseNode = HighlightableButtonNode()
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()
@ -141,9 +167,9 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
self.descriptionNode.displaysAsynchronously = false
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.displaysAsynchronously = false
self.rightDurationLabel = MediaPlayerTimeTextNode(textColor: theme.list.itemSecondaryTextColor)
@ -264,6 +290,8 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
strongSelf.currentItemId = valueItemId
strongSelf.scrubberNode.ignoreSeekId = nil
}
var rateButtonIsHidden = true
strongSelf.shareNode.isHidden = false
var displayData: SharedMediaPlaybackDisplayData?
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 {
strongSelf.rateButton.isHidden = false
strongSelf.scrubberNode.enableFineScrubbing = true
rateButtonIsHidden = false
} 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 {
strongSelf.playPauseButton.isEnabled = false
strongSelf.backwardButton.isEnabled = false
@ -324,7 +364,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
if strongSelf.displayData != displayData {
strongSelf.displayData = displayData
if let (_, valueOrLoading) = value, case let .state(value) = valueOrLoading, let source = value.item.playbackData?.source {
switch source {
case let .telegramFile(fileReference):
@ -377,8 +417,8 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
self.theme = theme
self.backgroundNode.image = generateBackground(theme: theme)
self.collapseNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/CollapseArrow"), color: theme.list.controlSecondaryColor), for: [])
self.shareNode.setImage(generateShareIcon(theme: theme), for: [])
self.collapseNode.setImage(generateCollapseIcon(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.leftDurationLabel.textColor = theme.list.itemSecondaryTextColor
self.rightDurationLabel.textColor = theme.list.itemSecondaryTextColor
@ -479,9 +519,9 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
private func updateRateButton(_ baseRate: AudioPlaybackRate) {
switch baseRate {
case .x2:
self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerRateActiveIcon(self.theme), for: [])
self.rateButton.setImage(PresentationResourcesRootController.navigationPlayerMaximizedRateActiveIcon(self.theme), for: [])
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)))
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
@ -598,7 +638,8 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
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.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)))

View File

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

View File

@ -129,7 +129,7 @@ private final class WalletQrViewScreenNode: ViewControllerTracingNode {
self.iconNode = AnimatedStickerNode()
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
}