Merge commit 'f67e97d41813704bf7822404479a991f66435687'

This commit is contained in:
Ali 2023-07-05 00:05:51 +02:00
commit 008f143c77
15 changed files with 357 additions and 108 deletions

View File

@ -197,6 +197,17 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
} }
} }
public func setup(with entities: [DrawingEntity]) {
self.clear()
for entity in entities {
if entity is DrawingMediaEntity {
continue
}
self.add(entity, announce: false)
}
}
public static func encodeEntities(_ entities: [DrawingEntity], entitiesView: DrawingEntitiesView? = nil) -> [CodableDrawingEntity] { public static func encodeEntities(_ entities: [DrawingEntity], entitiesView: DrawingEntitiesView? = nil) -> [CodableDrawingEntity] {
let entities = entities let entities = entities
guard !entities.isEmpty else { guard !entities.isEmpty else {
@ -230,7 +241,14 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
let entitiesData = self.entitiesData let entitiesData = self.entitiesData
return entitiesData != initialEntitiesData return entitiesData != initialEntitiesData
} else { } else {
let filteredEntities = self.entities.filter { !$0.isMedia } let filteredEntities = self.entities.filter { entity in
if entity.isMedia {
return false
} else if let stickerEntity = entity as? DrawingStickerEntity, case .dualVideoReference = stickerEntity.content {
return false
}
return true
}
return !filteredEntities.isEmpty return !filteredEntities.isEmpty
} }
} }

View File

@ -2929,7 +2929,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
} }
let images = imageItems as! [UIImage] let images = imageItems as! [UIImage]
if images.count == 1, let image = images.first, max(image.size.width, image.size.height) > 1.0 { if images.count == 1, let image = images.first, max(image.size.width, image.size.height) > 1.0 {
let entity = DrawingStickerEntity(content: .image(image)) let entity = DrawingStickerEntity(content: .image(image, false))
strongSelf.node.insertEntity.invoke(entity) strongSelf.node.insertEntity.invoke(entity)
} }
} }
@ -3010,6 +3010,10 @@ public final class DrawingToolsInteraction {
self.activate() self.activate()
} }
public func reset() {
self.drawingView.stateUpdated = { _ in }
}
public func activate() { public func activate() {
self.isActive = true self.isActive = true
@ -3106,8 +3110,11 @@ public final class DrawingToolsInteraction {
self.isActive = false self.isActive = false
} }
public func insertEntity(_ entity: DrawingEntity) { public func insertEntity(_ entity: DrawingEntity, scale: CGFloat? = nil) {
self.entitiesView.prepareNewEntity(entity) self.entitiesView.prepareNewEntity(entity)
if let scale {
entity.scale = scale
}
self.entitiesView.add(entity) self.entitiesView.add(entity)
self.entitiesView.selectEntity(entity) self.entitiesView.selectEntity(entity)

View File

@ -102,7 +102,7 @@ public final class DrawingStickerEntityView: DrawingEntityView {
} }
private var image: UIImage? { private var image: UIImage? {
if case let .image(image) = self.stickerEntity.content { if case let .image(image, _) = self.stickerEntity.content {
return image return image
} else { } else {
return nil return nil
@ -121,7 +121,7 @@ public final class DrawingStickerEntityView: DrawingEntityView {
switch self.stickerEntity.content { switch self.stickerEntity.content {
case let .file(file): case let .file(file):
return file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0) return file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0)
case let .image(image): case let .image(image, _):
return image.size return image.size
case let .video(_, image, _): case let .video(_, image, _):
if let image { if let image {
@ -605,6 +605,10 @@ final class DrawingStickerEntititySelectionView: DrawingEntitySelectionView {
} }
override func layoutSubviews() { override func layoutSubviews() {
guard let entityView = self.entityView, let entity = entityView.entity as? DrawingStickerEntity else {
return
}
let inset = self.selectionInset - 10.0 let inset = self.selectionInset - 10.0
let bounds = CGRect(origin: .zero, size: CGSize(width: entitySelectionViewHandleSize.width / self.scale, height: entitySelectionViewHandleSize.height / self.scale)) let bounds = CGRect(origin: .zero, size: CGSize(width: entitySelectionViewHandleSize.width / self.scale, height: entitySelectionViewHandleSize.height / self.scale))
@ -635,8 +639,16 @@ final class DrawingStickerEntititySelectionView: DrawingEntitySelectionView {
self.border.lineDashPattern = [dashLength * relativeDashLength, dashLength * relativeDashLength] as [NSNumber] self.border.lineDashPattern = [dashLength * relativeDashLength, dashLength * relativeDashLength] as [NSNumber]
self.border.lineWidth = 2.0 / self.scale self.border.lineWidth = 2.0 / self.scale
if entity.isRectangle {
let width: CGFloat = self.bounds.width - inset * 2.0
let height: CGFloat = self.bounds.height - inset * 2.0
let cornerRadius: CGFloat = 12.0 - self.scale
self.border.path = UIBezierPath(roundedRect: CGRect(origin: CGPoint(x: inset, y: inset), size: CGSize(width: width, height: height)), cornerRadius: cornerRadius).cgPath
} else {
self.border.path = UIBezierPath(ovalIn: CGRect(origin: CGPoint(x: inset, y: inset), size: CGSize(width: self.bounds.width - inset * 2.0, height: self.bounds.height - inset * 2.0))).cgPath self.border.path = UIBezierPath(ovalIn: CGRect(origin: CGPoint(x: inset, y: inset), size: CGSize(width: self.bounds.width - inset * 2.0, height: self.bounds.height - inset * 2.0))).cgPath
} }
}
} }
private let snapTimeout = 1.0 private let snapTimeout = 1.0

View File

@ -391,6 +391,8 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, UIPencilInt
} }
public func setup(withDrawing drawingData: Data?) { public func setup(withDrawing drawingData: Data?) {
self.undoStack = []
self.redoStack = []
if let drawingData = drawingData, let image = UIImage(data: drawingData) { if let drawingData = drawingData, let image = UIImage(data: drawingData) {
self.hasOpaqueData = true self.hasOpaqueData = true
@ -406,11 +408,15 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, UIPencilInt
} }
self.layer.contents = image.cgImage self.layer.contents = image.cgImage
self.updateInternalState() self.updateInternalState()
} else {
self.drawingImage = nil
self.layer.contents = nil
self.updateInternalState()
} }
} }
var hasOpaqueData = false var hasOpaqueData = false
var drawingData: Data? { public var drawingData: Data? {
guard !self.undoStack.isEmpty || self.hasOpaqueData else { guard !self.undoStack.isEmpty || self.hasOpaqueData else {
return nil return nil
} }

View File

@ -584,7 +584,7 @@ public class StickerPickerScreen: ViewController {
CTLineDraw(line, context) CTLineDraw(line, context)
context.translateBy(x: -lineOrigin.x, y: -lineOrigin.y) context.translateBy(x: -lineOrigin.x, y: -lineOrigin.y)
}) { }) {
strongSelf.controller?.completion(.image(image)) strongSelf.controller?.completion(.image(image, false))
} }
strongSelf.controller?.dismiss(animated: true) strongSelf.controller?.dismiss(animated: true)
} }

