diff --git a/submodules/StatisticsUI/Sources/StatsOverviewItem.swift b/submodules/StatisticsUI/Sources/StatsOverviewItem.swift index eaee3235e4..4810e266b4 100644 --- a/submodules/StatisticsUI/Sources/StatsOverviewItem.swift +++ b/submodules/StatisticsUI/Sources/StatsOverviewItem.swift @@ -379,7 +379,7 @@ class StatsOverviewItemNode: ListViewItemNode { middle1RightItemLayoutAndApply = makeMiddle1RightItemLayout( params.width, item.presentationData, - compactNumericCountString(views.forwardCount), + compactNumericCountString(views.forwardCount - Int(item.publicShares ?? 0)), item.presentationData.strings.Stats_Message_PrivateShares, nil ) diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift index aa545c2d84..ed0f632e78 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift @@ -107,8 +107,14 @@ public final class MediaEditor { private let clock = CMClockGetHostTimeClock() private var player: AVPlayer? + private var playerAudioMix: AVMutableAudioMix? + private var additionalPlayer: AVPlayer? + private var additionalPlayerAudioMix: AVMutableAudioMix? + private var audioPlayer: AVPlayer? + private var audioPlayerAudioMix: AVMutableAudioMix? + private var volumeFadeIn: SwiftSignalKit.Timer? private var timeObserver: Any? @@ -901,7 +907,12 @@ public final class MediaEditor { return values.withUpdatedVideoVolume(volume) } - self.player?.volume = Float(volume ?? 1.0) + if let audioMix = self.playerAudioMix, let asset = self.player?.currentItem?.asset { + let audioMixInputParameters = AVMutableAudioMixInputParameters(track: asset.tracks(withMediaType: .audio).first) + audioMixInputParameters.setVolume(Float(volume ?? 1.0), at: .zero) + audioMix.inputParameters = [audioMixInputParameters] + self.player?.currentItem?.audioMix = audioMix + } } public func setVideoIsMirrored(_ videoIsMirrored: Bool) { @@ -1334,6 +1345,7 @@ public final class MediaEditor { self.additionalPlayer = nil self.additionalPlayerPromise.set(.single(nil)) + self.additionalPlayerAudioMix = nil if let textureSource = self.renderer.textureSource as? UniversalTextureSource { textureSource.forceUpdates = true @@ -1376,12 +1388,18 @@ public final class MediaEditor { player.masterClock = clock } player.automaticallyWaitsToMinimizeStalling = false + + let audioMix = AVMutableAudioMix() + let audioMixInputParameters = AVMutableAudioMixInputParameters(track: asset.tracks(withMediaType: .audio).first) + if let volume = self.values.additionalVideoVolume { + audioMixInputParameters.setVolume(Float(volume), at: .zero) + } + audioMix.inputParameters = [audioMixInputParameters] + player.currentItem?.audioMix = audioMix + self.additionalPlayer = player self.additionalPlayerPromise.set(.single(player)) - - if let volume = self.values.additionalVideoVolume { - self.additionalPlayer?.volume = Float(volume) - } + self.additionalPlayerAudioMix = audioMix (self.renderer.textureSource as? UniversalTextureSource)?.setAdditionalInput(.video(playerItem)) } @@ -1417,7 +1435,12 @@ public final class MediaEditor { return values.withUpdatedAdditionalVideoVolume(volume) } - self.additionalPlayer?.volume = Float(volume ?? 1.0) + if let audioMix = self.additionalPlayerAudioMix, let asset = self.additionalPlayer?.currentItem?.asset { + let audioMixInputParameters = AVMutableAudioMixInputParameters(track: asset.tracks(withMediaType: .audio).first) + audioMixInputParameters.setVolume(Float(volume ?? 1.0), at: .zero) + audioMix.inputParameters = [audioMixInputParameters] + self.additionalPlayer?.currentItem?.audioMix = audioMix + } } private func updateAdditionalVideoPlaybackRange() { @@ -1444,6 +1467,7 @@ public final class MediaEditor { self.audioPlayer = nil self.audioPlayerPromise.set(.single(nil)) + self.audioPlayerAudioMix = nil self.audioDelayTimer?.invalidate() self.audioDelayTimer = nil @@ -1465,14 +1489,20 @@ public final class MediaEditor { let audioAsset = AVURLAsset(url: URL(fileURLWithPath: audioPath)) let audioPlayer = AVPlayer(playerItem: AVPlayerItem(asset: audioAsset)) audioPlayer.automaticallyWaitsToMinimizeStalling = false + + let audioMix = AVMutableAudioMix() + let audioMixInputParameters = AVMutableAudioMixInputParameters(track: audioAsset.tracks(withMediaType: .audio).first) + if let volume = self.values.audioTrackVolume { + audioMixInputParameters.setVolume(Float(volume), at: .zero) + } + audioMix.inputParameters = [audioMixInputParameters] + audioPlayer.currentItem?.audioMix = audioMix + self.audioPlayer = audioPlayer self.audioPlayerPromise.set(.single(audioPlayer)) + self.audioPlayerAudioMix = audioMix self.maybeGenerateAudioSamples(asset: audioAsset) - if let volume = self.values.audioTrackVolume { - self.audioPlayer?.volume = Float(volume) - } - self.setupTimeObservers() } @@ -1510,7 +1540,12 @@ public final class MediaEditor { return values.withUpdatedAudioTrackVolume(volume) } - self.audioPlayer?.volume = Float(volume ?? 1.0) + if let audioMix = self.audioPlayerAudioMix, let asset = self.audioPlayer?.currentItem?.asset { + let audioMixInputParameters = AVMutableAudioMixInputParameters(track: asset.tracks(withMediaType: .audio).first) + audioMixInputParameters.setVolume(Float(volume ?? 1.0), at: .zero) + audioMix.inputParameters = [audioMixInputParameters] + self.audioPlayer?.currentItem?.audioMix = audioMix + } } public func setDrawingAndEntities(data: Data?, image: UIImage?, entities: [CodableDrawingEntity]) { diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoExport.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoExport.swift index 4efefba8d5..6a88731dfd 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoExport.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoExport.swift @@ -520,7 +520,7 @@ public final class MediaEditorVideoExport { if let compositionTrack = composition?.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid) { try? compositionTrack.insertTimeRange(CMTimeRange(start: .zero, duration: asset.duration), of: audioAssetTrack, at: .zero) - if let volume = self.configuration.values.videoVolume, volume < 1.0 { + if let volume = self.configuration.values.videoVolume, volume != 1.0 { let trackParameters = AVMutableAudioMixInputParameters(track: compositionTrack) trackParameters.trackID = compositionTrack.trackID trackParameters.setVolume(Float(volume), at: .zero) @@ -558,7 +558,7 @@ public final class MediaEditorVideoExport { if let compositionTrack = composition?.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid) { try? compositionTrack.insertTimeRange(timeRange, of: audioAssetTrack, at: startTime) - if let volume = self.configuration.values.additionalVideoVolume, volume < 1.0 { + if let volume = self.configuration.values.additionalVideoVolume, volume != 1.0 { let trackParameters = AVMutableAudioMixInputParameters(track: compositionTrack) trackParameters.trackID = compositionTrack.trackID trackParameters.setVolume(Float(volume), at: .zero) @@ -582,7 +582,7 @@ public final class MediaEditorVideoExport { if let compositionTrack = composition?.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid) { try? compositionTrack.insertTimeRange(timeRange, of: audioAssetTrack, at: startTime) - if let volume = self.configuration.values.audioTrackVolume, volume < 1.0 { + if let volume = self.configuration.values.audioTrackVolume, volume != 1.0 { let trackParameters = AVMutableAudioMixInputParameters(track: compositionTrack) trackParameters.trackID = compositionTrack.trackID trackParameters.setVolume(Float(volume), at: .zero) diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 15d10ef507..6ac152c06c 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -3450,7 +3450,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate var items: [ContextMenuItem] = [] items.append( - .custom(VolumeSliderContextItem(minValue: 0.0, value: value, valueChanged: { [weak self] value, _ in + .custom(VolumeSliderContextItem(minValue: 0.0, maxValue: 1.5, value: value, valueChanged: { [weak self] value, _ in if let self, let mediaEditor = self.mediaEditor { if trackId == 0 { if mediaEditor.values.videoIsMuted { diff --git a/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/Sources/StoryFooterPanelComponent.swift b/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/Sources/StoryFooterPanelComponent.swift index 004615d863..2ff0324c26 100644 --- a/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/Sources/StoryFooterPanelComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/Sources/StoryFooterPanelComponent.swift @@ -126,7 +126,8 @@ public final class StoryFooterPanelComponent: Component { private var likeStatsText: AnimatedCountLabelView? private var forwardButton: ComponentView? private var repostButton: ComponentView? - + private var forwardStatsText: AnimatedCountLabelView? + private var reactionStatsIcon: UIImageView? private var reactionStatsText: AnimatedCountLabelView? @@ -368,11 +369,11 @@ public final class StoryFooterPanelComponent: Component { var viewCount = 0 var reactionCount = 0 - var repostCount = 0 + var forwardCount = 0 if let views = component.externalViews ?? component.storyItem.views, views.seenCount != 0 { viewCount = views.seenCount reactionCount = views.reactedCount - repostCount = 0 + forwardCount = views.forwardCount } if component.isChannel { @@ -388,9 +389,11 @@ public final class StoryFooterPanelComponent: Component { if component.isChannel { var likeStatsTransition = transition + var forwardStatsTransition = transition if transition.animation.isImmediate, !isFirstTime, let previousComponent, previousComponent.storyItem.id == component.storyItem.id, previousComponent.expandFraction == component.expandFraction { likeStatsTransition = .easeInOut(duration: 0.2) + forwardStatsTransition = .easeInOut(duration: 0.2) } let likeStatsText: AnimatedCountLabelView @@ -491,6 +494,40 @@ public final class StoryFooterPanelComponent: Component { } if component.canShare { + let forwardStatsText: AnimatedCountLabelView + if let current = self.forwardStatsText { + forwardStatsText = current + } else { + forwardStatsTransition = forwardStatsTransition.withAnimation(.none) + forwardStatsText = AnimatedCountLabelView(frame: CGRect()) + forwardStatsText.isUserInteractionEnabled = false + self.forwardStatsText = forwardStatsText + self.externalContainerView.addSubview(forwardStatsText) + } + + let forwardStatsLayout = forwardStatsText.update( + size: CGSize(width: availableSize.width, height: size.height), + segments: [ + .number(forwardCount, NSAttributedString(string: "\(forwardCount)", font: Font.with(size: 15.0, traits: .monospacedNumbers), textColor: .white)) + ], + transition: (isFirstTime || likeStatsTransition.animation.isImmediate) ? .immediate : ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut) + ) + var forwardStatsFrame = CGRect(origin: CGPoint(x: rightContentOffset - forwardStatsLayout.size.width, y: floor((size.height - forwardStatsLayout.size.height) * 0.5)), size: forwardStatsLayout.size) + forwardStatsFrame.origin.y += component.expandFraction * 45.0 + + forwardStatsTransition.setPosition(view: forwardStatsText, position: forwardStatsFrame.center) + forwardStatsTransition.setBounds(view: forwardStatsText, bounds: CGRect(origin: CGPoint(), size: forwardStatsFrame.size)) + var forwardStatsAlpha: CGFloat = (1.0 - component.expandFraction) + if forwardCount == 0 { + forwardStatsAlpha = 0.0 + } + forwardStatsTransition.setAlpha(view: forwardStatsText, alpha: forwardStatsAlpha) + forwardStatsTransition.setScale(view: forwardStatsText, scale: forwardCount == 0 ? 0.001 : 1.0) + + if forwardCount != 0 { + rightContentOffset -= forwardStatsLayout.size.width + 1.0 + } + let repostButton: ComponentView if let current = self.repostButton { repostButton = current @@ -720,7 +757,7 @@ public final class StoryFooterPanelComponent: Component { } } - if repostCount != 0 && !component.isChannel { + if forwardCount != 0 && !component.isChannel { var repostTransition = transition let repostStatsIcon: UIImageView if let current = self.repostStatsIcon { @@ -749,7 +786,7 @@ public final class StoryFooterPanelComponent: Component { let repostStatsLayout = repostStatsText.update( size: CGSize(width: availableSize.width, height: size.height), segments: [ - .number(repostCount, NSAttributedString(string: "\(repostCount)", font: Font.with(size: 15.0, traits: .monospacedNumbers), textColor: .white)) + .number(forwardCount, NSAttributedString(string: "\(forwardCount)", font: Font.with(size: 15.0, traits: .monospacedNumbers), textColor: .white)) ], reducedLetterSpacing: true, transition: (isFirstTime || repostTransition.animation.isImmediate) ? .immediate : ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut) diff --git a/submodules/TelegramUI/Components/VolumeSliderContextItem/Sources/VolumeSliderContextItem.swift b/submodules/TelegramUI/Components/VolumeSliderContextItem/Sources/VolumeSliderContextItem.swift index ad23105f2c..a1c9465026 100644 --- a/submodules/TelegramUI/Components/VolumeSliderContextItem/Sources/VolumeSliderContextItem.swift +++ b/submodules/TelegramUI/Components/VolumeSliderContextItem/Sources/VolumeSliderContextItem.swift @@ -10,17 +10,19 @@ import AnimatedCountLabelNode public final class VolumeSliderContextItem: ContextMenuCustomItem { private let minValue: CGFloat + private let maxValue: CGFloat private let value: CGFloat private let valueChanged: (CGFloat, Bool) -> Void - public init(minValue: CGFloat, value: CGFloat, valueChanged: @escaping (CGFloat, Bool) -> Void) { + public init(minValue: CGFloat, maxValue: CGFloat = 1.0, value: CGFloat, valueChanged: @escaping (CGFloat, Bool) -> Void) { self.minValue = minValue + self.maxValue = maxValue self.value = value self.valueChanged = valueChanged } public func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode { - return VolumeSliderContextItemNode(presentationData: presentationData, getController: getController, minValue: self.minValue, value: self.value, valueChanged: self.valueChanged) + return VolumeSliderContextItemNode(presentationData: presentationData, getController: getController, minValue: self.minValue, maxValue: self.maxValue, value: self.value, valueChanged: self.valueChanged) } } @@ -40,6 +42,7 @@ private final class VolumeSliderContextItemNode: ASDisplayNode, ContextMenuCusto private let foregroundTextNode: ImmediateAnimatedCountLabelNode let minValue: CGFloat + let maxValue: CGFloat var value: CGFloat = 1.0 { didSet { self.updateValue(transition: .animated(duration: 0.2, curve: .spring)) @@ -50,9 +53,10 @@ private final class VolumeSliderContextItemNode: ASDisplayNode, ContextMenuCusto private let hapticFeedback = HapticFeedback() - init(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, minValue: CGFloat, value: CGFloat, valueChanged: @escaping (CGFloat, Bool) -> Void) { + init(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, minValue: CGFloat, maxValue: CGFloat, value: CGFloat, valueChanged: @escaping (CGFloat, Bool) -> Void) { self.presentationData = presentationData self.minValue = minValue + self.maxValue = maxValue self.value = value self.valueChanged = valueChanged @@ -153,8 +157,9 @@ private final class VolumeSliderContextItemNode: ASDisplayNode, ContextMenuCusto private func updateValue(transition: ContainedViewLayoutTransition = .immediate) { let width = self.frame.width + let range = self.maxValue - self.minValue let value = self.value - transition.updateFrameAdditive(node: self.foregroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: value * width, height: self.frame.height))) + transition.updateFrameAdditive(node: self.foregroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: value / range * width, height: self.frame.height))) let stringValue = "\(Int(self.value * 100.0))%" @@ -236,10 +241,10 @@ private final class VolumeSliderContextItemNode: ASDisplayNode, ContextMenuCusto let translation: CGFloat = gestureRecognizer.translation(in: gestureRecognizer.view).x let delta = translation / self.bounds.width - self.value = max(self.minValue, min(1.0, self.value + delta)) + self.value = max(self.minValue, min(self.maxValue, self.value + delta)) gestureRecognizer.setTranslation(CGPoint(), in: gestureRecognizer.view) - if self.value == 1.0 && previousValue != 1.0 { + if self.value == self.maxValue && previousValue != self.maxValue { self.backgroundIconNode.layer.animateScale(from: 1.0, to: 1.1, duration: 0.16, removeOnCompletion: false, completion: { [weak self] _ in if let strongSelf = self { strongSelf.backgroundIconNode.layer.animateScale(from: 1.1, to: 1.0, duration: 0.16) @@ -251,6 +256,8 @@ private final class VolumeSliderContextItemNode: ASDisplayNode, ContextMenuCusto } }) self.hapticFeedback.impact(.soft) + } else if self.maxValue != 1.0 && self.value == 1.0 && previousValue != 1.0 { + self.hapticFeedback.impact(.soft) } else if self.value == 0.0 && previousValue != 0.0 { self.hapticFeedback.impact(.soft) } @@ -260,7 +267,7 @@ private final class VolumeSliderContextItemNode: ASDisplayNode, ContextMenuCusto case .ended: let translation: CGFloat = gestureRecognizer.translation(in: gestureRecognizer.view).x let delta = translation / self.bounds.width - self.value = max(self.minValue, min(1.0, self.value + delta)) + self.value = max(self.minValue, min(self.maxValue, self.value + delta)) self.valueChanged(self.value, true) default: break @@ -269,7 +276,7 @@ private final class VolumeSliderContextItemNode: ASDisplayNode, ContextMenuCusto @objc private func tapGesture(_ gestureRecognizer: UITapGestureRecognizer) { let location = gestureRecognizer.location(in: gestureRecognizer.view) - self.value = max(self.minValue, min(1.0, location.x / self.bounds.width)) + self.value = max(self.minValue, min(self.maxValue, location.x / self.bounds.width)) self.valueChanged(self.value, true) }