mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00

Fixed music playback panel glitches Added ability to display more than 100 message search results Fixed instagram videos playback
567 lines
30 KiB
Swift
567 lines
30 KiB
Swift
import Foundation
|
|
import AsyncDisplayKit
|
|
import Display
|
|
import Postbox
|
|
import TelegramCore
|
|
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
|
|
|
|
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 currentFileReference: FileMediaReference?
|
|
private var statusDisposable: Disposable?
|
|
|
|
private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, maxHeight: CGFloat)?
|
|
|
|
init(postbox: Postbox, theme: PresentationTheme, status: Signal<SharedMediaPlayerItemPlaybackStateOrLoading?, 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 = combineLatest(status, self.scrubberNode.scrubbingTimestamp) |> map { value, scrubbingTimestamp -> MediaPlayerStatus in
|
|
if let valueOrLoading = value, case let .state(value) = valueOrLoading {
|
|
return MediaPlayerStatus(generationTimestamp: value.status.generationTimestamp, duration: value.status.duration, dimensions: value.status.dimensions, timestamp: scrubbingTimestamp ?? value.status.timestamp, baseRate: value.status.baseRate, seekId: value.status.seekId, status: value.status.status)
|
|
} else {
|
|
return MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.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 {
|
|
var valueItemId: SharedMediaPlaylistItemId?
|
|
if let value = value, case let .state(state) = value {
|
|
valueItemId = state.item.id
|
|
}
|
|
if !areSharedMediaPlaylistItemIdsEqual(valueItemId, strongSelf.currentItemId) {
|
|
strongSelf.currentItemId = valueItemId
|
|
strongSelf.scrubberNode.ignoreSeekId = nil
|
|
}
|
|
strongSelf.shareNode.isHidden = false
|
|
var displayData: SharedMediaPlaybackDisplayData?
|
|
if let valueOrLoading = value, case let .state(value) = valueOrLoading {
|
|
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
|
|
|
|
let baseColor = strongSelf.theme.list.itemSecondaryTextColor
|
|
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: baseColor)
|
|
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.itemAccentColor)
|
|
}
|
|
}
|
|
if value.looping != strongSelf.currentLooping {
|
|
strongSelf.currentLooping = value.looping
|
|
|
|
switch value.looping {
|
|
case .none:
|
|
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.itemAccentColor)
|
|
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 valueOrLoading = value, case let .state(value) = valueOrLoading, let source = value.item.playbackData?.source {
|
|
switch source {
|
|
case let .telegramFile(fileReference):
|
|
strongSelf.currentFileReference = fileReference
|
|
if let size = fileReference.media.size {
|
|
strongSelf.scrubberNode.bufferingStatus = postbox.mediaBox.resourceRangesStatus(fileReference.media.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 - leftInset - rightInset - 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 - leftInset - rightInset - 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) : (leftInset + 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) : (leftInset + 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, fileReference: self.currentFileReference, albumArt: albumArt, thumbnail: true))
|
|
if let largeAlbumArtNode = self.largeAlbumArtNode {
|
|
largeAlbumArtNode.setSignal(playerAlbumArt(postbox: self.postbox, fileReference: self.currentFileReference, 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: leftInset + 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, fileReference: self.currentFileReference, 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: leftInset + sideInset, y: scrubberVerticalOrigin - 8.0), size: CGSize(width: width - sideInset * 2.0 - leftInset - rightInset, height: 10.0 + 8.0 * 2.0)))
|
|
transition.updateFrame(node: self.leftDurationLabel, frame: CGRect(origin: CGPoint(x: leftInset + 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 - rightInset - 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 = min(width - leftInset - rightInset - sideButtonsInset * 2.0, 320.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 {
|
|
if let supernode = self.supernode {
|
|
let bounds = supernode.bounds
|
|
if bounds.width > bounds.height {
|
|
return
|
|
}
|
|
}
|
|
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
|
|
}
|
|
}
|