import Foundation import UIKit import AsyncDisplayKit import Display import TelegramCore import SwiftSignalKit import Postbox import TelegramPresentationData import TelegramUIPreferences import UniversalMediaPlayer import AccountContext import OverlayStatusController public enum MediaAccessoryPanelVisibility { case none case specific(size: ContainerViewLayoutSizeClass) case always } public enum LocationBroadcastPanelSource { case none case summary case peer(PeerId) } private func presentLiveLocationController(context: AccountContext, peerId: PeerId, controller: ViewController) { let presentImpl: (Message?) -> Void = { [weak controller] message in if let message = message, let strongController = controller { let _ = context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: strongController.navigationController as? NavigationController, modal: true, dismissInput: { controller?.view.endEditing(true) }, present: { c, a in controller?.present(c, in: .window(.root), with: a, blockInteraction: true) }, transitionNode: { _, _ in return nil }, addToTransitionSurface: { _ in }, openUrl: { _ in }, openPeer: { peer, navigation in }, callPeer: { _ in }, enqueueMessage: { _ in }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in })) } } if let id = context.liveLocationManager?.internalMessageForPeerId(peerId) { let _ = (context.account.postbox.transaction { transaction -> Message? in return transaction.getMessage(id) } |> deliverOnMainQueue).start(next: presentImpl) } else if let liveLocationManager = context.liveLocationManager { let _ = (liveLocationManager.summaryManager.peersBroadcastingTo(peerId: peerId) |> take(1) |> map { peersAndMessages -> Message? in return peersAndMessages?.first?.1 } |> deliverOnMainQueue).start(next: presentImpl) } } open class TelegramBaseController: ViewController, KeyShortcutResponder { private let context: AccountContext public let mediaAccessoryPanelVisibility: MediaAccessoryPanelVisibility public let locationBroadcastPanelSource: LocationBroadcastPanelSource private var mediaStatusDisposable: Disposable? private var locationBroadcastDisposable: Disposable? public private(set) var playlistStateAndType: (SharedMediaPlaylistItem, SharedMediaPlaylistItem?, SharedMediaPlaylistItem?, MusicPlaybackSettingsOrder, MediaManagerPlayerType, Account)? public var tempVoicePlaylistEnded: (() -> Void)? public var tempVoicePlaylistItemChanged: ((SharedMediaPlaylistItem?, SharedMediaPlaylistItem?) -> Void)? private var mediaAccessoryPanel: (MediaNavigationAccessoryPanel, MediaManagerPlayerType)? private var locationBroadcastMode: LocationBroadcastNavigationAccessoryPanelMode? private var locationBroadcastPeers: [Peer]? private var locationBroadcastMessages: [MessageId: Message]? private var locationBroadcastAccessoryPanel: LocationBroadcastNavigationAccessoryPanel? private var dismissingPanel: ASDisplayNode? private var presentationData: PresentationData private var presentationDataDisposable: Disposable? private var playlistPreloadDisposable: Disposable? override open var navigationHeight: CGFloat { return super.navigationHeight + self.additionalHeight } override open var navigationInsetHeight: CGFloat { return super.navigationInsetHeight + self.additionalHeight } override open var visualNavigationInsetHeight: CGFloat { return super.visualNavigationInsetHeight + self.additionalHeight } private var additionalHeight: CGFloat { var height: CGFloat = 0.0 if let _ = self.mediaAccessoryPanel { height += MediaNavigationAccessoryHeaderNode.minimizedHeight } if let _ = self.locationBroadcastAccessoryPanel { height += MediaNavigationAccessoryHeaderNode.minimizedHeight } return height } open var primaryNavigationHeight: CGFloat { return super.navigationHeight } public init(context: AccountContext, navigationBarPresentationData: NavigationBarPresentationData?, mediaAccessoryPanelVisibility: MediaAccessoryPanelVisibility, locationBroadcastPanelSource: LocationBroadcastPanelSource) { self.context = context self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.mediaAccessoryPanelVisibility = mediaAccessoryPanelVisibility self.locationBroadcastPanelSource = locationBroadcastPanelSource super.init(navigationBarPresentationData: navigationBarPresentationData) if case .none = mediaAccessoryPanelVisibility { } else { self.mediaStatusDisposable = (context.sharedContext.mediaManager.globalMediaPlayerState |> mapToSignal { playlistStateAndType -> Signal<(Account, SharedMediaPlayerItemPlaybackState, MediaManagerPlayerType)?, NoError> in if let (account, state, type) = playlistStateAndType { switch state { case let .state(state): return .single((account, state, type)) case .loading: return .single(nil) |> delay(0.2, queue: .mainQueue()) } } else { return .single(nil) } } |> deliverOnMainQueue).start(next: { [weak self] playlistStateAndType in guard let strongSelf = self else { return } if !arePlaylistItemsEqual(strongSelf.playlistStateAndType?.0, playlistStateAndType?.1.item) || !arePlaylistItemsEqual(strongSelf.playlistStateAndType?.1, playlistStateAndType?.1.previousItem) || !arePlaylistItemsEqual(strongSelf.playlistStateAndType?.2, playlistStateAndType?.1.nextItem) || strongSelf.playlistStateAndType?.3 != playlistStateAndType?.1.order || strongSelf.playlistStateAndType?.4 != playlistStateAndType?.2 { var previousVoiceItem: SharedMediaPlaylistItem? if let playlistStateAndType = strongSelf.playlistStateAndType, playlistStateAndType.4 == .voice { previousVoiceItem = playlistStateAndType.0 } var updatedVoiceItem: SharedMediaPlaylistItem? if let playlistStateAndType = playlistStateAndType, playlistStateAndType.2 == .voice { updatedVoiceItem = playlistStateAndType.1.item } strongSelf.tempVoicePlaylistItemChanged?(previousVoiceItem, updatedVoiceItem) if let playlistStateAndType = playlistStateAndType { strongSelf.playlistStateAndType = (playlistStateAndType.1.item, playlistStateAndType.1.previousItem, playlistStateAndType.1.nextItem, playlistStateAndType.1.order, playlistStateAndType.2, playlistStateAndType.0) } else { var voiceEnded = false if strongSelf.playlistStateAndType?.4 == .voice { voiceEnded = true } strongSelf.playlistStateAndType = nil if voiceEnded { strongSelf.tempVoicePlaylistEnded?() } } strongSelf.requestLayout(transition: .animated(duration: 0.4, curve: .spring)) } }) } if let liveLocationManager = context.liveLocationManager { switch locationBroadcastPanelSource { case .none: self.locationBroadcastMode = nil case .summary, .peer: let signal: Signal<([Peer]?, [MessageId: Message]?), NoError> switch locationBroadcastPanelSource { case let .peer(peerId): self.locationBroadcastMode = .peer signal = combineLatest(liveLocationManager.summaryManager.peersBroadcastingTo(peerId: peerId), liveLocationManager.summaryManager.broadcastingToMessages()) |> map { peersAndMessages, outgoingMessages in var peers = peersAndMessages?.map { $0.0 } for message in outgoingMessages.values { if message.id.peerId == peerId, let author = message.author { if peers == nil { peers = [] } peers?.append(author) } } return (peers, outgoingMessages) } default: self.locationBroadcastMode = .summary signal = liveLocationManager.summaryManager.broadcastingToMessages() |> map { messages -> ([Peer]?, [MessageId: Message]?) in if messages.isEmpty { return (nil, nil) } else { var peers: [Peer] = [] for message in messages.values.sorted(by: { $0.index < $1.index }) { if let peer = message.peers[message.id.peerId] { peers.append(peer) } } return (peers, messages) } } } self.locationBroadcastDisposable = (signal |> deliverOnMainQueue).start(next: { [weak self] peers, messages in if let strongSelf = self { var updated = false if let current = strongSelf.locationBroadcastPeers, let peers = peers { updated = !arePeerArraysEqual(current, peers) } else if (strongSelf.locationBroadcastPeers != nil) != (peers != nil) { updated = true } strongSelf.locationBroadcastMessages = messages if updated { let wasEmpty = strongSelf.locationBroadcastPeers == nil strongSelf.locationBroadcastPeers = peers if wasEmpty != (peers == nil) { strongSelf.requestLayout(transition: .animated(duration: 0.4, curve: .spring)) } else if let peers = peers, let locationBroadcastMode = strongSelf.locationBroadcastMode { var canClose = true if case let .peer(peerId) = strongSelf.locationBroadcastPanelSource, let messages = messages { canClose = false for messageId in messages.keys { if messageId.peerId == peerId { canClose = true } } } strongSelf.locationBroadcastAccessoryPanel?.update(peers: peers, mode: locationBroadcastMode, canClose: canClose) } } } }) } } self.presentationDataDisposable = (context.sharedContext.presentationData |> deliverOnMainQueue).start(next: { [weak self] presentationData in if let strongSelf = self { let previousTheme = strongSelf.presentationData.theme let previousStrings = strongSelf.presentationData.strings strongSelf.presentationData = presentationData if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings { strongSelf.mediaAccessoryPanel?.0.containerNode.updatePresentationData(presentationData) strongSelf.locationBroadcastAccessoryPanel?.updatePresentationData(presentationData) } } }) } deinit { self.mediaStatusDisposable?.dispose() self.locationBroadcastDisposable?.dispose() self.presentationDataDisposable?.dispose() self.playlistPreloadDisposable?.dispose() } required public init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) var navigationHeight = super.navigationHeight if !self.displayNavigationBar { navigationHeight = 0.0 } var additionalHeight: CGFloat = 0.0 if let locationBroadcastPeers = self.locationBroadcastPeers, let locationBroadcastMode = self.locationBroadcastMode { let panelHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight.isZero ? -panelHeight : (navigationHeight + additionalHeight + UIScreenPixel)), size: CGSize(width: layout.size.width, height: panelHeight)) additionalHeight += panelHeight let locationBroadcastAccessoryPanel: LocationBroadcastNavigationAccessoryPanel if let current = self.locationBroadcastAccessoryPanel { locationBroadcastAccessoryPanel = current transition.updateFrame(node: locationBroadcastAccessoryPanel, frame: panelFrame) locationBroadcastAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition) } else { let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } locationBroadcastAccessoryPanel = LocationBroadcastNavigationAccessoryPanel(accountPeerId: self.context.account.peerId, theme: presentationData.theme, strings: presentationData.strings, tapAction: { [weak self] in if let strongSelf = self { switch strongSelf.locationBroadcastPanelSource { case .none: break case .summary: if let locationBroadcastMessages = strongSelf.locationBroadcastMessages { let messages = locationBroadcastMessages.values.sorted(by: { $0.index > $1.index }) if messages.count == 1 { presentLiveLocationController(context: strongSelf.context, peerId: messages[0].id.peerId, controller: strongSelf) } else { let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } let controller = ActionSheetController(presentationTheme: presentationData.theme) let dismissAction: () -> Void = { [weak controller] in controller?.dismissAnimated() } var items: [ActionSheetItem] = [] if !messages.isEmpty { items.append(ActionSheetTextItem(title: presentationData.strings.LiveLocation_MenuChatsCount(Int32(messages.count)))) for message in messages { if let peer = message.peers[message.id.peerId] { var beginTimeAndTimeout: (Double, Double)? for media in message.media { if let media = media as? TelegramMediaMap, let timeout = media.liveBroadcastingTimeout { beginTimeAndTimeout = (Double(message.timestamp), Double(timeout)) } } if let beginTimeAndTimeout = beginTimeAndTimeout { items.append(LocationBroadcastActionSheetItem(context: strongSelf.context, peer: peer, title: peer.displayTitle, beginTimestamp: beginTimeAndTimeout.0, timeout: beginTimeAndTimeout.1, strings: presentationData.strings, action: { dismissAction() if let strongSelf = self { presentLiveLocationController(context: strongSelf.context, peerId: peer.id, controller: strongSelf) } })) } } } items.append(ActionSheetButtonItem(title: presentationData.strings.LiveLocation_MenuStopAll, color: .destructive, action: { dismissAction() if let locationBroadcastPeers = strongSelf.locationBroadcastPeers { for peer in locationBroadcastPeers { self?.context.liveLocationManager?.cancelLiveLocation(peerId: peer.id) } } })) } controller.setItemGroups([ ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) ]) strongSelf.view.endEditing(true) strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) } } case let .peer(peerId): presentLiveLocationController(context: strongSelf.context, peerId: peerId, controller: strongSelf) } } }, close: { [weak self] in if let strongSelf = self { var closePeers: [Peer]? var closePeerId: PeerId? switch strongSelf.locationBroadcastPanelSource { case .none: break case .summary: if let locationBroadcastPeers = strongSelf.locationBroadcastPeers { if locationBroadcastPeers.count > 1 { closePeers = locationBroadcastPeers } else { closePeerId = locationBroadcastPeers.first?.id } } case let .peer(peerId): closePeerId = peerId } let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } let controller = ActionSheetController(presentationTheme: presentationData.theme) let dismissAction: () -> Void = { [weak controller] in controller?.dismissAnimated() } var items: [ActionSheetItem] = [] if let closePeers = closePeers, !closePeers.isEmpty { items.append(ActionSheetTextItem(title: presentationData.strings.LiveLocation_MenuChatsCount(Int32(closePeers.count)))) for peer in closePeers { items.append(ActionSheetButtonItem(title: peer.displayTitle, action: { dismissAction() if let strongSelf = self { presentLiveLocationController(context: strongSelf.context, peerId: peer.id, controller: strongSelf) } })) } items.append(ActionSheetButtonItem(title: presentationData.strings.LiveLocation_MenuStopAll, color: .destructive, action: { dismissAction() for peer in closePeers { self?.context.liveLocationManager?.cancelLiveLocation(peerId: peer.id) } })) } else if let closePeerId = closePeerId { items.append(ActionSheetButtonItem(title: presentationData.strings.Map_StopLiveLocation, color: .destructive, action: { dismissAction() self?.context.liveLocationManager?.cancelLiveLocation(peerId: closePeerId) })) } controller.setItemGroups([ ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) ]) strongSelf.view.endEditing(true) strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) } }) if let navigationBar = self.navigationBar { self.displayNode.insertSubnode(locationBroadcastAccessoryPanel, aboveSubnode: navigationBar) } else { self.displayNode.addSubnode(locationBroadcastAccessoryPanel) } self.locationBroadcastAccessoryPanel = locationBroadcastAccessoryPanel locationBroadcastAccessoryPanel.frame = panelFrame var canClose = true if case let .peer(peerId) = self.locationBroadcastPanelSource, let messages = self.locationBroadcastMessages { canClose = false for messageId in messages.keys { if messageId.peerId == peerId { canClose = true } } } locationBroadcastAccessoryPanel.update(peers: locationBroadcastPeers, mode: locationBroadcastMode, canClose: canClose) locationBroadcastAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: .immediate) if transition.isAnimated { locationBroadcastAccessoryPanel.animateIn(transition) } } } else if let locationBroadcastAccessoryPanel = self.locationBroadcastAccessoryPanel { self.locationBroadcastAccessoryPanel = nil if transition.isAnimated { locationBroadcastAccessoryPanel.animateOut(transition, completion: { [weak locationBroadcastAccessoryPanel] in locationBroadcastAccessoryPanel?.removeFromSupernode() }) } else { locationBroadcastAccessoryPanel.removeFromSupernode() } } let mediaAccessoryPanelHidden: Bool switch self.mediaAccessoryPanelVisibility { case .always: mediaAccessoryPanelHidden = false case .none: mediaAccessoryPanelHidden = true case let .specific(size): mediaAccessoryPanelHidden = size != layout.metrics.widthClass } if let (item, previousItem, nextItem, order, type, _) = self.playlistStateAndType, !mediaAccessoryPanelHidden { let panelHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight.isZero ? -panelHeight : (navigationHeight + additionalHeight + UIScreenPixel)), size: CGSize(width: layout.size.width, height: panelHeight)) if let (mediaAccessoryPanel, mediaType) = self.mediaAccessoryPanel, mediaType == type { transition.updateFrame(layer: mediaAccessoryPanel.layer, frame: panelFrame) mediaAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition) switch order { case .regular: mediaAccessoryPanel.containerNode.headerNode.playbackItems = (item, previousItem, nextItem) case .reversed: mediaAccessoryPanel.containerNode.headerNode.playbackItems = (item, nextItem, previousItem) case .random: mediaAccessoryPanel.containerNode.headerNode.playbackItems = (item, nil, nil) } let delayedStatus = self.context.sharedContext.mediaManager.globalMediaPlayerState |> mapToSignal { value -> Signal<(Account, SharedMediaPlayerItemPlaybackStateOrLoading, MediaManagerPlayerType)?, NoError> in guard let value = value else { return .single(nil) } switch value.1 { case .state: return .single(value) case .loading: return .single(value) |> delay(0.1, queue: .mainQueue()) } } mediaAccessoryPanel.containerNode.headerNode.playbackStatus = delayedStatus |> map { state -> MediaPlayerStatus in if let stateOrLoading = state?.1, case let .state(state) = stateOrLoading { return state.status } else { return MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused, soundEnabled: true) } } } else { if let (mediaAccessoryPanel, _) = self.mediaAccessoryPanel { self.mediaAccessoryPanel = nil self.dismissingPanel = mediaAccessoryPanel mediaAccessoryPanel.animateOut(transition: transition, completion: { [weak self, weak mediaAccessoryPanel] in mediaAccessoryPanel?.removeFromSupernode() if let strongSelf = self, strongSelf.dismissingPanel === mediaAccessoryPanel { strongSelf.dismissingPanel = nil } }) } let mediaAccessoryPanel = MediaNavigationAccessoryPanel(context: self.context) mediaAccessoryPanel.containerNode.headerNode.displayScrubber = type != .voice mediaAccessoryPanel.close = { [weak self] in if let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType { strongSelf.context.sharedContext.mediaManager.setPlaylist(nil, type: type, control: SharedMediaPlayerControlAction.playback(.pause)) } } mediaAccessoryPanel.toggleRate = { [weak self] in guard let strongSelf = self else { return } let _ = (strongSelf.context.sharedContext.accountManager.transaction { transaction -> AudioPlaybackRate in let settings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings) as? MusicPlaybackSettings ?? MusicPlaybackSettings.defaultSettings let nextRate: AudioPlaybackRate switch settings.voicePlaybackRate { case .x1: nextRate = .x2 case .x2: nextRate = .x1 } transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { _ in return settings.withUpdatedVoicePlaybackRate(nextRate) }) return nextRate } |> deliverOnMainQueue).start(next: { baseRate in guard let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType else { return } strongSelf.context.sharedContext.mediaManager.playlistControl(.setBaseRate(baseRate), type: type) }) } mediaAccessoryPanel.togglePlayPause = { [weak self] in if let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType { strongSelf.context.sharedContext.mediaManager.playlistControl(.playback(.togglePlayPause), type: type) } } mediaAccessoryPanel.playPrevious = { [weak self] in if let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType { strongSelf.context.sharedContext.mediaManager.playlistControl(.next, type: type) } } mediaAccessoryPanel.playNext = { [weak self] in if let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType { strongSelf.context.sharedContext.mediaManager.playlistControl(.previous, type: type) } } mediaAccessoryPanel.tapAction = { [weak self] in guard let strongSelf = self, let _ = strongSelf.navigationController as? NavigationController, let (state, _, _, order, type, account) = strongSelf.playlistStateAndType else { return } if let id = state.id as? PeerMessagesMediaPlaylistItemId { if type == .music { let signal = strongSelf.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(location: .id(id.messageId), count: 60), id: 0), account: account, chatLocation: .peer(id.messageId.peerId), tagMask: MessageTags.music) var cancelImpl: (() -> Void)? let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } let progressSignal = Signal { subscriber in let controller = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .loading(cancelled: { cancelImpl?() })) self?.present(controller, in: .window(.root)) return ActionDisposable { [weak controller] in Queue.mainQueue().async() { controller?.dismiss() } } } |> runOn(Queue.mainQueue()) |> delay(0.15, queue: Queue.mainQueue()) let progressDisposable = MetaDisposable() var progressStarted = false strongSelf.playlistPreloadDisposable?.dispose() strongSelf.playlistPreloadDisposable = (signal |> afterDisposed { Queue.mainQueue().async { progressDisposable.dispose() } } |> deliverOnMainQueue).start(next: { index in guard let strongSelf = self else { return } if let _ = index.0 { let controllerContext: AccountContext if account.id == strongSelf.context.account.id { controllerContext = strongSelf.context } else { controllerContext = strongSelf.context.sharedContext.makeTempAccountContext(account: account) } let controller = strongSelf.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, peerId: id.messageId.peerId, type: type, initialMessageId: id.messageId, initialOrder: order, parentNavigationController: strongSelf.navigationController as? NavigationController) strongSelf.displayNode.view.window?.endEditing(true) strongSelf.present(controller, in: .window(.root)) } else if index.1 { if !progressStarted { progressStarted = true progressDisposable.set(progressSignal.start()) } } }, completed: { }) cancelImpl = { self?.playlistPreloadDisposable?.dispose() } } else { strongSelf.context.sharedContext.navigateToChat(accountId: strongSelf.context.account.id, peerId: id.messageId.peerId, messageId: id.messageId) } } } mediaAccessoryPanel.frame = panelFrame if let dismissingPanel = self.dismissingPanel { self.displayNode.insertSubnode(mediaAccessoryPanel, aboveSubnode: dismissingPanel) } else if let navigationBar = self.navigationBar { self.displayNode.insertSubnode(mediaAccessoryPanel, aboveSubnode: navigationBar) } else { self.displayNode.addSubnode(mediaAccessoryPanel) } self.mediaAccessoryPanel = (mediaAccessoryPanel, type) mediaAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: .immediate) switch order { case .regular: mediaAccessoryPanel.containerNode.headerNode.playbackItems = (item, previousItem, nextItem) case .reversed: mediaAccessoryPanel.containerNode.headerNode.playbackItems = (item, nextItem, previousItem) case .random: mediaAccessoryPanel.containerNode.headerNode.playbackItems = (item, nil, nil) } mediaAccessoryPanel.containerNode.headerNode.playbackStatus = self.context.sharedContext.mediaManager.globalMediaPlayerState |> map { state -> MediaPlayerStatus in if let stateOrLoading = state?.1, case let .state(state) = stateOrLoading { return state.status } else { return MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused, soundEnabled: true) } } mediaAccessoryPanel.animateIn(transition: transition) } } else if let (mediaAccessoryPanel, _) = self.mediaAccessoryPanel { self.mediaAccessoryPanel = nil self.dismissingPanel = mediaAccessoryPanel mediaAccessoryPanel.animateOut(transition: transition, completion: { [weak self, weak mediaAccessoryPanel] in mediaAccessoryPanel?.removeFromSupernode() if let strongSelf = self, strongSelf.dismissingPanel === mediaAccessoryPanel { strongSelf.dismissingPanel = nil } }) } } open var keyShortcuts: [KeyShortcut] { return [KeyShortcut(input: UIKeyCommand.inputEscape, action: { [weak self] in if !(self?.navigationController?.topViewController is TabBarController) { _ = self?.navigationBar?.executeBack() } })] } }