Various fixes

This commit is contained in:
Ilya Laktyushin 2026-03-28 15:55:50 +01:00
parent 784b43934a
commit 265b502f76
11 changed files with 109 additions and 53 deletions

View file

@ -16155,3 +16155,6 @@ Error: %8$@";
"TextProcessing.StyleTooltip" = "Select Style";
"Bot.AlertCanNotCreateBots" = "%@ can't manage other bots.";
"MediaPlayer.PlaylistTitle" = "%@'s Playlist";
"MediaPlayer.PlaylistYourTitle" = "Your Playlist";

View file

@ -1235,7 +1235,7 @@ public enum ChatHistoryListDisplayHeaders {
public enum ChatHistoryListMode: Equatable {
case bubbles
case list(reversed: Bool, reverseGroups: Bool, displayHeaders: ChatHistoryListDisplayHeaders, hintLinks: Bool, isGlobalSearch: Bool)
case list(reversed: Bool, reverseGroups: Bool, displayHeaders: ChatHistoryListDisplayHeaders, hintLinks: Bool, isGlobalSearch: Bool, isMusicPlaylist: Bool)
}
public protocol ChatControllerInteractionProtocol: AnyObject {

View file

@ -209,7 +209,8 @@ public final class BrowserBookmarksScreen: ViewController {
reverseGroups: false,
displayHeaders: .none,
hintLinks: true,
isGlobalSearch: false
isGlobalSearch: false,
isMusicPlaylist: false
)
)

View file

@ -2717,7 +2717,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
let isForceLivePhoto = editingContext.isForceLivePhotoEnabled()
rightControlItems.insert(GlassControlGroupComponent.Item(
id: AnyHashable(isForceLivePhoto ? "livePhoto_on" : "livePhoto_off"),
content: .icon(isForceLivePhoto ? "Media Editor/LiveOn" : "Media Editor/LiveOff"),
content: .icon(isForceLivePhoto ? "Media Editor/LiveLargeOn" : "Media Editor/LiveLargeOff"),
action: { [weak self, weak editingContext] in
guard let self, let editingContext else {
return

View file

@ -221,7 +221,7 @@ private final class BoostsTabsItemNode: ListViewItemNode {
strongSelf.bottomStripeNode.isHidden = hasCorners
}
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: true) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)

View file

@ -290,7 +290,7 @@ final class MonetizationBalanceItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.bottomStripeNode.isHidden = hasCorners
}
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: true) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)

View file

