Various improvements

This commit is contained in:
Ilya Laktyushin 2026-03-26 18:43:26 +01:00
parent 77cc01f0d9
commit 9dde762a1c
8 changed files with 574 additions and 442 deletions

View file

@ -311,7 +311,15 @@ private enum AttachmentFileEntry: ItemListNodeEntry {
}
}
private func attachmentFileControllerEntries(presentationData: PresentationData, mode: AttachmentFileControllerMode, state: AttachmentFileControllerState, savedMusic: [Message]?, recentDocuments: [Message]?, hasScan: Bool, empty: Bool) -> [AttachmentFileEntry] {
private func attachmentFileControllerEntries(
presentationData: PresentationData,
mode: AttachmentFileControllerMode,
state: AttachmentFileControllerState,
savedMusic: [Message]?,
recentDocuments: [Message]?,
hasScan: Bool,
empty: Bool
) -> [AttachmentFileEntry] {
guard !empty else {
return []
}
@ -332,7 +340,7 @@ private func attachmentFileControllerEntries(presentationData: PresentationData,
listTitle = presentationData.strings.Attachment_SharedAudio
}
if case .audio = mode {
if case let .audio(audioMode) = mode, audioMode != .savedMusic {
if let savedMusic, savedMusic.count > 0 {
entries.append(.savedHeader(presentationData.theme, presentationData.strings.MediaEditor_Audio_SavedMusic.uppercased()))
@ -521,8 +529,14 @@ private func messageSelectionState(state: AttachmentFileControllerState, message
}
public enum AttachmentFileControllerMode {
public enum AudioMode {
case chat
case story
case savedMusic
}
case recent
case audio(story: Bool)
case audio(AudioMode)
var isAudio: Bool {
if case .audio = self {
@ -548,10 +562,10 @@ public func makeAttachmentFileControllerImpl(
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
mode: AttachmentFileControllerMode = .recent,
source: AttachmentFileControllerSource = .generic,
bannedSendMedia: (Int32, Bool)?,
presentGallery: @escaping () -> Void,
presentFiles: @escaping () -> Void,
presentDocumentScanner: (() -> Void)?,
bannedSendMedia: (Int32, Bool)? = nil,
presentGallery: @escaping () -> Void = {},
presentFiles: @escaping () -> Void = {},
presentDocumentScanner: (() -> Void)? = nil,
send: @escaping ([AnyMediaReference], Bool, Int32?, NSAttributedString?) -> Void
) -> AttachmentFileController {
let actionsDisposable = DisposableSet()
@ -573,9 +587,11 @@ public func makeAttachmentFileControllerImpl(
var isAudio = false
var isAttach = true
if case let .audio(isStory) = mode {
if case let .audio(mode) = mode {
isAudio = true
isAttach = !isStory
if case .story = mode {
isAttach = false
}
}
var didPreviewAudio = false
@ -1032,6 +1048,11 @@ public func makeAttachmentFileControllerImpl(
context.sharedContext.mediaManager.setPlaylist(nil, type: .music, control: .playback(.pause))
}
}
if case let .audio(audioMode) = mode, audioMode != .chat {
controller.hasBottomEdgeEffect = false
}
updateIsSearchingImpl = { [weak controller] isSearching in
controller?.isSearching = isSearching
}
@ -1074,14 +1095,13 @@ public func storyAudioPickerController(
let updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>) = (presentationData, .single(presentationData))
let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, style: .glass, chatLocation: nil, buttons: [.audio], initialButton: .audio, fromMenu: false, hasTextInput: false)
controller.requestController = { _, present in
let filePickerController = makeAttachmentFileControllerImpl(context: context, updatedPresentationData: updatedPresentationData, mode: .audio(story: true), bannedSendMedia: nil, presentGallery: {}, presentFiles: {
let filePickerController = makeAttachmentFileControllerImpl(context: context, updatedPresentationData: updatedPresentationData, mode: .audio(.story), bannedSendMedia: nil, presentGallery: {}, presentFiles: {
selectFromFiles()
dismissImpl?()
}, presentDocumentScanner: nil, send: { files, _, _, _ in
completion(files.first!)
dismissImpl?()
}) as! AttachmentFileControllerImpl
filePickerController.hasBottomEdgeEffect = false
present(filePickerController, filePickerController.mediaPickerContext)
return true
}

View file

@ -3028,7 +3028,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
playlistLocation: playlistLocation,
parentNavigationController: self.controller?.navigationController as? NavigationController
)
self.controller?.present(musicController, in: .window(.root))
musicController.navigationPresentation = .flatModal
self.controller?.push(musicController)
}
})
}

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "plus_30.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -8,6 +8,7 @@ import TelegramUIPreferences
import AccountContext
import ShareController
import UndoUI
import AttachmentFileController
final class OverlayAudioPlayerControllerImpl: ViewController, OverlayAudioPlayerController {
private let context: AccountContext
@ -62,88 +63,116 @@ final class OverlayAudioPlayerControllerImpl: ViewController, OverlayAudioPlayer
}
override public func loadDisplayNode() {
self.displayNode = OverlayAudioPlayerControllerNode(context: self.context, chatLocation: self.chatLocation, type: self.type, initialMessageId: self.initialMessageId, initialOrder: self.initialOrder, playlistLocation: self.playlistLocation, requestDismiss: { [weak self] in
self?.dismiss()
}, requestShare: { [weak self] subject in
if let strongSelf = self {
var canShowInChat = false
if case .messages = subject {
canShowInChat = true
}
let shareController = ShareController(context: strongSelf.context, subject: subject, showInChat: canShowInChat ? { message in
if let strongSelf = self {
strongSelf.context.sharedContext.navigateToChat(accountId: strongSelf.context.account.id, peerId: message.id.peerId, messageId: message.id)
strongSelf.dismiss()
self.displayNode = OverlayAudioPlayerControllerNode(
context: self.context,
chatLocation: self.chatLocation,
type: self.type,
initialMessageId: self.initialMessageId,
initialOrder: self.initialOrder,
playlistLocation: self.playlistLocation,
requestDismiss: { [weak self] in
self?.dismiss()
},
requestShare: { [weak self] subject in
if let strongSelf = self {
var canShowInChat = false
if case .messages = subject {
canShowInChat = true
}
} : nil, externalShare: true)
shareController.completed = { [weak self] peerIds in
if let strongSelf = self {
let _ = (strongSelf.context.engine.data.get(
EngineDataList(
peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init)
let shareController = ShareController(context: strongSelf.context, subject: subject, showInChat: canShowInChat ? { message in
if let strongSelf = self {
strongSelf.context.sharedContext.navigateToChat(accountId: strongSelf.context.account.id, peerId: message.id.peerId, messageId: message.id)
strongSelf.dismiss()
}
} : nil, externalShare: true)
shareController.completed = { [weak self] peerIds in
if let strongSelf = self {
let _ = (strongSelf.context.engine.data.get(
EngineDataList(
peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init)
)
)
)
|> deliverOnMainQueue).startStandalone(next: { [weak self] peerList in
if let strongSelf = self {
let peers = peerList.compactMap { $0 }
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let text: String
var savedMessages = false
if peerIds.count == 1, let peerId = peerIds.first, peerId == strongSelf.context.account.peerId {
text = presentationData.strings.Conversation_ForwardTooltip_SavedMessages_One
savedMessages = true
} else {
if peers.count == 1, let peer = peers.first {
var peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
peerName = peerName.replacingOccurrences(of: "**", with: "")
text = presentationData.strings.Conversation_ForwardTooltip_Chat_One(peerName).string
} else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last {
var firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
firstPeerName = firstPeerName.replacingOccurrences(of: "**", with: "")
var secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
secondPeerName = secondPeerName.replacingOccurrences(of: "**", with: "")
text = presentationData.strings.Conversation_ForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string
} else if let peer = peers.first {
var peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
peerName = peerName.replacingOccurrences(of: "**", with: "")
text = presentationData.strings.Conversation_ForwardTooltip_ManyChats_One(peerName, "\(peers.count - 1)").string
|> deliverOnMainQueue).startStandalone(next: { [weak self] peerList in
if let strongSelf = self {
let peers = peerList.compactMap { $0 }
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let text: String
var savedMessages = false
if peerIds.count == 1, let peerId = peerIds.first, peerId == strongSelf.context.account.peerId {
text = presentationData.strings.Conversation_ForwardTooltip_SavedMessages_One
savedMessages = true
} else {
text = ""
if peers.count == 1, let peer = peers.first {
var peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
peerName = peerName.replacingOccurrences(of: "**", with: "")
text = presentationData.strings.Conversation_ForwardTooltip_Chat_One(peerName).string
} else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last {
var firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
firstPeerName = firstPeerName.replacingOccurrences(of: "**", with: "")
var secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
secondPeerName = secondPeerName.replacingOccurrences(of: "**", with: "")
text = presentationData.strings.Conversation_ForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string
} else if let peer = peers.first {
var peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
peerName = peerName.replacingOccurrences(of: "**", with: "")
text = presentationData.strings.Conversation_ForwardTooltip_ManyChats_One(peerName, "\(peers.count - 1)").string
} else {
text = ""
}
}
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { action in
if savedMessages, let self, action == .info {
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let self, let peer else {
return
}
guard let navigationController = self.parentNavigationController else {
return
}
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), forceOpenChat: true))
})
}
return false
}), in: .current)
}
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { action in
if savedMessages, let self, action == .info {
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let self, let peer else {
return
}
guard let navigationController = self.parentNavigationController else {
return
}
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), forceOpenChat: true))
})
}
return false
}), in: .current)
}
})
})
}
}
strongSelf.controllerNode.view.endEditing(true)
strongSelf.present(shareController, in: .window(.root))
}
strongSelf.controllerNode.view.endEditing(true)
strongSelf.present(shareController, in: .window(.root))
},
requestSearchByArtist: { [weak self] artist in
guard let self else {
return
}
self.context.sharedContext.openSearch(filter: .music, query: artist)
self.dismiss()
},
requestAdd: { [weak self] in
guard let self, let navigationController = self.parentNavigationController else {
return
}
let controller = makeAttachmentFileControllerImpl(
context: self.context,
mode: .audio(.savedMusic),
send: { [weak self] mediaReferences, _, _, _ in
guard let self, let reference = mediaReferences.first?.concrete(TelegramMediaFile.self) else {
return
}
self.controllerNode.addToSavedMusic(file: reference)
}
) as! AttachmentFileControllerImpl
controller.navigationPresentation = .modal
navigationController.pushViewController(controller)
},
getParentController: { [weak self] in
return self
}
}, requestSearchByArtist: { [weak self] artist in
if let strongSelf = self {
strongSelf.context.sharedContext.openSearch(filter: .music, query: artist)
strongSelf.dismiss()
}
},
getParentController: { [weak self] in
return self
})
)
self.ready.set(self.controllerNode.ready.get())
@ -165,7 +194,11 @@ final class OverlayAudioPlayerControllerImpl: ViewController, OverlayAudioPlayer
override public func dismiss(completion: (() -> Void)? = nil) {
self.controllerNode.animateOut(completion: { [weak self] in
self?.presentingViewController?.dismiss(animated: false, completion: nil)
if let _ = self?.navigationController {
self?.dismiss(animated: false, completion: nil)
} else {
self?.presentingViewController?.dismiss(animated: false, completion: nil)
}
completion?()
})
}