View File

@ -143,7 +143,7 @@ private class LegacyPaintStickerEntity: LegacyPaintEntity {
} }
})) }))
} }
case let .image(image): case let .image(image, _):
self.file = nil self.file = nil
self.imagePromise.set(.single(image)) self.imagePromise.set(.single(image))
case .video: case .video:

View File

@ -528,7 +528,7 @@ private final class MultipartFetchManager {
if isStory { if isStory {
self.defaultPartSize = 512 * 1024 self.defaultPartSize = 512 * 1024
if let size, size > self.defaultPartSize { if let size = size, size > self.defaultPartSize {
self.parallelParts = 4 self.parallelParts = 4
} else { } else {
self.parallelParts = 1 self.parallelParts = 1

View File

@ -623,10 +623,15 @@ private func prepareUploadStoryContent(account: Account, media: EngineStoryInput
} }
} }
private func uploadedStoryContent(postbox: Postbox, network: Network, media: Media, accountPeerId: PeerId, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, auxiliaryMethods: AccountAuxiliaryMethods, passFetchProgress: Bool) -> (signal: Signal<PendingMessageUploadedContentResult?, NoError>, media: Media) { private func uploadedStoryContent(postbox: Postbox, network: Network, media: Media, embeddedStickers: [TelegramMediaFile], accountPeerId: PeerId, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, auxiliaryMethods: AccountAuxiliaryMethods, passFetchProgress: Bool) -> (signal: Signal<PendingMessageUploadedContentResult?, NoError>, media: Media) {
let originalMedia: Media = media let originalMedia: Media = media
let contentToUpload: MessageContentToUpload let contentToUpload: MessageContentToUpload
var attributes: [MessageAttribute] = []
if !embeddedStickers.isEmpty {
attributes.append(EmbeddedMediaStickersMessageAttribute(files: embeddedStickers))
}
contentToUpload = messageContentToUpload( contentToUpload = messageContentToUpload(
accountPeerId: accountPeerId, accountPeerId: accountPeerId,
network: network, network: network,
@ -640,7 +645,7 @@ private func uploadedStoryContent(postbox: Postbox, network: Network, media: Med
passFetchProgress: passFetchProgress, passFetchProgress: passFetchProgress,
peerId: accountPeerId, peerId: accountPeerId,
messageId: nil, messageId: nil,
attributes: [], attributes: attributes,
text: "", text: "",
media: [media] media: [media]
) )
@ -776,7 +781,7 @@ private func _internal_putPendingStoryIdMapping(accountPeerId: PeerId, stableId:
func _internal_uploadStoryImpl(postbox: Postbox, network: Network, accountPeerId: PeerId, stateManager: AccountStateManager, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, auxiliaryMethods: AccountAuxiliaryMethods, stableId: Int32, media: Media, text: String, entities: [MessageTextEntity], embeddedStickers: [TelegramMediaFile], pin: Bool, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, period: Int, randomId: Int64) -> Signal<StoryUploadResult, NoError> { func _internal_uploadStoryImpl(postbox: Postbox, network: Network, accountPeerId: PeerId, stateManager: AccountStateManager, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, auxiliaryMethods: AccountAuxiliaryMethods, stableId: Int32, media: Media, text: String, entities: [MessageTextEntity], embeddedStickers: [TelegramMediaFile], pin: Bool, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, period: Int, randomId: Int64) -> Signal<StoryUploadResult, NoError> {
let passFetchProgress = media is TelegramMediaFile let passFetchProgress = media is TelegramMediaFile
let (contentSignal, originalMedia) = uploadedStoryContent(postbox: postbox, network: network, media: media, accountPeerId: accountPeerId, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: revalidationContext, auxiliaryMethods: auxiliaryMethods, passFetchProgress: passFetchProgress) let (contentSignal, originalMedia) = uploadedStoryContent(postbox: postbox, network: network, media: media, embeddedStickers: embeddedStickers, accountPeerId: accountPeerId, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: revalidationContext, auxiliaryMethods: auxiliaryMethods, passFetchProgress: passFetchProgress)
return contentSignal return contentSignal
|> mapToSignal { result -> Signal<StoryUploadResult, NoError> in |> mapToSignal { result -> Signal<StoryUploadResult, NoError> in
switch result { switch result {
@ -819,17 +824,6 @@ func _internal_uploadStoryImpl(postbox: Postbox, network: Network, accountPeerId
flags |= 1 << 4 flags |= 1 << 4
} }
var inputMedia = inputMedia
if !embeddedStickers.isEmpty {
var stickersValue: [Api.InputDocument] = []
for file in embeddedStickers {
if let resource = file.resource as? CloudDocumentMediaResource, let fileReference = resource.fileReference {
stickersValue.append(Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: fileReference)))
}
}
inputMedia = inputMedia.withUpdatedStickers(stickersValue)
}
return network.request(Api.functions.stories.sendStory( return network.request(Api.functions.stories.sendStory(
flags: flags, flags: flags,
media: inputMedia, media: inputMedia,
@ -922,7 +916,7 @@ func _internal_uploadStoryImpl(postbox: Postbox, network: Network, accountPeerId
} }
} }
func _internal_editStory(account: Account, media: EngineStoryInputMedia?, id: Int32, text: String?, entities: [MessageTextEntity]?, privacy: EngineStoryPrivacy?) -> Signal<StoryUploadResult, NoError> { func _internal_editStory(account: Account, id: Int32, media: EngineStoryInputMedia?, text: String?, entities: [MessageTextEntity]?, privacy: EngineStoryPrivacy?) -> Signal<StoryUploadResult, NoError> {
let contentSignal: Signal<PendingMessageUploadedContentResult?, NoError> let contentSignal: Signal<PendingMessageUploadedContentResult?, NoError>
let originalMedia: Media? let originalMedia: Media?
if let media = media { if let media = media {
@ -930,7 +924,7 @@ func _internal_editStory(account: Account, media: EngineStoryInputMedia?, id: In
if case .video = media { if case .video = media {
passFetchProgress = true passFetchProgress = true
} }
(contentSignal, originalMedia) = uploadedStoryContent(postbox: account.postbox, network: account.network, media: prepareUploadStoryContent(account: account, media: media), accountPeerId: account.peerId, messageMediaPreuploadManager: account.messageMediaPreuploadManager, revalidationContext: account.mediaReferenceRevalidationContext, auxiliaryMethods: account.auxiliaryMethods, passFetchProgress: passFetchProgress) (contentSignal, originalMedia) = uploadedStoryContent(postbox: account.postbox, network: account.network, media: prepareUploadStoryContent(account: account, media: media), embeddedStickers: media.embeddedStickers, accountPeerId: account.peerId, messageMediaPreuploadManager: account.messageMediaPreuploadManager, revalidationContext: account.mediaReferenceRevalidationContext, auxiliaryMethods: account.auxiliaryMethods, passFetchProgress: passFetchProgress)
} else { } else {
contentSignal = .single(nil) contentSignal = .single(nil)
originalMedia = nil originalMedia = nil

View File

@ -889,7 +889,7 @@ public extension TelegramEngine {
} }
} }
if let nextItem = nextItem, case let .item(item) = nextItem, let lastTimestamp { if let nextItem = nextItem, case let .item(item) = nextItem, let lastTimestamp = lastTimestamp {
sortedItems.append((peer, item, hasUnseen, lastTimestamp)) sortedItems.append((peer, item, hasUnseen, lastTimestamp))
} }
} }
@ -1017,8 +1017,8 @@ public extension TelegramEngine {
_internal_cancelStoryUpload(account: self.account, stableId: stableId) _internal_cancelStoryUpload(account: self.account, stableId: stableId)
} }
public func editStory(media: EngineStoryInputMedia?, id: Int32, text: String?, entities: [MessageTextEntity]?, privacy: EngineStoryPrivacy?) -> Signal<StoryUploadResult, NoError> { public func editStory(id: Int32, media: EngineStoryInputMedia?, text: String?, entities: [MessageTextEntity]?, privacy: EngineStoryPrivacy?) -> Signal<StoryUploadResult, NoError> {
return _internal_editStory(account: self.account, media: media, id: id, text: text, entities: entities, privacy: privacy) return _internal_editStory(account: self.account, id: id, media: media, text: text, entities: entities, privacy: privacy)
} }
public func editStoryPrivacy(id: Int32, privacy: EngineStoryPrivacy) -> Signal<Never, NoError> { public func editStoryPrivacy(id: Int32, privacy: EngineStoryPrivacy) -> Signal<Never, NoError> {

View File

@ -15,7 +15,7 @@ private func fullEntityMediaPath(_ path: String) -> String {
public final class DrawingStickerEntity: DrawingEntity, Codable { public final class DrawingStickerEntity: DrawingEntity, Codable {
public enum Content: Equatable { public enum Content: Equatable {
case file(TelegramMediaFile) case file(TelegramMediaFile)
case image(UIImage) case image(UIImage, Bool)
case video(String, UIImage?, Bool) case video(String, UIImage?, Bool)
case dualVideoReference case dualVideoReference
@ -27,9 +27,9 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
} else { } else {
return false return false
} }
case let .image(lhsImage): case let .image(lhsImage, lhsIsRectangle):
if case let .image(rhsImage) = rhs { if case let .image(rhsImage, rhsIsRectangle) = rhs {
return lhsImage === rhsImage return lhsImage === rhsImage && lhsIsRectangle == rhsIsRectangle
} else { } else {
return false return false
} }
@ -55,6 +55,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
case videoPath case videoPath
case videoImagePath case videoImagePath
case videoMirrored case videoMirrored
case isRectangle
case dualVideo case dualVideo
case referenceDrawingSize case referenceDrawingSize
case position case position
@ -97,6 +98,15 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
} }
} }
public var isRectangle: Bool {
switch self.content {
case let .image(_, isRectangle):
return isRectangle
default:
return false
}
}
public var isMedia: Bool { public var isMedia: Bool {
return false return false
} }
@ -123,7 +133,8 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
} else if let file = try container.decodeIfPresent(TelegramMediaFile.self, forKey: .file) { } else if let file = try container.decodeIfPresent(TelegramMediaFile.self, forKey: .file) {
self.content = .file(file) self.content = .file(file)
} else if let imagePath = try container.decodeIfPresent(String.self, forKey: .imagePath), let image = UIImage(contentsOfFile: fullEntityMediaPath(imagePath)) { } else if let imagePath = try container.decodeIfPresent(String.self, forKey: .imagePath), let image = UIImage(contentsOfFile: fullEntityMediaPath(imagePath)) {
self.content = .image(image) let isRectangle = try container.decodeIfPresent(Bool.self, forKey: .isRectangle) ?? false
self.content = .image(image, isRectangle)
} else if let videoPath = try container.decodeIfPresent(String.self, forKey: .videoPath) { } else if let videoPath = try container.decodeIfPresent(String.self, forKey: .videoPath) {
var imageValue: UIImage? var imageValue: UIImage?
if let imagePath = try container.decodeIfPresent(String.self, forKey: .videoImagePath), let image = UIImage(contentsOfFile: fullEntityMediaPath(imagePath)) { if let imagePath = try container.decodeIfPresent(String.self, forKey: .videoImagePath), let image = UIImage(contentsOfFile: fullEntityMediaPath(imagePath)) {
@ -147,7 +158,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
switch self.content { switch self.content {
case let .file(file): case let .file(file):
try container.encode(file, forKey: .file) try container.encode(file, forKey: .file)
case let .image(image): case let .image(image, isRectangle):
let imagePath = "\(self.uuid).png" let imagePath = "\(self.uuid).png"
let fullImagePath = fullEntityMediaPath(imagePath) let fullImagePath = fullEntityMediaPath(imagePath)
if let imageData = image.pngData() { if let imageData = image.pngData() {
@ -155,6 +166,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
try? imageData.write(to: URL(fileURLWithPath: fullImagePath)) try? imageData.write(to: URL(fileURLWithPath: fullImagePath))
try container.encodeIfPresent(imagePath, forKey: .imagePath) try container.encodeIfPresent(imagePath, forKey: .imagePath)
} }
try container.encode(isRectangle, forKey: .isRectangle)
case let .video(path, image, videoMirrored): case let .video(path, image, videoMirrored):
try container.encode(path, forKey: .videoPath) try container.encode(path, forKey: .videoPath)
let imagePath = "\(self.uuid).jpg" let imagePath = "\(self.uuid).jpg"

View File

@ -18,7 +18,7 @@ func composerEntitiesForDrawingEntity(account: Account, entity: DrawingEntity, c
switch entity.content { switch entity.content {
case let .file(file): case let .file(file):
content = .file(file) content = .file(file)
case let .image(image): case let .image(image, _):
content = .image(image) content = .image(image)
case let .video(path, _, _): case let .video(path, _, _):
content = .video(path) content = .video(path)

View File

@ -1153,8 +1153,26 @@ final class MediaEditorScreenComponent: Component {
forwardAction: nil, forwardAction: nil,
moreAction: nil, moreAction: nil,
presentVoiceMessagesUnavailableTooltip: nil, presentVoiceMessagesUnavailableTooltip: nil,
paste: { data in paste: { [weak self] data in
let _ = data guard let self, let environment = self.environment, let controller = environment.controller() as? MediaEditorScreen else {
return
}
switch data {
case let .sticker(image, _):
if max(image.size.width, image.size.height) > 1.0 {
let entity = DrawingStickerEntity(content: .image(image, false))
controller.node.interaction?.insertEntity(entity, scale: 1.0)
self.deactivateInput()
}
case let .images(images):
if images.count == 1, let image = images.first, max(image.size.width, image.size.height) > 1.0 {
let entity = DrawingStickerEntity(content: .image(image, true))
controller.node.interaction?.insertEntity(entity, scale: 2.5)
self.deactivateInput()
}
default:
break
}
}, },
audioRecorder: nil, audioRecorder: nil,
videoRecordingStatus: nil, videoRecordingStatus: nil,
@ -1899,7 +1917,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
context.draw(cgImage, in: CGRect(origin: CGPoint(x: (size.width - additionalImage.size.width) / 2.0, y: (size.height - additionalImage.size.height) / 2.0), size: additionalImage.size)) context.draw(cgImage, in: CGRect(origin: CGPoint(x: (size.width - additionalImage.size.width) / 2.0, y: (size.height - additionalImage.size.height) / 2.0), size: additionalImage.size))
} }
}) })
let imageEntity = DrawingStickerEntity(content: .image(image ?? additionalImage)) let imageEntity = DrawingStickerEntity(content: .image(image ?? additionalImage, false))
imageEntity.referenceDrawingSize = storyDimensions imageEntity.referenceDrawingSize = storyDimensions
imageEntity.scale = 1.49 imageEntity.scale = 1.49
imageEntity.position = position.getPosition(storyDimensions) imageEntity.position = position.getPosition(storyDimensions)
@ -2737,6 +2755,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
private var drawingScreen: DrawingScreen? private var drawingScreen: DrawingScreen?
private var stickerScreen: StickerPickerScreen? private var stickerScreen: StickerPickerScreen?
private var previousDrawingData: Data?
private var previousDrawingEntities: [DrawingEntity]?
func requestLayout(forceUpdate: Bool, transition: Transition) { func requestLayout(forceUpdate: Bool, transition: Transition) {
guard let layout = self.validLayout else { guard let layout = self.validLayout else {
return return
@ -2862,38 +2883,57 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.controller?.requestLayout(transition: .immediate) self.controller?.requestLayout(transition: .immediate)
return return
case .drawing: case .drawing:
self.previousDrawingData = self.drawingView.drawingData
self.previousDrawingEntities = self.entitiesView.entities
self.interaction?.deactivate() self.interaction?.deactivate()
let controller = DrawingScreen(context: self.context, sourceHint: .storyEditor, size: self.previewContainerView.frame.size, originalSize: storyDimensions, isVideo: false, isAvatar: false, drawingView: self.drawingView, entitiesView: self.entitiesView, selectionContainerView: self.selectionContainerView, existingStickerPickerInputData: self.stickerPickerInputData) let controller = DrawingScreen(context: self.context, sourceHint: .storyEditor, size: self.previewContainerView.frame.size, originalSize: storyDimensions, isVideo: false, isAvatar: false, drawingView: self.drawingView, entitiesView: self.entitiesView, selectionContainerView: self.selectionContainerView, existingStickerPickerInputData: self.stickerPickerInputData)
self.drawingScreen = controller self.drawingScreen = controller
self.drawingView.isUserInteractionEnabled = true self.drawingView.isUserInteractionEnabled = true
controller.requestDismiss = { [weak controller, weak self] in controller.requestDismiss = { [weak controller, weak self] in
self?.drawingScreen = nil guard let self else {
return
}
self.drawingScreen = nil
controller?.animateOut({ controller?.animateOut({
controller?.dismiss() controller?.dismiss()
}) })
self?.drawingView.isUserInteractionEnabled = false self.drawingView.isUserInteractionEnabled = false
self?.animateInFromTool() self.animateInFromTool()
self?.interaction?.activate() self.interaction?.reset()
self?.entitiesView.selectEntity(nil)
self.interaction?.activate()
self.entitiesView.selectEntity(nil)
self.drawingView.setup(withDrawing: self.previousDrawingData)
self.entitiesView.setup(with: self.previousDrawingEntities ?? [])
self.previousDrawingData = nil
self.previousDrawingEntities = nil
} }
controller.requestApply = { [weak controller, weak self] in controller.requestApply = { [weak controller, weak self] in
self?.drawingScreen = nil guard let self else {
return
}
self.drawingScreen = nil
controller?.animateOut({ controller?.animateOut({
controller?.dismiss() controller?.dismiss()
}) })
self?.drawingView.isUserInteractionEnabled = false self.drawingView.isUserInteractionEnabled = false
self?.animateInFromTool() self.animateInFromTool()
self.interaction?.reset()
if let result = controller?.generateDrawingResultData() { if let result = controller?.generateDrawingResultData() {
self?.mediaEditor?.setDrawingAndEntities(data: result.data, image: result.drawingImage, entities: result.entities) self.mediaEditor?.setDrawingAndEntities(data: result.data, image: result.drawingImage, entities: result.entities)
} else { } else {
self?.mediaEditor?.setDrawingAndEntities(data: nil, image: nil, entities: []) self.mediaEditor?.setDrawingAndEntities(data: nil, image: nil, entities: [])
} }
self?.interaction?.activate() self.interaction?.activate()
self?.entitiesView.selectEntity(nil) self.entitiesView.selectEntity(nil)
} }
self.controller?.present(controller, in: .window(.root)) self.controller?.present(controller, in: .window(.root))
self.animateOutToTool() self.animateOutToTool()
@ -3995,7 +4035,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
} }
let images = imageItems as! [UIImage] let images = imageItems as! [UIImage]
if images.count == 1, let image = images.first, max(image.size.width, image.size.height) > 1.0 { if images.count == 1, let image = images.first, max(image.size.width, image.size.height) > 1.0 {
self.node.interaction?.insertEntity(DrawingStickerEntity(content: .image(image))) self.node.interaction?.insertEntity(DrawingStickerEntity(content: .image(image, false)), scale: 2.5)
} }
} }
} }

View File

@ -653,20 +653,16 @@ private final class StoryContainerScreenComponent: Component {
} }
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
guard let component = self.component, let environment = self.environment, let stateValue = component.content.stateValue, case .recognized = recognizer.state else { guard case .recognized = recognizer.state else {
return return
} }
let location = recognizer.location(in: recognizer.view) let location = recognizer.location(in: recognizer.view)
if let currentItemView = self.visibleItemSetViews.first?.value { if let currentItemView = self.visibleItemSetViews.first?.value {
if location.x < currentItemView.frame.minX { if location.x < currentItemView.frame.minX {
component.content.navigate(navigation: .item(.previous)) self.navigate(direction: .previous)
} else if location.x > currentItemView.frame.maxX { } else if location.x > currentItemView.frame.maxX {
if stateValue.nextSlice == nil { self.navigate(direction: .next)
environment.controller()?.dismiss()
} else {
component.content.navigate(navigation: .item(.next))
}
} }
} }
} }
@ -826,6 +822,47 @@ private final class StoryContainerScreenComponent: Component {
} }
} }
private func navigate(direction: StoryItemSetContainerComponent.NavigationDirection) {
guard let component = self.component, let environment = self.environment else {
return
}
if let stateValue = component.content.stateValue, let slice = stateValue.slice {
if case .next = direction, slice.nextItemId == nil, (slice.item.position == nil || slice.item.position == slice.totalCount - 1) {
if stateValue.nextSlice == nil {
environment.controller()?.dismiss()
} else {
self.beginHorizontalPan(translation: CGPoint())
self.updateHorizontalPan(translation: CGPoint())
self.commitHorizontalPan(velocity: CGPoint(x: -200.0, y: 0.0))
}
} else if case .previous = direction, slice.previousItemId == nil {
if stateValue.previousSlice == nil {
if let itemSetView = self.visibleItemSetViews[slice.peer.id] {
if let componentView = itemSetView.view.view as? StoryItemSetContainerComponent.View {
componentView.rewindCurrentItem()
}
}
} else {
self.beginHorizontalPan(translation: CGPoint())
self.updateHorizontalPan(translation: CGPoint())
self.commitHorizontalPan(velocity: CGPoint(x: 200.0, y: 0.0))
}
} else {
let mappedDirection: StoryContentContextNavigation.ItemDirection
switch direction {
case .previous:
mappedDirection = .previous
case .next:
mappedDirection = .next
case let .id(id):
mappedDirection = .id(id)
}
component.content.navigate(navigation: .item(mappedDirection))
}
}
}
func update(component: StoryContainerScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: Transition) -> CGSize { func update(component: StoryContainerScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: Transition) -> CGSize {
if self.didAnimateOut { if self.didAnimateOut {
return availableSize return availableSize
@ -1064,44 +1101,11 @@ private final class StoryContainerScreenComponent: Component {
environment.controller()?.dismiss() environment.controller()?.dismiss()
}, },
navigate: { [weak self] direction in navigate: { [weak self] direction in
guard let self, let component = self.component, let environment = self.environment else { guard let self else {
return return
} }
if let stateValue = component.content.stateValue, let slice = stateValue.slice { self.navigate(direction: direction)
if case .next = direction, slice.nextItemId == nil, (slice.item.position == nil || slice.item.position == slice.totalCount - 1) {
if stateValue.nextSlice == nil {
environment.controller()?.dismiss()
} else {
self.beginHorizontalPan(translation: CGPoint())
self.updateHorizontalPan(translation: CGPoint())
self.commitHorizontalPan(velocity: CGPoint(x: -200.0, y: 0.0))
}
} else if case .previous = direction, slice.previousItemId == nil {
if stateValue.previousSlice == nil {
if let itemSetView = self.visibleItemSetViews[slice.peer.id] {
if let componentView = itemSetView.view.view as? StoryItemSetContainerComponent.View {
componentView.rewindCurrentItem()
}
}
} else {
self.beginHorizontalPan(translation: CGPoint())
self.updateHorizontalPan(translation: CGPoint())
self.commitHorizontalPan(velocity: CGPoint(x: 200.0, y: 0.0))
}
} else {
let mappedDirection: StoryContentContextNavigation.ItemDirection
switch direction {
case .previous:
mappedDirection = .previous
case .next:
mappedDirection = .next
case let .id(id):
mappedDirection = .id(id)
}
component.content.navigate(navigation: .item(mappedDirection))
}
}
}, },
delete: { [weak self] in delete: { [weak self] in
guard let self else { guard let self else {

View File

@ -32,6 +32,8 @@ import BundleIconComponent
import PeerListItemComponent import PeerListItemComponent
import PremiumUI import PremiumUI
import AttachmentUI import AttachmentUI
import StickerPackPreviewUI
import TextNodeWithEntities
public final class StoryAvailableReactions: Equatable { public final class StoryAvailableReactions: Equatable {
let reactionItems: [ReactionItem] let reactionItems: [ReactionItem]
@ -3217,7 +3219,7 @@ public final class StoryItemSetContainerComponent: Component {
updateProgressImpl?(0.0) updateProgressImpl?(0.0)
if let imageData = compressImageToJPEG(image, quality: 0.7) { if let imageData = compressImageToJPEG(image, quality: 0.7) {
let _ = (context.engine.messages.editStory(media: .image(dimensions: dimensions, data: imageData, stickers: stickers), id: id, text: updatedText, entities: updatedEntities, privacy: updatedPrivacy) let _ = (context.engine.messages.editStory(id: id, media: .image(dimensions: dimensions, data: imageData, stickers: stickers), text: updatedText, entities: updatedEntities, privacy: updatedPrivacy)
|> deliverOnMainQueue).start(next: { [weak self] result in |> deliverOnMainQueue).start(next: { [weak self] result in
guard let self else { guard let self else {
return return
@ -3256,7 +3258,7 @@ public final class StoryItemSetContainerComponent: Component {
} }
let firstFrameImageData = firstFrameImage.flatMap { compressImageToJPEG($0, quality: 0.6) } let firstFrameImageData = firstFrameImage.flatMap { compressImageToJPEG($0, quality: 0.6) }
let _ = (context.engine.messages.editStory(media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameImageData: firstFrameImageData, stickers: stickers), id: id, text: updatedText, entities: updatedEntities, privacy: updatedPrivacy) let _ = (context.engine.messages.editStory(id: id, media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameImageData: firstFrameImageData, stickers: stickers), text: updatedText, entities: updatedEntities, privacy: updatedPrivacy)
|> deliverOnMainQueue).start(next: { [weak self] result in |> deliverOnMainQueue).start(next: { [weak self] result in
guard let self else { guard let self else {
return return
@ -3278,7 +3280,7 @@ public final class StoryItemSetContainerComponent: Component {
} }
} }
} else if updatedText != nil || updatedPrivacy != nil { } else if updatedText != nil || updatedPrivacy != nil {
let _ = (context.engine.messages.editStory(media: nil, id: id, text: updatedText, entities: updatedEntities, privacy: updatedPrivacy) let _ = (context.engine.messages.editStory(id: id, media: nil, text: updatedText, entities: updatedEntities, privacy: updatedPrivacy)
|> deliverOnMainQueue).start(next: { [weak self] result in |> deliverOnMainQueue).start(next: { [weak self] result in
switch result { switch result {
case .completed: case .completed:
@ -3347,6 +3349,62 @@ public final class StoryItemSetContainerComponent: Component {
} }
} }
private func openAttachedStickers(packs: Signal<[StickerPackReference], NoError>) {
guard let component = self.component else {
return
}
guard let parentController = component.controller() as? StoryContainerScreen else {
return
}
let context = component.context
let presentationData = context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkPresentationTheme)
let progressSignal = Signal<Never, NoError> { [weak parentController] subscriber in
let progressController = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil))
parentController?.present(progressController, in: .window(.root), with: nil)
return ActionDisposable { [weak progressController] in
Queue.mainQueue().async() {
progressController?.dismiss()
}
}
}
|> runOn(Queue.mainQueue())
|> delay(0.15, queue: Queue.mainQueue())
let progressDisposable = progressSignal.start()
let signal = packs
|> afterDisposed {
Queue.mainQueue().async {
progressDisposable.dispose()
}
}
let _ = (signal
|> deliverOnMainQueue).start(next: { [weak parentController] packs in
guard !packs.isEmpty else {
return
}
let controller = StickerPackScreen(context: context, updatedPresentationData: (presentationData, .single(presentationData)), mainStickerPack: packs[0], stickerPacks: packs, sendSticker: nil, actionPerformed: { actions in
if let (info, items, action) = actions.first {
let animateInAsReplacement = false
switch action {
case .add:
parentController?.present(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_AddedTitle, text: presentationData.strings.StickerPackActionInfo_AddedText(info.title).string, undo: false, info: info, topItem: items.first, context: context), elevatedLayout: true, animateInAsReplacement: animateInAsReplacement, action: { _ in
return true
}), in: .window(.root))
case let .remove(positionInList):
parentController?.present(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_RemovedTitle, text: presentationData.strings.StickerPackActionInfo_RemovedText(info.title).string, undo: true, info: info, topItem: items.first, context: context), elevatedLayout: true, animateInAsReplacement: animateInAsReplacement, action: { action in
if case .undo = action {
let _ = context.engine.stickers.addStickerPackInteractively(info: info, items: items, positionInList: positionInList).start()
}
return true
}), in: .window(.root))
}
}
})
parentController?.present(controller, in: .window(.root), with: nil)
})
}
private func performMoreAction(sourceView: UIView, gesture: ContextGesture?) { private func performMoreAction(sourceView: UIView, gesture: ContextGesture?) {
guard let component = self.component else { guard let component = self.component else {
return return
@ -3372,10 +3430,9 @@ public final class StoryItemSetContainerComponent: Component {
return true return true
} }
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
var items: [ContextMenuItem] = [] var items: [ContextMenuItem] = []
let additionalCount = component.slice.item.storyItem.privacy?.additionallyIncludePeers.count ?? 0
var hasLinkedStickers = false var hasLinkedStickers = false
let media = component.slice.item.storyItem.media._asMedia() let media = component.slice.item.storyItem.media._asMedia()
if let image = media as? TelegramMediaImage { if let image = media as? TelegramMediaImage {
@ -3384,6 +3441,7 @@ public final class StoryItemSetContainerComponent: Component {
hasLinkedStickers = file.hasLinkedStickers hasLinkedStickers = file.hasLinkedStickers
} }
let additionalCount = component.slice.item.storyItem.privacy?.additionallyIncludePeers.count ?? 0
let privacyText: String let privacyText: String
switch component.slice.item.storyItem.privacy?.base { switch component.slice.item.storyItem.privacy?.base {
case .closeFriends: case .closeFriends:
@ -3498,7 +3556,6 @@ public final class StoryItemSetContainerComponent: Component {
if let link { if let link {
UIPasteboard.general.string = link UIPasteboard.general.string = link
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
component.presentController(UndoOverlayController( component.presentController(UndoOverlayController(
presentationData: presentationData, presentationData: presentationData,
content: .linkCopied(text: "Link copied."), content: .linkCopied(text: "Link copied."),
@ -3521,10 +3578,93 @@ public final class StoryItemSetContainerComponent: Component {
}))) })))
} }
let _ = hasLinkedStickers var tip: ContextController.Tip?
var tipSignal: Signal<ContextController.Tip?, NoError>?
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) if hasLinkedStickers {
let contextController = ContextController(account: component.context.account, presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) let context = component.context
tip = .animatedEmoji(text: nil, arguments: nil, file: nil, action: nil)
let packsPromise = Promise<[StickerPackReference]>()
packsPromise.set(context.engine.stickers.stickerPacksAttachedToMedia(media: .standalone(media: media)))
let action: () -> Void = { [weak self] in
self?.openAttachedStickers(packs: packsPromise.get() |> take(1))
}
tipSignal = packsPromise.get()
|> mapToSignal { packReferences -> Signal<ContextController.Tip?, NoError> in
if packReferences.count > 1 {
return .single(.animatedEmoji(text: "This story contains stickers from [\(packReferences.count) packs]().", arguments: nil, file: nil, action: action))
} else if let reference = packReferences.first {
return context.engine.stickers.loadedStickerPack(reference: reference, forceActualized: false)
|> filter { result in
if case .result = result {
return true
} else {
return false
}
}
|> mapToSignal { result -> Signal<ContextController.Tip?, NoError> in
if case let .result(info, items, _) = result {
let tip: ContextController.Tip = .animatedEmoji(
text: "This story contains\n#[\(info.title)]() stickers.",
arguments: TextNodeWithEntities.Arguments(
context: context,
cache: context.animationCache,
renderer: context.animationRenderer,
placeholderColor: .clear,
attemptSynchronous: true
),
file: items.first?.file,
action: action)
return .single(tip)
} else {
return .complete()
}
}
} else {
return .complete()
}
}
// if packReferences.count > 1 {
// items.tip = .animatedEmoji(text: presentationData.strings.ChatContextMenu_EmojiSet(Int32(packReferences.count)), arguments: nil, file: nil, action: action)
// } else if let reference = packReferences.first {
// var tipSignal: Signal<LoadedStickerPack, NoError>
// tipSignal = context.engine.stickers.loadedStickerPack(reference: reference, forceActualized: false)
//
// items.tipSignal = tipSignal
// |> filter { result in
// if case .result = result {
// return true
// } else {
// return false
// }
// }
// |> mapToSignal { result -> Signal<ContextController.Tip?, NoError> in
// if case let .result(info, items, _) = result {
// let tip: ContextController.Tip = .animatedEmoji(
// text: presentationData.strings.ChatContextMenu_ReactionEmojiSetSingle(info.title).string,
// arguments: TextNodeWithEntities.Arguments(
// context: context,
// cache: presentationContext.animationCache,
// renderer: presentationContext.animationRenderer,
// placeholderColor: .clear,
// attemptSynchronous: true
// ),
// file: items.first?.file,
// action: action)
// return .single(tip)
// } else {
// return .complete()
// }
// }
// }
}
let contextItems = ContextController.Items(content: .list(items), tip: tip, tipSignal: tipSignal)
let contextController = ContextController(account: component.context.account, presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView)), items: .single(contextItems), gesture: gesture)
contextController.dismissed = { [weak self] in contextController.dismissed = { [weak self] in
guard let self else { guard let self else {
return return
@ -3560,6 +3700,15 @@ public final class StoryItemSetContainerComponent: Component {
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
var items: [ContextMenuItem] = [] var items: [ContextMenuItem] = []
var hasLinkedStickers = false
let media = component.slice.item.storyItem.media._asMedia()
if let image = media as? TelegramMediaImage {
hasLinkedStickers = image.flags.contains(.hasStickers)
} else if let file = media as? TelegramMediaFile {
hasLinkedStickers = file.hasLinkedStickers
}
let _ = hasLinkedStickers
let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: component.slice.peer._asPeer(), peerSettings: settings._asNotificationSettings()) let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: component.slice.peer._asPeer(), peerSettings: settings._asNotificationSettings())
items.append(.action(ContextMenuActionItem(text: isMuted ? "Notify" : "Don't Notify", icon: { theme in items.append(.action(ContextMenuActionItem(text: isMuted ? "Notify" : "Don't Notify", icon: { theme in

View File

@ -154,6 +154,13 @@ public final class TextFieldComponent: Component {
super.paste(sender) super.paste(sender)
} }
} }
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if action == #selector(self.paste(_:)) {
return true
}
return super.canPerformAction(action, withSender: sender)
}
} }
public final class View: UIView, UITextViewDelegate, UIScrollViewDelegate { public final class View: UIView, UITextViewDelegate, UIScrollViewDelegate {