mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-03 21:16:35 +00:00
Fixed gallery transition out for gifs in Instant View Don't display close button in live location panel in chats if not sharing own location
668 lines
39 KiB
Swift
668 lines
39 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import AsyncDisplayKit
|
|
import Display
|
|
import TelegramCore
|
|
import SwiftSignalKit
|
|
import Postbox
|
|
import TelegramPresentationData
|
|
import TelegramUIPreferences
|
|
import UniversalMediaPlayer
|
|
|
|
enum MediaAccessoryPanelVisibility {
|
|
case none
|
|
case specific(size: ContainerViewLayoutSizeClass)
|
|
case always
|
|
}
|
|
|
|
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 _ = openChatMessage(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)
|
|
}
|
|
}
|
|
|
|
public class TelegramController: ViewController, KeyShortcutResponder {
|
|
private let context: AccountContext
|
|
|
|
let mediaAccessoryPanelVisibility: MediaAccessoryPanelVisibility
|
|
let locationBroadcastPanelSource: LocationBroadcastPanelSource
|
|
|
|
private var mediaStatusDisposable: Disposable?
|
|
private var locationBroadcastDisposable: Disposable?
|
|
|
|
private(set) var playlistStateAndType: (SharedMediaPlaylistItem, SharedMediaPlaylistItem?, SharedMediaPlaylistItem?, MusicPlaybackSettingsOrder, MediaManagerPlayerType, Account)?
|
|
|
|
var tempVoicePlaylistEnded: (() -> Void)?
|
|
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 public var navigationHeight: CGFloat {
|
|
return super.navigationHeight + self.additionalHeight
|
|
}
|
|
|
|
override public var navigationInsetHeight: CGFloat {
|
|
return super.navigationInsetHeight + self.additionalHeight
|
|
}
|
|
|
|
override public 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
|
|
}
|
|
|
|
public var primaryNavigationHeight: CGFloat {
|
|
return super.navigationHeight
|
|
}
|
|
|
|
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")
|
|
}
|
|
|
|
public override 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)
|
|
}
|
|
}
|
|
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 historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: .id(id.messageId), count: 60), id: 0), account: account, chatLocation: .peer(id.messageId.peerId), fixedCombinedReadStates: nil, tagMask: MessageTags.music, additionalData: [])
|
|
let signal = historyView
|
|
|> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in
|
|
switch historyView {
|
|
case .Loading:
|
|
return .single((nil, true))
|
|
case let .HistoryView(view, _, _, _, _, _, _):
|
|
for entry in view.entries {
|
|
if entry.message.id == id.messageId {
|
|
return .single((entry.message.index, false))
|
|
}
|
|
}
|
|
return .single((nil, false))
|
|
}
|
|
}
|
|
|> take(until: { index in
|
|
return SignalTakeAction(passthrough: true, complete: !index.1)
|
|
})
|
|
|
|
var cancelImpl: (() -> Void)?
|
|
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
|
let progressSignal = Signal<Never, NoError> { 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 controller = OverlayPlayerController(context: account.id == strongSelf.context.account.id ? strongSelf.context : AccountContext(sharedContext: strongSelf.context.sharedContext, account: account, limitsConfiguration: .defaultValue), 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
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
public var keyShortcuts: [KeyShortcut] {
|
|
return [KeyShortcut(input: UIKeyInputEscape, action: { [weak self] in
|
|
if !(self?.navigationController?.topViewController is TabBarController) {
|
|
_ = self?.navigationBar?.executeBack()
|
|
}
|
|
})]
|
|
}
|
|
}
|