Video improvements

This commit is contained in:
Isaac 2024-10-29 14:13:09 +01:00
parent 2f9d5c41e3
commit 817e189709
10 changed files with 240 additions and 84 deletions

View File

@ -58,7 +58,9 @@ swift_library(
"//submodules/TelegramUI/Components/RasterizedCompositionComponent",
"//submodules/TelegramUI/Components/BadgeComponent",
"//submodules/TelegramUI/Components/AnimatedTextComponent",
"//submodules/TelegramUI/Components/LottieComponent",
"//submodules/Components/MultilineTextComponent",
"//submodules/Components/BalancedTextComponent",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/ComponentFlow",
],

View File

@ -5,10 +5,16 @@ import ComponentFlow
import AppBundle
final class GalleryRateToastAnimationComponent: Component {
init() {
let speedFraction: CGFloat
init(speedFraction: CGFloat) {
self.speedFraction = speedFraction
}
static func ==(lhs: GalleryRateToastAnimationComponent, rhs: GalleryRateToastAnimationComponent) -> Bool {
if lhs.speedFraction != rhs.speedFraction {
return false
}
return true
}
@ -16,6 +22,10 @@ final class GalleryRateToastAnimationComponent: Component {
private let itemViewContainer: UIView
private var itemViews: [UIImageView] = []
private var link: SharedDisplayLinkDriver.Link?
private var timeValue: CGFloat = 0.0
private var speedFraction: CGFloat = 1.0
override init(frame: CGRect) {
self.itemViewContainer = UIView()
@ -36,41 +46,49 @@ final class GalleryRateToastAnimationComponent: Component {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.link?.invalidate()
}
private func setupAnimations() {
let beginTime = self.layer.convertTime(CACurrentMediaTime(), from: nil)
for i in 0 ..< self.itemViews.count {
if self.itemViews[i].layer.animation(forKey: "idle-opacity") != nil {
continue
if self.link == nil {
var previousTimestamp = CACurrentMediaTime()
self.link = SharedDisplayLinkDriver.shared.add { [weak self] _ in
guard let self else {
return
}
let timestamp = CACurrentMediaTime()
let deltaMultiplier = 1.0 * (1.0 - self.speedFraction) + 3.0 * self.speedFraction
let deltaTime = (timestamp - previousTimestamp) * deltaMultiplier
previousTimestamp = timestamp
self.timeValue += deltaTime
let duration: CGFloat = 1.2
for i in 0 ..< self.itemViews.count {
var itemFraction = (self.timeValue + CGFloat(i) * 0.1).truncatingRemainder(dividingBy: duration) / duration
if itemFraction >= 0.5 {
itemFraction = (1.0 - itemFraction) / 0.5
} else {
itemFraction = itemFraction / 0.5
}
let itemAlpha = 0.6 * (1.0 - itemFraction) + 1.0 * itemFraction
let itemScale = 0.9 * (1.0 - itemFraction) + 1.1 * itemFraction
self.itemViews[i].alpha = itemAlpha
self.itemViews[i].transform = CGAffineTransformMakeScale(itemScale, itemScale)
}
}
let delay = Double(i) * 0.1
let animation = CABasicAnimation(keyPath: "opacity")
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
animation.beginTime = beginTime + delay
animation.fromValue = 0.6 as NSNumber
animation.toValue = 1.0 as NSNumber
animation.repeatCount = Float.infinity
animation.autoreverses = true
animation.fillMode = .both
animation.duration = 0.4
self.itemViews[i].layer.add(animation, forKey: "idle-opacity")
let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
scaleAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
scaleAnimation.beginTime = beginTime + delay
scaleAnimation.fromValue = 0.9 as NSNumber
scaleAnimation.toValue = 1.1 as NSNumber
scaleAnimation.repeatCount = Float.infinity
scaleAnimation.autoreverses = true
scaleAnimation.fillMode = .both
scaleAnimation.duration = 0.4
self.itemViews[i].layer.add(scaleAnimation, forKey: "idle-scale")
}
}
func update(component: GalleryRateToastAnimationComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.speedFraction = component.speedFraction
let itemSize = self.itemViews[0].image?.size ?? CGSize(width: 10.0, height: 10.0)
let itemSpacing: CGFloat = 1.0
@ -78,7 +96,10 @@ final class GalleryRateToastAnimationComponent: Component {
for i in 0 ..< self.itemViews.count {
let itemFrame = CGRect(origin: CGPoint(x: CGFloat(i) * (itemSize.width + itemSpacing), y: UIScreenPixel), size: itemSize)
self.itemViews[i].frame = itemFrame
self.itemViews[i].center = itemFrame.center
self.itemViews[i].bounds = CGRect(origin: CGPoint(), size: itemFrame.size)
self.itemViews[i].layer.speed = Float(1.0 * (1.0 - component.speedFraction) + 2.0 * component.speedFraction)
}
self.setupAnimations()

View File

@ -3,20 +3,26 @@ import UIKit
import Display
import ComponentFlow
import AppBundle
import MultilineTextComponent
import BalancedTextComponent
import AnimatedTextComponent
import LottieComponent
final class GalleryRateToastComponent: Component {
let rate: Double
let displayTooltip: String?
init(rate: Double) {
init(rate: Double, displayTooltip: String?) {
self.rate = rate
self.displayTooltip = displayTooltip
}
static func ==(lhs: GalleryRateToastComponent, rhs: GalleryRateToastComponent) -> Bool {
if lhs.rate != rhs.rate {
return false
}
if lhs.displayTooltip != rhs.displayTooltip {
return false
}
return true
}
@ -25,6 +31,14 @@ final class GalleryRateToastComponent: Component {
private let text = ComponentView<Empty>()
private let arrows = ComponentView<Empty>()
private var tooltipText: ComponentView<Empty>?
private var tooltipAnimation: ComponentView<Empty>?
private var tooltipIsHidden: Bool = false
private var tooltipTimer: Foundation.Timer?
private weak var state: EmptyComponentState?
override init(frame: CGRect) {
super.init(frame: frame)
}
@ -33,7 +47,13 @@ final class GalleryRateToastComponent: Component {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.tooltipTimer?.invalidate()
}
func update(component: GalleryRateToastComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.state = state
let insets = UIEdgeInsets(top: 5.0, left: 11.0, bottom: 5.0, right: 16.0)
let spacing: CGFloat = 5.0
@ -55,7 +75,7 @@ final class GalleryRateToastComponent: Component {
let textSize = self.text.update(
transition: transition,
component: AnyComponent(AnimatedTextComponent(
font: Font.semibold(17.0),
font: Font.with(size: 17.0, design: .round, weight: .semibold, traits: [.monospacedNumbers]),
color: .white,
items: textItems
)),
@ -63,9 +83,11 @@ final class GalleryRateToastComponent: Component {
containerSize: CGSize(width: 100.0, height: 100.0)
)
var speedFraction = (component.rate - 1.0) / (2.5 - 1.0)
speedFraction = max(0.0, min(1.0, speedFraction))
let arrowsSize = self.arrows.update(
transition: transition,
component: AnyComponent(GalleryRateToastAnimationComponent()),
component: AnyComponent(GalleryRateToastAnimationComponent(speedFraction: speedFraction)),
environment: {},
containerSize: CGSize(width: 200.0, height: 100.0)
)
@ -82,7 +104,7 @@ final class GalleryRateToastComponent: Component {
environment: {},
containerSize: size
)
let backgroundFrame = CGRect(origin: CGPoint(), size: size)
let backgroundFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - size.width) * 0.5), y: 0.0), size: size)
if let backgroundView = self.background.view {
if backgroundView.superview == nil {
self.addSubview(backgroundView)
@ -90,7 +112,7 @@ final class GalleryRateToastComponent: Component {
transition.setFrame(view: backgroundView, frame: backgroundFrame)
}
let textFrame = CGRect(origin: CGPoint(x: insets.left, y: floorToScreenPixels((size.height - textSize.height) * 0.5)), size: textSize)
let textFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + insets.left, y: backgroundFrame.minY + floorToScreenPixels((size.height - textSize.height) * 0.5)), size: textSize)
if let textView = self.text.view {
if textView.superview == nil {
textView.layer.anchorPoint = CGPoint()
@ -100,7 +122,7 @@ final class GalleryRateToastComponent: Component {
textView.bounds = CGRect(origin: CGPoint(), size: textFrame.size)
}
let arrowsFrame = CGRect(origin: CGPoint(x: textFrame.maxX + spacing, y: floorToScreenPixels((size.height - arrowsSize.height) * 0.5)), size: arrowsSize)
let arrowsFrame = CGRect(origin: CGPoint(x: textFrame.maxX + spacing, y: backgroundFrame.minY + floorToScreenPixels((size.height - arrowsSize.height) * 0.5)), size: arrowsSize)
if let arrowsView = self.arrows.view {
if arrowsView.superview == nil {
self.addSubview(arrowsView)
@ -108,7 +130,97 @@ final class GalleryRateToastComponent: Component {
transition.setFrame(view: arrowsView, frame: arrowsFrame)
}
return size
if let displayTooltip = component.displayTooltip {
var tooltipTransition = transition
let tooltipText: ComponentView<Empty>
if let current = self.tooltipText {
tooltipText = current
} else {
tooltipText = ComponentView()
self.tooltipText = tooltipText
tooltipTransition = tooltipTransition.withAnimation(.none)
}
let tooltipAnimation: ComponentView<Empty>
if let current = self.tooltipAnimation {
tooltipAnimation = current
} else {
tooltipAnimation = ComponentView()
self.tooltipAnimation = tooltipAnimation
}
let tooltipTextSize = tooltipText.update(
transition: .immediate,
component: AnyComponent(BalancedTextComponent(
text: .plain(NSAttributedString(string: displayTooltip, font: Font.regular(15.0), textColor: UIColor(white: 1.0, alpha: 0.8))),
horizontalAlignment: .center,
maximumNumberOfLines: 0
)),
environment: {},
containerSize: CGSize(width: availableSize.width - 8.0 * 2.0, height: 1000.0)
)
let tooltipTextFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - tooltipTextSize.width) * 0.5), y: backgroundFrame.maxY + 10.0), size: tooltipTextSize)
if let tooltipTextView = tooltipText.view {
if tooltipTextView.superview == nil {
self.addSubview(tooltipTextView)
}
tooltipTransition.setPosition(view: tooltipTextView, position: tooltipTextFrame.center)
tooltipTextView.bounds = CGRect(origin: CGPoint(), size: tooltipTextFrame.size)
transition.setAlpha(view: tooltipTextView, alpha: self.tooltipIsHidden ? 0.0 : 1.0)
}
let tooltipAnimationSize = tooltipAnimation.update(
transition: .immediate,
component: AnyComponent(LottieComponent(
content: LottieComponent.AppBundleContent(name: "video_toast_speedup"),
color: .white,
startingPosition: .begin,
loop: true
)),
environment: {},
containerSize: CGSize(width: 60.0, height: 60.0)
)
let tooltipAnimationFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - tooltipAnimationSize.width) * 0.5), y: tooltipTextFrame.maxY + 10.0), size: tooltipAnimationSize)
if let tooltipAnimationView = tooltipAnimation.view {
if tooltipAnimationView.superview == nil {
self.addSubview(tooltipAnimationView)
}
tooltipTransition.setFrame(view: tooltipAnimationView, frame: tooltipAnimationFrame)
transition.setAlpha(view: tooltipAnimationView, alpha: self.tooltipIsHidden ? 0.0 : 0.8)
}
} else {
if let tooltipText = self.tooltipText {
self.tooltipText = nil
if let tooltipTextView = tooltipText.view {
transition.setAlpha(view: tooltipTextView, alpha: 0.0, completion: { [weak tooltipTextView] _ in
tooltipTextView?.removeFromSuperview()
})
}
}
if let tooltipAnimation = self.tooltipAnimation {
self.tooltipAnimation = nil
if let tooltipAnimationView = tooltipAnimation.view {
transition.setAlpha(view: tooltipAnimationView, alpha: 0.0, completion: { [weak tooltipAnimationView] _ in
tooltipAnimationView?.removeFromSuperview()
})
}
}
}
if self.tooltipTimer == nil {
self.tooltipTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false, block: { [weak self] _ in
guard let self else {
return
}
self.tooltipIsHidden = true
self.state?.updated(transition: .easeInOut(duration: 0.25), isLocal: true)
})
}
return CGSize(width: availableSize.width, height: size.height)
}
}

View File

@ -1619,13 +1619,15 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
activeEdgeRateIndicatorTransition = .immediate
}
//TODO:localize
let activeEdgeRateIndicatorSize = activeEdgeRateIndicator.update(
transition: ComponentTransition(activeEdgeRateIndicatorTransition),
component: AnyComponent(GalleryRateToastComponent(
rate: activeEdgeRateState.currentRate
rate: activeEdgeRateState.currentRate,
displayTooltip: "Swipe sideways to adjust speed."
)),
environment: {},
containerSize: CGSize(width: 200.0, height: 100.0)
containerSize: CGSize(width: layout.size.width - layout.safeInsets.left * 2.0, height: 100.0)
)
let activeEdgeRateIndicatorFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - activeEdgeRateIndicatorSize.width) * 0.5), y: max(navigationBarHeight, layout.statusBarHeight ?? 0.0) + 8.0), size: activeEdgeRateIndicatorSize)
if let activeEdgeRateIndicatorView = activeEdgeRateIndicator.view {

View File

@ -362,7 +362,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
selectedStarsForeground: themeColors.reactionStarsActiveForeground.argb,
extractedBackground: arguments.presentationData.theme.theme.contextMenu.backgroundColor.argb,
extractedForeground: arguments.presentationData.theme.theme.contextMenu.primaryColor.argb,
extractedSelectedForeground: arguments.presentationData.theme.theme.overallDarkAppearance ? themeColors.reactionActiveForeground.argb : arguments.presentationData.theme.theme.list.itemCheckColors.foregroundColor.argb,
extractedSelectedForeground: arguments.presentationData.theme.theme.overallDarkAppearance ? themeColors.reactionActiveForeground.argb : arguments.presentationData.theme.theme.contextMenu.primaryColor.argb,
deselectedMediaPlaceholder: themeColors.reactionInactiveMediaPlaceholder.argb,
selectedMediaPlaceholder: themeColors.reactionActiveMediaPlaceholder.argb
)