@ -80,7 +80,7 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode {
self.selectedMessages = chatControllerInteraction.selectionState.flatMap { $0.selectedIds }
self.selectedMessagesPromise.set(.single(self.selectedMessages))
self.listNode = context.sharedContext.makeChatHistoryListNode(context: context, updatedPresentationData: updatedPresentationData ?? (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData), chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, tag: .tag(tagMask), source: .default, subject: nil, controllerInteraction: chatControllerInteraction, selectedMessages: self.selectedMessagesPromise.get(), mode: .list(reversed: false, reverseGroups: false, displayHeaders: .allButLast, hintLinks: tagMask == .webPage, isGlobalSearch: false))
self.listNode = context.sharedContext.makeChatHistoryListNode(context: context, updatedPresentationData: updatedPresentationData ?? (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData), chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, tag: .tag(tagMask), source: .default, subject: nil, controllerInteraction: chatControllerInteraction, selectedMessages: self.selectedMessagesPromise.get(), mode: .list(reversed: false, reverseGroups: false, displayHeaders: .allButLast, hintLinks: tagMask == .webPage, isGlobalSearch: false, isMusicPlaylist: false))
self.listNode.clipsToBounds = true
self.listNode.defaultToSynchronousTransactionWhileScrolling = true
self.listNode.scroller.bounces = false

View file

@ -58,7 +58,8 @@ func chatHistoryEntriesForView(
customThreadOutgoingReadState: MessageId?,
cachedData: CachedPeerData?,
adMessage: Message?,
dynamicAdMessages: [Message]
dynamicAdMessages: [Message],
isMusicPlaylist: Bool
) -> ([ChatHistoryEntry], ChatHistoryEntriesForViewState) {
var currentState = currentState
@ -453,7 +454,7 @@ func chatHistoryEntriesForView(
}
var addedThreadHead = false
if case let .replyThread(replyThreadMessage) = location, !replyThreadMessage.isForumPost, view.earlierId == nil, !view.holeEarlier, !view.isLoading {
if case let .replyThread(replyThreadMessage) = location, !replyThreadMessage.isForumPost, view.earlierId == nil, !view.holeEarlier, !view.isLoading, !isMusicPlaylist {
loop: for entry in view.additionalData {
switch entry {
case let .message(id, messages) where id == replyThreadMessage.effectiveTopId:
@ -833,6 +834,10 @@ func chatHistoryEntriesForView(
}
}
if isMusicPlaylist && entries.count == 1 {
return ([], currentState)
}
if reverse {
return (entries.reversed(), currentState)
} else {

View file

@ -229,7 +229,7 @@ private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLoca
switch mode {
case .bubbles:
item = ChatMessageItemImpl(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes, location: location), disableDate: disableFloatingDateHeaders || message.timestamp < 10)
case let .list(_, _, displayHeaders, hintLinks, isGlobalSearch):
case let .list(_, _, displayHeaders, hintLinks, isGlobalSearch, _):
let displayHeader: Bool
switch displayHeaders {
case .none:
@ -284,7 +284,7 @@ private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLoca
switch mode {
case .bubbles:
item = ChatMessageItemImpl(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes, location: location), disableDate: disableFloatingDateHeaders || message.timestamp < 10)
case let .list(_, _, displayHeaders, hintLinks, isGlobalSearch):
case let .list(_, _, displayHeaders, hintLinks, isGlobalSearch, _):
let displayHeader: Bool
switch displayHeaders {
case .none:
@ -676,6 +676,9 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH
private var messageReadMetricsTrackerPendingMetrics: [TelegramMessageReadMetric] = []
private var messageReadMetricsTrackerPendingMetricTimer: Foundation.Timer?
public private(set) var hasAnyMessages: Bool = false
public var hasAnyMessagesUpdated: ((Bool) -> Void)?
public private(set) var hasAtLeast3Messages: Bool = false
public var hasAtLeast3MessagesUpdated: ((Bool) -> Void)?
@ -2051,9 +2054,11 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH
var reverse = false
var reverseGroups = false
if case let .list(reverseValue, reverseGroupsValue, _, _, _) = mode {
var isMusicPlaylist = false
if case let .list(reverseValue, reverseGroupsValue, _, _, _, isMusicPlaylistValue) = mode {
reverse = reverseValue
reverseGroups = reverseGroupsValue
isMusicPlaylist = isMusicPlaylistValue
}
var isPremium = false
@ -2108,9 +2113,8 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH
if case let .replyThread(message) = chatLocation, message.peerId == context.account.peerId, !rotated {
includeEmbeddedSavedChatInfo = true
}
let previousChatHistoryEntriesForViewState = chatHistoryEntriesForViewState.with({ $0 })
let (filteredEntries, updatedChatHistoryEntriesForViewState) = chatHistoryEntriesForView(
currentState: previousChatHistoryEntriesForViewState,
context: context,
@ -2136,7 +2140,8 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH
customThreadOutgoingReadState: customThreadOutgoingReadState,
cachedData: data.cachedData,
adMessage: allAdMessages.fixed,
dynamicAdMessages: allAdMessages.opportunistic
dynamicAdMessages: allAdMessages.opportunistic,
isMusicPlaylist: isMusicPlaylist
)
let lastHeaderId = filteredEntries.last.flatMap { listMessageDateHeaderId(timestamp: $0.index.timestamp) } ?? 0
let processedView = ChatHistoryView(originalView: view, filteredEntries: filteredEntries, associatedData: associatedData, lastHeaderId: lastHeaderId, id: id, locationInput: update.2, ignoreMessagesInTimestampRange: update.3, ignoreMessageIds: update.4)
@ -4095,15 +4100,20 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH
}
}
var hasAnyMessages = false
var hasAtLeast3Messages = false
var hasPlentyOfMessages = false
var hasLotsOfMessages = false
if let historyView = strongSelf.historyView {
if historyView.originalView.holeEarlier || historyView.originalView.holeLater {
hasAnyMessages = true
hasAtLeast3Messages = true
hasPlentyOfMessages = true
hasLotsOfMessages = true
} else if !historyView.originalView.holeEarlier && !historyView.originalView.holeLater {
if historyView.filteredEntries.count >= 1 {
hasAnyMessages = true
}
if historyView.filteredEntries.count >= 3 {
hasAtLeast3Messages = true
}
@ -4116,6 +4126,11 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH
}
}
if strongSelf.hasAnyMessages != hasAnyMessages {
strongSelf.hasAnyMessages = hasAnyMessages
strongSelf.hasAnyMessagesUpdated?(hasAnyMessages)
}
if strongSelf.hasAtLeast3Messages != hasAtLeast3Messages {
strongSelf.hasAtLeast3Messages = hasAtLeast3Messages
strongSelf.hasAtLeast3MessagesUpdated?(hasAtLeast3Messages)
@ -4681,7 +4696,7 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH
switch self.mode {
case .bubbles:
item = ChatMessageItemImpl(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, associatedData: associatedData, controllerInteraction: self.controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes, location: location), disableDate: disableFloatingDateHeaders)
case let .list(_, _, displayHeaders, hintLinks, isGlobalSearch):
case let .list(_, _, displayHeaders, hintLinks, isGlobalSearch, _):
let displayHeader: Bool
switch displayHeaders {
case .none:
@ -4759,7 +4774,7 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH
switch self.mode {
case .bubbles:
item = ChatMessageItemImpl(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, associatedData: associatedData, controllerInteraction: self.controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes, location: location), disableDate: disableFloatingDateHeaders)
case let .list(_, _, displayHeaders, hintLinks, isGlobalSearch):
case let .list(_, _, displayHeaders, hintLinks, isGlobalSearch, _):
let displayHeader: Bool
switch displayHeaders {
case .none:

View file

@ -56,6 +56,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
private var floatingHeaderOffset: CGFloat?
private var historyContentOffset: CGFloat = 0.0
private var hasAnyHistoryMessages: Bool?
private var historyNode: ChatHistoryListNodeImpl
private var replacementHistoryNode: ChatHistoryListNodeImpl?
private var replacementHistoryNodeFloatingOffset: CGFloat?
@ -305,8 +306,12 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
tagMask = .file
}
let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil)
var isMusicPlaylist = true
if let playlistLocation = self.playlistLocation as? PeerMessagesPlaylistLocation, case .savedMusic = playlistLocation {
isMusicPlaylist = false
}
let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil)
self.historyNode = ChatHistoryListNodeImpl(
context: context,
updatedPresentationData: (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData),
@ -319,7 +324,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
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),
mode: .list(reversed: self.currentIsReversed, reverseGroups: !self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch, isMusicPlaylist: isMusicPlaylist),
isChatPreview: false,
messageTransitionNode: { return nil
}
@ -476,7 +481,9 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
let copyProtectionEnabled: Signal<Bool, NoError>
if case let .peer(peerId) = self.chatLocation {
if peerId.namespace == Namespaces.Peer.CloudUser {
if let playlistLocation = self.playlistLocation as? PeerMessagesPlaylistLocation, case .savedMusic = playlistLocation {
copyProtectionEnabled = .single(false)
} else if peerId.namespace == Namespaces.Peer.CloudUser {
copyProtectionEnabled = context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.CopyProtectionEnabled(id: peerId),
TelegramEngine.EngineData.Item.Peer.MyCopyProtectionEnabled(id: peerId)
@ -622,10 +629,10 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
func updatePresentationData(_ presentationData: PresentationData) {
self.presentationData = presentationData
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.historyBackgroundContentNode.backgroundColor = self.hasAnyHistoryMessages == true ? self.presentationData.theme.list.itemModalBlocksBackgroundColor : self.presentationData.theme.list.modalPlainBackgroundColor
self.historyFrameLeftOverlayNode.backgroundColor = self.hasAnyHistoryMessages == true ? self.presentationData.theme.list.modalBlocksBackgroundColor : self.presentationData.theme.list.modalPlainBackgroundColor
self.historyFrameRightOverlayNode.backgroundColor = self.hasAnyHistoryMessages == true ? self.presentationData.theme.list.modalBlocksBackgroundColor : self.presentationData.theme.list.modalPlainBackgroundColor
self.historyFrameTopOverlayNode.backgroundColor = self.hasAnyHistoryMessages == true ? self.presentationData.theme.list.modalBlocksBackgroundColor : self.presentationData.theme.list.modalPlainBackgroundColor
self.historyFrameTopMaskNode.image = PresentationResourcesItemList.cornersImage(self.presentationData.theme.withModalBlocksBackground(), top: true, bottom: false, glass: true)
self.collapseNode.setImage(generateCollapseIcon(theme: self.presentationData.theme), for: [])
@ -894,14 +901,6 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
)
)
}
// rightControlItems.append(
// GlassControlGroupComponent.Item(
// id: AnyHashable("search"),
// content: .icon("Navigation/Search"),
// action: {
// }
// )
// )
let headerInset: CGFloat = 16.0
let headerButtonsSize = self.headerButtons.update(
@ -932,13 +931,12 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
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"
titleString = self.presentationData.strings.MediaPlayer_PlaylistYourTitle
} else {
titleString = "\(peer.compactDisplayTitle)'s Playlist"
titleString = self.presentationData.strings.MediaPlayer_PlaylistTitle(peer.compactDisplayTitle).string
}
} else {
titleString = ""
@ -965,7 +963,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
self.updateHistoryContentOffset(self.historyNode.visibleContentOffset(), transition: transition)
var layout = layout
layout.intrinsicInsets.bottom = controlsHeight
layout.intrinsicInsets.bottom = controlsHeight + 8.0
self.getParentController()?.presentationContext.containerLayoutUpdated(layout, transition: transition)
}
@ -990,6 +988,9 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
if !self.bounds.contains(point) {
return nil
}
if point.y < self.historyFrameNode.frame.minY {
return self.dimNode.view
}
return result
}
@ -1069,14 +1070,20 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
return
}
self.floatingHeaderOffset = offset
let previousHasAnyHistoryMessages = self.hasAnyHistoryMessages
self.hasAnyHistoryMessages = self.historyNode.hasAnyMessages
self.controlsNode.hasPlainBackground = !self.historyNode.hasAnyMessages
let sideInset: CGFloat = 16.0
let headerHeight = self.effectiveHeaderHeight
let headerHeight: CGFloat = self.effectiveHeaderHeight
let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
let rawControlsOffset = offset + layoutTopInset
let backgroundOffset = max(layoutTopInset, rawControlsOffset)
var backgroundOffset = max(layoutTopInset, rawControlsOffset)
if !self.historyNode.hasAnyMessages {
backgroundOffset += 25.0
}
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: backgroundOffset + headerHeight), size: CGSize(width: layout.size.width, height: layout.size.height))
@ -1093,6 +1100,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
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))
}
self.historyFrameTopMaskNode.isHidden = self.controlsNode.hasPlainBackground
let previousBackgroundFrame = self.historyBackgroundNode.frame
if !backgroundFrame.equalTo(previousBackgroundFrame) {
@ -1103,6 +1111,10 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
transition.animateOffsetAdditive(node: self.historyBackgroundNode, offset: positionDelta.y)
}
if self.hasAnyHistoryMessages != previousHasAnyHistoryMessages {
self.updatePresentationData(self.presentationData)
}
}
private func transitionToUpdatedHistoryNode(atMessage messageId: MessageId) {
@ -1116,6 +1128,11 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
tagMask = .file
}
var isMusicPlaylist = true
if let playlistLocation = self.playlistLocation as? PeerMessagesPlaylistLocation, case .savedMusic = playlistLocation {
isMusicPlaylist = false
}
let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil)
let historyNode = ChatHistoryListNodeImpl(
context: self.context,
@ -1129,10 +1146,11 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
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),
mode: .list(reversed: self.currentIsReversed, reverseGroups: !self.currentIsReversed, displayHeaders: .none, hintLinks: false, isGlobalSearch: self.isGlobalSearch, isMusicPlaylist: isMusicPlaylist),
isChatPreview: false,
messageTransitionNode: { return nil
})
}
)
historyNode.clipsToBounds = true
historyNode.preloadPages = true
historyNode.stackFromBottom = true