View file

@ -2,6 +2,7 @@ import Foundation
import UIKit
import Display
import AsyncDisplayKit
import ComponentFlow
import SwiftSignalKit
import Postbox
import TelegramCore
@ -14,6 +15,8 @@ import ChatControllerInteraction
import ContextUI
import UndoUI
import ChatHistoryEntry
import MultilineTextComponent
import GlassControls
final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestureRecognizerDelegate {
let ready = Promise<Bool>()
@ -27,6 +30,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
private let requestDismiss: () -> Void
private let requestShare: (ShareControllerSubject) -> Void
private let requestSearchByArtist: (String) -> Void
private let requestAdd: () -> Void
private let playlistLocation: SharedMediaPlaylistLocation?
private let isGlobalSearch: Bool
@ -35,11 +39,21 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
private var currentIsReversed: Bool
private let dimNode: ASDisplayNode
private let containerContainingNode: ASDisplayNode
private let contentNode: ASDisplayNode
private let controlsNode: OverlayPlayerControlsNode
private let controlsNode: OverlayAudioPlayerControlsNode
private let historyBackgroundNode: ASDisplayNode
private let historyBackgroundContentNode: ASDisplayNode
private let historyFrameNode: SparseNode
private let historyFrameLeftOverlayNode: ASDisplayNode
private let historyFrameRightOverlayNode: ASDisplayNode
private let historyFrameTopOverlayNode: ASDisplayNode
private let historyFrameTopMaskNode: ASImageNode
private let headerButtons = ComponentView<Empty>()
private let title = ComponentView<Empty>()
private var floatingHeaderOffset: CGFloat?
private var historyContentOffset: CGFloat = 0.0
private var historyNode: ChatHistoryListNodeImpl
private var replacementHistoryNode: ChatHistoryListNodeImpl?
private var replacementHistoryNodeFloatingOffset: CGFloat?
@ -53,9 +67,10 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
private let getParentController: () -> ViewController?
private var savedIdsDisposable: Disposable?
private var dataDisposable: Disposable?
private var savedIdsPromise = Promise<Set<Int64>?>()
private var savedIds: Set<Int64>?
private var peer: EnginePeer?
private var copyProtectionEnabled = false
@ -69,6 +84,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
requestDismiss: @escaping () -> Void,
requestShare: @escaping (ShareControllerSubject) -> Void,
requestSearchByArtist: @escaping (String) -> Void,
requestAdd: @escaping () -> Void,
getParentController: @escaping () -> ViewController?
) {
self.context = context
@ -78,6 +94,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
self.requestDismiss = requestDismiss
self.requestShare = requestShare
self.requestSearchByArtist = requestSearchByArtist
self.requestAdd = requestAdd
self.playlistLocation = playlistLocation
self.getParentController = getParentController
@ -238,9 +255,12 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
self.dimNode = ASDisplayNode()
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
self.containerContainingNode = ASDisplayNode()
self.containerContainingNode.clipsToBounds = true
self.contentNode = ASDisplayNode()
self.controlsNode = OverlayPlayerControlsNode(account: context.account, engine: context.engine, accountManager: context.sharedContext.accountManager, presentationData: self.presentationData, status: context.sharedContext.mediaManager.musicMediaPlayerState, chatLocation: self.chatLocation, source: self.source)
self.controlsNode = OverlayAudioPlayerControlsNode(account: context.account, engine: context.engine, accountManager: context.sharedContext.accountManager, presentationData: self.presentationData, status: context.sharedContext.mediaManager.musicMediaPlayerState, chatLocation: self.chatLocation, source: self.source)
self.controlsNode.getParentController = getParentController
self.historyBackgroundNode = ASDisplayNode()
@ -248,10 +268,28 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
self.historyBackgroundContentNode = ASDisplayNode()
self.historyBackgroundContentNode.isLayerBacked = true
self.historyBackgroundContentNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.historyBackgroundContentNode.backgroundColor = self.presentationData.theme.list.itemModalBlocksBackgroundColor
self.historyBackgroundNode.addSubnode(self.historyBackgroundContentNode)
self.historyFrameNode = SparseNode()
self.historyFrameLeftOverlayNode = ASDisplayNode()
self.historyFrameLeftOverlayNode.backgroundColor = self.presentationData.theme.list.modalBlocksBackgroundColor
self.historyFrameRightOverlayNode = ASDisplayNode()
self.historyFrameRightOverlayNode.backgroundColor = self.presentationData.theme.list.modalBlocksBackgroundColor
self.historyFrameTopOverlayNode = ASDisplayNode()
self.historyFrameTopOverlayNode.cornerRadius = 38.0
self.historyFrameTopOverlayNode.clipsToBounds = true
self.historyFrameTopOverlayNode.backgroundColor = self.presentationData.theme.list.modalBlocksBackgroundColor
self.historyFrameTopOverlayNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
self.historyFrameTopMaskNode = ASImageNode()
self.historyFrameTopMaskNode.displaysAsynchronously = false
self.historyFrameTopMaskNode.image = PresentationResourcesItemList.cornersImage(self.presentationData.theme.withModalBlocksBackground(), top: true, bottom: false, glass: true)
self.historyFrameTopMaskNode.isUserInteractionEnabled = false
let tagMask: MessageTags
switch type {
case .music:
@ -264,7 +302,23 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil)
self.historyNode = ChatHistoryListNodeImpl(context: context, updatedPresentationData: (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData), chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, adMessagesContext: nil, tag: .tag(tagMask), source: self.source, subject: .message(id: .id(initialMessageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(reversed: self.currentIsReversed, reverseGroups: !self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch), isChatPreview: false, messageTransitionNode: { return nil })
self.historyNode = ChatHistoryListNodeImpl(
context: context,
updatedPresentationData: (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData),
systemStyle: .glass,
chatLocation: chatLocation,
chatLocationContextHolder: chatLocationContextHolder,
adMessagesContext: nil,
tag: .tag(tagMask),
source: self.source,
subject: .message(id: .id(initialMessageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false),
controllerInteraction: self.controllerInteraction,
selectedMessages: .single(nil),
mode: .list(reversed: self.currentIsReversed, reverseGroups: !self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch),
isChatPreview: false,
messageTransitionNode: { return nil
}
)
self.historyNode.clipsToBounds = true
super.init()
@ -275,6 +329,9 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
self.historyNode.preloadPages = true
self.historyNode.stackFromBottom = true
self.historyNode.areContentAnimationsEnabled = true
self.historyNode.contentPositionChanged = { [weak self] offset in
self?.updateHistoryContentOffset(offset, transition: .immediate)
}
self.historyNode.updateFloatingHeaderOffset = { [weak self] offset, transition in
if let strongSelf = self {
strongSelf.updateFloatingHeaderOffset(offset: offset, transition: transition)
@ -359,11 +416,18 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
}
self.addSubnode(self.dimNode)
self.addSubnode(self.contentNode)
self.addSubnode(self.containerContainingNode)
self.containerContainingNode.addSubnode(self.contentNode)
self.contentNode.addSubnode(self.historyBackgroundNode)
self.contentNode.addSubnode(self.historyNode)
self.contentNode.addSubnode(self.historyFrameNode)
self.contentNode.addSubnode(self.controlsNode)
self.historyFrameNode.addSubnode(self.historyFrameLeftOverlayNode)
self.historyFrameNode.addSubnode(self.historyFrameRightOverlayNode)
self.historyFrameNode.addSubnode(self.historyFrameTopOverlayNode)
self.historyFrameNode.addSubnode(self.historyFrameTopMaskNode)
self.historyNode.beganInteractiveDragging = { [weak self] _ in
self?.controlsNode.collapse()
}
@ -415,11 +479,19 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
copyProtectionEnabled = .single(false)
}
self.savedIdsDisposable = combineLatest(
let peer: Signal<EnginePeer?, NoError>
if let playlistLocation = self.playlistLocation as? PeerMessagesPlaylistLocation, case let .savedMusic(savedMusicContext, _, _) = playlistLocation {
peer = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: savedMusicContext.peerId))
} else {
peer = .single(nil)
}
self.dataDisposable = combineLatest(
queue: Queue.mainQueue(),
context.engine.peers.savedMusicIds(),
copyProtectionEnabled
).start(next: { [weak self] savedIds, copyProtectionEnabled in
copyProtectionEnabled,
peer
).start(next: { [weak self] savedIds, copyProtectionEnabled, peer in
guard let self else {
return
}
@ -428,9 +500,9 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
self.savedIdsPromise.set(.single(savedIds))
self.copyProtectionEnabled = copyProtectionEnabled
self.controlsNode.forceCopyProtected.set(copyProtectionEnabled)
self.peer = peer
let transition: ContainedViewLayoutTransition = isFirstTime ? .immediate : .animated(duration: 0.5, curve: .spring)
self.updateFloatingHeaderOffset(offset: self.floatingHeaderOffset ?? 0.0, transition: transition)
if let validLayout = self.validLayout {
self.containerLayoutUpdated(validLayout, transition: transition)
}
@ -457,7 +529,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
deinit {
self.presentationDataDisposable?.dispose()
self.replacementHistoryNodeReadyDisposable.dispose()
self.savedIdsDisposable?.dispose()
self.dataDisposable?.dispose()
self.saveMediaDisposable?.dispose()
}
@ -539,7 +611,12 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
func updatePresentationData(_ presentationData: PresentationData) {
self.presentationData = presentationData
self.historyBackgroundContentNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.historyBackgroundContentNode.backgroundColor = self.presentationData.theme.list.itemModalBlocksBackgroundColor
self.historyFrameLeftOverlayNode.backgroundColor = self.presentationData.theme.list.modalBlocksBackgroundColor
self.historyFrameRightOverlayNode.backgroundColor = self.presentationData.theme.list.modalBlocksBackgroundColor
self.historyFrameTopOverlayNode.backgroundColor = self.presentationData.theme.list.modalBlocksBackgroundColor
self.historyFrameTopMaskNode.image = PresentationResourcesItemList.cornersImage(self.presentationData.theme.withModalBlocksBackground(), top: true, bottom: false, glass: true)
self.controlsNode.updatePresentationData(self.presentationData)
}
@ -685,32 +762,70 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
return self.savedIds?.contains(fileReference.media.fileId.id)
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
self.validLayout = layout
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(), size: layout.size))
let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
var insets = UIEdgeInsets()
insets.left = layout.safeInsets.left
insets.right = layout.safeInsets.right
insets.bottom = layout.intrinsicInsets.bottom
if layout.size.width > layout.size.height && self.controlsNode.isExpanded {
self.controlsNode.isExpanded = false
private func updateHistoryContentOffset(_ offset: ListViewVisibleContentOffset, transition: ContainedViewLayoutTransition) {
switch offset {
case let .known(value):
self.historyContentOffset = value
case .none:
self.historyContentOffset = 0.0
case .unknown:
break
}
let maxHeight = layout.size.height - layoutTopInset - floor(56.0 * 0.5)
self.updateContainerContainingNodeTransform(transition: transition)
}
private func updateContainerContainingNodeTransform(transition: ContainedViewLayoutTransition) {
guard let layout = self.validLayout else {
return
}
let controlsHeight = OverlayPlayerControlsNode.heightForLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, maxHeight: maxHeight, isExpanded: self.controlsNode.isExpanded, hasSectionHeader: true, savedMusic: self.isSaved)
if case .regular = layout.metrics.widthClass {
transition.updateTransform(layer: self.containerContainingNode.layer, transform: CATransform3DIdentity)
transition.updateCornerRadius(node: self.containerContainingNode, cornerRadius: 0.0)
return
}
let listTopInset = layoutTopInset + controlsHeight
let expandDistance: CGFloat = 78.0
let topOffsetFraction = max(0.0, min(1.0, self.historyContentOffset / expandDistance))
let listNodeSize = CGSize(width: layout.size.width, height: layout.size.height - listTopInset)
let minScale: CGFloat = (layout.size.width - 6.0 * 2.0) / layout.size.width
let minScaledTranslation: CGFloat = (layout.size.height - layout.size.height * minScale) * 0.5 - 6.0
insets.top = max(0.0, listNodeSize.height - floor(56.0 * 3.5))
let scale = minScale * (1.0 - topOffsetFraction) + topOffsetFraction
let scaledTranslation = minScaledTranslation * (1.0 - topOffsetFraction)
var containerTransform = CATransform3DIdentity
containerTransform = CATransform3DTranslate(containerTransform, 0.0, scaledTranslation, 0.0)
containerTransform = CATransform3DScale(containerTransform, scale, scale, scale)
transition.updateTransform(layer: self.containerContainingNode.layer, transform: containerTransform)
transition.updateCornerRadius(node: self.containerContainingNode, cornerRadius: layout.deviceMetrics.screenCornerRadius)
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
self.validLayout = layout
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
transition.updateFrameAsPositionAndBounds(node: self.containerContainingNode, frame: CGRect(origin: CGPoint(), size: layout.size))
transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(), size: layout.size))
let controlsHeight = self.controlsNode.updateLayout(width: layout.size.width, leftInset: 0.0, rightInset: 0.0, bottomInset: layout.intrinsicInsets.bottom, maxHeight: layout.size.height, savedMusic: self.isSaved, transition: transition)
let controlsFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - controlsHeight), size: CGSize(width: layout.size.width, height: controlsHeight))
transition.updateFrame(node: self.controlsNode, frame: controlsFrame)
let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
var insets = UIEdgeInsets()
insets.left = 16.0
insets.right = 16.0
insets.bottom = 0.0
let headerHeight: CGFloat = 78.0
let listTopInset = layoutTopInset + headerHeight
let listNodeSize = CGSize(width: layout.size.width, height: layout.size.height - listTopInset - controlsHeight)
insets.top = max(0.0, listNodeSize.height - floor(62.0 * 3.5))
var itemOffsetInsets = insets
if let playlistLocation = self.playlistLocation as? PeerMessagesPlaylistLocation, case let .savedMusic(_, _, canReorder) = playlistLocation, canReorder {
@ -718,7 +833,6 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
itemOffsetInsets.bottom = 0.0
insets = itemOffsetInsets
}
transition.updateFrame(node: self.historyNode, frame: CGRect(origin: CGPoint(x: 0.0, y: listTopInset), size: listNodeSize))
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
@ -728,6 +842,101 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: listNodeSize, insets: insets, duration: 0.0, curve: .Default(duration: nil))
replacementHistoryNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets)
}
let leftControlItems: [GlassControlGroupComponent.Item] = [
GlassControlGroupComponent.Item(
id: AnyHashable("close"),
content: .icon("Navigation/Close"),
action: { [weak self] in
guard let self else {
return
}
self.requestDismiss()
}
)
]
var rightControlItems: [GlassControlGroupComponent.Item] = []
if let playlistLocation = self.playlistLocation as? PeerMessagesPlaylistLocation, case let .savedMusic(savedMusicContext, _, _) = playlistLocation, savedMusicContext.peerId == self.context.account.peerId {
rightControlItems.append(
GlassControlGroupComponent.Item(
id: AnyHashable("add"),
content: .icon("Navigation/Add"),
action: { [weak self] in
guard let self else {
return
}
self.requestAdd()
}
)
)
}
rightControlItems.append(
GlassControlGroupComponent.Item(
id: AnyHashable("search"),
content: .icon("Navigation/Search"),
action: {
}
)
)
let headerInset: CGFloat = 16.0
let headerButtonsSize = self.headerButtons.update(
transition: ComponentTransition(transition),
component: AnyComponent(
GlassControlPanelComponent(
theme: self.presentationData.theme,
leftItem: GlassControlPanelComponent.Item(
items: leftControlItems,
background: .panel
),
centralItem: nil,
rightItem: GlassControlPanelComponent.Item(
items: rightControlItems,
background: .panel
),
centerAlignmentIfPossible: true,
isDark: self.presentationData.theme.overallDarkAppearance
)
),
environment: {},
containerSize: CGSize(width: layout.size.width - headerInset * 2.0, height: 44.0)
)
if let headerButtonsView = self.headerButtons.view {
if headerButtonsView.superview == nil {
self.historyFrameNode.view.addSubview(headerButtonsView)
}
headerButtonsView.frame = CGRect(origin: CGPoint(x: headerInset, y: headerInset), size: headerButtonsSize)
}
//TODO:localize
let titleString: String
if let peer = self.peer {
if peer.id == self.context.account.peerId {
titleString = "Your Playlist"
} else {
titleString = "\(peer.compactDisplayTitle)'s Playlist"
}
} else {
titleString = "Playlist"
}
let titleSize = self.title.update(
transition: ComponentTransition(transition),
component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: titleString, font: Font.semibold(17.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)))
),
environment: {},
containerSize: CGSize(width: layout.size.width - 70.0 * 2.0, height: headerHeight)
)
if let titleView = self.title.view {
if titleView.superview == nil {
self.historyFrameNode.view.addSubview(titleView)
}
titleView.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: 29.0), size: titleSize)
}
self.updateHistoryContentOffset(self.historyNode.visibleContentOffset(), transition: transition)
}
func animateIn() {
@ -747,23 +956,10 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.controlsNode.bounds.contains(self.view.convert(point, to: self.controlsNode.view)) {
let controlsHitTest = self.controlsNode.view.hitTest(self.view.convert(point, to: self.controlsNode.view), with: event)
if controlsHitTest == nil {
if self.controlsNode.frame.maxY > self.historyNode.frame.minY {
return self.historyNode.view
}
}
}
let result = super.hitTest(point, with: event)
if !self.bounds.contains(point) {
return nil
}
if point.y < self.controlsNode.frame.minY {
return self.dimNode.view
}
return result
}
@ -839,41 +1035,35 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
}
private func updateFloatingHeaderOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
guard let validLayout = self.validLayout else {
guard let layout = self.validLayout else {
return
}
self.floatingHeaderOffset = offset
let layoutTopInset: CGFloat = max(validLayout.statusBarHeight ?? 0.0, validLayout.safeInsets.top)
let sideInset: CGFloat = 16.0
let headerHeight: CGFloat = 78.0
let maxHeight = validLayout.size.height - layoutTopInset - floor(56.0 * 0.5)
let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
let rawControlsOffset = offset + layoutTopInset
let backgroundOffset = max(layoutTopInset, rawControlsOffset)
let controlsHeight = self.controlsNode.updateLayout(width: validLayout.size.width, leftInset: validLayout.safeInsets.left, rightInset: validLayout.safeInsets.right, maxHeight: maxHeight, hasSectionHeader: true, savedMusic: self.isSaved, transition: transition)
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: backgroundOffset + headerHeight), size: CGSize(width: layout.size.width, height: layout.size.height))
let listTopInset = layoutTopInset + controlsHeight
let frameFrame = CGRect(origin: CGPoint(x: 0.0, y: backgroundOffset), size: CGSize(width: layout.size.width, height: layout.size.height))
let topOverlayFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: headerHeight))
let leftOverlayFrame = CGRect(origin: CGPoint(x: 0.0, y: headerHeight), size: CGSize(width: sideInset, height: layout.size.height))
let rightOverlayFrame = CGRect(origin: CGPoint(x: layout.size.width - sideInset, y: headerHeight), size: CGSize(width: sideInset, height: layout.size.height))
let rawControlsOffset = offset + listTopInset - controlsHeight
let controlsOffset = max(layoutTopInset, rawControlsOffset)
let isOverscrolling = rawControlsOffset <= layoutTopInset
let controlsFrame = CGRect(origin: CGPoint(x: 0.0, y: controlsOffset), size: CGSize(width: validLayout.size.width, height: controlsHeight))
let previousFrame = self.controlsNode.frame
if !controlsFrame.equalTo(previousFrame) {
self.controlsNode.frame = controlsFrame
let positionDelta = CGPoint(x: controlsFrame.minX - previousFrame.minX, y: controlsFrame.minY - previousFrame.minY)
transition.animateOffsetAdditive(node: self.controlsNode, offset: positionDelta.y)
self.historyFrameNode.frame = frameFrame
self.historyFrameTopOverlayNode.frame = topOverlayFrame
self.historyFrameLeftOverlayNode.frame = leftOverlayFrame
self.historyFrameRightOverlayNode.frame = rightOverlayFrame
if let image = self.historyFrameTopMaskNode.image {
self.historyFrameTopMaskNode.frame = CGRect(origin: CGPoint(x: sideInset, y: topOverlayFrame.maxY), size: CGSize(width: layout.size.width - sideInset * 2.0, height: image.size.height))
}
transition.updateAlpha(node: self.controlsNode.separatorNode, alpha: isOverscrolling ? 1.0 : 0.0)
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: controlsFrame.maxY), size: CGSize(width: validLayout.size.width, height: validLayout.size.height))
let previousBackgroundFrame = self.historyBackgroundNode.frame
if !backgroundFrame.equalTo(previousBackgroundFrame) {
self.historyBackgroundNode.frame = backgroundFrame
self.historyBackgroundContentNode.frame = CGRect(origin: CGPoint(), size: backgroundFrame.size)
@ -896,7 +1086,22 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
}
let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil)
let historyNode = ChatHistoryListNodeImpl(context: self.context, updatedPresentationData: (self.context.sharedContext.currentPresentationData.with({ $0 }), self.context.sharedContext.presentationData), chatLocation: self.chatLocation, chatLocationContextHolder: chatLocationContextHolder, adMessagesContext: nil, tag: .tag(tagMask), source: self.source, subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(reversed: self.currentIsReversed, reverseGroups: !self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch), isChatPreview: false, messageTransitionNode: { return nil })
let historyNode = ChatHistoryListNodeImpl(
context: self.context,
updatedPresentationData: (self.context.sharedContext.currentPresentationData.with({ $0 }), self.context.sharedContext.presentationData),
systemStyle: .glass,
chatLocation: self.chatLocation,
chatLocationContextHolder: chatLocationContextHolder,
adMessagesContext: nil,
tag: .tag(tagMask),
source: self.source,
subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false),
controllerInteraction: self.controllerInteraction,
selectedMessages: .single(nil),
mode: .list(reversed: self.currentIsReversed, reverseGroups: !self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch),
isChatPreview: false,
messageTransitionNode: { return nil
})
historyNode.clipsToBounds = true
historyNode.preloadPages = true
historyNode.stackFromBottom = true
@ -909,19 +1114,14 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
var insets = UIEdgeInsets()
insets.left = layout.safeInsets.left
insets.right = layout.safeInsets.right
insets.bottom = layout.intrinsicInsets.bottom
let maxHeight = layout.size.height - layoutTopInset - floor(56.0 * 0.5)
let controlsHeight = OverlayPlayerControlsNode.heightForLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, maxHeight: maxHeight, isExpanded: self.controlsNode.isExpanded, hasSectionHeader: true, savedMusic: self.isSaved)
let listTopInset = layoutTopInset + controlsHeight
insets.left = 16.0
insets.right = 16.0
insets.bottom = 0.0
let listTopInset = layoutTopInset
let listNodeSize = CGSize(width: layout.size.width, height: layout.size.height - listTopInset)
insets.top = max(0.0, listNodeSize.height - floor(56.0 * 3.5))
insets.top = max(0.0, listNodeSize.height - floor(62.0 * 3.5))
historyNode.frame = CGRect(origin: CGPoint(x: 0.0, y: listTopInset), size: listNodeSize)
@ -944,19 +1144,14 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
self.contentNode.insertSubnode(replacementHistoryNode, belowSubnode: self.historyNode)
self.historyNode = replacementHistoryNode
self.setupReordering()
self.updateHistoryContentOffset(replacementHistoryNode.visibleContentOffset(), transition: .immediate)
if let validLayout = self.validLayout, let offset = self.replacementHistoryNodeFloatingOffset, let previousOffset = self.floatingHeaderOffset {
let offsetDelta = offset - previousOffset
let layoutTopInset: CGFloat = max(validLayout.statusBarHeight ?? 0.0, validLayout.safeInsets.top)
let maxHeight = validLayout.size.height - layoutTopInset - floor(56.0 * 0.5)
let controlsHeight = OverlayPlayerControlsNode.heightForLayout(width: validLayout.size.width, leftInset: validLayout.safeInsets.left, rightInset: validLayout.safeInsets.right, maxHeight: maxHeight, isExpanded: self.controlsNode.isExpanded, hasSectionHeader: true, savedMusic: self.isSaved)
let listTopInset = layoutTopInset + controlsHeight
let controlsBottomOffset = max(layoutTopInset, offset + listTopInset)
let controlsBottomOffset = max(layoutTopInset, offset)
let previousBackgroundNode = ASDisplayNode()
previousBackgroundNode.isLayerBacked = true
@ -984,6 +1179,9 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
strongSelf.updateFloatingHeaderOffset(offset: offset, transition: transition)
}
}
self.historyNode.contentPositionChanged = { [weak self] offset in
self?.updateHistoryContentOffset(offset, transition: .immediate)
}
self.historyNode.endedInteractiveDragging = { [weak self] _ in
guard let strongSelf = self else {
@ -1007,19 +1205,14 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
var insets = UIEdgeInsets()
insets.left = layout.safeInsets.left
insets.right = layout.safeInsets.right
insets.bottom = layout.intrinsicInsets.bottom
let maxHeight = layout.size.height - layoutTopInset - floor(56.0 * 0.5)
let controlsHeight = OverlayPlayerControlsNode.heightForLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, maxHeight: maxHeight, isExpanded: self.controlsNode.isExpanded, hasSectionHeader: true, savedMusic: self.isSaved)
let listTopInset = layoutTopInset + controlsHeight
insets.left = 16.0
insets.right = 16.0
insets.bottom = 0.0
let listTopInset = layoutTopInset
let listNodeSize = CGSize(width: layout.size.width, height: layout.size.height - listTopInset)
insets.top = max(0.0, listNodeSize.height - floor(56.0 * 3.5))
insets.top = max(0.0, listNodeSize.height - floor(62.0 * 3.5))
var itemOffsetInsets = insets
if let playlistLocation = self.playlistLocation as? PeerMessagesPlaylistLocation, case let .savedMusic(_, _, canReorder) = playlistLocation, canReorder {
@ -1035,6 +1228,8 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
self.historyNode.recursivelyEnsureDisplaySynchronously(true)
}
self.updateHistoryContentOffset(self.historyNode.visibleContentOffset(), transition: .immediate)
}
}
@ -1170,16 +1365,6 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
)
}
// items.append(
// .action(ContextMenuActionItem(text: presentationData.strings.MediaPlayer_ContextMenu_Forward, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
// f(.default)
//
// if let _ = self {
//
// }
// }))
// )
var canDelete = false
if case .custom = self.source {
if self.savedIds?.contains(file.fileId.id) == true {

View file

@ -32,9 +32,9 @@ private func normalizeValue(_ value: CGFloat) -> CGFloat {
private func generateBackground(theme: PresentationTheme) -> UIImage? {
return generateImage(CGSize(width: 20.0, height: 10.0 + 8.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setShadow(offset: CGSize(width: 0.0, height: -4.0), blur: 20.0, color: UIColor(white: 0.0, alpha: 0.3).cgColor)
context.setShadow(offset: CGSize(width: 0.0, height: -4.0), blur: 30.0, color: UIColor(white: 0.0, alpha: 0.2).cgColor)
context.setFillColor(theme.list.plainBackgroundColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: 8.0), size: CGSize(width: 20.0, height: 20.0)))
context.fill(CGRect(origin: CGPoint(x: 0.0, y: 8.0), size: CGSize(width: 20.0, height: 20.0)))
})?.stretchableImage(withLeftCapWidth: 10, topCapHeight: 10 + 8)
}
@ -111,8 +111,8 @@ private func timestampLabelWidthForDuration(_ timestamp: Double) -> CGFloat {
return size.width
}
private let titleFont = Font.semibold(19.0)
private let descriptionFont = Font.regular(17.0)
private let titleFont = Font.semibold(17.0)
private let descriptionFont = Font.regular(15.0)
private func stringsForDisplayData(_ data: SharedMediaPlaybackDisplayData?, presentationData: PresentationData) -> (NSAttributedString?, NSAttributedString?, Bool, NSAttributedString?) {
var titleString: NSAttributedString?
@ -135,13 +135,13 @@ private func stringsForDisplayData(_ data: SharedMediaPlaybackDisplayData?, pres
}
titleString = NSAttributedString(string: titleText, font: titleFont, textColor: presentationData.theme.list.itemPrimaryTextColor)
descriptionString = NSAttributedString(string: subtitleText, font: descriptionFont, textColor: hasArtist ? presentationData.theme.list.itemAccentColor : presentationData.theme.list.itemSecondaryTextColor)
descriptionString = NSAttributedString(string: subtitleText, font: descriptionFont, textColor: presentationData.theme.list.itemSecondaryTextColor)
}
return (titleString, descriptionString, hasArtist, captionString)
}
final class OverlayPlayerControlsNode: ASDisplayNode {
final class OverlayAudioPlayerControlsNode: ASDisplayNode {
private let accountManager: AccountManager<TelegramAccountManagerTypes>
private let account: Account
private let engine: TelegramEngine
@ -162,7 +162,6 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
private let artistButton: HighlightTrackingButtonNode
private var profileAudio: ComponentView<Empty>?
private var cachedChevronImage: (UIImage, PresentationTheme)?
private let scrubberNode: MediaPlayerScrubbingNode
private let leftDurationLabel: MediaPlayerTimeTextNode
@ -190,10 +189,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
private let rateButton: AudioRateButton
let separatorNode: ASDisplayNode
private let sectionBackground: ASDisplayNode
private let sectionTitle: ComponentView<Empty>
var isExpanded = false
var updateIsExpanded: (() -> Void)?
@ -219,9 +215,6 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
private var statusDisposable: Disposable?
private var chapterDisposable: Disposable?
private var peerName: String?
private var peerDisposable: Disposable?
private var previousCaption: NSAttributedString?
private var chaptersPromise = ValuePromise<[MediaPlayerScrubbingChapter]>([])
private var currentChapter: MediaPlayerScrubbingChapter?
@ -236,7 +229,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
private var currentDuration: Double = 0.0
private var currentPosition: Double = 0.0
private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, maxHeight: CGFloat, hasSectionHeader: Bool, savedMusic: Bool?)?
private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, maxHeight: CGFloat, savedMusic: Bool?)?
init(account: Account, engine: TelegramEngine, accountManager: AccountManager<TelegramAccountManagerTypes>, presentationData: PresentationData, status: Signal<(Account, SharedMediaPlayerItemPlaybackStateOrLoading, MediaManagerPlayerType)?, NoError>, chatLocation: ChatLocation, source: ChatHistoryListSource) {
self.accountManager = accountManager
@ -271,13 +264,13 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
self.artistButton = HighlightTrackingButtonNode()
self.shareNode = HighlightableButtonNode()
self.shareNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/Share"), color: presentationData.theme.list.itemAccentColor), for: [])
self.shareNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/Share"), color: presentationData.theme.list.itemPrimaryTextColor), for: [])
self.scrubberNode = MediaPlayerScrubbingNode(content: .standard(lineHeight: 3.0, lineCap: .round, scrubberHandle: .circle, backgroundColor: presentationData.theme.list.controlSecondaryColor, foregroundColor: presentationData.theme.list.itemAccentColor, bufferingColor: presentationData.theme.list.itemAccentColor.withAlphaComponent(0.4), chapters: []))
self.leftDurationLabel = MediaPlayerTimeTextNode(textColor: presentationData.theme.list.itemSecondaryTextColor)
self.scrubberNode = MediaPlayerScrubbingNode(content: .standard(lineHeight: 7.0, lineCap: .round, scrubberHandle: .none, backgroundColor: presentationData.theme.list.controlSecondaryColor, foregroundColor: presentationData.theme.list.itemPrimaryTextColor, bufferingColor: presentationData.theme.list.itemPrimaryTextColor.withAlphaComponent(0.4), chapters: []))
self.leftDurationLabel = MediaPlayerTimeTextNode(textColor: presentationData.theme.list.itemSecondaryTextColor, textFont: Font.medium(12.0))
self.leftDurationLabel.displaysAsynchronously = false
self.leftDurationLabel.keepPreviousValueOnEmptyState = true
self.rightDurationLabel = MediaPlayerTimeTextNode(textColor: presentationData.theme.list.itemSecondaryTextColor)
self.rightDurationLabel = MediaPlayerTimeTextNode(textColor: presentationData.theme.list.itemSecondaryTextColor, textFont: Font.medium(12.0))
self.rightDurationLabel.displaysAsynchronously = false
self.rightDurationLabel.mode = .reversed
self.rightDurationLabel.alignment = .right
@ -316,16 +309,11 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
self.separatorNode.isLayerBacked = true
self.separatorNode.backgroundColor = presentationData.theme.list.itemPlainSeparatorColor
self.sectionBackground = ASDisplayNode()
self.sectionBackground.backgroundColor = presentationData.theme.chatList.sectionHeaderFillColor
self.sectionTitle = ComponentView<Empty>()
super.init()
self.addSubnode(self.backgroundNode)
self.addSubnode(self.collapseNode)
//self.addSubnode(self.collapseNode)
self.addSubnode(self.albumArtNode)
//self.addSubnode(self.titleNode)
@ -346,8 +334,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
self.addSubnode(self.playPauseButton)
self.playPauseButton.addSubnode(self.playPauseIconNode)
self.addSubnode(self.sectionBackground)
self.addSubnode(self.separatorNode)
//self.addSubnode(self.separatorNode)
let accountId = account.id
let delayedStatus = status
@ -375,33 +362,31 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
self.leftDurationLabel.status = mappedStatus
self.rightDurationLabel.status = mappedStatus
self.scrubbingDisposable = (self.scrubberNode.scrubbingPosition
|> deliverOnMainQueue).startStrict(next: { [weak self] value in
guard let strongSelf = self else {
return
}
let leftDurationLabelPushed: Bool
let rightDurationLabelPushed: Bool
let infoNodePushed: Bool
if let value = value {
leftDurationLabelPushed = value < 0.16
rightDurationLabelPushed = value > (strongSelf.rateButton.isHidden ? 0.84 : 0.74)
infoNodePushed = value >= 0.16 && value <= 0.84
} else {
leftDurationLabelPushed = false
rightDurationLabelPushed = false
infoNodePushed = false
}
if leftDurationLabelPushed != strongSelf.leftDurationLabelPushed || rightDurationLabelPushed != strongSelf.rightDurationLabelPushed || infoNodePushed != strongSelf.infoNodePushed {
strongSelf.leftDurationLabelPushed = leftDurationLabelPushed
strongSelf.rightDurationLabelPushed = rightDurationLabelPushed
strongSelf.infoNodePushed = infoNodePushed
if let layout = strongSelf.validLayout {
let _ = strongSelf.updateLayout(width: layout.0, leftInset: layout.1, rightInset: layout.2, maxHeight: layout.3, hasSectionHeader: layout.4, savedMusic: layout.5, transition: .animated(duration: 0.35, curve: .spring))
}
}
})
// self.scrubbingDisposable = (self.scrubberNode.scrubbingPosition
// |> deliverOnMainQueue).startStrict(next: { [weak self] value in
// guard let strongSelf = self else {
// return
// }
// let leftDurationLabelPushed: Bool
// let rightDurationLabelPushed: Bool
// let infoNodePushed: Bool
// if let value = value {
// leftDurationLabelPushed = value < 0.16
// rightDurationLabelPushed = value > (strongSelf.rateButton.isHidden ? 0.84 : 0.74)
// infoNodePushed = value >= 0.16 && value <= 0.84
// } else {
// leftDurationLabelPushed = false
// rightDurationLabelPushed = false
// infoNodePushed = false
// }
// if leftDurationLabelPushed != strongSelf.leftDurationLabelPushed || rightDurationLabelPushed != strongSelf.rightDurationLabelPushed || infoNodePushed != strongSelf.infoNodePushed {
// strongSelf.leftDurationLabelPushed = leftDurationLabelPushed
// strongSelf.rightDurationLabelPushed = rightDurationLabelPushed
// strongSelf.infoNodePushed = infoNodePushed
//
// strongSelf.requestLayout(transition: .animated(duration: 0.35, curve: .spring))
// }
// })
self.statusDisposable = combineLatest(
queue: Queue.mainQueue(),
@ -478,9 +463,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
let duration = value.status.duration
if duration != strongSelf.currentDuration && !duration.isZero {
strongSelf.currentDuration = duration
if let layout = strongSelf.validLayout {
let _ = strongSelf.updateLayout(width: layout.0, leftInset: layout.1, rightInset: layout.2, maxHeight: layout.3, hasSectionHeader: layout.4, savedMusic: layout.5, transition: .immediate)
}
strongSelf.requestLayout(transition: .immediate)
}
strongSelf.rateButton.isHidden = rateButtonIsHidden
@ -527,20 +510,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
strongSelf.requestLayout?(.animated(duration: 0.2, curve: .easeInOut))
}
})
if case .custom = self.source, case let .peer(peerId) = self.chatLocation, peerId != account.peerId {
self.peerDisposable = (engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let self, let peer else {
return
}
self.peerName = peer.compactDisplayTitle
if let layout = self.validLayout {
let _ = self.updateLayout(width: layout.0, leftInset: layout.1, rightInset: layout.2, maxHeight: layout.3, hasSectionHeader: layout.4, savedMusic: layout.5, transition: .immediate)
}
})
}
self.chapterDisposable = combineLatest(queue: Queue.mainQueue(), mappedStatus, self.chaptersPromise.get())
.startStrict(next: { [weak self] status, chapters in
if let strongSelf = self, status.duration > 1.0, chapters.count > 0 {
@ -585,10 +555,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
}
}
strongSelf.infoNode.attributedText = NSAttributedString(string: chapter.title, font: Font.regular(13.0), textColor: strongSelf.presentationData.theme.list.itemSecondaryTextColor)
if let layout = strongSelf.validLayout {
let _ = strongSelf.updateLayout(width: layout.0, leftInset: layout.1, rightInset: layout.2, maxHeight: layout.3, hasSectionHeader: layout.4, savedMusic: layout.5, transition: .immediate)
}
strongSelf.requestLayout(transition: .immediate)
}
}
})
@ -632,7 +599,6 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
self.statusDisposable?.dispose()
self.chapterDisposable?.dispose()
self.scrubbingDisposable?.dispose()
self.peerDisposable?.dispose()
}
override func didLoad() {
@ -740,8 +706,8 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
self.backgroundNode.image = generateBackground(theme: presentationData.theme)
self.collapseNode.setImage(generateCollapseIcon(theme: presentationData.theme), for: [])
self.shareNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/Share"), color: presentationData.theme.list.itemAccentColor), for: [])
self.scrubberNode.updateColors(backgroundColor: presentationData.theme.list.controlSecondaryColor, foregroundColor: presentationData.theme.list.itemAccentColor)
self.shareNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/Share"), color: presentationData.theme.list.itemPrimaryTextColor), for: [])
self.scrubberNode.updateColors(backgroundColor: presentationData.theme.list.controlSecondaryColor, foregroundColor: presentationData.theme.list.itemPrimaryTextColor)
self.leftDurationLabel.textColor = presentationData.theme.list.itemSecondaryTextColor
self.rightDurationLabel.textColor = presentationData.theme.list.itemSecondaryTextColor
self.backwardButton.icon = generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/Previous"), color: presentationData.theme.list.itemPrimaryTextColor)
@ -759,22 +725,23 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
self.updateRateButton(rate)
}
self.separatorNode.backgroundColor = presentationData.theme.list.itemPlainSeparatorColor
self.sectionBackground.backgroundColor = presentationData.theme.chatList.sectionHeaderFillColor
self.updateLabels(transition: .immediate)
}
private func updateLabels(transition: ContainedViewLayoutTransition) {
guard let (width, leftInset, rightInset, maxHeight, _, _) = self.validLayout else {
guard let (width, leftInset, rightInset, bottomInset, maxHeight, _) = self.validLayout else {
return
}
let panelHeight = OverlayPlayerControlsNode.heightForLayout(width: width, leftInset: leftInset, rightInset: rightInset, maxHeight: maxHeight, isExpanded: self.isExpanded, hasSectionHeader: false, savedMusic: nil)
let panelHeight = OverlayAudioPlayerControlsNode.heightForLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, maxHeight: maxHeight, isExpanded: self.isExpanded, savedMusic: nil)
let sideInset: CGFloat = 20.0
let infoLabelsLeftInset: CGFloat = 60.0
let infoLabelsLeftInset: CGFloat = 53.0
let infoLabelsRightInset: CGFloat = 32.0
let infoVerticalOrigin: CGFloat = panelHeight - OverlayPlayerControlsNode.basePanelHeight + 36.0
let infoVerticalOrigin: CGFloat = panelHeight - OverlayAudioPlayerControlsNode.basePanelHeight + 21.0
let (titleString, descriptionString, hasArtist, caption) = stringsForDisplayData(self.displayData, presentationData: self.presentationData)
@ -782,7 +749,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
self.previousCaption = caption
let chapters = caption.flatMap { parseMediaPlayerChapters($0) } ?? []
self.chaptersPromise.set(chapters)
self.scrubberNode.updateContent(.standard(lineHeight: 3.0, lineCap: .round, scrubberHandle: .circle, backgroundColor: self.presentationData.theme.list.controlSecondaryColor, foregroundColor: self.presentationData.theme.list.itemAccentColor, bufferingColor: self.presentationData.theme.list.itemAccentColor.withAlphaComponent(0.4), chapters: chapters))
self.scrubberNode.updateContent(.standard(lineHeight: 7.0, lineCap: .round, scrubberHandle: .none, backgroundColor: self.presentationData.theme.list.controlSecondaryColor, foregroundColor: self.presentationData.theme.list.itemPrimaryTextColor, bufferingColor: self.presentationData.theme.list.itemPrimaryTextColor.withAlphaComponent(0.4), chapters: chapters))
}
self.artistButton.isUserInteractionEnabled = hasArtist
@ -811,7 +778,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: self.isExpanded ? floor((width - titleLayout.size.width) / 2.0) : (leftInset + sideInset + infoLabelsLeftInset), y: infoVerticalOrigin + 1.0), size: titleLayout.size))
let _ = titleApply()
let descriptionFrame = CGRect(origin: CGPoint(x: self.isExpanded ? floor((width - descriptionLayout.size.width) / 2.0) : (leftInset + sideInset + infoLabelsLeftInset), y: infoVerticalOrigin + 25.0), size: descriptionLayout.size)
let descriptionFrame = CGRect(origin: CGPoint(x: self.isExpanded ? floor((width - descriptionLayout.size.width) / 2.0) : (leftInset + sideInset + infoLabelsLeftInset), y: infoVerticalOrigin + 24.0), size: descriptionLayout.size)
transition.updateFrame(node: self.descriptionNode, frame: descriptionFrame)
let _ = descriptionApply()
@ -874,51 +841,54 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
self.rateButton.setContent(.image(optionsRateImage(rate: rate.stringValue.uppercased(), color: self.presentationData.theme.list.itemSecondaryTextColor)))
}
static let basePanelHeight: CGFloat = 220.0
static let sectionHeaderHeight: CGFloat = 28.0
static let basePanelHeight: CGFloat = 164.0
static func heightForLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, maxHeight: CGFloat, isExpanded: Bool, hasSectionHeader: Bool, savedMusic: Bool?) -> CGFloat {
var panelHeight: CGFloat = OverlayPlayerControlsNode.basePanelHeight
static func heightForLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, maxHeight: CGFloat, isExpanded: Bool, savedMusic: Bool?) -> CGFloat {
var panelHeight: CGFloat = OverlayAudioPlayerControlsNode.basePanelHeight
if isExpanded {
let sideInset: CGFloat = 20.0
panelHeight += width - leftInset - rightInset - sideInset * 2.0 + 24.0
}
var height = min(panelHeight, maxHeight)
if hasSectionHeader {
height += sectionHeaderHeight
}
if let savedMusic {
height += savedMusic ? 38.0 : 70.0
if let _ = savedMusic {
height += 70.0
let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: bottomInset, innerDiameter: 52.0, sideInset: 30.0)
height += buttonInsets.bottom
}
return height
}
func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, maxHeight: CGFloat, hasSectionHeader: Bool, savedMusic: Bool?, transition: ContainedViewLayoutTransition) -> CGFloat {
let previousSavedMusic = self.validLayout?.savedMusic
self.validLayout = (width, leftInset, rightInset, maxHeight, hasSectionHeader, savedMusic)
func requestLayout(transition: ContainedViewLayoutTransition) {
guard let layout = self.validLayout else {
return
}
let _ = self.updateLayout(width: layout.width, leftInset: layout.leftInset, rightInset: layout.rightInset, bottomInset: layout.bottomInset, maxHeight: layout.maxHeight, savedMusic: layout.savedMusic, transition: transition)
}
let finalPanelHeight = OverlayPlayerControlsNode.heightForLayout(width: width, leftInset: leftInset, rightInset: rightInset, maxHeight: maxHeight, isExpanded: self.isExpanded, hasSectionHeader: hasSectionHeader, savedMusic: savedMusic)
let panelHeight = OverlayPlayerControlsNode.heightForLayout(width: width, leftInset: leftInset, rightInset: rightInset, maxHeight: maxHeight, isExpanded: self.isExpanded, hasSectionHeader: false, savedMusic: nil)
func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, maxHeight: CGFloat, savedMusic: Bool?, transition: ContainedViewLayoutTransition) -> CGFloat {
self.validLayout = (width, leftInset, rightInset, bottomInset, maxHeight, savedMusic)
let finalPanelHeight = OverlayAudioPlayerControlsNode.heightForLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, maxHeight: maxHeight, isExpanded: self.isExpanded, savedMusic: savedMusic)
let panelHeight = OverlayAudioPlayerControlsNode.heightForLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, maxHeight: maxHeight, isExpanded: self.isExpanded, savedMusic: nil)
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelHeight), size: CGSize(width: width, height: UIScreenPixel)))
transition.updateFrame(node: self.collapseNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 2.0), size: CGSize(width: width, height: 30.0)))
let sideInset: CGFloat = 20.0
let sideButtonsInset: CGFloat = sideInset + 36.0
let sideInset: CGFloat = 16.0
let sideButtonsInset: CGFloat = sideInset + 60.0
let infoVerticalOrigin: CGFloat = panelHeight - OverlayPlayerControlsNode.basePanelHeight + 36.0
let infoVerticalOrigin: CGFloat = panelHeight - OverlayAudioPlayerControlsNode.basePanelHeight + 21.0
self.updateLabels(transition: transition)
transition.updateFrame(node: self.shareNode, frame: CGRect(origin: CGPoint(x: width - rightInset - sideInset - 32.0, y: infoVerticalOrigin + 2.0), size: CGSize(width: 42.0, height: 42.0)))
transition.updateFrame(node: self.shareNode, frame: CGRect(origin: CGPoint(x: width - rightInset - sideInset - 34.0, y: infoVerticalOrigin), size: CGSize(width: 42.0, height: 42.0)))
let albumArtSize = CGSize(width: 48.0, height: 48.0)
let albumArtSize = CGSize(width: 46.0, height: 46.0)
let makeAlbumArtLayout = self.albumArtNode.asyncLayout()
let applyAlbumArt = makeAlbumArtLayout(TransformImageArguments(corners: ImageCorners(radius: 10.0), imageSize: albumArtSize, boundingSize: albumArtSize, intrinsicInsets: UIEdgeInsets()))
applyAlbumArt()
let albumArtFrame = CGRect(origin: CGPoint(x: leftInset + sideInset, y: infoVerticalOrigin - 1.0), size: albumArtSize)
let albumArtFrame = CGRect(origin: CGPoint(x: leftInset + sideInset, y: infoVerticalOrigin - 3.0), size: albumArtSize)
let previousAlbumArtNodeFrame = self.albumArtNode.frame
transition.updateFrame(node: self.albumArtNode, frame: albumArtFrame)
@ -930,9 +900,6 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
} else {
animateIn = true
largeAlbumArtNode = TransformImageNode()
if self.isNodeLoaded {
largeAlbumArtNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.albumArtTap(_:))))
}
self.largeAlbumArtNode = largeAlbumArtNode
self.addSubnode(largeAlbumArtNode)
if self.currentAlbumArtInitialized {
@ -940,7 +907,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
}
}
let albumArtHeight = max(1.0, panelHeight - OverlayPlayerControlsNode.basePanelHeight - 24.0)
let albumArtHeight = max(1.0, panelHeight - OverlayAudioPlayerControlsNode.basePanelHeight - 24.0)
let largeAlbumArtSize = CGSize(width: albumArtHeight, height: albumArtHeight)
let makeLargeAlbumArtLayout = largeAlbumArtNode.asyncLayout()
@ -988,15 +955,16 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
}
}
let scrubberVerticalOrigin: CGFloat = infoVerticalOrigin + 64.0
let scrubberVerticalOrigin: CGFloat = infoVerticalOrigin + 58.0
let scrubberInset: CGFloat = 9.0
transition.updateFrame(node: self.scrubberNode, frame: CGRect(origin: CGPoint(x: leftInset + sideInset, y: scrubberVerticalOrigin - 8.0), size: CGSize(width: width - sideInset * 2.0 - leftInset - rightInset, height: 10.0 + 8.0 * 2.0)))
transition.updateFrame(node: self.scrubberNode, frame: CGRect(origin: CGPoint(x: sideInset + scrubberInset, y: scrubberVerticalOrigin - 8.0), size: CGSize(width: width - (sideInset + scrubberInset) * 2.0, height: 10.0 + 8.0 * 2.0)))
let leftLabelVerticalOffset: CGFloat = self.leftDurationLabelPushed ? 6.0 : 0.0
transition.updateFrame(node: self.leftDurationLabel, frame: CGRect(origin: CGPoint(x: leftInset + sideInset, y: scrubberVerticalOrigin + 14.0 + leftLabelVerticalOffset), size: CGSize(width: 100.0, height: 20.0)))
transition.updateFrame(node: self.leftDurationLabel, frame: CGRect(origin: CGPoint(x: leftInset + sideInset + scrubberInset, y: scrubberVerticalOrigin + 19.0 + leftLabelVerticalOffset), size: CGSize(width: 100.0, height: 20.0)))
let rightLabelVerticalOffset: CGFloat = self.rightDurationLabelPushed ? 6.0 : 0.0
transition.updateFrame(node: self.rightDurationLabel, frame: CGRect(origin: CGPoint(x: width - sideInset - rightInset - 100.0, y: scrubberVerticalOrigin + 14.0 + rightLabelVerticalOffset), size: CGSize(width: 100.0, height: 20.0)))
transition.updateFrame(node: self.rightDurationLabel, frame: CGRect(origin: CGPoint(x: width - sideInset - rightInset - 100.0 - scrubberInset, y: scrubberVerticalOrigin + 19.0 + rightLabelVerticalOffset), size: CGSize(width: 100.0, height: 20.0)))
let infoLabelVerticalOffset: CGFloat = self.infoNodePushed ? 6.0 : 0.0
@ -1012,10 +980,10 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
let buttonSize = CGSize(width: 64.0, height: 64.0)
let buttonsWidth = min(width - leftInset - rightInset - sideButtonsInset * 2.0, 320.0)
let buttonsRect = CGRect(origin: CGPoint(x: floor((width - buttonsWidth) / 2.0), y: scrubberVerticalOrigin + 36.0), size: CGSize(width: buttonsWidth, height: buttonSize.height))
let buttonsRect = CGRect(origin: CGPoint(x: floor((width - buttonsWidth) / 2.0), y: scrubberVerticalOrigin + 26.0), size: CGSize(width: buttonsWidth, height: buttonSize.height))
transition.updateFrame(node: self.orderButton, frame: CGRect(origin: CGPoint(x: leftInset + sideInset - 22.0, y: buttonsRect.minY), size: buttonSize))
transition.updateFrame(node: self.loopingButton, frame: CGRect(origin: CGPoint(x: width - rightInset - sideInset - buttonSize.width + 22.0, y: buttonsRect.minY), size: buttonSize))
transition.updateFrame(node: self.orderButton, frame: CGRect(origin: CGPoint(x: leftInset + sideInset - 12.0, y: buttonsRect.minY), size: buttonSize))
transition.updateFrame(node: self.loopingButton, frame: CGRect(origin: CGPoint(x: width - rightInset - sideInset - buttonSize.width + 12.0, y: buttonsRect.minY), size: buttonSize))
transition.updateFrame(node: self.backwardButton, frame: CGRect(origin: buttonsRect.origin, size: buttonSize))
transition.updateFrame(node: self.forwardButton, frame: CGRect(origin: CGPoint(x: buttonsRect.maxX - buttonSize.width, y: buttonsRect.minY), size: buttonSize))
@ -1023,64 +991,9 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
let playPauseFrame = CGRect(origin: CGPoint(x: buttonsRect.minX + floor((buttonsRect.width - buttonSize.width) / 2.0), y: buttonsRect.minY), size: buttonSize)
transition.updateFrame(node: self.playPauseButton, frame: playPauseFrame)
transition.updateFrame(node: self.playPauseIconNode, frame: CGRect(origin: CGPoint(x: -6.0, y: -6.0), size: CGSize(width: 76.0, height: 76.0)))
var sectionHeaderTransition = transition
if self.sectionTitle.view?.superview == nil {
sectionHeaderTransition = .immediate
}
sectionHeaderTransition.updateFrame(node: self.sectionBackground, frame: CGRect(origin: CGPoint(x: 0.0, y: finalPanelHeight - OverlayPlayerControlsNode.sectionHeaderHeight), size: CGSize(width: width, height: OverlayPlayerControlsNode.sectionHeaderHeight)))
self.separatorNode.isHidden = hasSectionHeader
if hasSectionHeader {
let sideInset: CGFloat = 16.0
var sectionTitle = self.presentationData.strings.MediaPlayer_Playlist_ThisChat
if let peerName = self.peerName {
sectionTitle = self.presentationData.strings.MediaPlayer_Playlist_SavedMusic(peerName.uppercased()).string
} else if case .custom = self.source {
sectionTitle = self.presentationData.strings.MediaPlayer_Playlist_SavedMusicYou
}
let sectionTitleSize = self.sectionTitle.update(
transition: .immediate,
component: AnyComponent(
MultilineTextComponent(
text: .plain(NSAttributedString(string: sectionTitle, font: Font.regular(13.0), textColor: self.presentationData.theme.chatList.sectionHeaderTextColor)),
truncationType: .middle
)
),
environment: {},
containerSize: CGSize(width: width - leftInset - rightInset - sideInset * 2.0, height: OverlayPlayerControlsNode.sectionHeaderHeight)
)
if let sectionTitleView = self.sectionTitle.view {
if sectionTitleView.superview == nil {
self.view.addSubview(sectionTitleView)
}
sectionTitleView.bounds = CGRect(origin: .zero, size: sectionTitleSize)
sectionHeaderTransition.updateFrame(view: sectionTitleView, frame: CGRect(origin: CGPoint(x: leftInset + sideInset, y: finalPanelHeight - OverlayPlayerControlsNode.sectionHeaderHeight + 6.0 + UIScreenPixel), size: sectionTitleSize))
}
} else if let sectionTitleView = self.sectionTitle.view, sectionTitleView.superview != nil {
sectionTitleView.removeFromSuperview()
}
if let savedMusic {
var profileAudioTransition = transition
var animateIn = false
if previousSavedMusic != savedMusic, let profileAudio {
self.profileAudio = nil
if let profileAudioView = profileAudio.view {
if transition.isAnimated {
profileAudioView.layer.animateScale(from: 1.0, to: 0.0, duration: 0.25)
profileAudioView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
profileAudioView.removeFromSuperview()
})
animateIn = true
} else {
profileAudioView.removeFromSuperview()
}
}
}
if self.profileAudio == nil {
profileAudioTransition = .immediate
}
@ -1089,90 +1002,58 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
self.profileAudio = componentView
return componentView
}()
let profileAudioComponent: AnyComponent<Empty>
var profileAudioOffset: CGFloat = 0.0
let profileAudioButtonContent: AnyComponentWithIdentity<Empty>
var buttonBackgroundColor = self.presentationData.theme.list.itemCheckColors.fillColor
if savedMusic {
if self.cachedChevronImage == nil || self.cachedChevronImage?.1 !== self.presentationData.theme {
self.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: self.presentationData.theme.list.itemAccentColor)!, self.presentationData.theme)
}
let textFont = Font.regular(13.0)
let textColor = self.presentationData.theme.list.itemSecondaryTextColor
let linkColor = self.presentationData.theme.list.itemAccentColor
let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: textFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in
return (TelegramTextAttributes.URL, contents)
})
let attributedString = parseMarkdownIntoAttributedString(self.presentationData.strings.MediaPlayer_SavedMusic_RemoveFromProfile, attributes: markdownAttributes, textAlignment: .center).mutableCopy() as! NSMutableAttributedString
if let range = attributedString.string.range(of: ">"), let chevronImage = self.cachedChevronImage?.0 {
attributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: attributedString.string))
attributedString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: attributedString.string))
}
profileAudioComponent = AnyComponent(MultilineTextComponent(
text: .plain(attributedString),
horizontalAlignment: .center,
maximumNumberOfLines: 5,
lineSpacing: 0.2,
highlightColor: linkColor.withAlphaComponent(0.1),
highlightInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: -8.0),
highlightAction: { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
} else {
return nil
}
},
tapAction: { [weak self] attributes, _ in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
if let file = self?.currentFileReference {
self?.requestRemoveFromProfile?(file)
}
}
}
//TODO:localize
profileAudioButtonContent = AnyComponentWithIdentity(id: "removeFromProfile", component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: "Remove from Profile", font: Font.semibold(17.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)))
))
profileAudioOffset = 18.0
buttonBackgroundColor = self.presentationData.theme.list.controlSecondaryColor
} else {
profileAudioComponent = AnyComponent(ButtonComponent(
background: ButtonComponent.Background(
style: .glass,
color: self.presentationData.theme.list.itemCheckColors.fillColor,
foreground: self.presentationData.theme.list.itemCheckColors.foregroundColor,
pressedColor: self.presentationData.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9),
),
content: AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(
HStack([
AnyComponentWithIdentity(id: "icon", component: AnyComponent(
BundleIconComponent(name: "Peer Info/SaveMusic", tintColor: self.presentationData.theme.list.itemCheckColors.foregroundColor)
)),
AnyComponentWithIdentity(id: "label", component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: self.presentationData.strings.MediaPlayer_SavedMusic_AddToProfile, font: Font.semibold(17.0), textColor: self.presentationData.theme.list.itemCheckColors.foregroundColor)))
))
], spacing: 8.0)
)),
action: { [weak self] in
if let file = self?.currentFileReference {
self?.requestSaveToProfile?(file)
}
}
profileAudioButtonContent = AnyComponentWithIdentity(id: "addToProfile", component: AnyComponent(
HStack([
AnyComponentWithIdentity(id: "icon", component: AnyComponent(
BundleIconComponent(name: "Peer Info/SaveMusic", tintColor: self.presentationData.theme.list.itemCheckColors.foregroundColor)
)),
AnyComponentWithIdentity(id: "label", component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: self.presentationData.strings.MediaPlayer_SavedMusic_AddToProfile, font: Font.semibold(17.0), textColor: self.presentationData.theme.list.itemCheckColors.foregroundColor)))
))
], spacing: 8.0)
))
}
let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: bottomInset, innerDiameter: 52.0, sideInset: 30.0)
let profileAudioSize = profileAudio.update(
transition: .immediate,
component: profileAudioComponent,
transition: ComponentTransition(profileAudioTransition),
component: AnyComponent(ButtonComponent(
background: ButtonComponent.Background(
style: .glass,
color: buttonBackgroundColor,
foreground: self.presentationData.theme.list.itemCheckColors.foregroundColor,
pressedColor: buttonBackgroundColor
),
content: profileAudioButtonContent,
action: { [weak self] in
guard let self, let file = self.currentFileReference else {
return
}
if savedMusic {
self.requestRemoveFromProfile?(file)
} else {
self.requestSaveToProfile?(file)
}
}
)),
environment: {},
containerSize: CGSize(width: width - leftInset - rightInset - 32.0, height: 50.0)
containerSize: CGSize(width: width - buttonInsets.left - buttonInsets.right, height: 52.0)
)
let profileAudioOrigin: CGFloat = finalPanelHeight + profileAudioOffset - (hasSectionHeader ? OverlayPlayerControlsNode.sectionHeaderHeight : 0.0) - 42.0 - floorToScreenPixels(profileAudioSize.height / 2.0)
let profileAudioOrigin: CGFloat = finalPanelHeight - profileAudioSize.height - buttonInsets.bottom
let profileAudioFrame = CGRect(origin: CGPoint(x: floor((width - profileAudioSize.width) / 2.0), y: profileAudioOrigin), size: profileAudioSize)
if let profileAudioView = profileAudio.view {
if profileAudioView.superview == nil {
self.view.addSubview(profileAudioView)
if animateIn {
profileAudioView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
profileAudioView.layer.animateScale(from: 0.0, to: 1.0, duration: 0.3)
}
}
profileAudioTransition.updateFrame(view: profileAudioView, frame: profileAudioFrame)
}
@ -1412,7 +1293,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
}
@objc func albumArtTap(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
if !"".isEmpty, case .ended = recognizer.state {
if let supernode = self.supernode {
let bounds = supernode.bounds
if bounds.width > bounds.height {

View file

@ -2753,7 +2753,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}
public func makeAttachmentFileController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, audio: Bool, bannedSendMedia: (Int32, Bool)?, presentGallery: @escaping () -> Void, presentFiles: @escaping () -> Void, presentDocumentScanner: (() -> Void)?, send: @escaping ([AnyMediaReference], Bool, Int32?, NSAttributedString?) -> Void) -> AttachmentFileController {
return makeAttachmentFileControllerImpl(context: context, updatedPresentationData: updatedPresentationData, mode: audio ? .audio(story: false) : .recent, bannedSendMedia: bannedSendMedia, presentGallery: presentGallery, presentFiles: presentFiles, presentDocumentScanner: presentDocumentScanner, send: send)
return makeAttachmentFileControllerImpl(context: context, updatedPresentationData: updatedPresentationData, mode: audio ? .audio(.chat) : .recent, bannedSendMedia: bannedSendMedia, presentGallery: presentGallery, presentFiles: presentFiles, presentDocumentScanner: presentDocumentScanner, send: send)
}
public func makeGalleryCaptionPanelView(context: AccountContext, chatLocation: ChatLocation, isScheduledMessages: Bool, isFile: Bool, hasTimer: Bool, customEmojiAvailable: Bool, present: @escaping (ViewController) -> Void, presentInGlobalOverlay: @escaping (ViewController) -> Void) -> NSObject? {