diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 6cc516979b..ab011640b7 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -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"; diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index af42bca66a..12b6591828 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -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 { diff --git a/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift b/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift index c8c6f584cb..d72f35d457 100644 --- a/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift +++ b/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift @@ -209,7 +209,8 @@ public final class BrowserBookmarksScreen: ViewController { reverseGroups: false, displayHeaders: .none, hintLinks: true, - isGlobalSearch: false + isGlobalSearch: false, + isMusicPlaylist: false ) ) diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index e890be2a1f..f60c002367 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -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 diff --git a/submodules/StatisticsUI/Sources/BoostsTabsItem.swift b/submodules/StatisticsUI/Sources/BoostsTabsItem.swift index 334ede4237..f7f89edbe1 100644 --- a/submodules/StatisticsUI/Sources/BoostsTabsItem.swift +++ b/submodules/StatisticsUI/Sources/BoostsTabsItem.swift @@ -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) diff --git a/submodules/StatisticsUI/Sources/MonetizationBalanceItem.swift b/submodules/StatisticsUI/Sources/MonetizationBalanceItem.swift index 687d2a4236..e7fee379ae 100644 --- a/submodules/StatisticsUI/Sources/MonetizationBalanceItem.swift +++ b/submodules/StatisticsUI/Sources/MonetizationBalanceItem.swift @@ -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) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoListPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoListPaneNode.swift index a19c6eaf73..5faf856184 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoListPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoListPaneNode.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift index b5f894129e..0322eba0b9 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift @@ -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 { diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 5f97ca701d..1348dedfb8 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -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: diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index b33068592d..50aa04643a 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -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(value: nil) + var isMusicPlaylist = true + if let playlistLocation = self.playlistLocation as? PeerMessagesPlaylistLocation, case .savedMusic = playlistLocation { + isMusicPlaylist = false + } + let chatLocationContextHolder = Atomic(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 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(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 diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControlsNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControlsNode.swift index e5e79fdbb5..c475d9b884 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControlsNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControlsNode.swift @@ -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([