mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Cherry-pick fixes
This commit is contained in:
parent
b3d4c97da4
commit
d0d3b064ed
17 changed files with 223 additions and 158 deletions
|
|
@ -8771,6 +8771,7 @@ Sorry for the inconvenience.";
|
|||
"Premium.CurrentPlan" = "your current plan";
|
||||
"Premium.UpgradeFor" = "Upgrade for %@ / month";
|
||||
"Premium.UpgradeForAnnual" = "Upgrade for %@ / year";
|
||||
"Premium.UpgradeForBiannual" = "Upgrade for %@ / 2 years";
|
||||
|
||||
"ChatList.PremiumAnnualDiscountTitle" = "Telegram Premium with a discount of %@";
|
||||
"ChatList.PremiumAnnualDiscountText" = "Sign up for the annual payment plan for Telegram Premium now to get the discount.";
|
||||
|
|
|
|||
|
|
@ -129,11 +129,7 @@ public func chatMessageGalleryControllerData(
|
|||
for media in message.media {
|
||||
if let poll = media as? TelegramMediaPoll {
|
||||
standalone = true
|
||||
if let attachedMedia = poll.attachedMedia as? TelegramMediaMap {
|
||||
galleryMedia = attachedMedia
|
||||
} else {
|
||||
galleryMedia = poll
|
||||
}
|
||||
galleryMedia = poll
|
||||
} else if let paidContent = media as? TelegramMediaPaidContent, let extendedMedia = paidContent.extendedMedia.first, case .full = extendedMedia {
|
||||
standalone = true
|
||||
galleryMedia = paidContent
|
||||
|
|
@ -231,6 +227,29 @@ public func chatMessageGalleryControllerData(
|
|||
}, baseNavigationController: navigationController)
|
||||
return .instantPage(gallery, centralIndex, galleryMedia)
|
||||
} else if let galleryMedia = galleryMedia {
|
||||
var galleryMedia = galleryMedia
|
||||
if let poll = galleryMedia as? TelegramMediaPoll {
|
||||
if mediaSubject == nil || mediaSubject == .pollDescription, let attachedMedia = poll.attachedMedia {
|
||||
if let file = attachedMedia as? TelegramMediaFile, file.isMusic {
|
||||
galleryMedia = file
|
||||
} else if let map = attachedMedia as? TelegramMediaMap {
|
||||
galleryMedia = map
|
||||
}
|
||||
} else if case let .pollOption(opaqueIdentifier) = mediaSubject, let optionMedia = poll.options.first(where: { $0.opaqueIdentifier == opaqueIdentifier })?.media {
|
||||
if let file = optionMedia as? TelegramMediaFile, file.isMusic {
|
||||
galleryMedia = file
|
||||
} else if let map = optionMedia as? TelegramMediaMap {
|
||||
galleryMedia = map
|
||||
}
|
||||
} else if case .pollSolution = mediaSubject, let solutionMedia = poll.results.solution?.media {
|
||||
if let file = solutionMedia as? TelegramMediaFile, file.isMusic {
|
||||
galleryMedia = file
|
||||
} else if let map = solutionMedia as? TelegramMediaMap {
|
||||
galleryMedia = map
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let mapMedia = galleryMedia as? TelegramMediaMap {
|
||||
return .map(mapMedia)
|
||||
} else if let file = galleryMedia as? TelegramMediaFile, (file.isSticker || file.isAnimatedSticker) {
|
||||
|
|
|
|||
|
|
@ -436,6 +436,7 @@ public func galleryItemForEntry(
|
|||
context: context,
|
||||
presentationData: presentationData,
|
||||
message: message,
|
||||
mediaSubject: entry.mediaSubject,
|
||||
location: location,
|
||||
translateToLanguage: translateToLanguage,
|
||||
peerIsCopyProtected: peerIsCopyProtected,
|
||||
|
|
@ -664,8 +665,16 @@ private func galleryEntriesForMessageHistoryEntries(_ entries: [MessageHistoryEn
|
|||
case .pollOption:
|
||||
if let poll = entry.message.media.first(where: { $0 is TelegramMediaPoll }) as? TelegramMediaPoll {
|
||||
for option in poll.options {
|
||||
if let _ = option.media {
|
||||
results.append(GalleryEntry(entry: entry, mediaSubject: .pollOption(option.opaqueIdentifier)))
|
||||
if let optionMedia = option.media {
|
||||
var isGalleryMedia = false
|
||||
if optionMedia is TelegramMediaImage {
|
||||
isGalleryMedia = true
|
||||
} else if let file = optionMedia as? TelegramMediaFile, file.isVideo || file.mimeType.hasPrefix("image/") {
|
||||
isGalleryMedia = true
|
||||
}
|
||||
if isGalleryMedia {
|
||||
results.append(GalleryEntry(entry: entry, mediaSubject: .pollOption(option.opaqueIdentifier)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3288,7 +3288,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
guard let (message, _, _) = self.contentInfo() else {
|
||||
return false
|
||||
}
|
||||
|
||||
var canDelete = false
|
||||
if let peer = message.peers[message.id.peerId] {
|
||||
if peer is TelegramUser || peer is TelegramSecretChat {
|
||||
|
|
@ -3307,6 +3306,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
} else {
|
||||
canDelete = false
|
||||
}
|
||||
if let _ = message.media.first(where: { $0 is TelegramMediaPoll }) {
|
||||
canDelete = false
|
||||
}
|
||||
return canDelete
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -441,7 +441,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
|||
|
||||
if case let .assets(_, mode) = controller.subject {
|
||||
switch mode {
|
||||
case .default, .story, .poll:
|
||||
case .default, .poll:
|
||||
self.containerNode.view.addSubview(self.bottomEdgeEffectView)
|
||||
default:
|
||||
break
|
||||
|
|
|
|||
|
|
@ -3162,7 +3162,6 @@ private func albumArtFullSizeDatas(engine: TelegramEngine, file: FileMediaRefere
|
|||
return .single(Tuple(nil, nil, false))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged(isEqual: { lhs, rhs in
|
||||
|
|
@ -3229,25 +3228,32 @@ public func playerAlbumArt(postbox: Postbox, engine: TelegramEngine, fileReferen
|
|||
}
|
||||
)
|
||||
}
|
||||
|
||||
var immediateArtworkData: Signal<Tuple3<Data?, Data?, Bool>, NoError> = .single(Tuple(nil, nil, false))
|
||||
|
||||
if let fileReference = fileReference, let smallestRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) {
|
||||
|
||||
func previewArtworkData(fileReference: FileMediaReference) -> Signal<Data?, NoError> {
|
||||
guard let smallestRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) else {
|
||||
return .single(nil)
|
||||
}
|
||||
|
||||
let thumbnailResource = smallestRepresentation.resource
|
||||
|
||||
let fetchedThumbnail = fetchedMediaResource(mediaBox: postbox.mediaBox, userLocation: .other, userContentType: .image, reference: fileReference.resourceReference(thumbnailResource))
|
||||
|
||||
let thumbnail = Signal<Data?, NoError> { subscriber in
|
||||
|
||||
return Signal<Data?, NoError> { subscriber in
|
||||
let fetchedDisposable = fetchedThumbnail.start()
|
||||
let thumbnailDisposable = postbox.mediaBox.resourceData(thumbnailResource, attemptSynchronously: attemptSynchronously).start(next: { next in
|
||||
subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []))
|
||||
}, error: subscriber.putError, completed: subscriber.putCompletion)
|
||||
|
||||
|
||||
return ActionDisposable {
|
||||
fetchedDisposable.dispose()
|
||||
thumbnailDisposable.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var immediateArtworkData: Signal<Tuple3<Data?, Data?, Bool>, NoError> = .single(Tuple(nil, nil, false))
|
||||
|
||||
if let fileReference = fileReference, thumbnail, smallestImageRepresentation(fileReference.media.previewRepresentations) != nil {
|
||||
let thumbnail = previewArtworkData(fileReference: fileReference)
|
||||
immediateArtworkData = thumbnail
|
||||
|> map { thumbnailData in
|
||||
return Tuple(thumbnailData, nil, false)
|
||||
|
|
@ -3259,7 +3265,36 @@ public func playerAlbumArt(postbox: Postbox, engine: TelegramEngine, fileReferen
|
|||
return Tuple(thumbnailData, nil, false)
|
||||
}
|
||||
} else {
|
||||
immediateArtworkData = albumArtFullSizeDatas(engine: engine, file: fileReference, thumbnail: albumArt.thumbnailResource, fullSize: albumArt.fullSizeResource)
|
||||
let previewData: Signal<Data?, NoError>
|
||||
if let fileReference = fileReference {
|
||||
previewData = previewArtworkData(fileReference: fileReference)
|
||||
} else {
|
||||
previewData = .single(nil)
|
||||
}
|
||||
|
||||
immediateArtworkData = combineLatest(
|
||||
albumArtFullSizeDatas(engine: engine, file: fileReference, thumbnail: albumArt.thumbnailResource, fullSize: albumArt.fullSizeResource),
|
||||
previewData
|
||||
)
|
||||
|> map { remoteArtworkData, previewData in
|
||||
let remoteFullSizeData = remoteArtworkData._1
|
||||
let shouldUsePreviewFallback: Bool
|
||||
if remoteArtworkData._2 {
|
||||
if let remoteFullSizeData = remoteFullSizeData {
|
||||
shouldUsePreviewFallback = remoteFullSizeData.isEmpty
|
||||
} else {
|
||||
shouldUsePreviewFallback = true
|
||||
}
|
||||
} else {
|
||||
shouldUsePreviewFallback = false
|
||||
}
|
||||
|
||||
if shouldUsePreviewFallback {
|
||||
return Tuple(previewData, nil, false)
|
||||
} else {
|
||||
return remoteArtworkData
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
immediateArtworkData = .single(Tuple(nil, nil, false))
|
||||
|
|
|
|||
|
|
@ -3792,7 +3792,13 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||
} else if isUnusedGift {
|
||||
buttonTitle = environment.strings.Premium_Gift_ApplyLink
|
||||
} else if state.isPremium == true && state.canUpgrade {
|
||||
buttonTitle = state.isAnnual ? environment.strings.Premium_UpgradeForAnnual(state.price ?? "—").string : environment.strings.Premium_UpgradeFor(state.price ?? "—").string
|
||||
if state.isAnnual {
|
||||
buttonTitle = environment.strings.Premium_UpgradeForAnnual(state.price ?? "—").string
|
||||
} else if state.isBiannual {
|
||||
buttonTitle = environment.strings.Premium_UpgradeForBiannual(state.price ?? "—").string
|
||||
} else {
|
||||
buttonTitle = environment.strings.Premium_UpgradeFor(state.price ?? "—").string
|
||||
}
|
||||
} else {
|
||||
if state.isAnnual {
|
||||
buttonTitle = environment.strings.Premium_SubscribeForAnnual(state.price ?? "—").string
|
||||
|
|
@ -3803,9 +3809,6 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
let controller = environment.controller
|
||||
let button = button.update(
|
||||
component: SolidRoundedButtonComponent(
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ public struct PresentationResourcesDevices {
|
|||
public static let macbook = renderSettingsIcon(name: "Settings/Devices/Mac", backgroundColors: [colorBlue])
|
||||
public static let iOS = renderAttachAppIcon(iconImage: UIImage(bundleImageName: "Settings/Devices/iOS"))
|
||||
|
||||
public static let android = renderSettingsIcon(name: "Settings/Devices/Watch", backgroundColors: [colorGreen])
|
||||
public static let android = renderSettingsIcon(name: "Settings/Devices/Android", backgroundColors: [colorGreen])
|
||||
|
||||
public static let safari = renderAttachAppIcon(iconImage: UIImage(bundleImageName: "Settings/Devices/Safari"))
|
||||
public static let chrome = renderSettingsIcon(name: "Settings/Devices/Chrome", backgroundColors: [colorGreen])
|
||||
|
|
|
|||
|
|
@ -1346,7 +1346,7 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
|
|||
node.buttonNode.isAccessibilityElement = shouldHaveRadioNode
|
||||
|
||||
let previousResultBarWidth = minBarWidth + floor((width - leftInset - rightInset - minBarWidth) * (currentResult?.normalized ?? 0.0))
|
||||
let previousFrame = CGRect(origin: CGPoint(x: leftInset, y: contentHeight - 6.0 - 1.0), size: CGSize(width: previousResultBarWidth, height: 6.0))
|
||||
let previousFrame = CGRect(origin: CGPoint(x: leftInset, y: contentLayoutHeight - 6.0 - 1.0), size: CGSize(width: previousResultBarWidth, height: 4.0))
|
||||
|
||||
node.resultBarNode.layer.animateSpring(from: NSValue(cgPoint: previousFrame.center), to: NSValue(cgPoint: node.resultBarNode.frame.center), keyPath: "position", duration: 0.6, damping: 110.0)
|
||||
node.resultBarNode.layer.animateSpring(from: NSValue(cgRect: CGRect(origin: CGPoint(), size: previousFrame.size)), to: NSValue(cgRect: CGRect(origin: CGPoint(), size: node.resultBarNode.frame.size)), keyPath: "bounds", duration: 0.6, damping: 110.0)
|
||||
|
|
@ -2781,7 +2781,8 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
if let data = item.context.currentAppConfiguration.with({ $0 }).data, let value = data["poll_answers_max"] as? Double {
|
||||
maxPollOptions = Int(value)
|
||||
}
|
||||
let displayAddOption = poll.openAnswers && !isClosed && poll.pollId.namespace == Namespaces.Media.CloudPoll && orderedPollOptions.count < maxPollOptions
|
||||
|
||||
let displayAddOption = poll.openAnswers && !isClosed && poll.pollId.namespace == Namespaces.Media.CloudPoll && !Namespaces.Message.allScheduled.contains(item.message.id.namespace) && orderedPollOptions.count < maxPollOptions
|
||||
if displayAddOption {
|
||||
let addOptionResult = makeAddOptionLayout(item.context, item.presentationData, item.presentationData.strings, incoming, item.controllerInteraction.focusedTextInputIsMedia, currentNewOptionText, currentNewOptionAttachment, constrainedSize.width - layoutConstants.bubble.borderInset * 2.0)
|
||||
boundingSize.width = max(boundingSize.width, addOptionResult.minimumWidth + layoutConstants.bubble.borderInset * 2.0)
|
||||
|
|
|
|||
|
|
@ -543,7 +543,7 @@ final class ComposePollScreenComponent: Component {
|
|||
}
|
||||
|
||||
var mappedSolution: (String, [MessageTextEntity], AnyMediaReference?)?
|
||||
if self.isQuiz && self.quizAnswerTextInputState.text.length != 0 {
|
||||
if self.isQuiz && (self.quizAnswerTextInputState.text.length != 0 || self.quizAnswerMedia != nil) {
|
||||
var solutionTextEntities: [MessageTextEntity] = []
|
||||
for entity in generateChatInputTextEntities(self.quizAnswerTextInputState.text) {
|
||||
switch entity.type {
|
||||
|
|
|
|||
|
|
@ -254,7 +254,8 @@ public final class MediaPlaybackHeaderPanelComponent: Component {
|
|||
}
|
||||
let playerController = component.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, chatLocation: .peer(id: id.messageId.peerId), type: component.data.kind, initialMessageId: id.messageId, initialOrder: component.data.playbackOrder, playlistLocation: playlistLocation, parentNavigationController: navigationController)
|
||||
self.window?.endEditing(true)
|
||||
controller.present(playerController, in: .window(.root))
|
||||
playerController.navigationPresentation = .flatModal
|
||||
controller.push(playerController)
|
||||
case let .messages(chatLocation, _, _):
|
||||
let signal = component.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: .id(id.messageId)), count: 60, highlight: true, setupReply: false), id: 0), context: component.context, chatLocation: chatLocation, subject: nil, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>(value: nil), tag: .tag(MessageTags.music))
|
||||
|
||||
|
|
@ -295,7 +296,8 @@ public final class MediaPlaybackHeaderPanelComponent: Component {
|
|||
}
|
||||
let playerController = component.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, chatLocation: chatLocation, type: component.data.kind, initialMessageId: id.messageId, initialOrder: component.data.playbackOrder, playlistLocation: nil, parentNavigationController: navigationController)
|
||||
self.window?.endEditing(true)
|
||||
controller.present(playerController, in: .window(.root))
|
||||
playerController.navigationPresentation = .flatModal
|
||||
controller.push(playerController)
|
||||
} else if index.1 {
|
||||
if !progressStarted {
|
||||
progressStarted = true
|
||||
|
|
|
|||
|
|
@ -526,7 +526,10 @@ public final class PeerInfoCoverComponent: Component {
|
|||
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: -1000.0 + availableSize.height), size: CGSize(width: availableSize.width, height: 1000.0))
|
||||
transition.containedViewLayoutTransition.updateFrameAdditive(view: self.backgroundView, frame: backgroundFrame)
|
||||
|
||||
let patternWidth: CGFloat = min(380.0, availableSize.width - 32.0)
|
||||
var patternWidth: CGFloat = 380.0
|
||||
if case .managedBot = component.subject {
|
||||
patternWidth = min(380.0, availableSize.width - 32.0)
|
||||
}
|
||||
let avatarPatternFrame = CGSize(width: patternWidth, height: floor(component.defaultHeight * 1.0)).centered(around: component.avatarCenter)
|
||||
transition.setFrame(layer: self.avatarBackgroundPatternContentsLayer, frame: avatarPatternFrame)
|
||||
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ final class StoryContentCaptionComponent: Component {
|
|||
let textSelectionAction: (NSAttributedString, TextSelectionAction) -> Void
|
||||
let controller: () -> ViewController?
|
||||
let openStory: (EnginePeer, EngineStoryItem?) -> Void
|
||||
let openMusic: (FileMediaReference, UIView) -> Void
|
||||
let openMusic: (TelegramMediaFile, UIView) -> Void
|
||||
|
||||
init(
|
||||
externalState: ExternalState,
|
||||
|
|
@ -89,7 +89,7 @@ final class StoryContentCaptionComponent: Component {
|
|||
textSelectionAction: @escaping (NSAttributedString, TextSelectionAction) -> Void,
|
||||
controller: @escaping () -> ViewController?,
|
||||
openStory: @escaping (EnginePeer, EngineStoryItem?) -> Void,
|
||||
openMusic: @escaping (FileMediaReference, UIView) -> Void
|
||||
openMusic: @escaping (TelegramMediaFile, UIView) -> Void
|
||||
) {
|
||||
self.externalState = externalState
|
||||
self.context = context
|
||||
|
|
@ -756,7 +756,7 @@ final class StoryContentCaptionComponent: Component {
|
|||
return
|
||||
}
|
||||
if let sourceView = self.musicPanel?.view {
|
||||
self.component?.openMusic(.standalone(media: music), sourceView)
|
||||
self.component?.openMusic(music, sourceView)
|
||||
}
|
||||
},
|
||||
animateScale: false
|
||||
|
|
|
|||
|
|
@ -7547,8 +7547,8 @@ public final class StoryItemSetContainerComponent: Component {
|
|||
})
|
||||
}
|
||||
|
||||
private func performMusicAction(file: FileMediaReference, sourceView: UIView, gesture: ContextGesture?) {
|
||||
guard let component = self.component, let controller = component.controller() else {
|
||||
private func performMusicAction(file: TelegramMediaFile, sourceView: UIView, gesture: ContextGesture?) {
|
||||
guard let component = self.component, let controller = component.controller(), let peer = PeerReference(component.slice.peer._asPeer()) else {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -7556,6 +7556,8 @@ public final class StoryItemSetContainerComponent: Component {
|
|||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
|
||||
|
||||
let fileReference: FileMediaReference = .story(peer: peer, id: component.slice.item.storyItem.id, media: file)
|
||||
|
||||
let items = component.context.engine.peers.savedMusicIds()
|
||||
|> take(1)
|
||||
|> map { [weak self] savedIds -> ContextController.Items in
|
||||
|
|
@ -7580,7 +7582,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||
return
|
||||
}
|
||||
|
||||
let _ = component.context.engine.peers.addSavedMusic(file: file).start()
|
||||
let _ = component.context.engine.peers.addSavedMusic(file: fileReference).start()
|
||||
|
||||
guard let controller = component.controller() as? StoryContainerScreen else {
|
||||
return
|
||||
|
|
@ -7638,7 +7640,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||
return
|
||||
}
|
||||
|
||||
let _ = component.context.engine.messages.enqueueOutgoingMessage(to: component.context.account.peerId, replyTo: nil, content: .file(file)).start()
|
||||
let _ = component.context.engine.messages.enqueueOutgoingMessage(to: component.context.account.peerId, replyTo: nil, content: .file(fileReference)).start()
|
||||
|
||||
guard let controller = component.controller() as? StoryContainerScreen else {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -18,24 +18,18 @@ import StickerPackPreviewUI
|
|||
|
||||
extension ChatControllerImpl {
|
||||
func openPollMedia(message: Message, subject: ChatControllerInteraction.PollMediaSubject) -> Void {
|
||||
var media: Media?
|
||||
var text: String?
|
||||
var entities: [MessageTextEntity] = []
|
||||
let mediaSubject: GalleryMediaSubject
|
||||
var media: Media?
|
||||
switch subject {
|
||||
case let .option(option):
|
||||
text = option.text
|
||||
entities = option.entities
|
||||
media = option.media
|
||||
mediaSubject = .pollOption(option.opaqueIdentifier)
|
||||
case let .solution(solution):
|
||||
text = solution.text
|
||||
entities = solution.entities
|
||||
media = solution.media
|
||||
mediaSubject = .pollSolution
|
||||
}
|
||||
|
||||
guard let _ = text, let media else {
|
||||
guard let media else {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -112,13 +106,6 @@ extension ChatControllerImpl {
|
|||
|
||||
})
|
||||
} else {
|
||||
var attributes = message.attributes
|
||||
attributes.removeAll(where: { $0 is TextEntitiesMessageAttribute })
|
||||
if !entities.isEmpty {
|
||||
attributes.append(TextEntitiesMessageAttribute(entities: entities))
|
||||
}
|
||||
|
||||
let message = message //.withUpdatedText(text).withUpdatedAttributes(attributes)
|
||||
let _ = self.context.sharedContext.openChatMessage(OpenChatMessageParams(
|
||||
context: self.context,
|
||||
updatedPresentationData: self.controllerInteraction?.updatedPresentationData,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import UndoUI
|
|||
import ChatHistoryEntry
|
||||
import MultilineTextComponent
|
||||
import GlassControls
|
||||
import PhotoResources
|
||||
|
||||
final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestureRecognizerDelegate {
|
||||
let ready = Promise<Bool>()
|
||||
|
|
@ -61,6 +62,10 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
private var replacementHistoryNode: ChatHistoryListNodeImpl?
|
||||
private var replacementHistoryNodeFloatingOffset: CGFloat?
|
||||
|
||||
private var currentAlbumArt: (FileMediaReference, SharedMediaPlaybackAlbumArt)?
|
||||
private let albumArtBackground: UIVisualEffectView
|
||||
private let albumArtNode = TransformImageNode()
|
||||
|
||||
private var saveMediaDisposable: MetaDisposable?
|
||||
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
|
@ -293,7 +298,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
|
||||
self.historyFrameTopMaskNode = ASImageNode()
|
||||
self.historyFrameTopMaskNode.displaysAsynchronously = false
|
||||
self.historyFrameTopMaskNode.image = PresentationResourcesItemList.cornersImage(self.presentationData.theme.withModalBlocksBackground(), top: true, bottom: false, glass: true)
|
||||
self.historyFrameTopMaskNode.image = generateCornersImage(theme: self.presentationData.theme)
|
||||
self.historyFrameTopMaskNode.isUserInteractionEnabled = false
|
||||
|
||||
let tagMask: MessageTags
|
||||
|
|
@ -335,6 +340,10 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
self.collapseNode.displaysAsynchronously = false
|
||||
self.collapseNode.setImage(generateCollapseIcon(theme: self.presentationData.theme), for: [])
|
||||
|
||||
self.albumArtBackground = UIVisualEffectView()
|
||||
self.albumArtBackground.contentView.addSubview(self.albumArtNode.view)
|
||||
self.albumArtNode.isUserInteractionEnabled = false
|
||||
|
||||
super.init()
|
||||
|
||||
self.backgroundColor = nil
|
||||
|
|
@ -376,6 +385,19 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
}
|
||||
}
|
||||
|
||||
self.controlsNode.requestAlbumArtDisplay = { [weak self] fileReferenceAndAlbumArt in
|
||||
guard let self, let layout = self.validLayout else {
|
||||
return
|
||||
}
|
||||
self.currentAlbumArt = fileReferenceAndAlbumArt
|
||||
|
||||
if let (fileReference, albumArt) = fileReferenceAndAlbumArt {
|
||||
self.albumArtNode.setSignal(playerAlbumArt(postbox: self.context.account.postbox, engine: self.context.engine, fileReference: fileReference, albumArt: albumArt, thumbnail: false))
|
||||
}
|
||||
|
||||
self.containerLayoutUpdated(layout, transition: .animated(duration: 0.25, curve: .easeInOut))
|
||||
}
|
||||
|
||||
self.controlsNode.requestCollapse = { [weak self] in
|
||||
self?.requestDismiss()
|
||||
}
|
||||
|
|
@ -542,6 +564,8 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
)
|
||||
|
||||
self.setupReordering()
|
||||
|
||||
self.albumArtBackground.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.albumArtTapped(_:))))
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
|
@ -633,7 +657,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
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.historyFrameTopMaskNode.image = generateCornersImage(theme: self.presentationData.theme)
|
||||
|
||||
self.collapseNode.setImage(generateCollapseIcon(theme: self.presentationData.theme), for: [])
|
||||
|
||||
|
|
@ -841,7 +865,8 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
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 controlsTransition = self.controlsNode.frame.width > 0.0 ? transition : .immediate
|
||||
controlsTransition.updateFrame(node: self.controlsNode, frame: controlsFrame)
|
||||
|
||||
let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
|
||||
var insets = UIEdgeInsets()
|
||||
|
|
@ -962,6 +987,49 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
|
||||
self.updateHistoryContentOffset(self.historyNode.visibleContentOffset(), transition: transition)
|
||||
|
||||
self.albumArtBackground.frame = CGRect(origin: .zero, size: layout.size)
|
||||
|
||||
if let _ = self.currentAlbumArt {
|
||||
var animateIn = false
|
||||
if self.albumArtBackground.superview == nil {
|
||||
self.view.addSubview(self.albumArtBackground)
|
||||
animateIn = true
|
||||
}
|
||||
let albumArtSide = min(360.0, layout.size.width - 32.0)
|
||||
let albumArtSize = CGSize(width: albumArtSide, height: albumArtSide)
|
||||
self.albumArtNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - albumArtSize.width) / 2.0), y: floorToScreenPixels((layout.size.height - albumArtSize.height) / 2.0)), size: albumArtSize)
|
||||
|
||||
let makeLargeAlbumArtLayout = self.albumArtNode.asyncLayout()
|
||||
let applyLargeAlbumArt = makeLargeAlbumArtLayout(TransformImageArguments(corners: ImageCorners(radius: 4.0), imageSize: albumArtSize, boundingSize: albumArtSize, intrinsicInsets: UIEdgeInsets()))
|
||||
applyLargeAlbumArt()
|
||||
|
||||
if animateIn {
|
||||
self.controlsNode.albumArtNode.alpha = 0.0
|
||||
|
||||
let sourceFrame = self.controlsNode.albumArtNode.view.convert(self.controlsNode.albumArtNode.bounds, to: self.albumArtBackground.contentView)
|
||||
ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring).animateFrame(node: self.albumArtNode, from: sourceFrame)
|
||||
UIView.animate(withDuration: 0.2, animations: {
|
||||
self.albumArtBackground.effect = UIBlurEffect(style: self.presentationData.theme.overallDarkAppearance ? .dark : .light)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
self.controlsNode.albumArtNode.alpha = 1.0
|
||||
|
||||
if self.albumArtBackground.superview != nil {
|
||||
let fadeTransition = ComponentTransition(transition)
|
||||
fadeTransition.setBlur(layer: self.albumArtNode.layer, radius: 10.0)
|
||||
fadeTransition.setAlpha(layer: self.albumArtNode.layer, alpha: 0.0)
|
||||
|
||||
UIView.animate(withDuration: 0.2, animations: {
|
||||
self.albumArtBackground.effect = nil
|
||||
}, completion: { _ in
|
||||
self.albumArtBackground.removeFromSuperview()
|
||||
ComponentTransition.immediate.setBlur(layer: self.albumArtNode.layer, radius: 0.0)
|
||||
ComponentTransition.immediate.setAlpha(layer: self.albumArtNode.layer, alpha: 1.0)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var layout = layout
|
||||
layout.intrinsicInsets.bottom = controlsHeight + (self.historyNode.hasAnyMessages ? 0.0 : 8.0)
|
||||
self.getParentController()?.presentationContext.containerLayoutUpdated(layout, transition: transition)
|
||||
|
|
@ -988,7 +1056,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
if !self.bounds.contains(point) {
|
||||
return nil
|
||||
}
|
||||
if point.y < self.historyFrameNode.frame.minY {
|
||||
if self.albumArtBackground.superview == nil && point.y < self.historyFrameNode.frame.minY {
|
||||
return self.dimNode.view
|
||||
}
|
||||
return result
|
||||
|
|
@ -999,6 +1067,13 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
self.requestDismiss()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func albumArtTapped(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state, let layout = self.validLayout {
|
||||
self.currentAlbumArt = nil
|
||||
self.containerLayoutUpdated(layout, transition: .animated(duration: 0.3, curve: .easeInOut))
|
||||
}
|
||||
}
|
||||
|
||||
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
if let recognizer = gestureRecognizer as? UIPanGestureRecognizer {
|
||||
|
|
@ -1098,7 +1173,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
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 - UIScreenPixel), size: CGSize(width: layout.size.width - sideInset * 2.0, height: image.size.height))
|
||||
self.historyFrameTopMaskNode.frame = CGRect(origin: CGPoint(x: sideInset, y: topOverlayFrame.maxY - 1.0), size: CGSize(width: layout.size.width - sideInset * 2.0, height: image.size.height))
|
||||
}
|
||||
self.historyFrameTopMaskNode.isHidden = self.controlsNode.hasPlainBackground
|
||||
|
||||
|
|
@ -1562,3 +1637,22 @@ private func generateCollapseIcon(theme: PresentationTheme) -> UIImage? {
|
|||
context.fillPath()
|
||||
})
|
||||
}
|
||||
|
||||
private func generateCornersImage(theme: PresentationTheme) -> UIImage? {
|
||||
return generateImage(CGSize(width: 56.0, height: 56.0), rotatedContext: { (size, context) in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.setFillColor(theme.list.modalBlocksBackgroundColor.cgColor)
|
||||
context.fill(bounds)
|
||||
|
||||
context.setBlendMode(.clear)
|
||||
|
||||
var corners: UIRectCorner = []
|
||||
corners.insert(.topLeft)
|
||||
corners.insert(.topRight)
|
||||
|
||||
let cornerRadius: CGFloat = 26.0
|
||||
let path = UIBezierPath(roundedRect: bounds.offsetBy(dx: 0.0, dy: 1.0), byRoundingCorners: corners, cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
|
||||
context.addPath(path.cgPath)
|
||||
context.fillPath()
|
||||
})?.stretchableImage(withLeftCapWidth: 28, topCapHeight: 28)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -148,8 +148,7 @@ final class OverlayAudioPlayerControlsNode: ASDisplayNode {
|
|||
|
||||
private let backgroundNode: ASImageNode
|
||||
|
||||
private let albumArtNode: TransformImageNode
|
||||
private var largeAlbumArtNode: TransformImageNode?
|
||||
let albumArtNode: TransformImageNode
|
||||
private let titleNode: TextNode
|
||||
private let title: ComponentView<Empty>
|
||||
private let descriptionNode: TextNode
|
||||
|
|
@ -193,6 +192,7 @@ final class OverlayAudioPlayerControlsNode: ASDisplayNode {
|
|||
|
||||
var isExpanded = false
|
||||
var updateIsExpanded: (() -> Void)?
|
||||
var requestAlbumArtDisplay: (((FileMediaReference, SharedMediaPlaybackAlbumArt)?) -> Void)?
|
||||
|
||||
var requestCollapse: (() -> Void)?
|
||||
var requestShare: ((ShareControllerSubject) -> Void)?
|
||||
|
|
@ -222,7 +222,6 @@ final class OverlayAudioPlayerControlsNode: ASDisplayNode {
|
|||
|
||||
private let hapticFeedback = HapticFeedback()
|
||||
|
||||
private var scrubbingDisposable: Disposable?
|
||||
private var leftDurationLabelPushed = false
|
||||
private var rightDurationLabelPushed = false
|
||||
private var infoNodePushed = false
|
||||
|
|
@ -356,33 +355,7 @@ final class OverlayAudioPlayerControlsNode: ASDisplayNode {
|
|||
self.scrubberNode.status = mappedStatus
|
||||
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
|
||||
//
|
||||
// strongSelf.requestLayout(transition: .animated(duration: 0.35, curve: .spring))
|
||||
// }
|
||||
// })
|
||||
|
||||
|
||||
self.statusDisposable = combineLatest(
|
||||
queue: Queue.mainQueue(),
|
||||
delayedStatus,
|
||||
|
|
@ -592,7 +565,6 @@ final class OverlayAudioPlayerControlsNode: ASDisplayNode {
|
|||
deinit {
|
||||
self.statusDisposable?.dispose()
|
||||
self.chapterDisposable?.dispose()
|
||||
self.scrubbingDisposable?.dispose()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
|
|
@ -788,9 +760,7 @@ final class OverlayAudioPlayerControlsNode: ASDisplayNode {
|
|||
self.currentAlbumArtInitialized = true
|
||||
self.currentAlbumArt = albumArt
|
||||
self.albumArtNode.setSignal(playerAlbumArt(postbox: self.account.postbox, engine: self.engine, fileReference: self.currentFileReference, albumArt: albumArt, thumbnail: true))
|
||||
if let largeAlbumArtNode = self.largeAlbumArtNode {
|
||||
largeAlbumArtNode.setSignal(playerAlbumArt(postbox: self.account.postbox, engine: self.engine, fileReference: self.currentFileReference, albumArt: albumArt, thumbnail: false))
|
||||
}
|
||||
self.requestAlbumArtDisplay?(nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -880,72 +850,8 @@ final class OverlayAudioPlayerControlsNode: ASDisplayNode {
|
|||
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 - 3.0), size: albumArtSize)
|
||||
let previousAlbumArtNodeFrame = self.albumArtNode.frame
|
||||
transition.updateFrame(node: self.albumArtNode, frame: albumArtFrame)
|
||||
|
||||
if self.isExpanded {
|
||||
let largeAlbumArtNode: TransformImageNode
|
||||
var animateIn = false
|
||||
if let current = self.largeAlbumArtNode {
|
||||
largeAlbumArtNode = current
|
||||
} else {
|
||||
animateIn = true
|
||||
largeAlbumArtNode = TransformImageNode()
|
||||
self.largeAlbumArtNode = largeAlbumArtNode
|
||||
self.addSubnode(largeAlbumArtNode)
|
||||
if self.currentAlbumArtInitialized {
|
||||
largeAlbumArtNode.setSignal(playerAlbumArt(postbox: self.account.postbox, engine: self.engine, fileReference: self.currentFileReference, albumArt: self.currentAlbumArt, thumbnail: false))
|
||||
}
|
||||
}
|
||||
|
||||
let albumArtHeight = max(1.0, panelHeight - OverlayAudioPlayerControlsNode.basePanelHeight - 24.0)
|
||||
|
||||
let largeAlbumArtSize = CGSize(width: albumArtHeight, height: albumArtHeight)
|
||||
let makeLargeAlbumArtLayout = largeAlbumArtNode.asyncLayout()
|
||||
let applyLargeAlbumArt = makeLargeAlbumArtLayout(TransformImageArguments(corners: ImageCorners(radius: 4.0), imageSize: largeAlbumArtSize, boundingSize: largeAlbumArtSize, intrinsicInsets: UIEdgeInsets()))
|
||||
applyLargeAlbumArt()
|
||||
|
||||
let largeAlbumArtFrame = CGRect(origin: CGPoint(x: floor((width - largeAlbumArtSize.width) / 2.0), y: 34.0), size: largeAlbumArtSize)
|
||||
|
||||
if animateIn && transition.isAnimated {
|
||||
largeAlbumArtNode.frame = largeAlbumArtFrame
|
||||
transition.animatePositionAdditive(node: largeAlbumArtNode, offset: CGPoint(x: previousAlbumArtNodeFrame.center.x - largeAlbumArtFrame.center.x, y: previousAlbumArtNodeFrame.center.y - largeAlbumArtFrame.center.y))
|
||||
//largeAlbumArtNode.layer.animatePosition(from: CGPoint(x: -50.0, y: 0.0), to: CGPoint(), duration: 0.15, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, additive: true)
|
||||
transition.animateTransformScale(node: largeAlbumArtNode, from: previousAlbumArtNodeFrame.size.height / largeAlbumArtFrame.size.height)
|
||||
largeAlbumArtNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.12)
|
||||
if let copyView = self.albumArtNode.view.snapshotContentTree() {
|
||||
copyView.frame = previousAlbumArtNodeFrame
|
||||
copyView.center = largeAlbumArtFrame.center
|
||||
self.view.insertSubview(copyView, belowSubview: largeAlbumArtNode.view)
|
||||
transition.animatePositionAdditive(layer: copyView.layer, offset: CGPoint(x: previousAlbumArtNodeFrame.center.x - largeAlbumArtFrame.center.x, y: previousAlbumArtNodeFrame.center.y - largeAlbumArtFrame.center.y), completion: { [weak copyView] _ in
|
||||
copyView?.removeFromSuperview()
|
||||
})
|
||||
//copyView.layer.animatePosition(from: CGPoint(x: -50.0, y: 0.0), to: CGPoint(), duration: 0.15, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, additive: true)
|
||||
copyView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.28, removeOnCompletion: false)
|
||||
transition.updateTransformScale(layer: copyView.layer, scale: largeAlbumArtFrame.size.height / previousAlbumArtNodeFrame.size.height)
|
||||
}
|
||||
} else {
|
||||
transition.updateFrame(node: largeAlbumArtNode, frame: largeAlbumArtFrame)
|
||||
}
|
||||
self.albumArtNode.isHidden = true
|
||||
} else if let largeAlbumArtNode = self.largeAlbumArtNode {
|
||||
self.largeAlbumArtNode = nil
|
||||
self.albumArtNode.isHidden = false
|
||||
if transition.isAnimated {
|
||||
transition.animatePosition(node: self.albumArtNode, from: largeAlbumArtNode.frame.center)
|
||||
transition.animateTransformScale(node: self.albumArtNode, from: largeAlbumArtNode.frame.height / self.albumArtNode.frame.height)
|
||||
self.albumArtNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.12)
|
||||
|
||||
transition.updatePosition(node: largeAlbumArtNode, position: self.albumArtNode.frame.center, completion: { [weak largeAlbumArtNode] _ in
|
||||
largeAlbumArtNode?.removeFromSupernode()
|
||||
})
|
||||
largeAlbumArtNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.28, removeOnCompletion: false)
|
||||
transition.updateTransformScale(node: largeAlbumArtNode, scale: self.albumArtNode.frame.height / largeAlbumArtNode.frame.height)
|
||||
} else {
|
||||
largeAlbumArtNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
|
||||
let scrubberVerticalOrigin: CGFloat = infoVerticalOrigin + 58.0
|
||||
let scrubberInset: CGFloat = 9.0
|
||||
|
||||
|
|
@ -1283,15 +1189,16 @@ final class OverlayAudioPlayerControlsNode: ASDisplayNode {
|
|||
}
|
||||
|
||||
@objc func albumArtTap(_ recognizer: UITapGestureRecognizer) {
|
||||
if !"".isEmpty, case .ended = recognizer.state {
|
||||
if case .ended = recognizer.state {
|
||||
if let supernode = self.supernode {
|
||||
let bounds = supernode.bounds
|
||||
if bounds.width > bounds.height {
|
||||
return
|
||||
}
|
||||
}
|
||||
self.isExpanded = !self.isExpanded
|
||||
self.updateIsExpanded?()
|
||||
if let currentFileReference = self.currentFileReference, let currentAlbumArt = self.currentAlbumArt {
|
||||
self.requestAlbumArtDisplay?((currentFileReference, currentAlbumArt))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue