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] {
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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> {
|
||||||
|
@ -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"
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user