mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Message to story sharing improvements
This commit is contained in:
parent
2e26c3cdbd
commit
6dcfc09165
12 changed files with 495 additions and 298 deletions
|
|
@ -106,6 +106,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/EntityKeyboard",
|
||||
"//submodules/Camera",
|
||||
"//submodules/TelegramUI/Components/DustEffect",
|
||||
"//submodules/TelegramUI/Components/DynamicCornerRadiusView",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -3082,7 +3082,6 @@ public final class DrawingToolsInteraction {
|
|||
return
|
||||
}
|
||||
|
||||
var isRectangleImage = false
|
||||
var isVideo = false
|
||||
var isAdditional = false
|
||||
var isMessage = false
|
||||
|
|
@ -3090,8 +3089,6 @@ public final class DrawingToolsInteraction {
|
|||
if case let .dualVideoReference(isAdditionalValue) = entity.content {
|
||||
isVideo = true
|
||||
isAdditional = isAdditionalValue
|
||||
} else if case let .image(_, type) = entity.content, case .rectangle = type {
|
||||
isRectangleImage = true
|
||||
} else if case .message = entity.content {
|
||||
isMessage = true
|
||||
}
|
||||
|
|
@ -3154,7 +3151,7 @@ public final class DrawingToolsInteraction {
|
|||
}
|
||||
|
||||
|
||||
if #available(iOS 17.0, *), isRectangleImage {
|
||||
if #available(iOS 17.0, *), let stickerEntity = entityView.entity as? DrawingStickerEntity, stickerEntity.canCutOut {
|
||||
actions.append(ContextMenuAction(content: .text(title: presentationData.strings.Paint_CutOut, accessibilityLabel: presentationData.strings.Paint_CutOut), action: { [weak self, weak entityView] in
|
||||
if let self, let entityView, let entity = entityView.entity as? DrawingStickerEntity, case let .image(image, _) = entity.content {
|
||||
let _ = (cutoutStickerImage(from: image)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import UniversalMediaPlayer
|
|||
import TelegramPresentationData
|
||||
import TelegramUniversalVideoContent
|
||||
import DustEffect
|
||||
import DynamicCornerRadiusView
|
||||
|
||||
private class BlurView: UIVisualEffectView {
|
||||
private func setup() {
|
||||
|
|
@ -66,7 +67,9 @@ public class DrawingStickerEntityView: DrawingEntityView {
|
|||
let imageNode: TransformImageNode
|
||||
var animationNode: DefaultAnimatedStickerNodeImpl?
|
||||
var videoNode: UniversalVideoNode?
|
||||
var videoMaskView: DynamicCornerRadiusView?
|
||||
var animatedImageView: UIImageView?
|
||||
var overlayImageView: UIImageView?
|
||||
var cameraPreviewView: UIView?
|
||||
|
||||
let progressDisposable = MetaDisposable()
|
||||
|
|
@ -154,7 +157,7 @@ public class DrawingStickerEntityView: DrawingEntityView {
|
|||
return file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0)
|
||||
case .dualVideoReference:
|
||||
return CGSize(width: 512.0, height: 512.0)
|
||||
case let .message(_, _, size):
|
||||
case let .message(_, size, _, _, _):
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
|
@ -276,14 +279,17 @@ public class DrawingStickerEntityView: DrawingEntityView {
|
|||
self.animatedImageView = imageView
|
||||
self.addSubview(imageView)
|
||||
self.setNeedsLayout()
|
||||
} else if case .message = self.stickerEntity.content {
|
||||
} else if case let .message(_, _, file, mediaRect, _) = self.stickerEntity.content {
|
||||
if let image = self.stickerEntity.renderImage {
|
||||
self.setupWithImage(image)
|
||||
self.setupWithImage(image, overlayImage: self.stickerEntity.overlayRenderImage)
|
||||
}
|
||||
if let file, let _ = mediaRect {
|
||||
self.setupWithVideo(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func setupWithImage(_ image: UIImage) {
|
||||
private func setupWithImage(_ image: UIImage, overlayImage: UIImage? = nil) {
|
||||
let imageView: UIImageView
|
||||
if let current = self.animatedImageView {
|
||||
imageView = current
|
||||
|
|
@ -294,6 +300,20 @@ public class DrawingStickerEntityView: DrawingEntityView {
|
|||
self.animatedImageView = imageView
|
||||
}
|
||||
imageView.image = image
|
||||
|
||||
if let overlayImage {
|
||||
let imageView: UIImageView
|
||||
if let current = self.overlayImageView {
|
||||
imageView = current
|
||||
} else {
|
||||
imageView = UIImageView()
|
||||
imageView.contentMode = .scaleAspectFit
|
||||
self.addSubview(imageView)
|
||||
self.overlayImageView = imageView
|
||||
}
|
||||
imageView.image = overlayImage
|
||||
}
|
||||
|
||||
self.currentSize = nil
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
|
|
@ -335,6 +355,9 @@ public class DrawingStickerEntityView: DrawingEntityView {
|
|||
videoNode.isUserInteractionEnabled = false
|
||||
videoNode.clipsToBounds = true
|
||||
self.addSubnode(videoNode)
|
||||
if let overlayImageView = self.overlayImageView {
|
||||
self.addSubview(overlayImageView)
|
||||
}
|
||||
self.videoNode = videoNode
|
||||
self.setNeedsLayout()
|
||||
videoNode.play()
|
||||
|
|
@ -579,21 +602,53 @@ public class DrawingStickerEntityView: DrawingEntityView {
|
|||
}
|
||||
|
||||
if let videoNode = self.videoNode {
|
||||
var imageSize = imageSize
|
||||
if case let .message(_, file, _) = self.stickerEntity.content, let dimensions = file?.dimensions {
|
||||
let fittedDimensions = dimensions.cgSize.aspectFitted(boundingSize)
|
||||
imageSize = fittedDimensions
|
||||
videoNode.cornerRadius = 0.0
|
||||
if case let .message(_, size, _, rect, cornerRadius) = self.stickerEntity.content, let rect, let cornerRadius {
|
||||
let baseSize = self.stickerEntity.baseSize
|
||||
let scale = baseSize.width / size.width
|
||||
let scaledRect = CGRect(x: rect.minX * scale, y: rect.minY * scale, width: rect.width * scale, height: rect.height * scale)
|
||||
videoNode.frame = scaledRect
|
||||
videoNode.updateLayout(size: scaledRect.size, transition: .immediate)
|
||||
|
||||
if cornerRadius > 100.0 {
|
||||
videoNode.cornerRadius = cornerRadius * scale
|
||||
} else {
|
||||
videoNode.cornerRadius = 0.0
|
||||
|
||||
let hasRoundBottomCorners = scaledRect.maxY > baseSize.height - 6.0
|
||||
if hasRoundBottomCorners {
|
||||
let maskView: DynamicCornerRadiusView
|
||||
if let current = self.videoMaskView {
|
||||
maskView = current
|
||||
} else {
|
||||
maskView = DynamicCornerRadiusView()
|
||||
self.videoMaskView = maskView
|
||||
videoNode.view.mask = maskView
|
||||
}
|
||||
|
||||
let corners = DynamicCornerRadiusView.Corners(
|
||||
minXMinY: 0.0,
|
||||
maxXMinY: 0.0,
|
||||
minXMaxY: cornerRadius * scale,
|
||||
maxXMaxY: cornerRadius * scale
|
||||
)
|
||||
maskView.update(size: scaledRect.size, corners: corners, transition: .immediate)
|
||||
} else {
|
||||
videoNode.view.mask = nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
videoNode.cornerRadius = floor(imageSize.width * 0.03)
|
||||
videoNode.frame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) * 0.5), y: floor((size.height - imageSize.height) * 0.5)), size: imageSize)
|
||||
videoNode.updateLayout(size: imageSize, transition: .immediate)
|
||||
}
|
||||
videoNode.frame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) * 0.5), y: floor((size.height - imageSize.height) * 0.5)), size: imageSize)
|
||||
videoNode.updateLayout(size: imageSize, transition: .immediate)
|
||||
}
|
||||
|
||||
if let animatedImageView = self.animatedImageView {
|
||||
animatedImageView.frame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) * 0.5), y: floor((size.height - imageSize.height) * 0.5)), size: imageSize)
|
||||
}
|
||||
if let overlayImageView = self.overlayImageView {
|
||||
overlayImageView.frame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) * 0.5), y: floor((size.height - imageSize.height) * 0.5)), size: imageSize)
|
||||
}
|
||||
|
||||
if let cameraPreviewView = self.cameraPreviewView {
|
||||
cameraPreviewView.layer.cornerRadius = imageSize.width / 2.0
|
||||
|
|
@ -717,11 +772,42 @@ public class DrawingStickerEntityView: DrawingEntityView {
|
|||
}
|
||||
|
||||
func getRenderSubEntities() -> [DrawingEntity] {
|
||||
guard case let .message(_, file, _) = self.stickerEntity.content else {
|
||||
return []
|
||||
if case let .message(_, _, file, _, cornerRadius) = self.stickerEntity.content {
|
||||
if let file, let cornerRadius, let videoNode = self.videoNode {
|
||||
let _ = cornerRadius
|
||||
let stickerSize = self.bounds.size
|
||||
let stickerPosition = self.stickerEntity.position
|
||||
let videoSize = videoNode.frame.size
|
||||
let scale = self.stickerEntity.scale
|
||||
let rotation = self.stickerEntity.rotation
|
||||
|
||||
let videoPosition = videoNode.position.offsetBy(dx: -stickerSize.width / 2.0, dy: -stickerSize.height / 2.0)
|
||||
let videoScale = videoSize.width / stickerSize.width
|
||||
|
||||
let videoEntity = DrawingStickerEntity(content: .video(file))
|
||||
videoEntity.referenceDrawingSize = self.stickerEntity.referenceDrawingSize
|
||||
videoEntity.position = stickerPosition.offsetBy(
|
||||
dx: (videoPosition.x * cos(rotation) - videoPosition.y * sin(rotation)) * scale,
|
||||
dy: (videoPosition.y * cos(rotation) + videoPosition.x * sin(rotation)) * scale
|
||||
)
|
||||
videoEntity.scale = scale * videoScale
|
||||
videoEntity.rotation = rotation
|
||||
|
||||
var entities: [DrawingEntity] = []
|
||||
entities.append(videoEntity)
|
||||
|
||||
if let overlayImage = self.stickerEntity.overlayRenderImage {
|
||||
let overlayEntity = DrawingStickerEntity(content: .image(overlayImage, .sticker))
|
||||
overlayEntity.referenceDrawingSize = self.stickerEntity.referenceDrawingSize
|
||||
overlayEntity.position = self.stickerEntity.position
|
||||
overlayEntity.scale = self.stickerEntity.scale
|
||||
overlayEntity.rotation = self.stickerEntity.rotation
|
||||
entities.append(overlayEntity)
|
||||
}
|
||||
|
||||
return entities
|
||||
}
|
||||
}
|
||||
|
||||
let _ = file
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,10 +26,10 @@ private func generateBorderImage(theme: PresentationTheme, bordered: Bool, selec
|
|||
accentColor = UIColor(rgb: 0x999999)
|
||||
}
|
||||
context.setStrokeColor(accentColor.cgColor)
|
||||
lineWidth = 2.0
|
||||
lineWidth = 2.0 - UIScreenPixel
|
||||
} else {
|
||||
context.setStrokeColor(theme.list.disclosureArrowColor.withAlphaComponent(0.4).cgColor)
|
||||
lineWidth = 1.0
|
||||
lineWidth = 1.0 - UIScreenPixel
|
||||
}
|
||||
|
||||
if bordered || selected {
|
||||
|
|
@ -95,10 +95,13 @@ class ThemeSettingsAppIconItem: ListViewItem, ItemListItem {
|
|||
}
|
||||
}
|
||||
|
||||
private let badgeSize = CGSize(width: 24.0, height: 24.0)
|
||||
private let badgeStrokeSize: CGFloat = 2.0
|
||||
|
||||
private final class ThemeSettingsAppIconNode : ASDisplayNode {
|
||||
private let iconNode: ASImageNode
|
||||
private let overlayNode: ASImageNode
|
||||
private let lockNode: ASImageNode
|
||||
fileprivate let lockNode: ASImageNode
|
||||
private let textNode: ImmediateTextNode
|
||||
private var action: (() -> Void)?
|
||||
|
||||
|
|
@ -108,11 +111,11 @@ private final class ThemeSettingsAppIconNode : ASDisplayNode {
|
|||
|
||||
override init() {
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 62.0, height: 62.0))
|
||||
self.iconNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 63.0, height: 63.0))
|
||||
self.iconNode.isLayerBacked = true
|
||||
|
||||
self.overlayNode = ASImageNode()
|
||||
self.overlayNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 62.0, height: 62.0))
|
||||
self.overlayNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 63.0, height: 63.0))
|
||||
self.overlayNode.isLayerBacked = true
|
||||
|
||||
self.lockNode = ASImageNode()
|
||||
|
|
@ -141,7 +144,7 @@ private final class ThemeSettingsAppIconNode : ASDisplayNode {
|
|||
self.iconNode.image = icon
|
||||
self.textNode.attributedText = title
|
||||
self.overlayNode.image = generateBorderImage(theme: theme, bordered: bordered, selected: selected)
|
||||
self.lockNode.image = locked ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: color) : nil
|
||||
self.lockNode.isHidden = !locked
|
||||
self.action = {
|
||||
action()
|
||||
}
|
||||
|
|
@ -172,26 +175,25 @@ private final class ThemeSettingsAppIconNode : ASDisplayNode {
|
|||
super.layout()
|
||||
|
||||
let bounds = self.bounds
|
||||
let iconSize = CGSize(width: 63.0, height: 63.0)
|
||||
|
||||
self.iconNode.frame = CGRect(origin: CGPoint(x: 9.0, y: 14.0), size: CGSize(width: 62.0, height: 62.0))
|
||||
self.overlayNode.frame = CGRect(origin: CGPoint(x: 9.0, y: 14.0), size: CGSize(width: 62.0, height: 62.0))
|
||||
self.iconNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((bounds.width - iconSize.width) / 2.0), y: 13.0), size: iconSize)
|
||||
self.overlayNode.frame = self.iconNode.frame
|
||||
|
||||
let textSize = self.textNode.updateLayout(bounds.size)
|
||||
var textFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((bounds.width - textSize.width)) / 2.0, y: 87.0), size: textSize)
|
||||
if self.locked {
|
||||
textFrame = textFrame.offsetBy(dx: 5.0, dy: 0.0)
|
||||
}
|
||||
let textFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((bounds.width - textSize.width) / 2.0), y: 81.0), size: textSize)
|
||||
self.textNode.frame = textFrame
|
||||
|
||||
self.lockNode.frame = CGRect(x: self.textNode.frame.minX - 10.0, y: 90.0, width: 6.0, height: 8.0)
|
||||
let badgeFinalSize = CGSize(width: badgeSize.width + badgeStrokeSize * 2.0, height: badgeSize.height + badgeStrokeSize * 2.0)
|
||||
self.lockNode.frame = CGRect(x: bounds.width - 24.0, y: 4.0, width: badgeFinalSize.width, height: badgeFinalSize.height)
|
||||
|
||||
self.activateAreaNode.frame = self.bounds
|
||||
self.activateAreaNode.frame = bounds
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private let textFont = Font.regular(12.0)
|
||||
private let selectedTextFont = Font.bold(12.0)
|
||||
private let selectedTextFont = Font.medium(12.0)
|
||||
|
||||
class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
|
|
@ -199,7 +201,7 @@ class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode {
|
|||
private let bottomStripeNode: ASDisplayNode
|
||||
private let maskNode: ASImageNode
|
||||
|
||||
private let scrollNode: ASScrollNode
|
||||
private let containerNode: ASDisplayNode
|
||||
private var nodes: [ThemeSettingsAppIconNode] = []
|
||||
|
||||
private var item: ThemeSettingsAppIconItem?
|
||||
|
|
@ -209,6 +211,8 @@ class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode {
|
|||
return self.item?.tag
|
||||
}
|
||||
|
||||
private var lockImage: UIImage?
|
||||
|
||||
init() {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
|
|
@ -221,35 +225,23 @@ class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode {
|
|||
|
||||
self.maskNode = ASImageNode()
|
||||
|
||||
self.scrollNode = ASScrollNode()
|
||||
self.containerNode = ASDisplayNode()
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
|
||||
self.addSubnode(self.scrollNode)
|
||||
self.addSubnode(self.containerNode)
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
self.scrollNode.view.disablesInteractiveTransitionGestureRecognizer = true
|
||||
self.scrollNode.view.showsHorizontalScrollIndicator = false
|
||||
}
|
||||
|
||||
private func scrollToNode(_ node: ThemeSettingsAppIconNode, animated: Bool) {
|
||||
let bounds = self.scrollNode.view.bounds
|
||||
let frame = node.frame.insetBy(dx: -48.0, dy: 0.0)
|
||||
|
||||
if frame.minX < bounds.minX || frame.maxX > bounds.maxX {
|
||||
self.scrollNode.view.scrollRectToVisible(frame, animated: animated)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func asyncLayout() -> (_ item: ThemeSettingsAppIconItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
return { item, params, neighbors in
|
||||
let contentSize: CGSize
|
||||
let insets: UIEdgeInsets
|
||||
let separatorHeight = UIScreenPixel
|
||||
|
||||
contentSize = CGSize(width: params.width, height: 116.0)
|
||||
let nodeSize = CGSize(width: 74.0, height: 102.0)
|
||||
let height: CGFloat = nodeSize.height * ceil(CGFloat(item.icons.count) / 4.0) + 12.0
|
||||
|
||||
contentSize = CGSize(width: params.width, height: height)
|
||||
insets = itemListNeighborsGroupedInsets(neighbors, params)
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
|
|
@ -257,10 +249,32 @@ class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode {
|
|||
|
||||
return (layout, { [weak self] in
|
||||
if let strongSelf = self {
|
||||
let previousItem = strongSelf.item
|
||||
strongSelf.item = item
|
||||
strongSelf.layoutParams = params
|
||||
|
||||
strongSelf.scrollNode.view.contentInset = UIEdgeInsets()
|
||||
if previousItem?.theme !== item.theme {
|
||||
strongSelf.lockImage = generateImage(CGSize(width: badgeSize.width + badgeStrokeSize, height: badgeSize.height + badgeStrokeSize), contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: .zero, size: size))
|
||||
|
||||
context.setFillColor(item.theme.list.itemBlocksBackgroundColor.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: .zero, size: size))
|
||||
|
||||
context.addEllipse(in: CGRect(origin: .zero, size: size).insetBy(dx: badgeStrokeSize, dy: badgeStrokeSize))
|
||||
context.clip()
|
||||
|
||||
var locations: [CGFloat] = [0.0, 1.0]
|
||||
let colors: [CGColor] = [UIColor(rgb: 0x9076FF).cgColor, UIColor(rgb: 0xB86DEA).cgColor]
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: 0.0), options: CGGradientDrawingOptions())
|
||||
|
||||
if let icon = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: .white) {
|
||||
context.draw(icon.cgImage!, in: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - icon.size.width) / 2.0), y: floorToScreenPixels((size.height - icon.size.height) / 2.0)), size: icon.size), byTiling: false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
|
||||
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
|
|
@ -309,33 +323,37 @@ class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode {
|
|||
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
|
||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
|
||||
|
||||
strongSelf.scrollNode.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 2.0), size: CGSize(width: layoutSize.width - params.leftInset - params.rightInset, height: layoutSize.height))
|
||||
strongSelf.containerNode.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 2.0), size: CGSize(width: layoutSize.width - params.leftInset - params.rightInset, height: layoutSize.height))
|
||||
|
||||
let nodeInset: CGFloat = 4.0
|
||||
let nodeSize = CGSize(width: 80.0, height: 112.0)
|
||||
var nodeOffset = nodeInset
|
||||
let sideInset: CGFloat = 8.0
|
||||
let spacing: CGFloat = floorToScreenPixels((params.width - sideInset * 2.0 - params.leftInset - params.rightInset - nodeSize.width * 4.0) / 3.0)
|
||||
let verticalSpacing: CGFloat = 0.0
|
||||
|
||||
var updated = false
|
||||
var selectedNode: ThemeSettingsAppIconNode?
|
||||
var x: CGFloat = sideInset
|
||||
var y: CGFloat = 0.0
|
||||
|
||||
var i = 0
|
||||
for icon in item.icons {
|
||||
if i > 0 && i % 4 == 0 {
|
||||
x = sideInset
|
||||
y += nodeSize.height + verticalSpacing
|
||||
}
|
||||
let nodeFrame = CGRect(x: x, y: y, width: nodeSize.width, height: nodeSize.height)
|
||||
x += nodeSize.width + spacing
|
||||
|
||||
let imageNode: ThemeSettingsAppIconNode
|
||||
if strongSelf.nodes.count > i {
|
||||
imageNode = strongSelf.nodes[i]
|
||||
} else {
|
||||
imageNode = ThemeSettingsAppIconNode()
|
||||
strongSelf.nodes.append(imageNode)
|
||||
strongSelf.scrollNode.addSubnode(imageNode)
|
||||
updated = true
|
||||
strongSelf.containerNode.addSubnode(imageNode)
|
||||
}
|
||||
imageNode.lockNode.image = strongSelf.lockImage
|
||||
|
||||
if let image = UIImage(named: icon.imageName, in: getAppBundle(), compatibleWith: nil) {
|
||||
let selected = icon.name == item.currentIconName
|
||||
if selected {
|
||||
selectedNode = imageNode
|
||||
}
|
||||
|
||||
|
||||
var name = "Icon"
|
||||
var bordered = true
|
||||
switch icon.name {
|
||||
|
|
@ -369,30 +387,15 @@ class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode {
|
|||
name = icon.name
|
||||
}
|
||||
|
||||
imageNode.setup(theme: item.theme, icon: image, title: NSAttributedString(string: name, font: selected ? selectedTextFont : textFont, textColor: selected ? item.theme.list.itemAccentColor : item.theme.list.itemPrimaryTextColor, paragraphAlignment: .center), locked: !item.isPremium && icon.isPremium, color: item.theme.list.itemPrimaryTextColor, bordered: bordered, selected: selected, action: { [weak self, weak imageNode] in
|
||||
imageNode.setup(theme: item.theme, icon: image, title: NSAttributedString(string: name, font: selected ? selectedTextFont : textFont, textColor: selected ? item.theme.list.itemAccentColor : item.theme.list.itemPrimaryTextColor, paragraphAlignment: .center), locked: !item.isPremium && icon.isPremium, color: item.theme.list.itemPrimaryTextColor, bordered: bordered, selected: selected, action: {
|
||||
item.updated(icon)
|
||||
if let imageNode = imageNode {
|
||||
self?.scrollToNode(imageNode, animated: true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
imageNode.frame = CGRect(origin: CGPoint(x: nodeOffset, y: 0.0), size: nodeSize)
|
||||
nodeOffset += nodeSize.width + 15.0
|
||||
imageNode.frame = nodeFrame
|
||||
|
||||
i += 1
|
||||
}
|
||||
|
||||
if let lastNode = strongSelf.nodes.last {
|
||||
let contentSize = CGSize(width: lastNode.frame.maxX + nodeInset, height: strongSelf.scrollNode.frame.height)
|
||||
if strongSelf.scrollNode.view.contentSize != contentSize {
|
||||
strongSelf.scrollNode.view.contentSize = contentSize
|
||||
}
|
||||
}
|
||||
|
||||
if updated, let selectedNode = selectedNode {
|
||||
strongSelf.scrollToNode(selectedNode, animated: false)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ public enum CodableDrawingEntity: Equatable {
|
|||
reaction: reaction,
|
||||
flags: flags
|
||||
)
|
||||
} else if case let .message(messageIds, _, _) = entity.content, let messageId = messageIds.first {
|
||||
} else if case let .message(messageIds, _, _, _, _) = entity.content, let messageId = messageIds.first {
|
||||
return .channelMessage(coordinates: coordinates, messageId: messageId)
|
||||
} else {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
|||
case animatedImage(Data, UIImage)
|
||||
case video(TelegramMediaFile)
|
||||
case dualVideoReference(Bool)
|
||||
case message([MessageId], TelegramMediaFile?, CGSize)
|
||||
case message([MessageId], CGSize, TelegramMediaFile?, CGRect?, CGFloat?)
|
||||
|
||||
public static func == (lhs: Content, rhs: Content) -> Bool {
|
||||
switch lhs {
|
||||
|
|
@ -67,9 +67,9 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
|||
} else {
|
||||
return false
|
||||
}
|
||||
case let .message(messageIds, innerFile, size):
|
||||
if case .message(messageIds, innerFile, size) = rhs {
|
||||
return true
|
||||
case let .message(lhsMessageIds, lhsSize, lhsFile, lhsMediaFrame, lhsCornerRadius):
|
||||
if case let .message(rhsMessageIds, rhsSize, rhsFile, rhsMediaFrame, rhsCornerRadius) = rhs {
|
||||
return lhsMessageIds == rhsMessageIds && lhsSize == rhsSize && lhsFile?.fileId == rhsFile?.fileId && lhsMediaFrame == rhsMediaFrame && lhsCornerRadius == rhsCornerRadius
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
|
@ -89,14 +89,19 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
|||
case dualVideo
|
||||
case isAdditionalVideo
|
||||
case messageIds
|
||||
case explicitSize
|
||||
case messageFile
|
||||
case messageSize
|
||||
case messageMediaRect
|
||||
case messageMediaCornerRadius
|
||||
case referenceDrawingSize
|
||||
case position
|
||||
case scale
|
||||
case rotation
|
||||
case mirrored
|
||||
case isExplicitlyStatic
|
||||
case canCutOut
|
||||
case renderImage
|
||||
case renderSubEntities
|
||||
}
|
||||
|
||||
public var uuid: UUID
|
||||
|
|
@ -116,12 +121,15 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
|||
public var rotation: CGFloat
|
||||
public var mirrored: Bool
|
||||
|
||||
public var canCutOut = false
|
||||
|
||||
public var isExplicitlyStatic: Bool
|
||||
|
||||
public var color: DrawingColor = DrawingColor.clear
|
||||
public var lineWidth: CGFloat = 0.0
|
||||
|
||||
public var secondaryRenderImage: UIImage?
|
||||
public var overlayRenderImage: UIImage?
|
||||
|
||||
public var center: CGPoint {
|
||||
return self.position
|
||||
|
|
@ -146,7 +154,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
|||
dimensions = file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0)
|
||||
case .dualVideoReference:
|
||||
dimensions = CGSize(width: 512.0, height: 512.0)
|
||||
case let .message(_, _, size):
|
||||
case let .message(_, size, _, _, _):
|
||||
dimensions = size
|
||||
}
|
||||
|
||||
|
|
@ -217,8 +225,11 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
|||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.uuid = try container.decode(UUID.self, forKey: .uuid)
|
||||
if let messageIds = try container.decodeIfPresent([MessageId].self, forKey: .messageIds) {
|
||||
let size = try container.decodeIfPresent(CGSize.self, forKey: .explicitSize) ?? .zero
|
||||
self.content = .message(messageIds, nil, size)
|
||||
let size = try container.decodeIfPresent(CGSize.self, forKey: .messageSize) ?? .zero
|
||||
let file = try container.decodeIfPresent(TelegramMediaFile.self, forKey: .messageFile)
|
||||
let mediaRect = try container.decodeIfPresent(CGRect.self, forKey: .messageMediaRect)
|
||||
let mediaCornerRadius = try container.decodeIfPresent(CGFloat.self, forKey: .messageMediaCornerRadius)
|
||||
self.content = .message(messageIds, size, file, mediaRect, mediaCornerRadius)
|
||||
} else if let _ = try container.decodeIfPresent(Bool.self, forKey: .dualVideo) {
|
||||
let isAdditional = try container.decodeIfPresent(Bool.self, forKey: .isAdditionalVideo) ?? false
|
||||
self.content = .dualVideoReference(isAdditional)
|
||||
|
|
@ -260,9 +271,14 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
|||
self.mirrored = try container.decode(Bool.self, forKey: .mirrored)
|
||||
self.isExplicitlyStatic = try container.decodeIfPresent(Bool.self, forKey: .isExplicitlyStatic) ?? false
|
||||
|
||||
self.canCutOut = try container.decodeIfPresent(Bool.self, forKey: .canCutOut) ?? false
|
||||
|
||||
if let renderImageData = try? container.decodeIfPresent(Data.self, forKey: .renderImage) {
|
||||
self.renderImage = UIImage(data: renderImageData)
|
||||
}
|
||||
if let renderSubEntities = try? container.decodeIfPresent([CodableDrawingEntity].self, forKey: .renderSubEntities) {
|
||||
self.renderSubEntities = renderSubEntities.compactMap { $0.entity as? DrawingStickerEntity }
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
|
|
@ -313,10 +329,12 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
|||
case let .dualVideoReference(isAdditional):
|
||||
try container.encode(true, forKey: .dualVideo)
|
||||
try container.encode(isAdditional, forKey: .isAdditionalVideo)
|
||||
case let .message(messageIds, innerFile, size):
|
||||
case let .message(messageIds, size, file, mediaRect, mediaCornerRadius):
|
||||
try container.encode(messageIds, forKey: .messageIds)
|
||||
let _ = innerFile
|
||||
try container.encode(size, forKey: .explicitSize)
|
||||
try container.encode(size, forKey: .messageSize)
|
||||
try container.encodeIfPresent(file, forKey: .messageFile)
|
||||
try container.encodeIfPresent(mediaRect, forKey: .messageMediaRect)
|
||||
try container.encodeIfPresent(mediaCornerRadius, forKey: .messageMediaCornerRadius)
|
||||
}
|
||||
try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize)
|
||||
try container.encode(self.position, forKey: .position)
|
||||
|
|
@ -325,9 +343,15 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
|||
try container.encode(self.mirrored, forKey: .mirrored)
|
||||
try container.encode(self.isExplicitlyStatic, forKey: .isExplicitlyStatic)
|
||||
|
||||
try container.encode(self.canCutOut, forKey: .canCutOut)
|
||||
|
||||
if let renderImage, let data = renderImage.pngData() {
|
||||
try container.encode(data, forKey: .renderImage)
|
||||
}
|
||||
if let renderSubEntities = self.renderSubEntities {
|
||||
let codableEntities: [CodableDrawingEntity] = renderSubEntities.compactMap { CodableDrawingEntity(entity: $0) }
|
||||
try container.encode(codableEntities, forKey: .renderSubEntities)
|
||||
}
|
||||
}
|
||||
|
||||
public func duplicate(copy: Bool) -> DrawingEntity {
|
||||
|
|
@ -341,6 +365,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
|||
newEntity.rotation = self.rotation
|
||||
newEntity.mirrored = self.mirrored
|
||||
newEntity.isExplicitlyStatic = self.isExplicitlyStatic
|
||||
newEntity.canCutOut = self.canCutOut
|
||||
return newEntity
|
||||
}
|
||||
|
||||
|
|
@ -372,6 +397,9 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
|||
if self.isExplicitlyStatic != other.isExplicitlyStatic {
|
||||
return false
|
||||
}
|
||||
if self.canCutOut != other.canCutOut {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,15 +85,17 @@ public final class DrawingMessageRenderer {
|
|||
private let context: AccountContext
|
||||
private let messages: [Message]
|
||||
private let isNight: Bool
|
||||
private let isOverlay: Bool
|
||||
|
||||
private let messagesContainerNode: ASDisplayNode
|
||||
private var avatarHeaderNode: ListViewItemHeaderNode?
|
||||
private var messageNodes: [ListViewItemNode]?
|
||||
|
||||
init(context: AccountContext, messages: [Message], isNight: Bool = false) {
|
||||
init(context: AccountContext, messages: [Message], isNight: Bool = false, isOverlay: Bool = false) {
|
||||
self.context = context
|
||||
self.messages = messages
|
||||
self.isNight = isNight
|
||||
self.isOverlay = isOverlay
|
||||
|
||||
self.messagesContainerNode = ASDisplayNode()
|
||||
self.messagesContainerNode.clipsToBounds = true
|
||||
|
|
@ -104,25 +106,8 @@ public final class DrawingMessageRenderer {
|
|||
self.addSubnode(self.messagesContainerNode)
|
||||
}
|
||||
|
||||
public func render(completion: @escaping (CGSize, UIImage?) -> Void) {
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let defaultPresentationData = defaultPresentationData()
|
||||
|
||||
var mockPresentationData = PresentationData(
|
||||
strings: presentationData.strings,
|
||||
theme: defaultPresentationTheme,
|
||||
autoNightModeTriggered: false,
|
||||
chatWallpaper: presentationData.chatWallpaper,
|
||||
chatFontSize: defaultPresentationData.chatFontSize,
|
||||
chatBubbleCorners: defaultPresentationData.chatBubbleCorners,
|
||||
listsFontSize: defaultPresentationData.listsFontSize,
|
||||
dateTimeFormat: presentationData.dateTimeFormat,
|
||||
nameDisplayOrder: presentationData.nameDisplayOrder,
|
||||
nameSortOrder: presentationData.nameSortOrder,
|
||||
reduceMotion: false,
|
||||
largeEmoji: true
|
||||
)
|
||||
|
||||
public func render(presentationData: PresentationData, completion: @escaping (CGSize, UIImage?, CGRect?) -> Void) {
|
||||
var mockPresentationData = presentationData
|
||||
if self.isNight {
|
||||
let darkTheme = defaultDarkColorPresentationTheme
|
||||
mockPresentationData = mockPresentationData.withUpdated(theme: darkTheme).withUpdated(chatWallpaper: darkTheme.chat.defaultWallpaper)
|
||||
|
|
@ -132,8 +117,48 @@ public final class DrawingMessageRenderer {
|
|||
let size = self.updateMessagesLayout(layout: layout, presentationData: mockPresentationData)
|
||||
|
||||
Queue.mainQueue().after(0.05, {
|
||||
var mediaRect: CGRect?
|
||||
if let messageNode = self.messageNodes?.first {
|
||||
if self.isOverlay {
|
||||
func hideNonOverlayViews(_ view: UIView) -> Bool {
|
||||
var hasResult = false
|
||||
for view in view.subviews {
|
||||
if view.tag == 0xFACE {
|
||||
hasResult = true
|
||||
} else {
|
||||
if hideNonOverlayViews(view) {
|
||||
hasResult = true
|
||||
} else {
|
||||
view.isHidden = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return hasResult
|
||||
}
|
||||
let _ = hideNonOverlayViews(messageNode.view)
|
||||
} else if !self.isNight {
|
||||
func findMediaView(_ view: UIView) -> UIView? {
|
||||
for view in view.subviews {
|
||||
if let _ = view.asyncdisplaykit_node as? UniversalVideoNode {
|
||||
return view
|
||||
} else {
|
||||
if let result = findMediaView(view) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if let mediaView = findMediaView(messageNode.view) {
|
||||
var rect = mediaView.convert(mediaView.bounds, to: self.messagesContainerNode.view)
|
||||
rect.origin.y = self.messagesContainerNode.frame.height - rect.maxY
|
||||
mediaRect = rect
|
||||
}
|
||||
}
|
||||
}
|
||||
self.generate(size: size) { image in
|
||||
completion(size, image)
|
||||
completion(size, image, mediaRect)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -259,11 +284,25 @@ public final class DrawingMessageRenderer {
|
|||
}
|
||||
}
|
||||
|
||||
public struct Result {
|
||||
public struct MediaFrame {
|
||||
public let rect: CGRect
|
||||
public let cornerRadius: CGFloat
|
||||
}
|
||||
|
||||
public let size: CGSize
|
||||
public let dayImage: UIImage
|
||||
public let nightImage: UIImage
|
||||
public let overlayImage: UIImage
|
||||
public let mediaFrame: MediaFrame?
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
private let messages: [Message]
|
||||
|
||||
private let dayContainerNode: ContainerNode
|
||||
private let nightContainerNode: ContainerNode
|
||||
private let overlayContainerNode: ContainerNode
|
||||
|
||||
public init(context: AccountContext, messages: [Message]) {
|
||||
self.context = context
|
||||
|
|
@ -271,27 +310,58 @@ public final class DrawingMessageRenderer {
|
|||
|
||||
self.dayContainerNode = ContainerNode(context: context, messages: messages)
|
||||
self.nightContainerNode = ContainerNode(context: context, messages: messages, isNight: true)
|
||||
self.overlayContainerNode = ContainerNode(context: context, messages: messages, isOverlay: true)
|
||||
}
|
||||
|
||||
public func render(completion: @escaping (CGSize, UIImage?, UIImage?) -> Void) {
|
||||
public func render(completion: @escaping (Result) -> Void) {
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let defaultPresentationData = defaultPresentationData()
|
||||
|
||||
let mockPresentationData = PresentationData(
|
||||
strings: presentationData.strings,
|
||||
theme: defaultPresentationTheme,
|
||||
autoNightModeTriggered: false,
|
||||
chatWallpaper: presentationData.chatWallpaper,
|
||||
chatFontSize: defaultPresentationData.chatFontSize,
|
||||
chatBubbleCorners: defaultPresentationData.chatBubbleCorners,
|
||||
listsFontSize: defaultPresentationData.listsFontSize,
|
||||
dateTimeFormat: presentationData.dateTimeFormat,
|
||||
nameDisplayOrder: presentationData.nameDisplayOrder,
|
||||
nameSortOrder: presentationData.nameSortOrder,
|
||||
reduceMotion: false,
|
||||
largeEmoji: true
|
||||
)
|
||||
|
||||
var finalSize: CGSize = .zero
|
||||
var dayImage: UIImage?
|
||||
var nightImage: UIImage?
|
||||
var overlayImage: UIImage?
|
||||
var mediaRect: CGRect?
|
||||
|
||||
let completeIfReady = {
|
||||
if let dayImage, let nightImage {
|
||||
completion(finalSize, dayImage, nightImage)
|
||||
if let dayImage, let nightImage, let overlayImage {
|
||||
var cornerRadius: CGFloat = defaultPresentationData.chatBubbleCorners.mainRadius
|
||||
if let mediaRect, mediaRect.width == mediaRect.height, mediaRect.width == 240.0 {
|
||||
cornerRadius = mediaRect.width / 2.0
|
||||
} else if let rect = mediaRect {
|
||||
mediaRect = CGRect(x: rect.minX + 4.0, y: rect.minY, width: rect.width - 6.0, height: rect.height - 1.0)
|
||||
}
|
||||
completion(Result(size: finalSize, dayImage: dayImage, nightImage: nightImage, overlayImage: overlayImage, mediaFrame: mediaRect.flatMap { Result.MediaFrame(rect: $0, cornerRadius: cornerRadius) }))
|
||||
}
|
||||
}
|
||||
self.dayContainerNode.render { size, image in
|
||||
self.dayContainerNode.render(presentationData: mockPresentationData) { size, image, rect in
|
||||
finalSize = size
|
||||
dayImage = image
|
||||
mediaRect = rect
|
||||
completeIfReady()
|
||||
}
|
||||
self.nightContainerNode.render { size, image in
|
||||
finalSize = size
|
||||
self.nightContainerNode.render(presentationData: mockPresentationData) { size, image, _ in
|
||||
nightImage = image
|
||||
completeIfReady()
|
||||
}
|
||||
self.overlayContainerNode.render(presentationData: mockPresentationData) { size, image, _ in
|
||||
overlayImage = image
|
||||
completeIfReady()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,6 +74,16 @@ public final class MediaEditor {
|
|||
public let top: UIColor
|
||||
public let bottom: UIColor
|
||||
|
||||
public init(colors: [UIColor]) {
|
||||
if colors.count == 2 || colors.count == 1 {
|
||||
self.top = colors.first!
|
||||
self.bottom = colors.last!
|
||||
} else {
|
||||
self.top = .black
|
||||
self.bottom = .black
|
||||
}
|
||||
}
|
||||
|
||||
public init(top: UIColor, bottom: UIColor) {
|
||||
self.top = top
|
||||
self.bottom = bottom
|
||||
|
|
@ -110,7 +120,11 @@ public final class MediaEditor {
|
|||
|
||||
private let clock = CMClockGetHostTimeClock()
|
||||
|
||||
private var player: AVPlayer?
|
||||
private var player: AVPlayer? {
|
||||
didSet {
|
||||
|
||||
}
|
||||
}
|
||||
private var playerAudioMix: AVMutableAudioMix?
|
||||
|
||||
private var additionalPlayer: AVPlayer?
|
||||
|
|
@ -146,11 +160,6 @@ public final class MediaEditor {
|
|||
private var textureSourceDisposable: Disposable?
|
||||
|
||||
private let gradientColorsPromise = Promise<GradientColors?>()
|
||||
private var gradientColorsValue: GradientColors? {
|
||||
didSet {
|
||||
self.gradientColorsPromise.set(.single(self.gradientColorsValue))
|
||||
}
|
||||
}
|
||||
public var gradientColors: Signal<GradientColors?, NoError> {
|
||||
return self.gradientColorsPromise.get()
|
||||
}
|
||||
|
|
@ -468,103 +477,53 @@ public final class MediaEditor {
|
|||
return
|
||||
}
|
||||
|
||||
let context = self.context
|
||||
let clock = self.clock
|
||||
if let device = renderTarget.mtlDevice, CVMetalTextureCacheCreate(nil, nil, device, nil, &self.textureCache) != kCVReturnSuccess {
|
||||
print("error")
|
||||
}
|
||||
|
||||
let context = self.context
|
||||
let clock = self.clock
|
||||
let textureSource: Signal<(UIImage?, UIImage?, AVPlayer?, AVPlayer?, GradientColors), NoError>
|
||||
switch subject {
|
||||
case let .image(image, _):
|
||||
let colors = mediaEditorGetGradientColors(from: image)
|
||||
textureSource = .single((image, nil, nil, nil, colors))
|
||||
case let .draft(draft):
|
||||
if draft.isVideo {
|
||||
textureSource = Signal { subscriber in
|
||||
let url = URL(fileURLWithPath: draft.fullPath(engine: context.engine))
|
||||
let asset = AVURLAsset(url: url)
|
||||
|
||||
let playerItem = AVPlayerItem(asset: asset)
|
||||
let player = AVPlayer(playerItem: playerItem)
|
||||
if #available(iOS 15.0, *) {
|
||||
player.sourceClock = clock
|
||||
} else {
|
||||
player.masterClock = clock
|
||||
}
|
||||
player.automaticallyWaitsToMinimizeStalling = false
|
||||
|
||||
if let gradientColors = draft.values.gradientColors {
|
||||
let colors = GradientColors(top: gradientColors.first!, bottom: gradientColors.last!)
|
||||
subscriber.putNext((nil, nil, player, nil, colors))
|
||||
subscriber.putCompletion()
|
||||
|
||||
return EmptyDisposable
|
||||
} else {
|
||||
let imageGenerator = AVAssetImageGenerator(asset: asset)
|
||||
imageGenerator.appliesPreferredTrackTransform = true
|
||||
imageGenerator.maximumSize = CGSize(width: 72, height: 128)
|
||||
imageGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: CMTime(seconds: 0, preferredTimescale: CMTimeScale(30.0)))]) { _, image, _, _, _ in
|
||||
let colors: GradientColors = image.flatMap({ mediaEditorGetGradientColors(from: UIImage(cgImage: $0)) }) ?? GradientColors(top: .black, bottom: .black)
|
||||
subscriber.putNext((nil, nil, player, nil, colors))
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
return ActionDisposable {
|
||||
imageGenerator.cancelAllCGImageGeneration()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
guard let image = UIImage(contentsOfFile: draft.fullPath(engine: context.engine)) else {
|
||||
return
|
||||
}
|
||||
let colors: GradientColors
|
||||
if let gradientColors = draft.values.gradientColors {
|
||||
colors = GradientColors(top: gradientColors.first!, bottom: gradientColors.last!)
|
||||
} else {
|
||||
colors = mediaEditorGetGradientColors(from: image)
|
||||
}
|
||||
textureSource = .single((image, nil, nil, nil, colors))
|
||||
|
||||
struct TextureSourceResult {
|
||||
let image: UIImage?
|
||||
let nightImage: UIImage?
|
||||
let player: AVPlayer?
|
||||
let playerIsReference: Bool
|
||||
let gradientColors: GradientColors
|
||||
|
||||
init(image: UIImage? = nil, nightImage: UIImage? = nil, player: AVPlayer? = nil, playerIsReference: Bool = false, gradientColors: GradientColors) {
|
||||
self.image = image
|
||||
self.nightImage = nightImage
|
||||
self.player = player
|
||||
self.playerIsReference = playerIsReference
|
||||
self.gradientColors = gradientColors
|
||||
}
|
||||
case let .video(path, transitionImage, mirror, _, _, _):
|
||||
let _ = mirror
|
||||
textureSource = Signal { subscriber in
|
||||
let asset = AVURLAsset(url: URL(fileURLWithPath: path))
|
||||
let player = AVPlayer(playerItem: AVPlayerItem(asset: asset))
|
||||
if #available(iOS 15.0, *) {
|
||||
player.sourceClock = clock
|
||||
} else {
|
||||
player.masterClock = clock
|
||||
}
|
||||
player.automaticallyWaitsToMinimizeStalling = false
|
||||
|
||||
// var additionalPlayer: AVPlayer?
|
||||
// if let additionalPath {
|
||||
// let additionalAsset = AVURLAsset(url: URL(fileURLWithPath: additionalPath))
|
||||
// additionalPlayer = AVPlayer(playerItem: AVPlayerItem(asset: additionalAsset))
|
||||
// if #available(iOS 15.0, *) {
|
||||
// additionalPlayer?.sourceClock = clock
|
||||
// } else {
|
||||
// additionalPlayer?.masterClock = clock
|
||||
// }
|
||||
// additionalPlayer?.automaticallyWaitsToMinimizeStalling = false
|
||||
// }
|
||||
|
||||
if let transitionImage {
|
||||
let colors = mediaEditorGetGradientColors(from: transitionImage)
|
||||
//TODO pass mirror
|
||||
subscriber.putNext((nil, nil, player, nil, colors))
|
||||
}
|
||||
|
||||
func makePlayer(asset: AVAsset) -> AVPlayer {
|
||||
let player = AVPlayer(playerItem: AVPlayerItem(asset: asset))
|
||||
if #available(iOS 15.0, *) {
|
||||
player.sourceClock = clock
|
||||
} else {
|
||||
player.masterClock = clock
|
||||
}
|
||||
player.automaticallyWaitsToMinimizeStalling = false
|
||||
return player
|
||||
}
|
||||
|
||||
func textureSourceResult(for asset: AVAsset, gradientColors: GradientColors? = nil) -> Signal<TextureSourceResult, NoError> {
|
||||
return Signal { subscriber in
|
||||
let player = makePlayer(asset: asset)
|
||||
if let gradientColors {
|
||||
subscriber.putNext(TextureSourceResult(player: player, gradientColors: gradientColors))
|
||||
subscriber.putCompletion()
|
||||
|
||||
return EmptyDisposable
|
||||
} else {
|
||||
let imageGenerator = AVAssetImageGenerator(asset: asset)
|
||||
imageGenerator.appliesPreferredTrackTransform = true
|
||||
imageGenerator.maximumSize = CGSize(width: 72, height: 128)
|
||||
imageGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: CMTime(seconds: 0, preferredTimescale: CMTimeScale(30.0)))]) { _, image, _, _, _ in
|
||||
let colors: GradientColors = image.flatMap({ mediaEditorGetGradientColors(from: UIImage(cgImage: $0)) }) ?? GradientColors(top: .black, bottom: .black)
|
||||
//TODO pass mirror
|
||||
subscriber.putNext((nil, nil, player, nil, colors))
|
||||
let gradientColors: GradientColors = image.flatMap({ mediaEditorGetGradientColors(from: UIImage(cgImage: $0)) }) ?? GradientColors(top: .black, bottom: .black)
|
||||
subscriber.putNext(TextureSourceResult(player: player, gradientColors: gradientColors))
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
return ActionDisposable {
|
||||
|
|
@ -572,47 +531,55 @@ public final class MediaEditor {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let textureSource: Signal<TextureSourceResult, NoError>
|
||||
switch subject {
|
||||
case let .image(image, _):
|
||||
textureSource = .single(
|
||||
TextureSourceResult(
|
||||
image: image,
|
||||
gradientColors: mediaEditorGetGradientColors(from: image)
|
||||
)
|
||||
)
|
||||
case let .draft(draft):
|
||||
let gradientColors = draft.values.gradientColors.flatMap { GradientColors(colors: $0) }
|
||||
let fullPath = draft.fullPath(engine: context.engine)
|
||||
if draft.isVideo {
|
||||
let url = URL(fileURLWithPath: fullPath)
|
||||
let asset = AVURLAsset(url: url)
|
||||
textureSource = textureSourceResult(for: asset, gradientColors: gradientColors)
|
||||
} else {
|
||||
guard let image = UIImage(contentsOfFile: fullPath) else {
|
||||
return
|
||||
}
|
||||
textureSource = .single(
|
||||
TextureSourceResult(
|
||||
image: image,
|
||||
gradientColors: gradientColors ?? mediaEditorGetGradientColors(from: image)
|
||||
)
|
||||
)
|
||||
}
|
||||
case let .video(path, _, mirror, _, _, _):
|
||||
//TODO: pass mirror
|
||||
let _ = mirror
|
||||
let asset = AVURLAsset(url: URL(fileURLWithPath: path))
|
||||
textureSource = textureSourceResult(for: asset)
|
||||
case let .asset(asset):
|
||||
textureSource = Signal { subscriber in
|
||||
if asset.mediaType == .video {
|
||||
let options = PHImageRequestOptions()
|
||||
options.deliveryMode = .fastFormat
|
||||
options.isNetworkAccessAllowed = true
|
||||
let requestId = PHImageManager.default().requestImage(for: asset, targetSize: CGSize(width: 128.0, height: 128.0), contentMode: .aspectFit, options: options, resultHandler: { image, info in
|
||||
if let image {
|
||||
if let info {
|
||||
if let cancelled = info[PHImageCancelledKey] as? Bool, cancelled {
|
||||
return
|
||||
}
|
||||
}
|
||||
let colors = mediaEditorGetGradientColors(from: image)
|
||||
PHImageManager.default().requestAVAsset(forVideo: asset, options: nil, resultHandler: { asset, _, _ in
|
||||
if let asset {
|
||||
let playerItem = AVPlayerItem(asset: asset)
|
||||
let player = AVPlayer(playerItem: playerItem)
|
||||
player.automaticallyWaitsToMinimizeStalling = false
|
||||
|
||||
#if targetEnvironment(simulator)
|
||||
let additionalPlayerItem = AVPlayerItem(asset: asset)
|
||||
let additionalPlayer = AVPlayer(playerItem: additionalPlayerItem)
|
||||
additionalPlayer.automaticallyWaitsToMinimizeStalling = false
|
||||
subscriber.putNext((nil, nil, player, additionalPlayer, colors))
|
||||
#else
|
||||
subscriber.putNext((nil, nil, player, nil, colors))
|
||||
#endif
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
return ActionDisposable {
|
||||
PHImageManager.default().cancelImageRequest(requestId)
|
||||
}
|
||||
} else {
|
||||
let options = PHImageRequestOptions()
|
||||
options.deliveryMode = .highQualityFormat
|
||||
options.isNetworkAccessAllowed = true
|
||||
let requestId = PHImageManager.default().requestImage(for: asset, targetSize: CGSize(width: 1920.0, height: 1920.0), contentMode: .aspectFit, options: options, resultHandler: { image, info in
|
||||
let isVideo = asset.mediaType == .video
|
||||
|
||||
let targetSize = isVideo ? CGSize(width: 128.0, height: 128.0) : CGSize(width: 1920.0, height: 1920.0)
|
||||
let options = PHImageRequestOptions()
|
||||
options.deliveryMode = isVideo ? .fastFormat : .highQualityFormat
|
||||
options.isNetworkAccessAllowed = true
|
||||
|
||||
let requestId = PHImageManager.default().requestImage(
|
||||
for: asset,
|
||||
targetSize: targetSize,
|
||||
contentMode: .aspectFit,
|
||||
options: options,
|
||||
resultHandler: { image, info in
|
||||
if let image {
|
||||
var degraded = false
|
||||
if let info {
|
||||
|
|
@ -623,29 +590,63 @@ public final class MediaEditor {
|
|||
degraded = true
|
||||
}
|
||||
}
|
||||
if !degraded {
|
||||
let colors = mediaEditorGetGradientColors(from: image)
|
||||
subscriber.putNext((image, nil, nil, nil, colors))
|
||||
subscriber.putCompletion()
|
||||
if isVideo {
|
||||
PHImageManager.default().requestAVAsset(forVideo: asset, options: nil, resultHandler: { asset, _, _ in
|
||||
if let asset {
|
||||
let player = makePlayer(asset: asset)
|
||||
subscriber.putNext(
|
||||
TextureSourceResult(
|
||||
player: player,
|
||||
gradientColors: mediaEditorGetGradientColors(from: image)
|
||||
)
|
||||
)
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if !degraded {
|
||||
subscriber.putNext(
|
||||
TextureSourceResult(
|
||||
image: image,
|
||||
gradientColors: mediaEditorGetGradientColors(from: image)
|
||||
)
|
||||
)
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return ActionDisposable {
|
||||
PHImageManager.default().cancelImageRequest(requestId)
|
||||
}
|
||||
)
|
||||
return ActionDisposable {
|
||||
PHImageManager.default().cancelImageRequest(requestId)
|
||||
}
|
||||
}
|
||||
case let .message(messageId):
|
||||
textureSource = getChatWallpaperImage(context: self.context, messageId: messageId)
|
||||
|> map { _, image, nightImage in
|
||||
return (image, nightImage, nil, nil, GradientColors(top: .black, bottom: .black))
|
||||
textureSource = self.context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: messageId))
|
||||
|> mapToSignal { message in
|
||||
var player: AVPlayer?
|
||||
if let message {
|
||||
if let maybeFile = message.media.first(where: { $0 is TelegramMediaFile }) as? TelegramMediaFile, maybeFile.isVideo, let path = self.context.account.postbox.mediaBox.completedResourcePath(maybeFile.resource, pathExtension: "mp4") {
|
||||
let asset = AVURLAsset(url: URL(fileURLWithPath: path))
|
||||
player = makePlayer(asset: asset)
|
||||
}
|
||||
}
|
||||
return getChatWallpaperImage(context: self.context, messageId: messageId)
|
||||
|> map { _, image, nightImage in
|
||||
return TextureSourceResult(
|
||||
image: image,
|
||||
nightImage: nightImage,
|
||||
player: player,
|
||||
playerIsReference: true,
|
||||
gradientColors: GradientColors(top: .black, bottom: .black)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.textureSourceDisposable = (textureSource
|
||||
|> deliverOnMainQueue).start(next: { [weak self] sourceAndColors in
|
||||
|> deliverOnMainQueue).start(next: { [weak self] textureSourceResult in
|
||||
if let self {
|
||||
let (image, nightImage, player, additionalPlayer, colors) = sourceAndColors
|
||||
self.renderer.onNextRender = { [weak self] in
|
||||
self?.onFirstDisplay()
|
||||
}
|
||||
|
|
@ -653,25 +654,22 @@ public final class MediaEditor {
|
|||
let textureSource = UniversalTextureSource(renderTarget: renderTarget)
|
||||
|
||||
if case .message = self.self.subject {
|
||||
if let image {
|
||||
self.wallpapers = (image, nightImage ?? image)
|
||||
if let image = textureSourceResult.image {
|
||||
self.wallpapers = (image, textureSourceResult.nightImage ?? image)
|
||||
}
|
||||
}
|
||||
|
||||
self.player = player
|
||||
self.player = textureSourceResult.player
|
||||
self.playerPromise.set(.single(player))
|
||||
|
||||
self.additionalPlayer = additionalPlayer
|
||||
self.additionalPlayerPromise.set(.single(additionalPlayer))
|
||||
|
||||
if let image {
|
||||
if self.values.nightTheme, let nightImage {
|
||||
if let image = textureSourceResult.image {
|
||||
if self.values.nightTheme, let nightImage = textureSourceResult.nightImage {
|
||||
textureSource.setMainInput(.image(nightImage))
|
||||
} else {
|
||||
textureSource.setMainInput(.image(image))
|
||||
}
|
||||
}
|
||||
if let player, let playerItem = player.currentItem {
|
||||
if let player, let playerItem = player.currentItem, !textureSourceResult.playerIsReference {
|
||||
textureSource.setMainInput(.video(playerItem))
|
||||
}
|
||||
if let additionalPlayer, let playerItem = additionalPlayer.currentItem {
|
||||
|
|
@ -679,13 +677,12 @@ public final class MediaEditor {
|
|||
}
|
||||
self.renderer.textureSource = textureSource
|
||||
|
||||
self.gradientColorsValue = colors
|
||||
self.setGradientColors(colors.array)
|
||||
self.setGradientColors(textureSourceResult.gradientColors)
|
||||
|
||||
if player == nil {
|
||||
if let _ = textureSourceResult.player {
|
||||
self.updateRenderChain()
|
||||
// let _ = image
|
||||
self.maybeGeneratePersonSegmentation(image)
|
||||
// self.maybeGeneratePersonSegmentation(image)
|
||||
}
|
||||
|
||||
if let _ = self.values.audioTrack {
|
||||
|
|
@ -697,7 +694,7 @@ public final class MediaEditor {
|
|||
player.isMuted = self.values.videoIsMuted
|
||||
if let trimRange = self.values.videoTrimRange {
|
||||
player.currentItem?.forwardPlaybackEndTime = CMTime(seconds: trimRange.upperBound, preferredTimescale: CMTimeScale(1000))
|
||||
additionalPlayer?.currentItem?.forwardPlaybackEndTime = CMTime(seconds: trimRange.upperBound, preferredTimescale: CMTimeScale(1000))
|
||||
// additionalPlayer?.currentItem?.forwardPlaybackEndTime = CMTime(seconds: trimRange.upperBound, preferredTimescale: CMTimeScale(1000))
|
||||
}
|
||||
|
||||
if let initialSeekPosition = self.initialSeekPosition {
|
||||
|
|
@ -711,7 +708,7 @@ public final class MediaEditor {
|
|||
Queue.mainQueue().justDispatch {
|
||||
let startPlayback = {
|
||||
player.playImmediately(atRate: 1.0)
|
||||
additionalPlayer?.playImmediately(atRate: 1.0)
|
||||
// additionalPlayer?.playImmediately(atRate: 1.0)
|
||||
self.audioPlayer?.playImmediately(atRate: 1.0)
|
||||
self.onPlaybackAction(.play)
|
||||
self.volumeFadeIn = player.fadeVolume(from: 0.0, to: 1.0, duration: 0.4)
|
||||
|
|
@ -1616,9 +1613,10 @@ public final class MediaEditor {
|
|||
}
|
||||
}
|
||||
|
||||
public func setGradientColors(_ gradientColors: [UIColor]) {
|
||||
public func setGradientColors(_ gradientColors: GradientColors) {
|
||||
self.gradientColorsPromise.set(.single(gradientColors))
|
||||
self.updateValues(mode: .skipRendering) { values in
|
||||
return values.withUpdatedGradientColors(gradientColors: gradientColors)
|
||||
return values.withUpdatedGradientColors(gradientColors: gradientColors.array)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -81,7 +81,14 @@ func composerEntitiesForDrawingEntity(postbox: Postbox, textScale: CGFloat, enti
|
|||
return []
|
||||
case .message:
|
||||
if let renderImage = entity.renderImage, let image = CIImage(image: renderImage, options: [.colorSpace: colorSpace]) {
|
||||
return [MediaEditorComposerStaticEntity(image: image, position: entity.position, scale: entity.scale, rotation: entity.rotation, baseSize: entity.baseSize, mirrored: false)]
|
||||
var entities: [MediaEditorComposerEntity] = []
|
||||
entities.append(MediaEditorComposerStaticEntity(image: image, position: entity.position, scale: entity.scale, rotation: entity.rotation, baseSize: entity.baseSize, mirrored: false))
|
||||
if let renderSubEntities = entity.renderSubEntities {
|
||||
for subEntity in renderSubEntities {
|
||||
entities.append(contentsOf: composerEntitiesForDrawingEntity(postbox: postbox, textScale: textScale, entity: subEntity, colorSpace: colorSpace))
|
||||
}
|
||||
}
|
||||
return entities
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2277,7 +2277,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||
var effectiveSubject = subject
|
||||
if case let .draft(draft, _ ) = subject {
|
||||
for entity in draft.values.entities {
|
||||
if case let .sticker(sticker) = entity, case let .message(ids, _, _) = sticker.content {
|
||||
if case let .sticker(sticker) = entity, case let .message(ids, _, _, _, _) = sticker.content {
|
||||
effectiveSubject = .message(ids)
|
||||
break
|
||||
}
|
||||
|
|
@ -2351,7 +2351,6 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||
|
||||
if let entityView = self.entitiesView.getView(for: mediaEntity.uuid) as? DrawingMediaEntityView {
|
||||
self.entitiesView.sendSubviewToBack(entityView)
|
||||
// entityView.previewView = self.previewView
|
||||
entityView.updated = { [weak self, weak mediaEntity] in
|
||||
if let self, let mediaEntity {
|
||||
let rotationDelta = mediaEntity.rotation - initialRotation
|
||||
|
|
@ -2445,10 +2444,13 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||
}
|
||||
}
|
||||
|
||||
let maybeFile = messages.first?.media.first(where: { $0 is TelegramMediaFile }) as? TelegramMediaFile
|
||||
var messageFile: TelegramMediaFile?
|
||||
if let maybeFile = messages.first?.media.first(where: { $0 is TelegramMediaFile }) as? TelegramMediaFile, maybeFile.isVideo, let _ = self.context.account.postbox.mediaBox.completedResourcePath(maybeFile.resource, pathExtension: nil) {
|
||||
messageFile = maybeFile
|
||||
}
|
||||
|
||||
let renderer = DrawingMessageRenderer(context: self.context, messages: messages)
|
||||
renderer.render(completion: { size, dayImage, nightImage in
|
||||
renderer.render(completion: { result in
|
||||
if case .draft = subject, let existingEntityView = self.entitiesView.getView(where: { entityView in
|
||||
if let stickerEntityView = entityView as? DrawingStickerEntityView, case .message = (stickerEntityView.entity as! DrawingStickerEntity).content {
|
||||
return true
|
||||
|
|
@ -2458,17 +2460,19 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||
}) as? DrawingStickerEntityView {
|
||||
existingEntityView.isNightTheme = isNightTheme
|
||||
let messageEntity = existingEntityView.entity as! DrawingStickerEntity
|
||||
messageEntity.renderImage = dayImage
|
||||
messageEntity.secondaryRenderImage = nightImage
|
||||
messageEntity.renderImage = result.dayImage
|
||||
messageEntity.secondaryRenderImage = result.nightImage
|
||||
messageEntity.overlayRenderImage = result.overlayImage
|
||||
existingEntityView.update(animated: false)
|
||||
} else {
|
||||
let messageEntity = DrawingStickerEntity(content: .message(messageIds, maybeFile?.isVideo == true ? maybeFile : nil, size))
|
||||
messageEntity.renderImage = dayImage
|
||||
messageEntity.secondaryRenderImage = nightImage
|
||||
let messageEntity = DrawingStickerEntity(content: .message(messageIds, result.size, messageFile, result.mediaFrame?.rect, result.mediaFrame?.cornerRadius))
|
||||
messageEntity.renderImage = result.dayImage
|
||||
messageEntity.secondaryRenderImage = result.nightImage
|
||||
messageEntity.overlayRenderImage = result.overlayImage
|
||||
messageEntity.referenceDrawingSize = storyDimensions
|
||||
messageEntity.position = CGPoint(x: storyDimensions.width / 2.0, y: storyDimensions.height / 2.0)
|
||||
|
||||
let fraction = max(size.width, size.height) / 353.0
|
||||
let fraction = max(result.size.width, result.size.height) / 353.0
|
||||
messageEntity.scale = min(6.0, 3.3 * fraction)
|
||||
|
||||
if let entityView = self.entitiesView.add(messageEntity, announce: false) as? DrawingStickerEntityView {
|
||||
|
|
@ -3365,7 +3369,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||
|
||||
let completeWithImage: (UIImage) -> Void = { [weak self] image in
|
||||
let updatedImage = roundedImageWithTransparentCorners(image: image, cornerRadius: floor(image.size.width * 0.03))!
|
||||
self?.interaction?.insertEntity(DrawingStickerEntity(content: .image(updatedImage, .rectangle)), scale: 2.5)
|
||||
let entity = DrawingStickerEntity(content: .image(updatedImage, .rectangle))
|
||||
entity.canCutOut = false
|
||||
|
||||
self?.interaction?.insertEntity(entity, scale: 2.5)
|
||||
}
|
||||
|
||||
if let asset = result as? PHAsset {
|
||||
|
|
|
|||
|
|
@ -804,9 +804,9 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||
}
|
||||
|
||||
icons.append(PresentationAppIcon(name: "Premium", imageName: "Premium", isPremium: true))
|
||||
icons.append(PresentationAppIcon(name: "PremiumBlack", imageName: "PremiumBlack", isPremium: true))
|
||||
icons.append(PresentationAppIcon(name: "PremiumTurbo", imageName: "PremiumTurbo", isPremium: true))
|
||||
|
||||
icons.append(PresentationAppIcon(name: "PremiumBlack", imageName: "PremiumBlack", isPremium: true))
|
||||
|
||||
return icons
|
||||
} else {
|
||||
return []
|
||||
|
|
|
|||
|
|
@ -352,7 +352,7 @@ func messageMediaEditingOptions(message: Message) -> MessageMediaEditingOptions
|
|||
case .Sticker:
|
||||
return []
|
||||
case .Animated:
|
||||
return []
|
||||
break
|
||||
case let .Video(_, _, flags, _):
|
||||
if flags.contains(.instantRoundVideo) {
|
||||
return []
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue