Message to story sharing improvements

This commit is contained in:
Ilya Laktyushin 2023-12-27 22:27:49 +04:00
parent 2e26c3cdbd
commit 6dcfc09165
12 changed files with 495 additions and 298 deletions

View file

@ -106,6 +106,7 @@ swift_library(
"//submodules/TelegramUI/Components/EntityKeyboard",
"//submodules/Camera",
"//submodules/TelegramUI/Components/DustEffect",
"//submodules/TelegramUI/Components/DynamicCornerRadiusView",
],
visibility = [
"//visibility:public",

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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