mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge commit 'f67e97d41813704bf7822404479a991f66435687'
This commit is contained in:
commit
008f143c77
@ -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] {
|
||||
let entities = entities
|
||||
guard !entities.isEmpty else {
|
||||
@ -230,7 +241,14 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
|
||||
let entitiesData = self.entitiesData
|
||||
return entitiesData != initialEntitiesData
|
||||
} 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
|
||||
}
|
||||
}
|
||||
|
@ -2929,7 +2929,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
|
||||
}
|
||||
let images = imageItems as! [UIImage]
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -3010,6 +3010,10 @@ public final class DrawingToolsInteraction {
|
||||
self.activate()
|
||||
}
|
||||
|
||||
public func reset() {
|
||||
self.drawingView.stateUpdated = { _ in }
|
||||
}
|
||||
|
||||
public func activate() {
|
||||
self.isActive = true
|
||||
|
||||
@ -3106,8 +3110,11 @@ public final class DrawingToolsInteraction {
|
||||
self.isActive = false
|
||||
}
|
||||
|
||||
public func insertEntity(_ entity: DrawingEntity) {
|
||||
public func insertEntity(_ entity: DrawingEntity, scale: CGFloat? = nil) {
|
||||
self.entitiesView.prepareNewEntity(entity)
|
||||
if let scale {
|
||||
entity.scale = scale
|
||||
}
|
||||
self.entitiesView.add(entity)
|
||||
self.entitiesView.selectEntity(entity)
|
||||
|
||||
|
@ -102,7 +102,7 @@ public final class DrawingStickerEntityView: DrawingEntityView {
|
||||
}
|
||||
|
||||
private var image: UIImage? {
|
||||
if case let .image(image) = self.stickerEntity.content {
|
||||
if case let .image(image, _) = self.stickerEntity.content {
|
||||
return image
|
||||
} else {
|
||||
return nil
|
||||
@ -121,7 +121,7 @@ public final class DrawingStickerEntityView: DrawingEntityView {
|
||||
switch self.stickerEntity.content {
|
||||
case let .file(file):
|
||||
return file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0)
|
||||
case let .image(image):
|
||||
case let .image(image, _):
|
||||
return image.size
|
||||
case let .video(_, image, _):
|
||||
if let image {
|
||||
@ -605,6 +605,10 @@ final class DrawingStickerEntititySelectionView: DrawingEntitySelectionView {
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
guard let entityView = self.entityView, let entity = entityView.entity as? DrawingStickerEntity else {
|
||||
return
|
||||
}
|
||||
|
||||
let inset = self.selectionInset - 10.0
|
||||
|
||||
let bounds = CGRect(origin: .zero, size: CGSize(width: entitySelectionViewHandleSize.width / self.scale, height: entitySelectionViewHandleSize.height / self.scale))
|
||||
@ -635,7 +639,15 @@ final class DrawingStickerEntititySelectionView: DrawingEntitySelectionView {
|
||||
self.border.lineDashPattern = [dashLength * relativeDashLength, dashLength * relativeDashLength] as [NSNumber]
|
||||
|
||||
self.border.lineWidth = 2.0 / self.scale
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -391,6 +391,8 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, UIPencilInt
|
||||
}
|
||||
|
||||
public func setup(withDrawing drawingData: Data?) {
|
||||
self.undoStack = []
|
||||
self.redoStack = []
|
||||
if let drawingData = drawingData, let image = UIImage(data: drawingData) {
|
||||
self.hasOpaqueData = true
|
||||
|
||||
@ -406,11 +408,15 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, UIPencilInt
|
||||
}
|
||||
self.layer.contents = image.cgImage
|
||||
self.updateInternalState()
|
||||
} else {
|
||||
self.drawingImage = nil
|
||||
self.layer.contents = nil
|
||||
self.updateInternalState()
|
||||
}
|
||||
}
|
||||
|
||||
var hasOpaqueData = false
|
||||
var drawingData: Data? {
|
||||
public var drawingData: Data? {
|
||||
guard !self.undoStack.isEmpty || self.hasOpaqueData else {
|
||||
return nil
|
||||
}
|
||||
|
@ -584,7 +584,7 @@ public class StickerPickerScreen: ViewController {
|
||||
CTLineDraw(line, context)
|
||||
context.translateBy(x: -lineOrigin.x, y: -lineOrigin.y)
|
||||
}) {
|
||||
strongSelf.controller?.completion(.image(image))
|
||||
strongSelf.controller?.completion(.image(image, false))
|
||||
}
|
||||
strongSelf.controller?.dismiss(animated: true)
|
||||
}
|
||||
|
@ -143,7 +143,7 @@ private class LegacyPaintStickerEntity: LegacyPaintEntity {
|
||||
}
|
||||
}))
|
||||
}
|
||||
case let .image(image):
|
||||
case let .image(image, _):
|
||||
self.file = nil
|
||||
self.imagePromise.set(.single(image))
|
||||
case .video:
|
||||
|
@ -528,7 +528,7 @@ private final class MultipartFetchManager {
|
||||
|
||||
if isStory {
|
||||
self.defaultPartSize = 512 * 1024
|
||||
if let size, size > self.defaultPartSize {
|
||||
if let size = size, size > self.defaultPartSize {
|
||||
self.parallelParts = 4
|
||||
} else {
|
||||
self.parallelParts = 1
|
||||
|
@ -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 contentToUpload: MessageContentToUpload
|
||||
|
||||
var attributes: [MessageAttribute] = []
|
||||
if !embeddedStickers.isEmpty {
|
||||
attributes.append(EmbeddedMediaStickersMessageAttribute(files: embeddedStickers))
|
||||
}
|
||||
|
||||
contentToUpload = messageContentToUpload(
|
||||
accountPeerId: accountPeerId,
|
||||
network: network,
|
||||
@ -640,7 +645,7 @@ private func uploadedStoryContent(postbox: Postbox, network: Network, media: Med
|
||||
passFetchProgress: passFetchProgress,
|
||||
peerId: accountPeerId,
|
||||
messageId: nil,
|
||||
attributes: [],
|
||||
attributes: attributes,
|
||||
text: "",
|
||||
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> {
|
||||
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
|
||||
|> mapToSignal { result -> Signal<StoryUploadResult, NoError> in
|
||||
switch result {
|
||||
@ -818,18 +823,7 @@ func _internal_uploadStoryImpl(postbox: Postbox, network: Network, accountPeerId
|
||||
if isForwardingDisabled {
|
||||
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(
|
||||
flags: flags,
|
||||
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 originalMedia: Media?
|
||||
if let media = media {
|
||||
@ -930,7 +924,7 @@ func _internal_editStory(account: Account, media: EngineStoryInputMedia?, id: In
|
||||
if case .video = media {
|
||||
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 {
|
||||
contentSignal = .single(nil)
|
||||
originalMedia = nil
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
@ -1017,8 +1017,8 @@ public extension TelegramEngine {
|
||||
_internal_cancelStoryUpload(account: self.account, stableId: stableId)
|
||||
}
|
||||
|
||||
public func editStory(media: EngineStoryInputMedia?, id: Int32, 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)
|
||||
public func editStory(id: Int32, media: EngineStoryInputMedia?, text: String?, entities: [MessageTextEntity]?, privacy: EngineStoryPrivacy?) -> Signal<StoryUploadResult, NoError> {
|
||||
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> {
|
||||
|
@ -15,7 +15,7 @@ private func fullEntityMediaPath(_ path: String) -> String {
|
||||
public final class DrawingStickerEntity: DrawingEntity, Codable {
|
||||
public enum Content: Equatable {
|
||||
case file(TelegramMediaFile)
|
||||
case image(UIImage)
|
||||
case image(UIImage, Bool)
|
||||
case video(String, UIImage?, Bool)
|
||||
case dualVideoReference
|
||||
|
||||
@ -27,9 +27,9 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .image(lhsImage):
|
||||
if case let .image(rhsImage) = rhs {
|
||||
return lhsImage === rhsImage
|
||||
case let .image(lhsImage, lhsIsRectangle):
|
||||
if case let .image(rhsImage, rhsIsRectangle) = rhs {
|
||||
return lhsImage === rhsImage && lhsIsRectangle == rhsIsRectangle
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
@ -55,6 +55,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
||||
case videoPath
|
||||
case videoImagePath
|
||||
case videoMirrored
|
||||
case isRectangle
|
||||
case dualVideo
|
||||
case referenceDrawingSize
|
||||
case position
|
||||
@ -71,7 +72,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
||||
public var scale: CGFloat
|
||||
public var rotation: CGFloat
|
||||
public var mirrored: Bool
|
||||
|
||||
|
||||
public var color: DrawingColor = DrawingColor.clear
|
||||
public var lineWidth: CGFloat = 0.0
|
||||
|
||||
@ -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 {
|
||||
return false
|
||||
}
|
||||
@ -123,7 +133,8 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
||||
} else if let file = try container.decodeIfPresent(TelegramMediaFile.self, forKey: .file) {
|
||||
self.content = .file(file)
|
||||
} 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) {
|
||||
var imageValue: UIImage?
|
||||
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 {
|
||||
case let .file(file):
|
||||
try container.encode(file, forKey: .file)
|
||||
case let .image(image):
|
||||
case let .image(image, isRectangle):
|
||||
let imagePath = "\(self.uuid).png"
|
||||
let fullImagePath = fullEntityMediaPath(imagePath)
|
||||
if let imageData = image.pngData() {
|
||||
@ -155,6 +166,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
||||
try? imageData.write(to: URL(fileURLWithPath: fullImagePath))
|
||||
try container.encodeIfPresent(imagePath, forKey: .imagePath)
|
||||
}
|
||||
try container.encode(isRectangle, forKey: .isRectangle)
|
||||
case let .video(path, image, videoMirrored):
|
||||
try container.encode(path, forKey: .videoPath)
|
||||
let imagePath = "\(self.uuid).jpg"
|
||||
|
@ -18,7 +18,7 @@ func composerEntitiesForDrawingEntity(account: Account, entity: DrawingEntity, c
|
||||
switch entity.content {
|
||||
case let .file(file):
|
||||
content = .file(file)
|
||||
case let .image(image):
|
||||
case let .image(image, _):
|
||||
content = .image(image)
|
||||
case let .video(path, _, _):
|
||||
content = .video(path)
|
||||
|
@ -1153,8 +1153,26 @@ final class MediaEditorScreenComponent: Component {
|
||||
forwardAction: nil,
|
||||
moreAction: nil,
|
||||
presentVoiceMessagesUnavailableTooltip: nil,
|
||||
paste: { data in
|
||||
let _ = data
|
||||
paste: { [weak self] data in
|
||||
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,
|
||||
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))
|
||||
}
|
||||
})
|
||||
let imageEntity = DrawingStickerEntity(content: .image(image ?? additionalImage))
|
||||
let imageEntity = DrawingStickerEntity(content: .image(image ?? additionalImage, false))
|
||||
imageEntity.referenceDrawingSize = storyDimensions
|
||||
imageEntity.scale = 1.49
|
||||
imageEntity.position = position.getPosition(storyDimensions)
|
||||
@ -2737,6 +2755,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
private var drawingScreen: DrawingScreen?
|
||||
private var stickerScreen: StickerPickerScreen?
|
||||
|
||||
private var previousDrawingData: Data?
|
||||
private var previousDrawingEntities: [DrawingEntity]?
|
||||
|
||||
func requestLayout(forceUpdate: Bool, transition: Transition) {
|
||||
guard let layout = self.validLayout else {
|
||||
return
|
||||
@ -2862,38 +2883,57 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
self.controller?.requestLayout(transition: .immediate)
|
||||
return
|
||||
case .drawing:
|
||||
self.previousDrawingData = self.drawingView.drawingData
|
||||
self.previousDrawingEntities = self.entitiesView.entities
|
||||
|
||||
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)
|
||||
self.drawingScreen = controller
|
||||
self.drawingView.isUserInteractionEnabled = true
|
||||
|
||||
controller.requestDismiss = { [weak controller, weak self] in
|
||||
self?.drawingScreen = nil
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.drawingScreen = nil
|
||||
controller?.animateOut({
|
||||
controller?.dismiss()
|
||||
})
|
||||
self?.drawingView.isUserInteractionEnabled = false
|
||||
self?.animateInFromTool()
|
||||
self.drawingView.isUserInteractionEnabled = false
|
||||
self.animateInFromTool()
|
||||
|
||||
self?.interaction?.activate()
|
||||
self?.entitiesView.selectEntity(nil)
|
||||
self.interaction?.reset()
|
||||
|
||||
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
|
||||
self?.drawingScreen = nil
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.drawingScreen = nil
|
||||
controller?.animateOut({
|
||||
controller?.dismiss()
|
||||
})
|
||||
self?.drawingView.isUserInteractionEnabled = false
|
||||
self?.animateInFromTool()
|
||||
self.drawingView.isUserInteractionEnabled = false
|
||||
self.animateInFromTool()
|
||||
|
||||
self.interaction?.reset()
|
||||
|
||||
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 {
|
||||
self?.mediaEditor?.setDrawingAndEntities(data: nil, image: nil, entities: [])
|
||||
self.mediaEditor?.setDrawingAndEntities(data: nil, image: nil, entities: [])
|
||||
}
|
||||
|
||||
self?.interaction?.activate()
|
||||
self?.entitiesView.selectEntity(nil)
|
||||
self.interaction?.activate()
|
||||
self.entitiesView.selectEntity(nil)
|
||||
}
|
||||
self.controller?.present(controller, in: .window(.root))
|
||||
self.animateOutToTool()
|
||||
@ -3995,7 +4035,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
}
|
||||
let images = imageItems as! [UIImage]
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -653,20 +653,16 @@ private final class StoryContainerScreenComponent: Component {
|
||||
}
|
||||
|
||||
@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
|
||||
}
|
||||
|
||||
let location = recognizer.location(in: recognizer.view)
|
||||
if let currentItemView = self.visibleItemSetViews.first?.value {
|
||||
if location.x < currentItemView.frame.minX {
|
||||
component.content.navigate(navigation: .item(.previous))
|
||||
self.navigate(direction: .previous)
|
||||
} else if location.x > currentItemView.frame.maxX {
|
||||
if stateValue.nextSlice == nil {
|
||||
environment.controller()?.dismiss()
|
||||
} else {
|
||||
component.content.navigate(navigation: .item(.next))
|
||||
}
|
||||
self.navigate(direction: .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 {
|
||||
if self.didAnimateOut {
|
||||
return availableSize
|
||||
@ -1064,44 +1101,11 @@ private final class StoryContainerScreenComponent: Component {
|
||||
environment.controller()?.dismiss()
|
||||
},
|
||||
navigate: { [weak self] direction in
|
||||
guard let self, let component = self.component, let environment = self.environment else {
|
||||
guard let self 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))
|
||||
}
|
||||
}
|
||||
self.navigate(direction: direction)
|
||||
},
|
||||
delete: { [weak self] in
|
||||
guard let self else {
|
||||
|
@ -32,6 +32,8 @@ import BundleIconComponent
|
||||
import PeerListItemComponent
|
||||
import PremiumUI
|
||||
import AttachmentUI
|
||||
import StickerPackPreviewUI
|
||||
import TextNodeWithEntities
|
||||
|
||||
public final class StoryAvailableReactions: Equatable {
|
||||
let reactionItems: [ReactionItem]
|
||||
@ -3217,7 +3219,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
updateProgressImpl?(0.0)
|
||||
|
||||
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
|
||||
guard let self else {
|
||||
return
|
||||
@ -3256,7 +3258,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
}
|
||||
|
||||
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
|
||||
guard let self else {
|
||||
return
|
||||
@ -3278,7 +3280,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
}
|
||||
}
|
||||
} 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
|
||||
switch result {
|
||||
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?) {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
@ -3372,10 +3430,9 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
return true
|
||||
}
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
let additionalCount = component.slice.item.storyItem.privacy?.additionallyIncludePeers.count ?? 0
|
||||
|
||||
|
||||
var hasLinkedStickers = false
|
||||
let media = component.slice.item.storyItem.media._asMedia()
|
||||
if let image = media as? TelegramMediaImage {
|
||||
@ -3384,6 +3441,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
hasLinkedStickers = file.hasLinkedStickers
|
||||
}
|
||||
|
||||
let additionalCount = component.slice.item.storyItem.privacy?.additionallyIncludePeers.count ?? 0
|
||||
let privacyText: String
|
||||
switch component.slice.item.storyItem.privacy?.base {
|
||||
case .closeFriends:
|
||||
@ -3498,7 +3556,6 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
if let link {
|
||||
UIPasteboard.general.string = link
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
|
||||
component.presentController(UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .linkCopied(text: "Link copied."),
|
||||
@ -3521,10 +3578,93 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
})))
|
||||
}
|
||||
|
||||
let _ = hasLinkedStickers
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
|
||||
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)
|
||||
var tip: ContextController.Tip?
|
||||
var tipSignal: Signal<ContextController.Tip?, NoError>?
|
||||
|
||||
if hasLinkedStickers {
|
||||
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
|
||||
guard let self else {
|
||||
return
|
||||
@ -3560,6 +3700,15 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
|
||||
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())
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: isMuted ? "Notify" : "Don't Notify", icon: { theme in
|
||||
@ -3684,7 +3833,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
}
|
||||
)
|
||||
})))
|
||||
|
||||
|
||||
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)
|
||||
contextController.dismissed = { [weak self] in
|
||||
guard let self else {
|
||||
|
@ -154,6 +154,13 @@ public final class TextFieldComponent: Component {
|
||||
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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user