mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
556 lines
29 KiB
Swift
556 lines
29 KiB
Swift
import Foundation
|
|
import AsyncDisplayKit
|
|
import Display
|
|
import Postbox
|
|
import SwiftSignalKit
|
|
|
|
private func generateBackground(theme: PresentationTheme) -> UIImage? {
|
|
return generateImage(CGSize(width: 20.0, height: 10.0 + 8.0), rotatedContext: { size, context in
|
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
|
context.setShadow(offset: CGSize(width: 0.0, height: -4.0), blur: 20.0, color: UIColor(white: 0.0, alpha: 0.3).cgColor)
|
|
context.setFillColor(theme.list.plainBackgroundColor.cgColor)
|
|
context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: 8.0), size: CGSize(width: 20.0, height: 20.0)))
|
|
})?.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 let titleFont = Font.medium(16.0)
|
|
private let descriptionFont = Font.regular(12.0)
|
|
|
|
private func stringsForDisplayData(_ data: SharedMediaPlaybackDisplayData?, theme: PresentationTheme) -> (NSAttributedString?, NSAttributedString?) {
|
|
var titleString: NSAttributedString?
|
|
var descriptionString: NSAttributedString?
|
|
|
|
if let data = data {
|
|
let titleText: String
|
|
let subtitleText: String
|
|
switch data {
|
|
case let .music(title, performer, _):
|
|
titleText = title ?? "Unknown Track"
|
|
subtitleText = performer ?? "Unknown Artist"
|
|
case .voice, .instantVideo:
|
|
titleText = ""
|
|
subtitleText = ""
|
|
}
|
|
|
|
titleString = NSAttributedString(string: titleText, font: titleFont, textColor: theme.list.itemPrimaryTextColor)
|
|
descriptionString = NSAttributedString(string: subtitleText, font: descriptionFont, textColor: theme.list.itemSecondaryTextColor)
|
|
}
|
|
|
|
return (titleString, descriptionString)
|
|
}
|
|
|
|
final class OverlayPlayerControlsNode: ASDisplayNode {
|
|
private let postbox: Postbox
|
|
private let theme: PresentationTheme
|
|
|
|
private let backgroundNode: ASImageNode
|
|
|
|
private let collapseNode: HighlightableButtonNode
|
|
|
|
private let albumArtNode: TransformImageNode
|
|
private var largeAlbumArtNode: TransformImageNode?
|
|
private let titleNode: TextNode
|
|
private let descriptionNode: TextNode
|
|
private let shareNode: HighlightableButtonNode
|
|
|
|
private let scrubberNode: MediaPlayerScrubbingNode
|
|
private let leftDurationLabel: MediaPlayerTimeTextNode
|
|
private let rightDurationLabel: MediaPlayerTimeTextNode
|
|
|
|
private let backwardButton: IconButtonNode
|
|
private let forwardButton: IconButtonNode
|
|
|
|
private var currentIsPaused: Bool?
|
|
private let playPauseButton: IconButtonNode
|
|
|
|
private var currentOrder: MusicPlaybackSettingsOrder?
|
|
private let orderButton: IconButtonNode
|
|
|
|
private var currentLooping: MusicPlaybackSettingsLooping?
|
|
private let loopingButton: IconButtonNode
|
|
|
|
let separatorNode: ASDisplayNode
|
|
|
|
private(set) var isExpanded = false
|
|
var updateIsExpanded: (() -> Void)?
|
|
|
|
var requestCollapse: (() -> Void)?
|
|
var requestShare: ((MessageId) -> Void)?
|
|
|
|
var updateOrder: ((MusicPlaybackSettingsOrder) -> Void)?
|
|
var control: ((SharedMediaPlayerControlAction) -> Void)?
|
|
|
|
private(set) var currentItemId: SharedMediaPlaylistItemId?
|
|
private var displayData: SharedMediaPlaybackDisplayData?
|
|
private var currentAlbumArtInitialized = false
|
|
private var currentAlbumArt: SharedMediaPlaybackAlbumArt?
|
|
private var statusDisposable: Disposable?
|
|
|
|
private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, maxHeight: CGFloat)?
|
|
|
|
init(postbox: Postbox, theme: PresentationTheme, status: Signal<SharedMediaPlayerItemPlaybackState?, NoError>) {
|
|
self.postbox = postbox
|
|
self.theme = theme
|
|
|
|
self.backgroundNode = ASImageNode()
|
|
self.backgroundNode.isLayerBacked = true
|
|
self.backgroundNode.displayWithoutProcessing = true
|
|
self.backgroundNode.displaysAsynchronously = false
|
|
self.backgroundNode.image = generateBackground(theme: theme)
|
|
|
|
self.collapseNode = HighlightableButtonNode()
|
|
self.collapseNode.displaysAsynchronously = false
|
|
self.collapseNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/CollapseArrow"), color: theme.list.controlSecondaryColor), for: [])
|
|
|
|
self.albumArtNode = TransformImageNode()
|
|
|
|
self.titleNode = TextNode()
|
|
self.titleNode.isLayerBacked = true
|
|
self.titleNode.displaysAsynchronously = false
|
|
|
|
self.descriptionNode = TextNode()
|
|
self.descriptionNode.isLayerBacked = true
|
|
self.descriptionNode.displaysAsynchronously = false
|
|
|
|
self.shareNode = HighlightableButtonNode()
|
|
self.shareNode.setImage(generateShareIcon(theme: theme), for: [])
|
|
|
|
self.scrubberNode = MediaPlayerScrubbingNode(content: .standard(lineHeight: 3.0, lineCap: .round, scrubberHandle: .circle, backgroundColor: theme.list.controlSecondaryColor, foregroundColor: theme.list.itemAccentColor))
|
|
self.leftDurationLabel = MediaPlayerTimeTextNode(textColor: theme.list.itemSecondaryTextColor)
|
|
self.rightDurationLabel = MediaPlayerTimeTextNode(textColor: theme.list.itemSecondaryTextColor)
|
|
self.rightDurationLabel.mode = .reversed
|
|
self.rightDurationLabel.alignment = .right
|
|
|
|
self.backwardButton = IconButtonNode()
|
|
self.backwardButton.displaysAsynchronously = false
|
|
|
|
self.forwardButton = IconButtonNode()
|
|
self.forwardButton.displaysAsynchronously = false
|
|
|
|
self.orderButton = IconButtonNode()
|
|
self.orderButton.displaysAsynchronously = false
|
|
|
|
self.loopingButton = IconButtonNode()
|
|
self.loopingButton.displaysAsynchronously = false
|
|
|
|
self.playPauseButton = IconButtonNode()
|
|
self.playPauseButton.displaysAsynchronously = false
|
|
|
|
self.backwardButton.icon = generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/Previous"), color: theme.list.itemPrimaryTextColor)
|
|
self.forwardButton.icon = generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/Next"), color: theme.list.itemPrimaryTextColor)
|
|
|
|
self.separatorNode = ASDisplayNode()
|
|
self.separatorNode.isLayerBacked = true
|
|
self.separatorNode.backgroundColor = theme.list.itemPlainSeparatorColor
|
|
|
|
super.init()
|
|
|
|
self.addSubnode(self.backgroundNode)
|
|
|
|
self.addSubnode(self.collapseNode)
|
|
|
|
self.addSubnode(self.albumArtNode)
|
|
self.addSubnode(self.titleNode)
|
|
self.addSubnode(self.descriptionNode)
|
|
self.addSubnode(self.shareNode)
|
|
|
|
self.addSubnode(self.scrubberNode)
|
|
self.addSubnode(self.leftDurationLabel)
|
|
self.addSubnode(self.rightDurationLabel)
|
|
|
|
self.addSubnode(self.orderButton)
|
|
self.addSubnode(self.loopingButton)
|
|
self.addSubnode(self.backwardButton)
|
|
self.addSubnode(self.forwardButton)
|
|
self.addSubnode(self.playPauseButton)
|
|
|
|
self.addSubnode(self.separatorNode)
|
|
|
|
let mappedStatus = status |> map { value -> MediaPlayerStatus in
|
|
if let value = value {
|
|
return value.status
|
|
} else {
|
|
return MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, timestamp: 0.0, seekId: 0, status: .paused)
|
|
}
|
|
}
|
|
self.scrubberNode.status = mappedStatus
|
|
self.leftDurationLabel.status = mappedStatus
|
|
self.rightDurationLabel.status = mappedStatus
|
|
|
|
self.statusDisposable = (status |> deliverOnMainQueue).start(next: { [weak self] value in
|
|
if let strongSelf = self {
|
|
if !areSharedMediaPlaylistItemIdsEqual(value?.item.id, strongSelf.currentItemId) {
|
|
strongSelf.currentItemId = value?.item.id
|
|
strongSelf.scrubberNode.ignoreSeekId = nil
|
|
}
|
|
var displayData: SharedMediaPlaybackDisplayData?
|
|
if let value = value {
|
|
let isPaused: Bool
|
|
switch value.status.status {
|
|
case .playing:
|
|
isPaused = false
|
|
case .paused:
|
|
isPaused = true
|
|
case let .buffering(_, whilePlaying):
|
|
isPaused = !whilePlaying
|
|
}
|
|
if strongSelf.currentIsPaused != isPaused {
|
|
strongSelf.currentIsPaused = isPaused
|
|
|
|
if isPaused {
|
|
strongSelf.playPauseButton.icon = generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/Play"), color: strongSelf.theme.list.itemPrimaryTextColor)
|
|
} else {
|
|
strongSelf.playPauseButton.icon = generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/Pause"), color: strongSelf.theme.list.itemPrimaryTextColor)
|
|
}
|
|
}
|
|
|
|
strongSelf.playPauseButton.isEnabled = true
|
|
strongSelf.backwardButton.isEnabled = true
|
|
strongSelf.forwardButton.isEnabled = true
|
|
|
|
displayData = value.item.displayData
|
|
|
|
if value.order != strongSelf.currentOrder {
|
|
strongSelf.updateOrder?(value.order)
|
|
strongSelf.currentOrder = value.order
|
|
switch value.order {
|
|
case .regular:
|
|
strongSelf.orderButton.icon = generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/OrderReverse"), color: strongSelf.theme.list.itemPrimaryTextColor)
|
|
case .reversed:
|
|
strongSelf.orderButton.icon = generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/OrderReverse"), color: strongSelf.theme.list.itemAccentColor)
|
|
case .random:
|
|
strongSelf.orderButton.icon = generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/OrderRandom"), color: strongSelf.theme.list.itemPrimaryTextColor)
|
|
}
|
|
}
|
|
if value.looping != strongSelf.currentLooping {
|
|
strongSelf.currentLooping = value.looping
|
|
|
|
switch value.looping {
|
|
case .none:
|
|
let baseColor: UIColor
|
|
if strongSelf.theme.list.itemPrimaryTextColor.isEqual(strongSelf.theme.list.itemAccentColor) {
|
|
baseColor = strongSelf.theme.list.controlSecondaryColor
|
|
} else {
|
|
baseColor = strongSelf.theme.list.itemPrimaryTextColor
|
|
}
|
|
strongSelf.loopingButton.icon = generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/Repeat"), color: baseColor)
|
|
case .item:
|
|
strongSelf.loopingButton.icon = generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/RepeatOne"), color: strongSelf.theme.list.itemPrimaryTextColor)
|
|
case .all:
|
|
strongSelf.loopingButton.icon = generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/Repeat"), color: strongSelf.theme.list.itemAccentColor)
|
|
}
|
|
}
|
|
} else {
|
|
strongSelf.playPauseButton.isEnabled = false
|
|
strongSelf.backwardButton.isEnabled = false
|
|
strongSelf.forwardButton.isEnabled = false
|
|
displayData = nil
|
|
}
|
|
|
|
if strongSelf.displayData != displayData {
|
|
strongSelf.displayData = displayData
|
|
strongSelf.updateLabels(transition: .immediate)
|
|
|
|
if let source = value?.item.playbackData?.source {
|
|
switch source {
|
|
case let .telegramFile(file):
|
|
if let size = file.size {
|
|
strongSelf.scrubberNode.bufferingStatus = postbox.mediaBox.resourceRangesStatus(file.resource)
|
|
|> map { ranges -> (IndexSet, Int) in
|
|
return (ranges, size)
|
|
}
|
|
} else {
|
|
strongSelf.scrubberNode.bufferingStatus = nil
|
|
}
|
|
}
|
|
} else {
|
|
strongSelf.scrubberNode.bufferingStatus = nil
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
self.scrubberNode.seek = { [weak self] value in
|
|
self?.control?(.seek(value))
|
|
}
|
|
|
|
self.collapseNode.addTarget(self, action: #selector(self.collapsePressed), forControlEvents: .touchUpInside)
|
|
self.shareNode.addTarget(self, action: #selector(self.sharePressed), forControlEvents: .touchUpInside)
|
|
self.orderButton.addTarget(self, action: #selector(self.orderPressed), forControlEvents: .touchUpInside)
|
|
self.loopingButton.addTarget(self, action: #selector(self.loopingPressed), forControlEvents: .touchUpInside)
|
|
self.backwardButton.addTarget(self, action: #selector(self.backwardPressed), forControlEvents: .touchUpInside)
|
|
self.forwardButton.addTarget(self, action: #selector(self.forwardPressed), forControlEvents: .touchUpInside)
|
|
self.playPauseButton.addTarget(self, action: #selector(self.playPausePressed), forControlEvents: .touchUpInside)
|
|
}
|
|
|
|
deinit {
|
|
self.statusDisposable?.dispose()
|
|
}
|
|
|
|
override func didLoad() {
|
|
super.didLoad()
|
|
|
|
self.albumArtNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.albumArtTap(_:))))
|
|
}
|
|
|
|
private func updateLabels(transition: ContainedViewLayoutTransition) {
|
|
guard let (width, leftInset, rightInset, maxHeight) = self.validLayout else {
|
|
return
|
|
}
|
|
|
|
let panelHeight = OverlayPlayerControlsNode.heightForLayout(width: width, leftInset: leftInset, rightInset: rightInset, maxHeight: maxHeight, isExpanded: self.isExpanded)
|
|
|
|
let sideInset: CGFloat = 20.0
|
|
|
|
let infoLabelsLeftInset: CGFloat = 64.0
|
|
let infoLabelsRightInset: CGFloat = 32.0
|
|
|
|
let infoVerticalOrigin: CGFloat = panelHeight - OverlayPlayerControlsNode.basePanelHeight + 36.0
|
|
|
|
let (titleString, descriptionString) = stringsForDisplayData(self.displayData, theme: self.theme)
|
|
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
|
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - sideInset * 2.0 - infoLabelsLeftInset - infoLabelsRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .left, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
|
let makeDescriptionLayout = TextNode.asyncLayout(self.descriptionNode)
|
|
let (descriptionLayout, descriptionApply) = makeDescriptionLayout(TextNodeLayoutArguments(attributedString: descriptionString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - sideInset * 2.0 - infoLabelsLeftInset - infoLabelsRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .left, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
|
|
|
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: self.isExpanded ? floor((width - titleLayout.size.width) / 2.0) : (sideInset + infoLabelsLeftInset), y: infoVerticalOrigin + 1.0), size: titleLayout.size))
|
|
let _ = titleApply()
|
|
|
|
transition.updateFrame(node: self.descriptionNode, frame: CGRect(origin: CGPoint(x: self.isExpanded ? floor((width - descriptionLayout.size.width) / 2.0) : (sideInset + infoLabelsLeftInset), y: infoVerticalOrigin + 27.0), size: descriptionLayout.size))
|
|
let _ = descriptionApply()
|
|
|
|
var albumArt: SharedMediaPlaybackAlbumArt?
|
|
if let displayData = self.displayData {
|
|
switch displayData {
|
|
case let .music(_, _, value):
|
|
albumArt = value
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
if self.currentAlbumArt != albumArt || !self.currentAlbumArtInitialized {
|
|
self.currentAlbumArtInitialized = true
|
|
self.currentAlbumArt = albumArt
|
|
self.albumArtNode.setSignal(playerAlbumArt(postbox: self.postbox, albumArt: albumArt, thumbnail: true))
|
|
if let largeAlbumArtNode = self.largeAlbumArtNode {
|
|
largeAlbumArtNode.setSignal(playerAlbumArt(postbox: self.postbox, albumArt: albumArt, thumbnail: false))
|
|
}
|
|
}
|
|
}
|
|
|
|
static let basePanelHeight: CGFloat = 220.0
|
|
|
|
static func heightForLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, maxHeight: CGFloat, isExpanded: Bool) -> CGFloat {
|
|
var panelHeight: CGFloat = OverlayPlayerControlsNode.basePanelHeight
|
|
if isExpanded {
|
|
let sideInset: CGFloat = 20.0
|
|
panelHeight += width - leftInset - rightInset - sideInset * 2.0 + 24.0
|
|
}
|
|
return min(panelHeight, maxHeight)
|
|
}
|
|
|
|
func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, maxHeight: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
|
self.validLayout = (width, leftInset, rightInset, maxHeight)
|
|
|
|
let panelHeight = OverlayPlayerControlsNode.heightForLayout(width: width, leftInset: leftInset, rightInset: rightInset, maxHeight: maxHeight, isExpanded: self.isExpanded)
|
|
|
|
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelHeight), size: CGSize(width: width, height: UIScreenPixel)))
|
|
|
|
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 infoVerticalOrigin: CGFloat = panelHeight - OverlayPlayerControlsNode.basePanelHeight + 36.0
|
|
|
|
self.updateLabels(transition: transition)
|
|
|
|
transition.updateFrame(node: self.shareNode, frame: CGRect(origin: CGPoint(x: width - rightInset - sideInset - 32.0, y: infoVerticalOrigin + 2.0), size: CGSize(width: 42.0, height: 42.0)))
|
|
|
|
let albumArtSize = CGSize(width: 48.0, height: 48.0)
|
|
let makeAlbumArtLayout = self.albumArtNode.asyncLayout()
|
|
let applyAlbumArt = makeAlbumArtLayout(TransformImageArguments(corners: ImageCorners(radius: 4.0), imageSize: albumArtSize, boundingSize: albumArtSize, intrinsicInsets: UIEdgeInsets()))
|
|
applyAlbumArt()
|
|
let albumArtFrame = CGRect(origin: CGPoint(x: sideInset, y: infoVerticalOrigin - 1.0), size: albumArtSize)
|
|
let previousAlbumArtNodeFrame = self.albumArtNode.frame
|
|
transition.updateFrame(node: self.albumArtNode, frame: albumArtFrame)
|
|
|
|
if self.isExpanded {
|
|
let largeAlbumArtNode: TransformImageNode
|
|
var animateIn = false
|
|
if let current = self.largeAlbumArtNode {
|
|
largeAlbumArtNode = current
|
|
} else {
|
|
animateIn = true
|
|
largeAlbumArtNode = TransformImageNode()
|
|
if self.isNodeLoaded {
|
|
largeAlbumArtNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.albumArtTap(_:))))
|
|
}
|
|
self.largeAlbumArtNode = largeAlbumArtNode
|
|
self.addSubnode(largeAlbumArtNode)
|
|
if self.currentAlbumArtInitialized {
|
|
largeAlbumArtNode.setSignal(playerAlbumArt(postbox: self.postbox, albumArt: self.currentAlbumArt, thumbnail: false))
|
|
}
|
|
}
|
|
|
|
let albumArtHeight = max(1.0, panelHeight - OverlayPlayerControlsNode.basePanelHeight - 24.0)
|
|
|
|
let largeAlbumArtSize = CGSize(width: albumArtHeight, height: albumArtHeight)
|
|
let makeLargeAlbumArtLayout = largeAlbumArtNode.asyncLayout()
|
|
let applyLargeAlbumArt = makeLargeAlbumArtLayout(TransformImageArguments(corners: ImageCorners(radius: 4.0), imageSize: largeAlbumArtSize, boundingSize: largeAlbumArtSize, intrinsicInsets: UIEdgeInsets()))
|
|
applyLargeAlbumArt()
|
|
|
|
let largeAlbumArtFrame = CGRect(origin: CGPoint(x: floor((width - largeAlbumArtSize.width) / 2.0), y: 34.0), size: largeAlbumArtSize)
|
|
|
|
if animateIn && transition.isAnimated {
|
|
largeAlbumArtNode.frame = largeAlbumArtFrame
|
|
transition.animatePositionAdditive(node: largeAlbumArtNode, offset: CGPoint(x: previousAlbumArtNodeFrame.center.x - largeAlbumArtFrame.center.x, y: previousAlbumArtNodeFrame.center.y - largeAlbumArtFrame.center.y))
|
|
//largeAlbumArtNode.layer.animatePosition(from: CGPoint(x: -50.0, y: 0.0), to: CGPoint(), duration: 0.15, timingFunction: kCAMediaTimingFunctionEaseInEaseOut, additive: true)
|
|
transition.animateTransformScale(node: largeAlbumArtNode, from: previousAlbumArtNodeFrame.size.height / largeAlbumArtFrame.size.height)
|
|
largeAlbumArtNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.12)
|
|
if let copyView = self.albumArtNode.view.snapshotContentTree() {
|
|
copyView.frame = previousAlbumArtNodeFrame
|
|
copyView.center = largeAlbumArtFrame.center
|
|
self.view.insertSubview(copyView, belowSubview: largeAlbumArtNode.view)
|
|
transition.animatePositionAdditive(layer: copyView.layer, offset: CGPoint(x: previousAlbumArtNodeFrame.center.x - largeAlbumArtFrame.center.x, y: previousAlbumArtNodeFrame.center.y - largeAlbumArtFrame.center.y), completion: { [weak copyView] in
|
|
copyView?.removeFromSuperview()
|
|
})
|
|
//copyView.layer.animatePosition(from: CGPoint(x: -50.0, y: 0.0), to: CGPoint(), duration: 0.15, timingFunction: kCAMediaTimingFunctionEaseInEaseOut, additive: true)
|
|
copyView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.28, removeOnCompletion: false)
|
|
transition.updateTransformScale(layer: copyView.layer, scale: largeAlbumArtFrame.size.height / previousAlbumArtNodeFrame.size.height)
|
|
}
|
|
} else {
|
|
transition.updateFrame(node: largeAlbumArtNode, frame: largeAlbumArtFrame)
|
|
}
|
|
self.albumArtNode.isHidden = true
|
|
} else if let largeAlbumArtNode = self.largeAlbumArtNode {
|
|
self.largeAlbumArtNode = nil
|
|
self.albumArtNode.isHidden = false
|
|
if transition.isAnimated {
|
|
transition.animatePosition(node: self.albumArtNode, from: largeAlbumArtNode.frame.center)
|
|
transition.animateTransformScale(node: self.albumArtNode, from: largeAlbumArtNode.frame.height / self.albumArtNode.frame.height)
|
|
self.albumArtNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.12)
|
|
|
|
transition.updatePosition(node: largeAlbumArtNode, position: self.albumArtNode.frame.center, completion: { [weak largeAlbumArtNode] _ in
|
|
largeAlbumArtNode?.removeFromSupernode()
|
|
})
|
|
largeAlbumArtNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.28, removeOnCompletion: false)
|
|
transition.updateTransformScale(node: largeAlbumArtNode, scale: self.albumArtNode.frame.height / largeAlbumArtNode.frame.height)
|
|
} else {
|
|
largeAlbumArtNode.removeFromSupernode()
|
|
}
|
|
}
|
|
|
|
let scrubberVerticalOrigin: CGFloat = infoVerticalOrigin + 64.0
|
|
|
|
transition.updateFrame(node: self.scrubberNode, frame: CGRect(origin: CGPoint(x: sideInset, y: scrubberVerticalOrigin - 8.0), size: CGSize(width: width - sideInset * 2.0, height: 10.0 + 8.0 * 2.0)))
|
|
transition.updateFrame(node: self.leftDurationLabel, frame: CGRect(origin: CGPoint(x: sideInset, y: scrubberVerticalOrigin + 12.0), size: CGSize(width: 40.0, height: 20.0)))
|
|
transition.updateFrame(node: self.rightDurationLabel, frame: CGRect(origin: CGPoint(x: width - sideInset - 40.0, y: scrubberVerticalOrigin + 12.0), size: CGSize(width: 40.0, height: 20.0)))
|
|
|
|
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -8.0), size: CGSize(width: width, height: panelHeight + 8.0)))
|
|
|
|
let buttonSize = CGSize(width: 64.0, height: 64.0)
|
|
let buttonsWidth = width - leftInset - rightInset - sideButtonsInset * 2.0
|
|
let buttonsRect = CGRect(origin: CGPoint(x: floor((width - buttonsWidth) / 2.0), y: scrubberVerticalOrigin + 36.0), size: CGSize(width: buttonsWidth, height: buttonSize.height))
|
|
|
|
transition.updateFrame(node: self.orderButton, frame: CGRect(origin: CGPoint(x: leftInset + sideInset - 22.0, y: buttonsRect.minY), size: buttonSize))
|
|
transition.updateFrame(node: self.loopingButton, frame: CGRect(origin: CGPoint(x: width - rightInset - sideInset - buttonSize.width + 22.0, y: buttonsRect.minY), size: buttonSize))
|
|
|
|
transition.updateFrame(node: self.backwardButton, frame: CGRect(origin: buttonsRect.origin, size: buttonSize))
|
|
transition.updateFrame(node: self.forwardButton, frame: CGRect(origin: CGPoint(x: buttonsRect.maxX - buttonSize.width, y: buttonsRect.minY), size: buttonSize))
|
|
transition.updateFrame(node: self.playPauseButton, frame: CGRect(origin: CGPoint(x: buttonsRect.minX + floor((buttonsRect.width - buttonSize.width) / 2.0), y: buttonsRect.minY), size: buttonSize))
|
|
|
|
return panelHeight
|
|
}
|
|
|
|
func collapse() {
|
|
if self.isExpanded {
|
|
self.isExpanded = false
|
|
self.updateIsExpanded?()
|
|
}
|
|
}
|
|
|
|
@objc func collapsePressed() {
|
|
self.requestCollapse?()
|
|
}
|
|
|
|
@objc func sharePressed() {
|
|
if let itemId = self.currentItemId as? PeerMessagesMediaPlaylistItemId {
|
|
self.requestShare?(itemId.messageId)
|
|
}
|
|
}
|
|
|
|
@objc func orderPressed() {
|
|
if let order = self.currentOrder {
|
|
let nextOrder: MusicPlaybackSettingsOrder
|
|
switch order {
|
|
case .regular:
|
|
nextOrder = .reversed
|
|
case .reversed:
|
|
nextOrder = .random
|
|
case .random:
|
|
nextOrder = .regular
|
|
}
|
|
let _ = updateMusicPlaybackSettingsInteractively(postbox: self.postbox, {
|
|
return $0.withUpdatedOrder(nextOrder)
|
|
}).start()
|
|
self.control?(.setOrder(nextOrder))
|
|
}
|
|
}
|
|
|
|
@objc func loopingPressed() {
|
|
if let looping = self.currentLooping {
|
|
let nextLooping: MusicPlaybackSettingsLooping
|
|
switch looping {
|
|
case .none:
|
|
nextLooping = .item
|
|
case .item:
|
|
nextLooping = .all
|
|
case .all:
|
|
nextLooping = .none
|
|
}
|
|
let _ = updateMusicPlaybackSettingsInteractively(postbox: self.postbox, {
|
|
return $0.withUpdatedLooping(nextLooping)
|
|
}).start()
|
|
self.control?(.setLooping(nextLooping))
|
|
}
|
|
}
|
|
|
|
@objc func backwardPressed() {
|
|
self.control?(.previous)
|
|
}
|
|
|
|
@objc func forwardPressed() {
|
|
self.control?(.next)
|
|
}
|
|
|
|
@objc func playPausePressed() {
|
|
self.control?(.playback(.togglePlayPause))
|
|
}
|
|
|
|
@objc func albumArtTap(_ recognizer: UITapGestureRecognizer) {
|
|
if case .ended = recognizer.state {
|
|
self.isExpanded = !self.isExpanded
|
|
self.updateIsExpanded?()
|
|
}
|
|
}
|
|
|
|
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
|
let result = super.hitTest(point, with: event)
|
|
if result == self.view {
|
|
return nil
|
|
}
|
|
return result
|
|
}
|
|
}
|