Cherry-pick fixes

This commit is contained in:
Ilya Laktyushin 2026-04-01 00:59:30 +02:00
parent b3d4c97da4
commit d0d3b064ed
17 changed files with 223 additions and 158 deletions

View file

@ -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.";

View file

@ -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) {

View file

@ -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)))
}
}
}
}

View file

@ -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
}

View file

@ -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

View file

@ -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))

View file

@ -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(

View file

@ -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])

View file

@ -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)

View file

@ -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 {

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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,

View file

@ -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)
}

View file

@ -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))
}
}
}