View file

@ -30,12 +30,20 @@ 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
return generateImage(CGSize(width: 20.0, height: 18.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
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.modalPlainBackgroundColor.cgColor)
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)
})?.stretchableImage(withLeftCapWidth: 10, topCapHeight: 18)
}
private func generatePlainBackground(theme: PresentationTheme) -> UIImage? {
return generateImage(CGSize(width: 20.0, height: 18.0 + 25.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(theme.list.modalPlainBackgroundColor.cgColor)
context.fill(CGRect(origin: CGPoint(x: 0.0, y: 8.0 + 25.0), size: CGSize(width: 20.0, height: 20.0)))
})?.stretchableImage(withLeftCapWidth: 10, topCapHeight: 18 + 25)
}
@ -176,6 +184,12 @@ final class OverlayAudioPlayerControlsNode: ASDisplayNode {
private let rateButton: AudioRateButton
let separatorNode: ASDisplayNode
var hasPlainBackground = false {
didSet {
self.backgroundNode.image = self.hasPlainBackground ? generatePlainBackground(theme: self.presentationData.theme) : generateBackground(theme: self.presentationData.theme)
}
}
var isExpanded = false
var updateIsExpanded: (() -> Void)?
@ -249,7 +263,7 @@ final class OverlayAudioPlayerControlsNode: ASDisplayNode {
self.shareNode = HighlightableButtonNode()
self.shareNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "GlobalMusicPlayer/Share"), color: presentationData.theme.list.itemPrimaryTextColor), for: [])
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.scrubberNode = MediaPlayerScrubbingNode(content: .standard(lineHeight: 7.0, lineCap: .round, scrubberHandle: .none, backgroundColor: presentationData.theme.list.controlSecondaryColor.withMultipliedAlpha(0.5), 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
@ -684,9 +698,9 @@ final class OverlayAudioPlayerControlsNode: ASDisplayNode {
self.backwardButton.circleColor = presentationData.theme.list.controlSecondaryColor.withAlphaComponent(0.35)
self.forwardButton.circleColor = presentationData.theme.list.controlSecondaryColor.withAlphaComponent(0.35)
self.backgroundNode.image = generateBackground(theme: presentationData.theme)
self.backgroundNode.image = self.hasPlainBackground ? generatePlainBackground(theme: presentationData.theme) : generateBackground(theme: presentationData.theme)
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.scrubberNode.updateColors(backgroundColor: presentationData.theme.list.controlSecondaryColor.withMultipliedAlpha(0.5), 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)
@ -709,18 +723,16 @@ final class OverlayAudioPlayerControlsNode: ASDisplayNode {
}
private func updateLabels(transition: ContainedViewLayoutTransition) {
guard let (width, leftInset, rightInset, bottomInset, maxHeight, _) = self.validLayout else {
guard let (width, leftInset, rightInset, _, _, _) = self.validLayout else {
return
}
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 = 53.0
let infoLabelsRightInset: CGFloat = 32.0
let infoVerticalOrigin: CGFloat = panelHeight - OverlayAudioPlayerControlsNode.basePanelHeight + 21.0
let infoVerticalOrigin: CGFloat = 21.0
let (titleString, descriptionString, hasArtist, caption) = stringsForDisplayData(self.displayData, presentationData: self.presentationData)
@ -728,7 +740,7 @@ final class OverlayAudioPlayerControlsNode: ASDisplayNode {
self.previousCaption = caption
let chapters = caption.flatMap { parseMediaPlayerChapters($0) } ?? []
self.chaptersPromise.set(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.scrubberNode.updateContent(.standard(lineHeight: 7.0, lineCap: .round, scrubberHandle: .none, backgroundColor: self.presentationData.theme.list.controlSecondaryColor.withMultipliedAlpha(0.5), foregroundColor: self.presentationData.theme.list.itemPrimaryTextColor, bufferingColor: self.presentationData.theme.list.itemPrimaryTextColor.withAlphaComponent(0.4), chapters: chapters))
}
self.artistButton.isUserInteractionEnabled = hasArtist
@ -820,7 +832,7 @@ final class OverlayAudioPlayerControlsNode: ASDisplayNode {
self.rateButton.setContent(.image(optionsRateImage(rate: rate.stringValue.uppercased(), color: self.presentationData.theme.list.itemSecondaryTextColor)))
}
static let basePanelHeight: CGFloat = 164.0
static let basePanelHeight: CGFloat = 182.0
static func heightForLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, maxHeight: CGFloat, isExpanded: Bool, savedMusic: Bool?) -> CGFloat {
var panelHeight: CGFloat = OverlayAudioPlayerControlsNode.basePanelHeight
@ -830,9 +842,11 @@ final class OverlayAudioPlayerControlsNode: ASDisplayNode {
}
var height = min(panelHeight, maxHeight)
if let _ = savedMusic {
height += 70.0
height += 52.0
let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: bottomInset, innerDiameter: 52.0, sideInset: 30.0)
height += buttonInsets.bottom
} else {
height += 8.0
}
return height
}
@ -855,7 +869,7 @@ final class OverlayAudioPlayerControlsNode: ASDisplayNode {
let sideInset: CGFloat = 16.0
let sideButtonsInset: CGFloat = sideInset + 60.0
let infoVerticalOrigin: CGFloat = panelHeight - OverlayAudioPlayerControlsNode.basePanelHeight + 21.0
let infoVerticalOrigin: CGFloat = 21.0
self.updateLabels(transition: transition)
@ -986,7 +1000,7 @@ final class OverlayAudioPlayerControlsNode: ASDisplayNode {
profileAudioButtonContent = AnyComponentWithIdentity(id: "removeFromProfile", component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: self.presentationData.strings.MediaPlayer_SavedMusic_RemoveFromProfileShort, font: Font.semibold(17.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)))
))
buttonBackgroundColor = self.presentationData.theme.list.controlSecondaryColor
buttonBackgroundColor = self.presentationData.theme.list.controlSecondaryColor.withMultipliedAlpha(0.5)
} else {
profileAudioButtonContent = AnyComponentWithIdentity(id: "addToProfile", component: AnyComponent(
HStack([