mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
1035 lines
61 KiB
Swift
1035 lines
61 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import AsyncDisplayKit
|
|
import Display
|
|
import TelegramCore
|
|
import SwiftSignalKit
|
|
import Postbox
|
|
import TelegramPresentationData
|
|
import TelegramUIPreferences
|
|
import UniversalMediaPlayer
|
|
import AccountContext
|
|
import OverlayStatusController
|
|
import PresentationDataUtils
|
|
import TelegramCallsUI
|
|
import UndoUI
|
|
|
|
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: (EngineMessage?) -> Void = { [weak controller] message in
|
|
if let message = message, let strongController = controller {
|
|
let _ = context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: nil, chatFilterTag: nil, chatLocationContextHolder: nil, message: message._asMessage(), 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: { message in
|
|
let _ = enqueueMessages(account: context.account, peerId: peerId, messages: [message]).start()
|
|
}, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in
|
|
}, chatAvatarHiddenMedia: { _, _ in
|
|
}))
|
|
}
|
|
}
|
|
if let id = context.liveLocationManager?.internalMessageForPeerId(peerId) {
|
|
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: id))
|
|
|> deliverOnMainQueue).start(next: presentImpl)
|
|
} else if let liveLocationManager = context.liveLocationManager {
|
|
let _ = (liveLocationManager.summaryManager.peersBroadcastingTo(peerId: peerId)
|
|
|> take(1)
|
|
|> map { peersAndMessages -> EngineMessage? in
|
|
return peersAndMessages?.first?.1
|
|
} |> deliverOnMainQueue).start(next: presentImpl)
|
|
}
|
|
}
|
|
|
|
open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
|
private let context: AccountContext
|
|
|
|
public var accessoryPanelContainer: ASDisplayNode?
|
|
public private(set) var accessoryPanelContainerHeight: CGFloat = 0.0
|
|
|
|
public let mediaAccessoryPanelVisibility: MediaAccessoryPanelVisibility
|
|
public var tempHideAccessoryPanels: Bool = false
|
|
|
|
public let locationBroadcastPanelSource: LocationBroadcastPanelSource
|
|
public let groupCallPanelSource: GroupCallPanelSource
|
|
|
|
private var mediaStatusDisposable: Disposable?
|
|
private var locationBroadcastDisposable: Disposable?
|
|
private var currentGroupCallDisposable: Disposable?
|
|
|
|
public private(set) var playlistStateAndType: (SharedMediaPlaylistItem, SharedMediaPlaylistItem?, SharedMediaPlaylistItem?, MusicPlaybackSettingsOrder, MediaManagerPlayerType, Account)?
|
|
private var playlistLocation: SharedMediaPlaylistLocation?
|
|
|
|
public var tempVoicePlaylistEnded: (() -> Void)?
|
|
public var tempVoicePlaylistItemChanged: ((SharedMediaPlaylistItem?, SharedMediaPlaylistItem?) -> Void)?
|
|
public var tempVoicePlaylistCurrentItem: SharedMediaPlaylistItem?
|
|
|
|
public private(set) var mediaAccessoryPanel: (MediaNavigationAccessoryPanel, MediaManagerPlayerType)?
|
|
|
|
private var locationBroadcastMode: LocationBroadcastNavigationAccessoryPanelMode?
|
|
private var locationBroadcastPeers: [EnginePeer]?
|
|
private var locationBroadcastMessages: [EngineMessage.Id: EngineMessage]?
|
|
private var locationBroadcastAccessoryPanel: LocationBroadcastNavigationAccessoryPanel?
|
|
|
|
private var groupCallPanelData: GroupCallPanelData?
|
|
public private(set) var groupCallAccessoryPanel: GroupCallNavigationAccessoryPanel?
|
|
|
|
private var dismissingPanel: ASDisplayNode?
|
|
|
|
private weak var audioRateTooltipController: UndoOverlayController?
|
|
|
|
private var presentationData: PresentationData
|
|
private var presentationDataDisposable: Disposable?
|
|
private var playlistPreloadDisposable: Disposable?
|
|
|
|
override open var additionalNavigationBarHeight: CGFloat {
|
|
var height: CGFloat = 0.0
|
|
if self.accessoryPanelContainer == nil {
|
|
if let _ = self.groupCallAccessoryPanel {
|
|
height += 50.0
|
|
}
|
|
if let _ = self.mediaAccessoryPanel {
|
|
height += MediaNavigationAccessoryHeaderNode.minimizedHeight
|
|
}
|
|
if let _ = self.locationBroadcastAccessoryPanel {
|
|
height += MediaNavigationAccessoryHeaderNode.minimizedHeight
|
|
}
|
|
}
|
|
return height
|
|
}
|
|
|
|
public init(context: AccountContext, navigationBarPresentationData: NavigationBarPresentationData?, mediaAccessoryPanelVisibility: MediaAccessoryPanelVisibility, locationBroadcastPanelSource: LocationBroadcastPanelSource, groupCallPanelSource: GroupCallPanelSource) {
|
|
self.context = context
|
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
self.mediaAccessoryPanelVisibility = mediaAccessoryPanelVisibility
|
|
self.locationBroadcastPanelSource = locationBroadcastPanelSource
|
|
self.groupCallPanelSource = groupCallPanelSource
|
|
|
|
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.tempVoicePlaylistCurrentItem = updatedVoiceItem
|
|
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))
|
|
}
|
|
strongSelf.playlistLocation = playlistStateAndType?.1.playlistLocation
|
|
})
|
|
}
|
|
|
|
if let liveLocationManager = context.liveLocationManager {
|
|
switch locationBroadcastPanelSource {
|
|
case .none:
|
|
self.locationBroadcastMode = nil
|
|
case .summary, .peer:
|
|
let signal: Signal<([EnginePeer]?, [EngineMessage.Id: EngineMessage]?), 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 -> ([EnginePeer]?, [EngineMessage.Id: EngineMessage]?) in
|
|
if messages.isEmpty {
|
|
return (nil, nil)
|
|
} else {
|
|
var peers: [EnginePeer] = []
|
|
for message in messages.values.sorted(by: { $0.index < $1.index }) {
|
|
if let peer = message.peers[message.id.peerId] {
|
|
peers.append(EnginePeer(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 = 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)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
if let callManager = context.sharedContext.callManager {
|
|
switch groupCallPanelSource {
|
|
case .none, .all:
|
|
break
|
|
case let .peer(peerId):
|
|
let currentGroupCall: Signal<PresentationGroupCall?, NoError> = callManager.currentGroupCallSignal
|
|
|> distinctUntilChanged(isEqual: { lhs, rhs in
|
|
return lhs?.internalId == rhs?.internalId
|
|
})
|
|
|> map { call -> PresentationGroupCall? in
|
|
guard let call = call, call.peerId == peerId && call.account.peerId == context.account.peerId else {
|
|
return nil
|
|
}
|
|
return call
|
|
}
|
|
|
|
let availableGroupCall: Signal<GroupCallPanelData?, NoError>
|
|
if case let .peer(peerId) = groupCallPanelSource {
|
|
availableGroupCall = context.account.viewTracker.peerView(peerId)
|
|
|> map { peerView -> (CachedChannelData.ActiveCall?, EnginePeer?) in
|
|
let peer = peerView.peers[peerId].flatMap(EnginePeer.init)
|
|
if let cachedData = peerView.cachedData as? CachedChannelData {
|
|
return (cachedData.activeCall, peer)
|
|
} else if let cachedData = peerView.cachedData as? CachedGroupData {
|
|
return (cachedData.activeCall, peer)
|
|
} else {
|
|
return (nil, peer)
|
|
}
|
|
}
|
|
|> distinctUntilChanged(isEqual: { lhs, rhs in
|
|
if lhs.0 != rhs.0 {
|
|
return false
|
|
}
|
|
return true
|
|
})
|
|
|> mapToSignal { activeCall, peer -> Signal<GroupCallPanelData?, NoError> in
|
|
guard let activeCall = activeCall else {
|
|
return .single(nil)
|
|
}
|
|
|
|
var isChannel = false
|
|
if let peer = peer, case let .channel(channel) = peer, case .broadcast = channel.info {
|
|
isChannel = true
|
|
}
|
|
|
|
return Signal { [weak context] subscriber in
|
|
guard let context = context, let callContextCache = context.cachedGroupCallContexts as? AccountGroupCallContextCacheImpl else {
|
|
return EmptyDisposable
|
|
}
|
|
|
|
let disposable = MetaDisposable()
|
|
|
|
callContextCache.impl.syncWith { impl in
|
|
let callContext = impl.get(account: context.account, engine: context.engine, peerId: peerId, isChannel: isChannel, call: EngineGroupCallDescription(activeCall))
|
|
disposable.set((callContext.context.panelData
|
|
|> deliverOnMainQueue).start(next: { panelData in
|
|
callContext.keep()
|
|
var updatedPanelData = panelData
|
|
if let panelData {
|
|
var updatedInfo = panelData.info
|
|
updatedInfo.subscribedToScheduled = activeCall.subscribedToScheduled
|
|
updatedPanelData = panelData.withInfo(updatedInfo)
|
|
}
|
|
subscriber.putNext(updatedPanelData)
|
|
}))
|
|
}
|
|
|
|
return ActionDisposable {
|
|
disposable.dispose()
|
|
}
|
|
}
|
|
|> runOn(.mainQueue())
|
|
}
|
|
} else {
|
|
availableGroupCall = .single(nil)
|
|
}
|
|
|
|
let previousCurrentGroupCall = Atomic<PresentationGroupCall?>(value: nil)
|
|
self.currentGroupCallDisposable = combineLatest(queue: .mainQueue(), availableGroupCall, currentGroupCall).start(next: { [weak self] availableState, currentGroupCall in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
|
|
let previousCurrentGroupCall = previousCurrentGroupCall.swap(currentGroupCall)
|
|
|
|
let panelData: GroupCallPanelData?
|
|
if previousCurrentGroupCall != nil && currentGroupCall == nil && availableState?.participantCount == 1 {
|
|
panelData = nil
|
|
} else {
|
|
panelData = currentGroupCall != nil || (availableState?.participantCount == 0 && availableState?.info.scheduleTimestamp == nil && availableState?.info.isStream == false) ? nil : availableState
|
|
}
|
|
|
|
let wasEmpty = strongSelf.groupCallPanelData == nil
|
|
strongSelf.groupCallPanelData = panelData
|
|
let isEmpty = strongSelf.groupCallPanelData == nil
|
|
if wasEmpty != isEmpty {
|
|
strongSelf.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
|
|
} else if let groupCallPanelData = strongSelf.groupCallPanelData {
|
|
strongSelf.groupCallAccessoryPanel?.update(data: groupCallPanelData)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
self.presentationDataDisposable = (self.updatedPresentationData.1
|
|
|> 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)
|
|
strongSelf.groupCallAccessoryPanel?.updatePresentationData(presentationData)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
open var updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>) {
|
|
return (self.presentationData, self.context.sharedContext.presentationData)
|
|
}
|
|
|
|
deinit {
|
|
self.mediaStatusDisposable?.dispose()
|
|
self.locationBroadcastDisposable?.dispose()
|
|
self.currentGroupCallDisposable?.dispose()
|
|
self.presentationDataDisposable?.dispose()
|
|
self.playlistPreloadDisposable?.dispose()
|
|
}
|
|
|
|
required public init(coder aDecoder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
private var suspendNavigationBarLayout: Bool = false
|
|
private var suspendedNavigationBarLayout: ContainerViewLayout?
|
|
private var additionalNavigationBarBackgroundHeight: CGFloat = 0.0
|
|
|
|
override open func updateNavigationBarLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
|
if self.suspendNavigationBarLayout {
|
|
self.suspendedNavigationBarLayout = layout
|
|
return
|
|
}
|
|
self.applyNavigationBarLayout(layout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, transition: transition)
|
|
}
|
|
|
|
override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
|
self.suspendNavigationBarLayout = true
|
|
|
|
super.containerLayoutUpdated(layout, transition: transition)
|
|
|
|
let navigationHeight = super.navigationLayout(layout: layout).navigationFrame.height - self.additionalNavigationBarHeight
|
|
|
|
let mediaAccessoryPanelHidden: Bool
|
|
if self.tempHideAccessoryPanels {
|
|
mediaAccessoryPanelHidden = true
|
|
} else {
|
|
switch self.mediaAccessoryPanelVisibility {
|
|
case .always:
|
|
mediaAccessoryPanelHidden = false
|
|
case .none:
|
|
mediaAccessoryPanelHidden = true
|
|
case let .specific(size):
|
|
mediaAccessoryPanelHidden = size != layout.metrics.widthClass
|
|
}
|
|
}
|
|
|
|
var additionalHeight: CGFloat = 0.0
|
|
var panelStartY: CGFloat = 0.0
|
|
if self.accessoryPanelContainer == nil {
|
|
var negativeHeight: CGFloat = 0.0
|
|
if let _ = self.groupCallPanelData {
|
|
negativeHeight += 50.0
|
|
}
|
|
if let _ = self.locationBroadcastPeers, let _ = self.locationBroadcastMode {
|
|
negativeHeight += MediaNavigationAccessoryHeaderNode.minimizedHeight
|
|
}
|
|
if let _ = self.playlistStateAndType, !mediaAccessoryPanelHidden {
|
|
negativeHeight += MediaNavigationAccessoryHeaderNode.minimizedHeight
|
|
}
|
|
panelStartY = navigationHeight.isZero ? (-negativeHeight) : (navigationHeight + additionalHeight + UIScreenPixel)
|
|
}
|
|
|
|
if let groupCallPanelData = self.groupCallPanelData {
|
|
let panelHeight: CGFloat = 50.0
|
|
let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: panelStartY), size: CGSize(width: layout.size.width, height: panelHeight))
|
|
additionalHeight += panelHeight
|
|
panelStartY += panelHeight
|
|
|
|
let groupCallAccessoryPanel: GroupCallNavigationAccessoryPanel
|
|
if let current = self.groupCallAccessoryPanel {
|
|
groupCallAccessoryPanel = current
|
|
transition.updateFrame(node: groupCallAccessoryPanel, frame: panelFrame)
|
|
groupCallAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, isHidden: !self.displayNavigationBar, transition: transition)
|
|
} else {
|
|
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
|
groupCallAccessoryPanel = GroupCallNavigationAccessoryPanel(context: self.context, presentationData: presentationData, tapAction: { [weak self] in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
strongSelf.joinGroupCall(
|
|
peerId: groupCallPanelData.peerId,
|
|
invite: nil,
|
|
activeCall: EngineGroupCallDescription(id: groupCallPanelData.info.id, accessHash: groupCallPanelData.info.accessHash, title: groupCallPanelData.info.title, scheduleTimestamp: groupCallPanelData.info.scheduleTimestamp, subscribedToScheduled: groupCallPanelData.info.subscribedToScheduled, isStream: groupCallPanelData.info.isStream)
|
|
)
|
|
})
|
|
if let accessoryPanelContainer = self.accessoryPanelContainer {
|
|
accessoryPanelContainer.addSubnode(groupCallAccessoryPanel)
|
|
} else {
|
|
self.navigationBar?.additionalContentNode.addSubnode(groupCallAccessoryPanel)
|
|
}
|
|
self.groupCallAccessoryPanel = groupCallAccessoryPanel
|
|
groupCallAccessoryPanel.frame = panelFrame
|
|
|
|
groupCallAccessoryPanel.update(data: groupCallPanelData)
|
|
groupCallAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, isHidden: !self.displayNavigationBar, transition: .immediate)
|
|
if transition.isAnimated {
|
|
groupCallAccessoryPanel.animateIn(transition)
|
|
}
|
|
}
|
|
} else if let groupCallAccessoryPanel = self.groupCallAccessoryPanel {
|
|
self.groupCallAccessoryPanel = nil
|
|
if transition.isAnimated {
|
|
groupCallAccessoryPanel.animateOut(transition, completion: { [weak groupCallAccessoryPanel] in
|
|
groupCallAccessoryPanel?.removeFromSupernode()
|
|
})
|
|
} else {
|
|
groupCallAccessoryPanel.removeFromSupernode()
|
|
}
|
|
}
|
|
|
|
if let locationBroadcastPeers = self.locationBroadcastPeers, let locationBroadcastMode = self.locationBroadcastMode {
|
|
let panelHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight
|
|
let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: panelStartY), size: CGSize(width: layout.size.width, height: panelHeight))
|
|
additionalHeight += panelHeight
|
|
panelStartY += 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, isHidden: !self.displayNavigationBar, transition: transition)
|
|
} else {
|
|
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
|
locationBroadcastAccessoryPanel = LocationBroadcastNavigationAccessoryPanel(accountPeerId: self.context.account.peerId, theme: presentationData.theme, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, 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(presentationData: presentationData)
|
|
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: EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), 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: [EnginePeer]?
|
|
var closePeerId: EnginePeer.Id?
|
|
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(presentationData: presentationData)
|
|
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(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), 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 accessoryPanelContainer = self.accessoryPanelContainer {
|
|
accessoryPanelContainer.addSubnode(locationBroadcastAccessoryPanel)
|
|
} else {
|
|
self.navigationBar?.additionalContentNode.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, isHidden: !self.displayNavigationBar, 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()
|
|
}
|
|
}
|
|
|
|
var isViewOnceMessage = false
|
|
if let (item, _, _, _, _, _) = self.playlistStateAndType, let source = item.playbackData?.source, case let .telegramFile(_, _, isViewOnce) = source, isViewOnce {
|
|
isViewOnceMessage = true
|
|
}
|
|
|
|
if let (item, previousItem, nextItem, order, type, _) = self.playlistStateAndType, !mediaAccessoryPanelHidden && !isViewOnceMessage {
|
|
let panelHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight
|
|
let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: panelStartY), size: CGSize(width: layout.size.width, height: panelHeight))
|
|
additionalHeight += panelHeight
|
|
panelStartY += 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, isHidden: !self.displayNavigationBar, 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
|
|
self.audioRateTooltipController?.dismissWithCommitAction()
|
|
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, presentationData: self.updatedPresentationData.0)
|
|
mediaAccessoryPanel.containerNode.headerNode.displayScrubber = item.playbackData?.type != .instantVideo
|
|
mediaAccessoryPanel.getController = { [weak self] in
|
|
return self
|
|
}
|
|
mediaAccessoryPanel.presentInGlobalOverlay = { [weak self] c in
|
|
self?.presentInGlobalOverlay(c)
|
|
}
|
|
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.setRate = { [weak self] rate, changeType in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
let _ = (strongSelf.context.sharedContext.accountManager.transaction { transaction -> AudioPlaybackRate in
|
|
let settings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings)?.get(MusicPlaybackSettings.self) ?? MusicPlaybackSettings.defaultSettings
|
|
|
|
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { _ in
|
|
return PreferencesEntry(settings.withUpdatedVoicePlaybackRate(rate))
|
|
})
|
|
return rate
|
|
}
|
|
|> deliverOnMainQueue).start(next: { baseRate in
|
|
guard let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType else {
|
|
return
|
|
}
|
|
strongSelf.context.sharedContext.mediaManager.playlistControl(.setBaseRate(baseRate), type: type)
|
|
|
|
var hasTooltip = false
|
|
strongSelf.forEachController({ controller in
|
|
if let controller = controller as? UndoOverlayController {
|
|
hasTooltip = true
|
|
controller.dismissWithCommitAction()
|
|
}
|
|
return true
|
|
})
|
|
|
|
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
|
let text: String?
|
|
let rate: CGFloat?
|
|
if case let .sliderCommit(previousValue, newValue) = changeType {
|
|
let value = String(format: "%0.1f", baseRate.doubleValue)
|
|
if baseRate == .x1 {
|
|
text = presentationData.strings.Conversation_AudioRateTooltipNormal
|
|
} else {
|
|
text = presentationData.strings.Conversation_AudioRateTooltipCustom(value).string
|
|
}
|
|
if newValue > previousValue {
|
|
rate = .infinity
|
|
} else if newValue < previousValue {
|
|
rate = -.infinity
|
|
} else {
|
|
rate = nil
|
|
}
|
|
} else if baseRate == .x1 {
|
|
text = presentationData.strings.Conversation_AudioRateTooltipNormal
|
|
rate = 1.0
|
|
} else if baseRate == .x1_5 {
|
|
text = presentationData.strings.Conversation_AudioRateTooltip15X
|
|
rate = 1.5
|
|
} else if baseRate == .x2 {
|
|
text = presentationData.strings.Conversation_AudioRateTooltipSpeedUp
|
|
rate = 2.0
|
|
} else {
|
|
text = nil
|
|
rate = nil
|
|
}
|
|
var showTooltip = true
|
|
if case .sliderChange = changeType {
|
|
showTooltip = false
|
|
}
|
|
if let rate, let text, showTooltip {
|
|
let controller = UndoOverlayController(
|
|
presentationData: presentationData,
|
|
content: .audioRate(
|
|
rate: rate,
|
|
text: text
|
|
),
|
|
elevatedLayout: false,
|
|
animateInAsReplacement: hasTooltip,
|
|
action: { action in
|
|
return true
|
|
}
|
|
)
|
|
strongSelf.audioRateTooltipController = controller
|
|
strongSelf.present(controller, in: .current)
|
|
}
|
|
})
|
|
}
|
|
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, let playlistLocation = strongSelf.playlistLocation as? PeerMessagesPlaylistLocation {
|
|
if type == .music {
|
|
if case .custom = playlistLocation {
|
|
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, chatLocation: .peer(id: id.messageId.peerId), type: type, initialMessageId: id.messageId, initialOrder: order, playlistLocation: playlistLocation, parentNavigationController: strongSelf.navigationController as? NavigationController)
|
|
strongSelf.displayNode.view.window?.endEditing(true)
|
|
strongSelf.present(controller, in: .window(.root))
|
|
} else if case let .messages(chatLocation, _, _) = playlistLocation {
|
|
let signal = strongSelf.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: .id(id.messageId), quote: nil), count: 60, highlight: true), id: 0), context: strongSelf.context, chatLocation: chatLocation, subject: nil, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>(value: nil), tag: .tag(MessageTags.music))
|
|
|
|
var cancelImpl: (() -> Void)?
|
|
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
|
let progressSignal = Signal<Never, NoError> { subscriber in
|
|
let controller = OverlayStatusController(theme: presentationData.theme, 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, chatLocation: chatLocation, type: type, initialMessageId: id.messageId, initialOrder: order, playlistLocation: nil, 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 {
|
|
if let accessoryPanelContainer = self.accessoryPanelContainer {
|
|
accessoryPanelContainer.insertSubnode(mediaAccessoryPanel, aboveSubnode: dismissingPanel)
|
|
} else {
|
|
self.navigationBar?.additionalContentNode.insertSubnode(mediaAccessoryPanel, aboveSubnode: dismissingPanel)
|
|
}
|
|
} else {
|
|
if let accessoryPanelContainer = self.accessoryPanelContainer {
|
|
accessoryPanelContainer.addSubnode(mediaAccessoryPanel)
|
|
} else {
|
|
self.navigationBar?.additionalContentNode.addSubnode(mediaAccessoryPanel)
|
|
}
|
|
}
|
|
self.mediaAccessoryPanel = (mediaAccessoryPanel, type)
|
|
mediaAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, isHidden: !self.displayNavigationBar, 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
|
|
self.audioRateTooltipController?.dismissWithCommitAction()
|
|
mediaAccessoryPanel.animateOut(transition: transition, completion: { [weak self, weak mediaAccessoryPanel] in
|
|
mediaAccessoryPanel?.removeFromSupernode()
|
|
if let strongSelf = self, strongSelf.dismissingPanel === mediaAccessoryPanel {
|
|
strongSelf.dismissingPanel = nil
|
|
}
|
|
})
|
|
}
|
|
|
|
self.suspendNavigationBarLayout = false
|
|
if let suspendedNavigationBarLayout = self.suspendedNavigationBarLayout {
|
|
self.suspendedNavigationBarLayout = suspendedNavigationBarLayout
|
|
self.applyNavigationBarLayout(suspendedNavigationBarLayout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, transition: transition)
|
|
}
|
|
|
|
self.accessoryPanelContainerHeight = additionalHeight
|
|
}
|
|
|
|
open var keyShortcuts: [KeyShortcut] {
|
|
return [KeyShortcut(input: UIKeyCommand.inputEscape, action: { [weak self] in
|
|
if !(self?.navigationController?.topViewController is TabBarController) {
|
|
_ = self?.navigationBar?.executeBack()
|
|
}
|
|
})]
|
|
}
|
|
|
|
open func joinGroupCall(peerId: PeerId, invite: String?, activeCall: EngineGroupCallDescription) {
|
|
let context = self.context
|
|
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
|
|
|
self.view.endEditing(true)
|
|
|
|
self.context.joinGroupCall(peerId: peerId, invite: invite, requestJoinAsPeerId: { completion in
|
|
let currentAccountPeer = context.account.postbox.loadedPeerWithId(context.account.peerId)
|
|
|> map { peer in
|
|
return [FoundPeer(peer: peer, subscribers: nil)]
|
|
}
|
|
|
|
let _ = (combineLatest(
|
|
currentAccountPeer,
|
|
context.engine.calls.cachedGroupCallDisplayAsAvailablePeers(peerId: peerId),
|
|
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.CallJoinAsPeerId(id: peerId))
|
|
)
|
|
|> map { currentAccountPeer, availablePeers, callJoinAsPeerId -> ([FoundPeer], EnginePeer.Id?) in
|
|
var result = currentAccountPeer
|
|
result.append(contentsOf: availablePeers)
|
|
return (result, callJoinAsPeerId)
|
|
}
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { [weak self] peers, callJoinAsPeerId in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
|
|
let defaultJoinAsPeerId: PeerId? = callJoinAsPeerId
|
|
|
|
if peers.count == 1, let peer = peers.first {
|
|
completion(peer.peer.id)
|
|
} else {
|
|
if let defaultJoinAsPeerId = defaultJoinAsPeerId {
|
|
completion(defaultJoinAsPeerId)
|
|
} else {
|
|
let controller = ActionSheetController(presentationData: presentationData)
|
|
let dismissAction: () -> Void = { [weak controller] in
|
|
controller?.dismissAnimated()
|
|
}
|
|
|
|
var items: [ActionSheetItem] = []
|
|
var isGroup = false
|
|
for peer in peers {
|
|
if peer.peer is TelegramGroup {
|
|
isGroup = true
|
|
break
|
|
} else if let peer = peer.peer as? TelegramChannel, case .group = peer.info {
|
|
isGroup = true
|
|
break
|
|
}
|
|
}
|
|
|
|
items.append(VoiceChatAccountHeaderActionSheetItem(title: presentationData.strings.VoiceChat_SelectAccount, text: isGroup ? presentationData.strings.VoiceChat_DisplayAsInfoGroup : presentationData.strings.VoiceChat_DisplayAsInfo))
|
|
for peer in peers {
|
|
var subtitle: String?
|
|
if peer.peer.id.namespace == Namespaces.Peer.CloudUser {
|
|
subtitle = presentationData.strings.VoiceChat_PersonalAccount
|
|
} else if let subscribers = peer.subscribers {
|
|
if let peer = peer.peer as? TelegramChannel, case .broadcast = peer.info {
|
|
subtitle = strongSelf.presentationData.strings.Conversation_StatusSubscribers(subscribers)
|
|
} else {
|
|
subtitle = strongSelf.presentationData.strings.Conversation_StatusMembers(subscribers)
|
|
}
|
|
}
|
|
|
|
items.append(VoiceChatPeerActionSheetItem(context: context, peer: EnginePeer(peer.peer), title: EnginePeer(peer.peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), subtitle: subtitle ?? "", action: {
|
|
dismissAction()
|
|
completion(peer.peer.id)
|
|
}))
|
|
}
|
|
|
|
controller.setItemGroups([
|
|
ActionSheetItemGroup(items: items),
|
|
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
|
])
|
|
strongSelf.present(controller, in: .window(.root))
|
|
}
|
|
}
|
|
})
|
|
}, activeCall: activeCall)
|
|
}
|
|
}
|