File diff suppressed because one or more lines are too long

View File

@ -4613,7 +4613,7 @@ extension ChatControllerImpl {
title: "Improving video...",
text: "The video will be published after it's optimized for the bese viewing experience.",
customUndoText: nil,
timeout: 3.5
timeout: 5.0
),
elevatedLayout: false,
position: .top,

View File

@ -10358,7 +10358,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let location = CGPoint(x: bounds.midX, y: bounds.minY - 8.0)
//TODO:localize
let tooltipController = TooltipController(content: .text("Processing video may take a few minutes."), baseFontSize: self.presentationData.listsFontSize.baseDisplaySize, balancedTextLayout: true, isBlurred: true, timeout: 3.5, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true)
let tooltipController = TooltipController(content: .text("Processing video may take a few minutes."), baseFontSize: self.presentationData.listsFontSize.baseDisplaySize, balancedTextLayout: true, isBlurred: true, timeout: 4.5, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true)
self.checksTooltipController = tooltipController
tooltipController.dismissed = { [weak self, weak tooltipController] _ in
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.checksTooltipController === tooltipController {

View File

@ -223,23 +223,6 @@ func chatHistoryEntriesForView(
}
if groupMessages || reverseGroupedMessages {
/*if !groupBucket.isEmpty && message.groupInfo != groupBucket[0].0.groupInfo {
if reverseGroupedMessages {
groupBucket.reverse()
}
if groupMessages {
let groupStableId = groupBucket[0].0.groupInfo!.stableId
if !existingGroupStableIds.contains(groupStableId) {
existingGroupStableIds.append(groupStableId)
entries.append(.MessageGroupEntry(groupBucket[0].0.groupInfo!, groupBucket, presentationData))
}
} else {
for (message, isRead, selection, attributes, location) in groupBucket {
entries.append(.MessageEntry(message, presentationData, isRead, location, selection, attributes))
}
}
groupBucket.removeAll()
}*/
if let messageGroupingKey = message.groupingKey, (groupMessages || reverseGroupedMessages) {
let selection: ChatHistoryMessageSelection
if let selectedMessages = selectedMessages {
@ -286,14 +269,6 @@ func chatHistoryEntriesForView(
if !found {
entries.append(.MessageEntry(message, presentationData, isRead, entry.location, selection, attributes))
}
/*let selection: ChatHistoryMessageSelection
if let selectedMessages = selectedMessages {
selection = .selectable(selected: selectedMessages.contains(message.id))
} else {
selection = .none
}
groupBucket.append((message, isRead, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: false, isCentered: false, authorStoryStats: message.author.flatMap { view.peerStoryStats[$0.id] }), entry.location))*/
} else {
let selection: ChatHistoryMessageSelection
if let selectedMessages = selectedMessages {
@ -316,27 +291,64 @@ func chatHistoryEntriesForView(
} else {
selection = .none
}
entries.append(.MessageEntry(message, presentationData, isRead, entry.location, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: message.index == associatedData.currentlyPlayingMessageId, isCentered: false, authorStoryStats: message.author.flatMap { view.peerStoryStats[$0.id] })))
}
}
/*if !groupBucket.isEmpty {
assert(groupMessages || reverseGroupedMessages)
if reverseGroupedMessages {
groupBucket.reverse()
}
if groupMessages {
let groupStableId = groupBucket[0].0.groupInfo!.stableId
if !existingGroupStableIds.contains(groupStableId) {
existingGroupStableIds.append(groupStableId)
entries.append(.MessageGroupEntry(groupBucket[0].0.groupInfo!, groupBucket, presentationData))
let insertPendingProcessingMessage: ([Message], Int) -> Void = { messages, index in
//TODO:localize
let serviceMessage = Message(
stableId: UInt32.max - messages[0].stableId,
stableVersion: 0,
id: MessageId(peerId: messages[0].id.peerId, namespace: -1, id: messages[0].id.id),
globallyUniqueId: nil,
groupingKey: nil,
groupInfo: nil,
threadId: nil,
timestamp: messages[0].timestamp,
flags: [.Incoming],
tags: [],
globalTags: [],
localTags: [],
customTags: [],
forwardInfo: nil,
author: nil,
text: "",
attributes: [],
media: [TelegramMediaAction(action: .customText(text: "This video will be published once converted and optimized", entities: [], additionalAttributes: nil))],
peers: SimpleDictionary<PeerId, Peer>(),
associatedMessages: SimpleDictionary<MessageId, Message>(),
associatedMessageIds: [],
associatedMedia: [:],
associatedThreadInfo: nil,
associatedStories: [:]
)
entries.insert(.MessageEntry(serviceMessage, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false, authorStoryStats: nil)), at: index)
}
for i in (0 ..< entries.count).reversed() {
switch entries[i] {
case let .MessageEntry(message, _, _, _, _, _):
if message.id.namespace == Namespaces.Message.ScheduledCloud && message.pendingProcessingAttribute != nil {
insertPendingProcessingMessage([message], i)
}
} else {
for (message, isRead, selection, attributes, location) in groupBucket {
entries.append(.MessageEntry(message, presentationData, isRead, location, selection, attributes))
case let .MessageGroupEntry(_, messages, _):
if !messages.isEmpty && messages[0].0.id.namespace == Namespaces.Message.ScheduledCloud {
var videoCount = 0
for message in messages {
if message.0.pendingProcessingAttribute != nil {
videoCount += 1
}
}
if videoCount != 0 {
insertPendingProcessingMessage(messages.map(\.0), i)
}
}
default:
break
}
}*/
}
if let lowerTimestamp = view.entries.last?.message.timestamp, let upperTimestamp = view.entries.first?.message.timestamp {
if let joinMessage {
@ -657,6 +669,9 @@ func chatHistoryEntriesForView(
if reverse {
return (entries.reversed(), currentState)
} else {
#if DEBUG
assert(entries.map(\.stableId) == entries.sorted().map(\.stableId))
#endif
return (entries, currentState)
}
}

View File

@ -476,6 +476,9 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
guard let interfaceInteraction = interfaceInteraction, let controllerInteraction = controllerInteraction else {
return .single(ContextController.Items(content: .list([])))
}
if let message = messages.first, message.id.namespace < 0 {
return .single(ContextController.Items(content: .list([])))
}
var isEmbeddedMode = false
if case .standard(.embedded) = chatPresentationInterfaceState.mode {