diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index c93eb4a2ab..3fa7236b1c 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -12845,6 +12845,8 @@ Sorry for the inconvenience."; "Chat.ToastStarsSent.Title_1" = "Star sent!"; "Chat.ToastStarsSent.Title_any" = "Stars sent!"; +"Chat.ToastStarsSent.TitleChannel_1" = "Star sent as {name}!"; +"Chat.ToastStarsSent.TitleChannel_any" = "Stars sent as {name}!"; "Chat.ToastStarsSent.AnonymousTitle_1" = "Star sent anonymously!"; "Chat.ToastStarsSent.AnonymousTitle_any" = "Stars sent anonymously!"; @@ -13841,3 +13843,5 @@ Sorry for the inconvenience."; "Stars.Transfer.Terms" = "By purchasing you agree to the [Terms of Service]()."; "Stars.Transfer.Terms_URL" = "https://telegram.org/tos/stars"; + +"StarsTransaction.Commission" = "Commission"; diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index 9bdc79bc6f..c608ff148d 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -238,8 +238,21 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll case .Local: break case .Remote, .Paused: - if let image = cloudFetchIcon { - statusState = .customIcon(image) + var isHLS = false + if let message = self.currentMessage { + for media in message.media { + if let file = media as? TelegramMediaFile { + isHLS = NativeVideoContent.isHLSVideo(file: file) + break + } + } + } + if isHLS { + statusState = .none + } else { + if let image = cloudFetchIcon { + statusState = .customIcon(image) + } } } self.statusNode.transitionToState(statusState, completion: {}) @@ -954,6 +967,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll var messageText = NSMutableAttributedString(string: "") var hasCaption = false + var mediaDuration: Double? for media in message.media { if media is TelegramMediaPaidContent { hasCaption = true @@ -961,6 +975,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll hasCaption = true } else if let file = media as? TelegramMediaFile { hasCaption = file.mimeType.hasPrefix("image/") || file.mimeType.hasPrefix("video/") + mediaDuration = file.duration } else if media is TelegramMediaInvoice { hasCaption = true } @@ -974,6 +989,9 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll } } var text = message.text + if let result = addLocallyGeneratedEntities(text, enabledTypes: [.timecode], entities: entities, mediaDuration: mediaDuration) { + entities = result + } if let translateToLanguage, !text.isEmpty { for attribute in message.attributes { if let attribute = attribute as? TranslationMessageAttribute, !attribute.text.isEmpty, attribute.toLang == translateToLanguage { diff --git a/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift b/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift index 8f520156b5..2288152ed9 100644 --- a/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift +++ b/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift @@ -388,15 +388,14 @@ final class ChatVideoGalleryItemScrubberView: UIView { let targetCloneView = scrubberTransition.makeView() self.addSubview(targetCloneView) targetCloneView.frame = fromRect - scrubberTransition.updateView(targetCloneView, GalleryItemScrubberTransition.Scrubber.TransitionState(sourceSize: fromRect.size, destinationSize: CGSize(width: self.scrubberNode.bounds.width, height: fromRect.height), progress: 0.0), .immediate) + scrubberTransition.updateView(targetCloneView, GalleryItemScrubberTransition.Scrubber.TransitionState(sourceSize: fromRect.size, destinationSize: CGSize(width: self.scrubberNode.bounds.width, height: fromRect.height), progress: 0.0, direction: .in), .immediate) targetCloneView.alpha = 1.0 transition.updateFrame(view: targetCloneView, frame: CGRect(origin: CGPoint(x: self.scrubberNode.frame.minX, y: self.scrubberNode.frame.maxY - fromRect.height - 3.0), size: CGSize(width: self.scrubberNode.bounds.width, height: fromRect.height))) - scrubberTransition.updateView(targetCloneView, GalleryItemScrubberTransition.Scrubber.TransitionState(sourceSize: fromRect.size, destinationSize: CGSize(width: self.scrubberNode.bounds.width, height: fromRect.height), progress: 1.0), transition) + scrubberTransition.updateView(targetCloneView, GalleryItemScrubberTransition.Scrubber.TransitionState(sourceSize: fromRect.size, destinationSize: CGSize(width: self.scrubberNode.bounds.width, height: fromRect.height), progress: 1.0, direction: .in), transition) let scrubberTransitionView = scrubberTransition.view scrubberTransitionView.isHidden = true - ContainedViewLayoutTransition.animated(duration: 0.08, curve: .easeInOut).updateAlpha(layer: targetCloneView.layer, alpha: 0.0, completion: { [weak scrubberTransitionView, weak targetCloneView] _ in - scrubberTransitionView?.isHidden = false + ContainedViewLayoutTransition.animated(duration: 0.08, curve: .easeInOut).updateAlpha(layer: targetCloneView.layer, alpha: 0.0, completion: { [weak targetCloneView] _ in targetCloneView?.removeFromSuperview() }) @@ -428,11 +427,11 @@ final class ChatVideoGalleryItemScrubberView: UIView { let targetCloneView = scrubberTransition.makeView() self.addSubview(targetCloneView) targetCloneView.frame = CGRect(origin: CGPoint(x: self.scrubberNode.frame.minX, y: self.scrubberNode.frame.maxY - toRect.height), size: CGSize(width: self.scrubberNode.bounds.width, height: toRect.height)) - scrubberTransition.updateView(targetCloneView, GalleryItemScrubberTransition.Scrubber.TransitionState(sourceSize: CGSize(width: self.scrubberNode.bounds.width, height: toRect.height), destinationSize: toRect.size, progress: 0.0), .immediate) + scrubberTransition.updateView(targetCloneView, GalleryItemScrubberTransition.Scrubber.TransitionState(sourceSize: CGSize(width: self.scrubberNode.bounds.width, height: toRect.height), destinationSize: toRect.size, progress: 0.0, direction: .out), .immediate) targetCloneView.alpha = 0.0 transition.updateFrame(view: targetCloneView, frame: toRect) - scrubberTransition.updateView(targetCloneView, GalleryItemScrubberTransition.Scrubber.TransitionState(sourceSize: CGSize(width: self.scrubberNode.bounds.width, height: toRect.height), destinationSize: toRect.size, progress: 1.0), transition) + scrubberTransition.updateView(targetCloneView, GalleryItemScrubberTransition.Scrubber.TransitionState(sourceSize: CGSize(width: self.scrubberNode.bounds.width, height: toRect.height), destinationSize: toRect.size, progress: 1.0, direction: .out), transition) let scrubberTransitionView = scrubberTransition.view scrubberTransitionView.isHidden = true transition.updateAlpha(layer: targetCloneView.layer, alpha: 1.0, completion: { [weak scrubberTransitionView] _ in diff --git a/submodules/GalleryUI/Sources/GalleryItemTransitionNode.swift b/submodules/GalleryUI/Sources/GalleryItemTransitionNode.swift index c5b2853b25..e43776052d 100644 --- a/submodules/GalleryUI/Sources/GalleryItemTransitionNode.swift +++ b/submodules/GalleryUI/Sources/GalleryItemTransitionNode.swift @@ -6,18 +6,26 @@ import Display public final class GalleryItemScrubberTransition { public final class Scrubber { public struct TransitionState: Equatable { + public enum Direction { + case `in` + case out + } + public var sourceSize: CGSize public var destinationSize: CGSize public var progress: CGFloat + public var direction: Direction public init( sourceSize: CGSize, destinationSize: CGSize, - progress: CGFloat + progress: CGFloat, + direction: Direction ) { self.sourceSize = sourceSize self.destinationSize = destinationSize self.progress = progress + self.direction = direction } } diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index 2537e21ede..fd2211a549 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -1006,6 +1006,7 @@ private final class PictureInPictureContentImpl: NSObject, PictureInPictureConte guard let overlayController = self.overlayController else { return } + overlayController.removePictureInPictureContent(content: self) self.node.canAttachContent = false if self.didExpand { @@ -1413,7 +1414,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { if playerStatusValue.duration >= 60.0 * 10.0 { var publicLinkPrefix: ShareControllerSubject.PublicLinkPrefix? - if case let .message(message, _) = self.item?.contentInfo, message.id.namespace == Namespaces.Message.Cloud, let peer = message.peers[message.id.peerId] as? TelegramChannel, let username = peer.username { + if case let .message(message, _) = self.item?.contentInfo, message.id.namespace == Namespaces.Message.Cloud, let peer = message.peers[message.id.peerId] as? TelegramChannel, let username = peer.username ?? peer.usernames.first?.username { let visibleString = "t.me/\(username)/\(message.id.id)" publicLinkPrefix = ShareControllerSubject.PublicLinkPrefix( visibleString: visibleString, @@ -1878,23 +1879,12 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { self.mediaPlaybackStateDisposable.set((throttledSignal |> deliverOnMainQueue).start(next: { [weak self] status in - guard let strongSelf = self, let videoNode = strongSelf.videoNode, videoNode.ownsContentNode else { + guard let self else { return } - if let status = status { - let shouldStorePlaybacksState: Bool - shouldStorePlaybacksState = status.duration >= 20.0 - - if shouldStorePlaybacksState { - var timestamp: Double? - if status.timestamp > 5.0 && status.timestamp < status.duration - 5.0 { - timestamp = status.timestamp - } - item.storeMediaPlaybackState(message.id, timestamp, status.baseRate) - } else { - item.storeMediaPlaybackState(message.id, nil, status.baseRate) - } + if let status { + self.maybeStorePlaybackStatus(status: status) } })) } @@ -2007,6 +1997,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { case let .buffering(_, whilePlaying, _, display): displayProgress = display initialBuffering = !whilePlaying + if item.content is HLSVideoContent && display { + initialBuffering = true + } isPaused = !whilePlaying var isStreaming = false if let fetchStatus = strongSelf.fetchStatus { @@ -2101,7 +2094,11 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { if hasStarted || strongSelf.didPause { strongSelf.footerContentNode.content = .playback(paused: true, seekable: seekable) } else if let fetchStatus = fetchStatus, !strongSelf.requiresDownload { - strongSelf.footerContentNode.content = .fetch(status: fetchStatus, seekable: seekable) + if item.content is HLSVideoContent { + strongSelf.footerContentNode.content = .playback(paused: true, seekable: seekable) + } else { + strongSelf.footerContentNode.content = .fetch(status: fetchStatus, seekable: seekable) + } } } else { strongSelf.footerContentNode.content = .playback(paused: false, seekable: seekable) @@ -2369,6 +2366,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { videoNode.seek(0.0) } } else { + if let status = self.playerStatusValue { + self.maybeStorePlaybackStatus(status: status) + } videoNode.continuePlayingWithoutSound() } self.updateDisplayPlaceholder() @@ -2447,6 +2447,30 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } } + private func maybeStorePlaybackStatus(status: MediaPlayerStatus) { + guard let item = self.item else { + return + } + guard let contentInfo = item.contentInfo, case let .message(message, _) = contentInfo else { + return + } + + let shouldStorePlaybacksState: Bool + shouldStorePlaybacksState = status.duration >= 20.0 + + if shouldStorePlaybacksState { + var timestamp: Double? + if status.timestamp > 5.0 && status.timestamp < status.duration - 5.0 { + timestamp = status.timestamp + } else { + timestamp = 0.0 + } + item.storeMediaPlaybackState(message.id, timestamp, status.baseRate) + } else { + item.storeMediaPlaybackState(message.id, nil, status.baseRate) + } + } + private var actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd { if let item = self.item { if !item.isSecret, let content = item.content as? NativeVideoContent, content.duration <= 30 { @@ -2634,6 +2658,10 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } override func animateOut(to node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) { + if let status = self.playerStatusValue { + self.maybeStorePlaybackStatus(status: status) + } + self.isAnimatingOut = true guard let videoNode = self.videoNode else { @@ -2785,6 +2813,10 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { toTransform = CATransform3DScale(videoNode.layer.transform, transformScale, transformScale, 1.0) if videoNode.hasAttachedContext { + if let status = self.playerStatusValue { + self.maybeStorePlaybackStatus(status: status) + } + if self.isPaused || !self.keepSoundOnDismiss { if let item = self.item, item.content is HLSVideoContent { } else { diff --git a/submodules/MediaPlayer/Sources/ChunkMediaPlayerV2.swift b/submodules/MediaPlayer/Sources/ChunkMediaPlayerV2.swift index 96debdd7ce..149bf8f97e 100644 --- a/submodules/MediaPlayer/Sources/ChunkMediaPlayerV2.swift +++ b/submodules/MediaPlayer/Sources/ChunkMediaPlayerV2.swift @@ -205,13 +205,14 @@ public final class ChunkMediaPlayerV2: ChunkMediaPlayer { private var isSoundEnabled: Bool private var isMuted: Bool private var isAmbientMode: Bool - + private var seekId: Int = 0 private var seekTimestamp: Double = 0.0 private var pendingSeekTimestamp: Double? private var pendingContinuePlaybackAfterSeekToTimestamp: Double? private var shouldNotifySeeked: Bool = false private var stoppedAtEnd: Bool = false + private var bufferingStartTime: Double? private var renderSynchronizerRate: Double = 0.0 private var videoIsRequestingMediaData: Bool = false @@ -704,9 +705,21 @@ public final class ChunkMediaPlayerV2: ChunkMediaPlayer { self.triggerRequestMediaData() } + if isBuffering { + if self.bufferingStartTime == nil { + self.bufferingStartTime = CFAbsoluteTimeGetCurrent() + } + } else { + self.bufferingStartTime = nil + } + let playbackStatus: MediaPlayerPlaybackStatus if isBuffering { - playbackStatus = .buffering(initial: false, whilePlaying: self.isPlaying, progress: 0.0, display: true) + var displayBuffering = false + if let bufferingStartTime = self.bufferingStartTime, (CFAbsoluteTimeGetCurrent() - bufferingStartTime) >= 0.3 { + displayBuffering = true + } + playbackStatus = .buffering(initial: false, whilePlaying: self.isPlaying, progress: 0.0, display: displayBuffering) } else if self.isPlaying { playbackStatus = .playing } else { @@ -918,6 +931,7 @@ public final class ChunkMediaPlayerV2: ChunkMediaPlayer { } self.pendingSeekTimestamp = nil + self.stoppedAtEnd = false self.updateInternalState() } } diff --git a/submodules/MediaPlayer/Sources/MediaDataReader.swift b/submodules/MediaPlayer/Sources/MediaDataReader.swift index 6d6686780b..b07c6ec251 100644 --- a/submodules/MediaPlayer/Sources/MediaDataReader.swift +++ b/submodules/MediaPlayer/Sources/MediaDataReader.swift @@ -80,7 +80,11 @@ public final class FFMpegMediaDataReaderV2: MediaDataReader { if (codecName == "h264" || codecName == "hevc") { passthroughDecoder = false + #if targetEnvironment(simulator) + useHardwareAcceleration = false + #else useHardwareAcceleration = true + #endif } if (codecName == "av1" || codecName == "av01") { passthroughDecoder = false diff --git a/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift b/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift index c9ad9ba20a..b15072d563 100644 --- a/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift +++ b/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift @@ -489,7 +489,7 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode { chapterNodesContainer.isUserInteractionEnabled = false chapterNodesContainerImpl = chapterNodesContainer - var chapters = chapters + var chapters = chapters.sorted(by: { $0.start < $1.start }) if let firstChapter = chapters.first, firstChapter.start > 0.0 { chapters.insert(MediaPlayerScrubbingChapter(title: "", start: 0.0), at: 0) } @@ -915,7 +915,7 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode { } let endPosition: CGFloat = max(startPosition, floor(backgroundFrame.width * CGFloat(chapter.start / duration)) - lineWidth / 2.0) let width = endPosition - startPosition - if width < lineWidth * 0.5 { + if width < lineWidth * 0.5 && i != node.chapterNodes.count - 1 { previousChapterNode.frame = CGRect() continue } diff --git a/submodules/ShareController/Sources/ShareController.swift b/submodules/ShareController/Sources/ShareController.swift index 5d870191ef..b2d215f889 100644 --- a/submodules/ShareController/Sources/ShareController.swift +++ b/submodules/ShareController/Sources/ShareController.swift @@ -2055,9 +2055,16 @@ public final class ShareController: ViewController { shareSignals.append(enqueueMessages(account: currentContext.context.account, peerId: peerId, messages: messages)) } case let .media(mediaReference, mediaParameters): + var forwardSourceMessageId: EngineMessage.Id? + if case let .message(message, _) = mediaReference, let sourceMessageId = message.id, (sourceMessageId.peerId.namespace == Namespaces.Peer.CloudUser || sourceMessageId.peerId.namespace == Namespaces.Peer.CloudGroup || sourceMessageId.peerId.namespace == Namespaces.Peer.CloudChannel) { + forwardSourceMessageId = sourceMessageId + } + var sendTextAsCaption = false - if mediaReference.media is TelegramMediaImage || mediaReference.media is TelegramMediaFile { - sendTextAsCaption = true + if forwardSourceMessageId == nil { + if mediaReference.media is TelegramMediaImage || mediaReference.media is TelegramMediaFile { + sendTextAsCaption = true + } } for peerId in peerIds { @@ -2133,8 +2140,8 @@ public final class ShareController: ViewController { if let startAtTimestamp = mediaParameters?.startAtTimestamp, let startAtTimestampNode = strongSelf.controllerNode.startAtTimestampNode, startAtTimestampNode.value { attributes.append(ForwardVideoTimestampAttribute(timestamp: startAtTimestamp)) } - if case let .message(message, _) = mediaReference, let sourceMessageId = message.id, (sourceMessageId.peerId.namespace == Namespaces.Peer.CloudUser || sourceMessageId.peerId.namespace == Namespaces.Peer.CloudGroup || sourceMessageId.peerId.namespace == Namespaces.Peer.CloudChannel) { - messages.append(.forward(source: sourceMessageId, threadId: threadId, grouping: .auto, attributes: attributes, correlationId: nil)) + if let forwardSourceMessageId { + messages.append(.forward(source: forwardSourceMessageId, threadId: threadId, grouping: .auto, attributes: attributes, correlationId: nil)) } else { messages.append(.message(text: sendTextAsCaption ? text : "", attributes: attributes, inlineStickers: [:], mediaReference: mediaReference, threadId: threadId, replyToMessageId: replyToMessageId.flatMap { EngineMessageReplySubject(messageId: $0, quote: nil) }, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])) } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift index 63817c1930..675697a25d 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift @@ -2048,7 +2048,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr } } - if displayInlineScrubber, let videoTimestamp, let file = media as? TelegramMediaFile, let duration = file.duration, duration > 1.0 { + if displayInlineScrubber, videoTimestamp != nil, let file = media as? TelegramMediaFile, let duration = file.duration, duration > 1.0 { let timestampContainerView: UIView if let current = strongSelf.timestampContainerView { timestampContainerView = current @@ -2097,12 +2097,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr let videoTimestampBackgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: imageFrame.height - 3.0), size: CGSize(width: imageFrame.width, height: 3.0)) videoTimestampBackgroundLayer.frame = videoTimestampBackgroundFrame - var fraction = Double(videoTimestamp) / duration - fraction = max(0.0, min(1.0, fraction)) - - let foregroundWidth = round(fraction * videoTimestampBackgroundFrame.width) - let videoTimestampForegroundFrame = CGRect(origin: CGPoint(x: videoTimestampBackgroundFrame.minX, y: videoTimestampBackgroundFrame.minY), size: CGSize(width: foregroundWidth, height: videoTimestampBackgroundFrame.height)) - videoTimestampForegroundLayer.frame = videoTimestampForegroundFrame + strongSelf.updatePlaybackPosition() } else { if let timestampContainerView = strongSelf.timestampContainerView { strongSelf.timestampContainerView = nil @@ -2977,6 +2972,52 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr secretTimer.invalidate() } } + + self.updatePlaybackPosition() + } + + private func updatePlaybackPosition() { + guard let message = self.message else { + return + } + guard let videoTimestampBackgroundLayer = self.videoTimestampBackgroundLayer, let videoTimestampForegroundLayer = self.videoTimestampForegroundLayer else { + return + } + guard let file = self.media as? TelegramMediaFile, let duration = file.duration else { + return + } + + var videoTimestamp: Double? + var storedVideoTimestamp: Double? + for attribute in message.attributes { + if let attribute = attribute as? ForwardVideoTimestampAttribute { + videoTimestamp = Double(attribute.timestamp) + } else if let attribute = attribute as? DerivedDataMessageAttribute { + if let value = attribute.data["mps"]?.get(MediaPlaybackStoredState.self) { + storedVideoTimestamp = value.timestamp + } + } + } + if let storedVideoTimestamp { + videoTimestamp = storedVideoTimestamp + } + + if let playerStatus = self.playerStatus { + videoTimestamp = playerStatus.timestamp + } + + guard let videoTimestamp else { + return + } + + let videoTimestampBackgroundFrame = videoTimestampBackgroundLayer.frame + + var fraction = videoTimestamp / duration + fraction = max(0.0, min(1.0, fraction)) + + let foregroundWidth = floorToScreenPixels(fraction * videoTimestampBackgroundFrame.width) + let videoTimestampForegroundFrame = CGRect(origin: CGPoint(x: videoTimestampBackgroundFrame.minX, y: videoTimestampBackgroundFrame.minY), size: CGSize(width: foregroundWidth, height: videoTimestampBackgroundFrame.height)) + videoTimestampForegroundLayer.frame = videoTimestampForegroundFrame } public func reveal() { @@ -3059,6 +3100,16 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr self.dateAndStatusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } } + if let timestampContainerView = self.timestampContainerView { + if isHidden { + timestampContainerView.isHidden = true + } else { + if timestampContainerView.isHidden { + timestampContainerView.isHidden = false + timestampContainerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + } + } } public func transitionNode(adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { diff --git a/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift b/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift index f2f5a3387e..33a46dbca8 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift @@ -1011,6 +1011,7 @@ private final class ChatSendStarsScreenComponent: Component { private let closeButton = ComponentView() private let title = ComponentView() + private let subtitle = ComponentView() private let descriptionText = ComponentView() private let badgeStars = BadgeStarsView() @@ -1790,6 +1791,18 @@ private final class ChatSendStarsScreenComponent: Component { let title = self.title let descriptionText = self.descriptionText let actionButton = self.actionButton + + let titleSubtitleSpacing: CGFloat = 1.0 + + //TODO:localize + let subtitleSize = self.subtitle.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: "from \(currentMyPeer.compactDisplayTitle)", font: Font.regular(12.0), textColor: environment.theme.list.itemSecondaryTextColor)) + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - leftButtonFrame.maxX * 2.0, height: 100.0) + ) let titleSize = title.update( transition: .immediate, @@ -1799,13 +1812,24 @@ private final class ChatSendStarsScreenComponent: Component { environment: {}, containerSize: CGSize(width: availableSize.width - leftButtonFrame.maxX * 2.0, height: 100.0) ) - let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: floor((56.0 - titleSize.height) * 0.5)), size: titleSize) + + let titleSubtitleHeight = titleSize.height + titleSubtitleSpacing + subtitleSize.height + + let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: floor((56.0 - titleSubtitleHeight) * 0.5)), size: titleSize) if let titleView = title.view { if titleView.superview == nil { self.navigationBarContainer.addSubview(titleView) } transition.setFrame(view: titleView, frame: titleFrame) } + + let subtitleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - subtitleSize.width) * 0.5), y: titleFrame.maxY + titleSubtitleSpacing), size: subtitleSize) + if let subtitleView = subtitle.view { + if subtitleView.superview == nil { + self.navigationBarContainer.addSubview(subtitleView) + } + transition.setFrame(view: subtitleView, frame: subtitleFrame) + } contentHeight += 56.0 contentHeight += 8.0 @@ -1995,17 +2019,37 @@ private final class ChatSendStarsScreenComponent: Component { guard let self, let component = self.component, let peer = topPeer.peer else { return } - if let peerInfoController = component.context.sharedContext.makePeerInfoController( - context: component.context, - updatedPresentationData: nil, - peer: peer._asPeer(), - mode: .generic, - avatarInitiallyExpanded: false, - fromChat: false, - requestsContext: nil - ) { - self.environment?.controller()?.push(peerInfoController) + guard let controller = self.environment?.controller() else { + return } + guard let navigationController = controller.navigationController as? NavigationController else { + return + } + var viewControllers = navigationController.viewControllers + guard let index = viewControllers.firstIndex(where: { $0 === controller }) else { + return + } + + let context = component.context + + if case .user = peer { + if let peerInfoController = context.sharedContext.makePeerInfoController( + context: context, + updatedPresentationData: nil, + peer: peer._asPeer(), + mode: .generic, + avatarInitiallyExpanded: false, + fromChat: false, + requestsContext: nil + ) { + viewControllers.insert(peerInfoController, at: index) + } + } else { + let chatController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(id: peer.id), subject: nil, botStart: nil, mode: .standard(.default), params: nil) + viewControllers.insert(chatController, at: index) + } + navigationController.setViewControllers(viewControllers, animated: true) + controller.dismiss() }, isEnabled: topPeer.peer != nil && topPeer.peer?.id != component.context.account.peerId, animateAlpha: false diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/GifPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/GifPagerContentComponent.swift index 3269639803..0a1e838a2f 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/GifPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/GifPagerContentComponent.swift @@ -576,7 +576,8 @@ public final class GifPagerContentComponent: Component { private let standaloneShimmerEffect: StandaloneShimmerEffect private let backgroundView: BlurredBackgroundView - private var vibrancyEffectView: UIVisualEffectView? + private let backgroundTintView: UIView + private var vibrancyEffectView: UIView? private let mirrorContentScrollView: UIView private let scrollView: ContentScrollView private let scrollClippingView: UIView @@ -598,6 +599,7 @@ public final class GifPagerContentComponent: Component { override init(frame: CGRect) { self.backgroundView = BlurredBackgroundView(color: nil) + self.backgroundTintView = UIView() self.shimmerHostView = PortalSourceView() self.standaloneShimmerEffect = StandaloneShimmerEffect() @@ -620,6 +622,7 @@ public final class GifPagerContentComponent: Component { super.init(frame: frame) + self.backgroundView.addSubview(self.backgroundTintView) self.addSubview(self.backgroundView) self.shimmerHostView.alpha = 0.0 @@ -983,15 +986,15 @@ public final class GifPagerContentComponent: Component { } } else { if self.vibrancyEffectView == nil { - let style: UIBlurEffect.Style - style = .extraLight - let blurEffect = UIBlurEffect(style: style) - let vibrancyEffect = UIVibrancyEffect(blurEffect: blurEffect) - let vibrancyEffectView = UIVisualEffectView(effect: vibrancyEffect) + let vibrancyEffectView = UIView() + vibrancyEffectView.backgroundColor = .white + if let filter = CALayer.luminanceToAlpha() { + vibrancyEffectView.layer.filters = [filter] + } self.vibrancyEffectView = vibrancyEffectView - self.backgroundView.addSubview(vibrancyEffectView) - vibrancyEffectView.contentView.addSubview(self.mirrorContentScrollView) - vibrancyEffectView.contentView.addSubview(self.mirrorSearchHeaderContainer) + self.backgroundTintView.mask = vibrancyEffectView + vibrancyEffectView.addSubview(self.mirrorContentScrollView) + vibrancyEffectView.addSubview(self.mirrorSearchHeaderContainer) } } @@ -1000,7 +1003,11 @@ public final class GifPagerContentComponent: Component { if hideBackground { backgroundColor = backgroundColor.withAlphaComponent(0.01) } - self.backgroundView.updateColor(color: backgroundColor, enableBlur: true, forceKeepBlur: false, transition: transition.containedViewLayoutTransition) + + self.backgroundTintView.backgroundColor = backgroundColor + transition.setFrame(view: self.backgroundTintView, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size)) + + self.backgroundView.updateColor(color: .clear, enableBlur: true, forceKeepBlur: true, transition: transition.containedViewLayoutTransition) transition.setFrame(view: self.backgroundView, frame: backgroundFrame) self.backgroundView.update(size: backgroundFrame.size, transition: transition.containedViewLayoutTransition) diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift index 87b847cbda..de35cf8bc7 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift @@ -1089,7 +1089,7 @@ private final class StarsTransactionSheetContent: CombinedComponent { if let starrefCommissionPermille = transaction.starrefCommissionPermille, transaction.starrefPeerId != nil { tableItems.append(.init( id: "commission", - title: "Commission", + title: strings.StarsTransaction_Commission, component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "\(formatPermille(starrefCommissionPermille))%", font: tableFont, textColor: tableTextColor)) )), insets: UIEdgeInsets(top: 0.0, left: 12.0, bottom: 0.0, right: 5.0) diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift index 8dcfbc918e..f5ccebff92 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift @@ -408,12 +408,9 @@ extension ChatControllerImpl { guard let starsContext = self.context.starsContext else { return } - guard let peerId = self.chatLocation.peerId else { - return - } let _ = (combineLatest( starsContext.state, - self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.ReactionSettings(id: peerId)) + self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.ReactionSettings(id: message.id.peerId)) ) |> take(1) |> deliverOnMainQueue).start(next: { [weak self] state, reactionSettings in @@ -446,7 +443,7 @@ extension ChatControllerImpl { return } - let purchaseScreen = strongSelf.context.sharedContext.makeStarsPurchaseScreen(context: strongSelf.context, starsContext: starsContext, options: options, purpose: .reactions(peerId: peerId, requiredStars: 1), completion: { result in + let purchaseScreen = strongSelf.context.sharedContext.makeStarsPurchaseScreen(context: strongSelf.context, starsContext: starsContext, options: options, purpose: .reactions(peerId: message.id.peerId, requiredStars: 1), completion: { result in let _ = result //TODO:release }) diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift index 974d800cef..7faf9b1ec1 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift @@ -493,46 +493,62 @@ extension ChatControllerImpl { } func displayOrUpdateSendStarsUndo(messageId: EngineMessage.Id, count: Int, privacy: TelegramPaidReactionPrivacy) { - if self.currentSendStarsUndoMessageId != messageId { - if let current = self.currentSendStarsUndoController { - self.currentSendStarsUndoController = nil - current.dismiss() + var privacyPeer: Signal = .single(nil) + if case let .peer(id) = privacy { + privacyPeer = self.context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: id) + ) + } + let _ = (privacyPeer + |> deliverOnMainQueue).startStandalone(next: { [weak self] privacyPeer in + guard let self else { + return } - } - - if let _ = self.currentSendStarsUndoController { - self.currentSendStarsUndoCount += count - } else { - self.currentSendStarsUndoCount = count - } - - let title: String - if case .anonymous = privacy { - title = self.presentationData.strings.Chat_ToastStarsSent_AnonymousTitle(Int32(self.currentSendStarsUndoCount)) - } else { - title = self.presentationData.strings.Chat_ToastStarsSent_Title(Int32(self.currentSendStarsUndoCount)) - } - - let textItems = AnimatedTextComponent.extractAnimatedTextString(string: self.presentationData.strings.Chat_ToastStarsSent_Text("", ""), id: "text", mapping: [ - 0: .number(self.currentSendStarsUndoCount, minDigits: 1), - 1: .text(self.presentationData.strings.Chat_ToastStarsSent_TextStarAmount(Int32(self.currentSendStarsUndoCount))) - ]) - - self.currentSendStarsUndoMessageId = messageId - if let current = self.currentSendStarsUndoController { - current.content = .starsSent(context: self.context, title: title, text: textItems) - } else { - let controller = UndoOverlayController(presentationData: self.presentationData, content: .starsSent(context: self.context, title: title, text: textItems), elevatedLayout: false, position: .top, action: { [weak self] action in - guard let self else { + + if self.currentSendStarsUndoMessageId != messageId { + if let current = self.currentSendStarsUndoController { + self.currentSendStarsUndoController = nil + current.dismiss() + } + } + + if let _ = self.currentSendStarsUndoController { + self.currentSendStarsUndoCount += count + } else { + self.currentSendStarsUndoCount = count + } + + let title: String + if case .anonymous = privacy { + title = self.presentationData.strings.Chat_ToastStarsSent_AnonymousTitle(Int32(self.currentSendStarsUndoCount)) + } else if case .peer = privacy, let privacyPeer { + let rawTitle = self.presentationData.strings.Chat_ToastStarsSent_TitleChannel(Int32(self.currentSendStarsUndoCount)) + title = rawTitle.replacingOccurrences(of: "{name}", with: privacyPeer.compactDisplayTitle) + } else { + title = self.presentationData.strings.Chat_ToastStarsSent_Title(Int32(self.currentSendStarsUndoCount)) + } + + let textItems = AnimatedTextComponent.extractAnimatedTextString(string: self.presentationData.strings.Chat_ToastStarsSent_Text("", ""), id: "text", mapping: [ + 0: .number(self.currentSendStarsUndoCount, minDigits: 1), + 1: .text(self.presentationData.strings.Chat_ToastStarsSent_TextStarAmount(Int32(self.currentSendStarsUndoCount))) + ]) + + self.currentSendStarsUndoMessageId = messageId + if let current = self.currentSendStarsUndoController { + current.content = .starsSent(context: self.context, title: title, text: textItems) + } else { + let controller = UndoOverlayController(presentationData: self.presentationData, content: .starsSent(context: self.context, title: title, text: textItems), elevatedLayout: false, position: .top, action: { [weak self] action in + guard let self else { + return false + } + if case .undo = action { + self.context.engine.messages.cancelPendingSendStarsReaction(id: messageId) + } return false - } - if case .undo = action { - self.context.engine.messages.cancelPendingSendStarsReaction(id: messageId) - } - return false - }) - self.currentSendStarsUndoController = controller - self.present(controller, in: .current) - } + }) + self.currentSendStarsUndoController = controller + self.present(controller, in: .current) + } + }) } }