mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Add story editing from stories grid
This commit is contained in:
parent
bd4de97bd8
commit
bb543f49ea
@ -3109,6 +3109,13 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
if controller.isEmbeddedEditor == true {
|
if controller.isEmbeddedEditor == true {
|
||||||
mediaEditor.onFirstDisplay = { [weak self] in
|
mediaEditor.onFirstDisplay = { [weak self] in
|
||||||
if let self {
|
if let self {
|
||||||
|
if let transitionInView = self.transitionInView {
|
||||||
|
self.transitionInView = nil
|
||||||
|
transitionInView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak transitionInView] _ in
|
||||||
|
transitionInView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if effectiveSubject.isPhoto {
|
if effectiveSubject.isPhoto {
|
||||||
self.previewContainerView.layer.allowsGroupOpacity = true
|
self.previewContainerView.layer.allowsGroupOpacity = true
|
||||||
self.previewContainerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { _ in
|
self.previewContainerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { _ in
|
||||||
@ -3765,6 +3772,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
|
|
||||||
if let transitionOut = controller.transitionOut(finished, isNew), let destinationView = transitionOut.destinationView {
|
if let transitionOut = controller.transitionOut(finished, isNew), let destinationView = transitionOut.destinationView {
|
||||||
var destinationTransitionView: UIView?
|
var destinationTransitionView: UIView?
|
||||||
|
var destinationTransitionRect: CGRect = .zero
|
||||||
if !finished {
|
if !finished {
|
||||||
if let transitionIn = controller.transitionIn, case let .gallery(galleryTransitionIn) = transitionIn, let sourceImage = galleryTransitionIn.sourceImage, isNew != true {
|
if let transitionIn = controller.transitionIn, case let .gallery(galleryTransitionIn) = transitionIn, let sourceImage = galleryTransitionIn.sourceImage, isNew != true {
|
||||||
let sourceSuperView = galleryTransitionIn.sourceView?.superview?.superview
|
let sourceSuperView = galleryTransitionIn.sourceView?.superview?.superview
|
||||||
@ -3774,6 +3782,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
destinationTransitionOutView.frame = self.previewContainerView.convert(self.previewContainerView.bounds, to: sourceSuperView)
|
destinationTransitionOutView.frame = self.previewContainerView.convert(self.previewContainerView.bounds, to: sourceSuperView)
|
||||||
sourceSuperView?.addSubview(destinationTransitionOutView)
|
sourceSuperView?.addSubview(destinationTransitionOutView)
|
||||||
destinationTransitionView = destinationTransitionOutView
|
destinationTransitionView = destinationTransitionOutView
|
||||||
|
destinationTransitionRect = galleryTransitionIn.sourceRect
|
||||||
}
|
}
|
||||||
if let view = self.componentHost.view as? MediaEditorScreenComponent.View {
|
if let view = self.componentHost.view as? MediaEditorScreenComponent.View {
|
||||||
view.animateOut(to: .gallery)
|
view.animateOut(to: .gallery)
|
||||||
@ -3853,7 +3862,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
if let destinationTransitionView {
|
if let destinationTransitionView {
|
||||||
self.previewContainerView.layer.allowsGroupOpacity = true
|
self.previewContainerView.layer.allowsGroupOpacity = true
|
||||||
self.previewContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
self.previewContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
||||||
destinationTransitionView.layer.animateFrame(from: destinationTransitionView.frame, to: destinationView.convert(destinationView.bounds, to: destinationTransitionView.superview), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak destinationTransitionView] _ in
|
destinationTransitionView.layer.animateFrame(from: destinationTransitionView.frame, to: destinationView.convert(destinationTransitionRect, to: destinationTransitionView.superview), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak destinationTransitionView] _ in
|
||||||
destinationTransitionView?.removeFromSuperview()
|
destinationTransitionView?.removeFromSuperview()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,7 @@ swift_library(
|
|||||||
"//submodules/UndoUI",
|
"//submodules/UndoUI",
|
||||||
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||||
"//submodules/Components/ComponentDisplayAdapters",
|
"//submodules/Components/ComponentDisplayAdapters",
|
||||||
|
"//submodules/TelegramUI/Components/MediaEditorScreen",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -33,6 +33,7 @@ import ShareController
|
|||||||
import UndoUI
|
import UndoUI
|
||||||
import PlainButtonComponent
|
import PlainButtonComponent
|
||||||
import ComponentDisplayAdapters
|
import ComponentDisplayAdapters
|
||||||
|
import MediaEditorScreen
|
||||||
|
|
||||||
private let mediaBadgeBackgroundColor = UIColor(white: 0.0, alpha: 0.6)
|
private let mediaBadgeBackgroundColor = UIColor(white: 0.0, alpha: 0.6)
|
||||||
private let mediaBadgeTextColor = UIColor.white
|
private let mediaBadgeTextColor = UIColor.white
|
||||||
@ -1266,6 +1267,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
|
|
||||||
private let listDisposable = MetaDisposable()
|
private let listDisposable = MetaDisposable()
|
||||||
private var hiddenMediaDisposable: Disposable?
|
private var hiddenMediaDisposable: Disposable?
|
||||||
|
private let updateDisposable = MetaDisposable()
|
||||||
|
|
||||||
private var numberOfItemsToRequest: Int = 50
|
private var numberOfItemsToRequest: Int = 50
|
||||||
private var isRequestingView: Bool = false
|
private var isRequestingView: Bool = false
|
||||||
@ -1765,6 +1767,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
self.hiddenMediaDisposable?.dispose()
|
self.hiddenMediaDisposable?.dispose()
|
||||||
self.animationTimer?.invalidate()
|
self.animationTimer?.invalidate()
|
||||||
self.presentationDataDisposable?.dispose()
|
self.presentationDataDisposable?.dispose()
|
||||||
|
self.updateDisposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func loadHole(anchor: SparseItemGrid.HoleAnchor, at location: SparseItemGrid.HoleLocation) -> Signal<Never, NoError> {
|
public func loadHole(anchor: SparseItemGrid.HoleAnchor, at location: SparseItemGrid.HoleLocation) -> Signal<Never, NoError> {
|
||||||
@ -1858,16 +1861,54 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
/*items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.StoryList_ItemAction_Edit, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
|
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.StoryList_ItemAction_Edit, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
|
||||||
c.dismiss(completion: {
|
c.dismiss(completion: {
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let _ = self
|
let _ = (self.context.engine.data.get(
|
||||||
|
TelegramEngine.EngineData.Item.Peer.Peer(id: self.peerId)
|
||||||
|
)
|
||||||
})
|
|> deliverOnMainQueue).startStandalone(next: { [weak self] peer in
|
||||||
})))*/
|
guard let self, let peer else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var foundItemLayer: SparseItemGridLayer?
|
||||||
|
var sourceImage: UIImage?
|
||||||
|
self.itemGrid.forEachVisibleItem { gridItem in
|
||||||
|
guard let itemLayer = gridItem.layer as? ItemLayer else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let listItem = itemLayer.item, listItem.story.id == item.id {
|
||||||
|
foundItemLayer = itemLayer
|
||||||
|
if let contents = itemLayer.contents, CFGetTypeID(contents as CFTypeRef) == CGImage.typeID {
|
||||||
|
sourceImage = UIImage(cgImage: contents as! CGImage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let controller = MediaEditorScreen.makeEditStoryController(
|
||||||
|
context: self.context,
|
||||||
|
peer: peer,
|
||||||
|
storyItem: item,
|
||||||
|
videoPlaybackPosition: nil,
|
||||||
|
repost: false,
|
||||||
|
transitionIn: .gallery(MediaEditorScreen.TransitionIn.GalleryTransitionIn(sourceView: self.itemGrid.view, sourceRect: foundItemLayer?.frame ?? .zero, sourceImage: sourceImage)),
|
||||||
|
transitionOut: MediaEditorScreen.TransitionOut(destinationView: self.itemGrid.view, destinationRect: foundItemLayer?.frame ?? .zero, destinationCornerRadius: 0.0),
|
||||||
|
update: { [weak self] disposable in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.updateDisposable.set(disposable)
|
||||||
|
}
|
||||||
|
) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.parentController?.push(controller)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !item.isForwardingDisabled, case .everyone = item.privacy?.base {
|
if !item.isForwardingDisabled, case .everyone = item.privacy?.base {
|
||||||
@ -1880,7 +1921,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
|
|||||||
let _ = (self.context.engine.data.get(
|
let _ = (self.context.engine.data.get(
|
||||||
TelegramEngine.EngineData.Item.Peer.Peer(id: self.peerId)
|
TelegramEngine.EngineData.Item.Peer.Peer(id: self.peerId)
|
||||||
)
|
)
|
||||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] peer in
|
|> deliverOnMainQueue).startStandalone(next: { [weak self] peer in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -4308,7 +4308,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
self.sendMessageContext.currentSpeechHolder = speechHolder
|
self.sendMessageContext.currentSpeechHolder = speechHolder
|
||||||
}
|
}
|
||||||
case .translate:
|
case .translate:
|
||||||
self.sendMessageContext.performTranslateTextAction(view: self, text: text.string)
|
self.sendMessageContext.performTranslateTextAction(view: self, text: text.string, entities: [])
|
||||||
case .quote:
|
case .quote:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -5359,13 +5359,9 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
|
|
||||||
private let updateDisposable = MetaDisposable()
|
private let updateDisposable = MetaDisposable()
|
||||||
func openStoryEditing(repost: Bool = false) {
|
func openStoryEditing(repost: Bool = false) {
|
||||||
guard let component = self.component, let peerReference = PeerReference(component.slice.peer._asPeer()) else {
|
guard let component = self.component else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let context = component.context
|
|
||||||
let peerId = component.slice.peer.id
|
|
||||||
let item = component.slice.item.storyItem
|
|
||||||
let id = item.id
|
|
||||||
|
|
||||||
self.isEditingStory = true
|
self.isEditingStory = true
|
||||||
self.updateIsProgressPaused()
|
self.updateIsProgressPaused()
|
||||||
@ -5376,277 +5372,39 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
videoPlaybackPosition = view.videoPlaybackPosition
|
videoPlaybackPosition = view.videoPlaybackPosition
|
||||||
}
|
}
|
||||||
|
|
||||||
let subject: Signal<MediaEditorScreen.Subject?, NoError>
|
guard let controller = MediaEditorScreen.makeEditStoryController(
|
||||||
subject = getStorySource(engine: component.context.engine, peerId: component.context.account.peerId, id: Int64(item.id))
|
context: component.context,
|
||||||
|> mapToSignal { source in
|
peer: component.slice.peer,
|
||||||
if !repost, let source {
|
storyItem: component.slice.item.storyItem,
|
||||||
return .single(.draft(source, Int64(item.id)))
|
videoPlaybackPosition: videoPlaybackPosition,
|
||||||
} else {
|
repost: repost,
|
||||||
let media = item.media._asMedia()
|
|
||||||
return fetchMediaData(context: context, postbox: context.account.postbox, userLocation: .peer(peerReference.id), customUserContentType: .story, mediaReference: .story(peer: peerReference, id: item.id, media: media))
|
|
||||||
|> mapToSignal { (value, isImage) -> Signal<MediaEditorScreen.Subject?, NoError> in
|
|
||||||
guard case let .data(data) = value, data.complete else {
|
|
||||||
return .complete()
|
|
||||||
}
|
|
||||||
if let image = UIImage(contentsOfFile: data.path) {
|
|
||||||
return .single(nil)
|
|
||||||
|> then(
|
|
||||||
.single(.image(image, PixelDimensions(image.size), nil, .bottomRight))
|
|
||||||
|> delay(0.1, queue: Queue.mainQueue())
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
var duration: Double?
|
|
||||||
if let file = media as? TelegramMediaFile {
|
|
||||||
duration = file.duration
|
|
||||||
}
|
|
||||||
let symlinkPath = data.path + ".mp4"
|
|
||||||
if fileSize(symlinkPath) == nil {
|
|
||||||
let _ = try? FileManager.default.linkItem(atPath: data.path, toPath: symlinkPath)
|
|
||||||
}
|
|
||||||
return .single(nil)
|
|
||||||
|> then(
|
|
||||||
.single(.video(symlinkPath, nil, false, nil, nil, PixelDimensions(width: 720, height: 1280), duration ?? 0.0, [], .bottomRight))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let initialCaption: NSAttributedString?
|
|
||||||
let initialPrivacy: EngineStoryPrivacy?
|
|
||||||
let initialMediaAreas: [MediaArea]
|
|
||||||
if repost {
|
|
||||||
initialCaption = nil
|
|
||||||
initialPrivacy = nil
|
|
||||||
initialMediaAreas = []
|
|
||||||
} else {
|
|
||||||
initialCaption = chatInputStateStringWithAppliedEntities(item.text, entities: item.entities)
|
|
||||||
initialPrivacy = item.privacy
|
|
||||||
initialMediaAreas = item.mediaAreas
|
|
||||||
}
|
|
||||||
|
|
||||||
let externalState = MediaEditorTransitionOutExternalState(
|
|
||||||
storyTarget: nil,
|
|
||||||
isForcedTarget: false,
|
|
||||||
isPeerArchived: false,
|
|
||||||
transitionOut: nil
|
|
||||||
)
|
|
||||||
|
|
||||||
var updateProgressImpl: ((Float) -> Void)?
|
|
||||||
let controller = MediaEditorScreen(
|
|
||||||
context: context,
|
|
||||||
mode: .storyEditor,
|
|
||||||
subject: subject,
|
|
||||||
isEditing: !repost,
|
|
||||||
forwardSource: repost ? (component.slice.peer, item) : nil,
|
|
||||||
initialCaption: initialCaption,
|
|
||||||
initialPrivacy: initialPrivacy,
|
|
||||||
initialMediaAreas: initialMediaAreas,
|
|
||||||
initialVideoPosition: videoPlaybackPosition,
|
|
||||||
transitionIn: .noAnimation,
|
transitionIn: .noAnimation,
|
||||||
transitionOut: { finished, isNew in
|
transitionOut: nil,
|
||||||
if repost && finished {
|
completed: { [weak self] in
|
||||||
if let transitionOut = externalState.transitionOut?(externalState.storyTarget, externalState.isPeerArchived), let destinationView = transitionOut.destinationView {
|
|
||||||
return MediaEditorScreen.TransitionOut(
|
|
||||||
destinationView: destinationView,
|
|
||||||
destinationRect: transitionOut.destinationRect,
|
|
||||||
destinationCornerRadius: transitionOut.destinationCornerRadius
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
},
|
|
||||||
completion: { [weak self] result, commit in
|
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
self.component?.controller()?.dismiss(animated: false)
|
||||||
let entities = generateChatInputTextEntities(result.caption)
|
},
|
||||||
|
willDismiss: { [weak self] in
|
||||||
if repost {
|
guard let self else {
|
||||||
let target: Stories.PendingTarget
|
return
|
||||||
let targetPeerId: EnginePeer.Id
|
|
||||||
if let sendAsPeerId = result.options.sendAsPeerId {
|
|
||||||
target = .peer(sendAsPeerId)
|
|
||||||
targetPeerId = sendAsPeerId
|
|
||||||
} else {
|
|
||||||
target = .myStories
|
|
||||||
targetPeerId = context.account.peerId
|
|
||||||
}
|
|
||||||
externalState.storyTarget = target
|
|
||||||
|
|
||||||
self.component?.controller()?.dismiss(animated: false)
|
|
||||||
|
|
||||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: targetPeerId))
|
|
||||||
|> deliverOnMainQueue).startStandalone(next: { peer in
|
|
||||||
guard let peer else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if case let .user(user) = peer {
|
|
||||||
externalState.isPeerArchived = user.storiesHidden ?? false
|
|
||||||
|
|
||||||
} else if case let .channel(channel) = peer {
|
|
||||||
externalState.isPeerArchived = channel.storiesHidden ?? false
|
|
||||||
}
|
|
||||||
|
|
||||||
let forwardInfo = Stories.PendingForwardInfo(peerId: component.slice.peer.id, storyId: item.id, isModified: result.media != nil)
|
|
||||||
|
|
||||||
if let rootController = context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {
|
|
||||||
var existingMedia: EngineMedia?
|
|
||||||
if let _ = result.media {
|
|
||||||
} else {
|
|
||||||
existingMedia = item.media
|
|
||||||
}
|
|
||||||
rootController.proceedWithStoryUpload(target: target, result: result as! MediaEditorScreenResult, existingMedia: existingMedia, forwardInfo: forwardInfo, externalState: externalState, commit: commit)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
var updatedText: String?
|
|
||||||
var updatedEntities: [MessageTextEntity]?
|
|
||||||
if result.caption.string != item.text || entities != item.entities {
|
|
||||||
updatedText = result.caption.string
|
|
||||||
updatedEntities = entities
|
|
||||||
}
|
|
||||||
|
|
||||||
if let mediaResult = result.media {
|
|
||||||
switch mediaResult {
|
|
||||||
case let .image(image, dimensions):
|
|
||||||
updateProgressImpl?(0.0)
|
|
||||||
|
|
||||||
let tempFile = TempBox.shared.tempFile(fileName: "file")
|
|
||||||
defer {
|
|
||||||
TempBox.shared.dispose(tempFile)
|
|
||||||
}
|
|
||||||
if let imageData = compressImageToJPEG(image, quality: 0.7, tempFilePath: tempFile.path) {
|
|
||||||
self.updateDisposable.set((context.engine.messages.editStory(peerId: peerId, id: id, media: .image(dimensions: dimensions, data: imageData, stickers: result.stickers), mediaAreas: result.mediaAreas, text: updatedText, entities: updatedEntities, privacy: nil)
|
|
||||||
|> deliverOnMainQueue).startStrict(next: { [weak self] result in
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch result {
|
|
||||||
case let .progress(progress):
|
|
||||||
updateProgressImpl?(progress)
|
|
||||||
case .completed:
|
|
||||||
Queue.mainQueue().after(0.1) {
|
|
||||||
self.isEditingStory = false
|
|
||||||
self.rewindCurrentItem()
|
|
||||||
self.updateIsProgressPaused()
|
|
||||||
self.state?.updated(transition: .easeInOut(duration: 0.2))
|
|
||||||
|
|
||||||
HapticFeedback().success()
|
|
||||||
|
|
||||||
commit({})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
case let .video(content, firstFrameImage, values, duration, dimensions):
|
|
||||||
updateProgressImpl?(0.0)
|
|
||||||
|
|
||||||
if let valuesData = try? JSONEncoder().encode(values) {
|
|
||||||
let data = MemoryBuffer(data: valuesData)
|
|
||||||
let digest = MemoryBuffer(data: data.md5Digest())
|
|
||||||
let adjustments = VideoMediaResourceAdjustments(data: data, digest: digest, isStory: true)
|
|
||||||
|
|
||||||
let resource: TelegramMediaResource
|
|
||||||
switch content {
|
|
||||||
case let .imageFile(path):
|
|
||||||
resource = LocalFileVideoMediaResource(randomId: Int64.random(in: .min ... .max), path: path, adjustments: adjustments)
|
|
||||||
case let .videoFile(path):
|
|
||||||
resource = LocalFileVideoMediaResource(randomId: Int64.random(in: .min ... .max), path: path, adjustments: adjustments)
|
|
||||||
case let .asset(localIdentifier):
|
|
||||||
resource = VideoLibraryMediaResource(localIdentifier: localIdentifier, conversion: .compress(adjustments))
|
|
||||||
}
|
|
||||||
|
|
||||||
let tempFile = TempBox.shared.tempFile(fileName: "file")
|
|
||||||
defer {
|
|
||||||
TempBox.shared.dispose(tempFile)
|
|
||||||
}
|
|
||||||
let firstFrameImageData = firstFrameImage.flatMap { compressImageToJPEG($0, quality: 0.6, tempFilePath: tempFile.path) }
|
|
||||||
let firstFrameFile = firstFrameImageData.flatMap { data -> TempBoxFile? in
|
|
||||||
let file = TempBox.shared.tempFile(fileName: "image.jpg")
|
|
||||||
if let _ = try? data.write(to: URL(fileURLWithPath: file.path)) {
|
|
||||||
return file
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.updateDisposable.set((context.engine.messages.editStory(peerId: peerId, id: id, media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameFile: firstFrameFile, stickers: result.stickers), mediaAreas: result.mediaAreas, text: updatedText, entities: updatedEntities, privacy: nil)
|
|
||||||
|> deliverOnMainQueue).startStrict(next: { [weak self] result in
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch result {
|
|
||||||
case let .progress(progress):
|
|
||||||
updateProgressImpl?(progress)
|
|
||||||
case .completed:
|
|
||||||
Queue.mainQueue().after(0.1) {
|
|
||||||
self.isEditingStory = false
|
|
||||||
self.rewindCurrentItem()
|
|
||||||
self.updateIsProgressPaused()
|
|
||||||
self.state?.updated(transition: .easeInOut(duration: 0.2))
|
|
||||||
|
|
||||||
HapticFeedback().success()
|
|
||||||
|
|
||||||
commit({})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else if updatedText != nil {
|
|
||||||
let _ = (context.engine.messages.editStory(peerId: peerId, id: id, media: nil, mediaAreas: nil, text: updatedText, entities: updatedEntities, privacy: nil)
|
|
||||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] result in
|
|
||||||
switch result {
|
|
||||||
case .completed:
|
|
||||||
Queue.mainQueue().after(0.1) {
|
|
||||||
if let self {
|
|
||||||
self.isEditingStory = false
|
|
||||||
self.rewindCurrentItem()
|
|
||||||
self.updateIsProgressPaused()
|
|
||||||
self.state?.updated(transition: .easeInOut(duration: 0.2))
|
|
||||||
|
|
||||||
HapticFeedback().success()
|
|
||||||
}
|
|
||||||
commit({})
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
self.isEditingStory = false
|
|
||||||
self.rewindCurrentItem()
|
|
||||||
self.updateIsProgressPaused()
|
|
||||||
self.state?.updated(transition: .easeInOut(duration: 0.2))
|
|
||||||
|
|
||||||
HapticFeedback().success()
|
|
||||||
|
|
||||||
commit({})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
self.isEditingStory = false
|
||||||
|
self.rewindCurrentItem()
|
||||||
|
self.updateIsProgressPaused()
|
||||||
|
self.state?.updated(transition: .easeInOut(duration: 0.2))
|
||||||
|
},
|
||||||
|
update: { [weak self] disposable in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.updateDisposable.set(disposable)
|
||||||
}
|
}
|
||||||
)
|
) else {
|
||||||
controller.willDismiss = { [weak self] in
|
return
|
||||||
self?.isEditingStory = false
|
|
||||||
self?.rewindCurrentItem()
|
|
||||||
self?.updateIsProgressPaused()
|
|
||||||
self?.state?.updated(transition: .easeInOut(duration: 0.2))
|
|
||||||
}
|
}
|
||||||
controller.navigationPresentation = .flatModal
|
|
||||||
self.component?.controller()?.push(controller)
|
self.component?.controller()?.push(controller)
|
||||||
updateProgressImpl = { [weak controller, weak self] progress in
|
|
||||||
controller?.updateEditProgress(progress, cancel: { [weak self] in
|
|
||||||
self?.updateDisposable.set(nil)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func presentSaveUpgradeScreen() {
|
private func presentSaveUpgradeScreen() {
|
||||||
@ -7059,7 +6817,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
guard let self, let component = self.component else {
|
guard let self, let component = self.component else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.sendMessageContext.performTranslateTextAction(view: self, text: component.slice.item.storyItem.text)
|
self.sendMessageContext.performTranslateTextAction(view: self, text: component.slice.item.storyItem.text, entities: component.slice.item.storyItem.entities)
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1159,7 +1159,7 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
controller.present(shareController, in: .window(.root))
|
controller.present(shareController, in: .window(.root))
|
||||||
}
|
}
|
||||||
|
|
||||||
func performTranslateTextAction(view: StoryItemSetContainerComponent.View, text: String) {
|
func performTranslateTextAction(view: StoryItemSetContainerComponent.View, text: String, entities: [MessageTextEntity]) {
|
||||||
guard let component = view.component else {
|
guard let component = view.component else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1190,7 +1190,7 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
|
|
||||||
let _ = ApplicationSpecificNotice.incrementTranslationSuggestion(accountManager: component.context.sharedContext.accountManager, timestamp: Int32(Date().timeIntervalSince1970)).start()
|
let _ = ApplicationSpecificNotice.incrementTranslationSuggestion(accountManager: component.context.sharedContext.accountManager, timestamp: Int32(Date().timeIntervalSince1970)).start()
|
||||||
|
|
||||||
let translateController = TranslateScreen(context: component.context, forceTheme: defaultDarkPresentationTheme, text: text, canCopy: true, fromLanguage: language, ignoredLanguages: translationSettings.ignoredLanguages)
|
let translateController = TranslateScreen(context: component.context, forceTheme: defaultDarkPresentationTheme, text: text, entities: entities, canCopy: true, fromLanguage: language, ignoredLanguages: translationSettings.ignoredLanguages)
|
||||||
translateController.pushController = { [weak view] c in
|
translateController.pushController = { [weak view] c in
|
||||||
guard let view, let component = view.component else {
|
guard let view, let component = view.component else {
|
||||||
return
|
return
|
||||||
@ -1762,7 +1762,7 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
|
|
||||||
self.sendMessages(view: view, peer: targetPeer, messages: enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime)
|
self.sendMessages(view: view, peer: targetPeer, messages: enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime)
|
||||||
} else {
|
} else {
|
||||||
let contactController = component.context.sharedContext.makeDeviceContactInfoController(context: component.context, subject: .filter(peer: peerAndContactData.0?._asPeer(), contactId: nil, contactData: contactData, completion: { [weak self, weak view] peer, contactData in
|
let contactController = component.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: component.context), environment: ShareControllerAppEnvironment(sharedContext: component.context.sharedContext), subject: .filter(peer: peerAndContactData.0?._asPeer(), contactId: nil, contactData: contactData, completion: { [weak self, weak view] peer, contactData in
|
||||||
guard let self, let view else {
|
guard let self, let view else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ swift_library(
|
|||||||
"//submodules/ComponentFlow:ComponentFlow",
|
"//submodules/ComponentFlow:ComponentFlow",
|
||||||
"//submodules/Components/ViewControllerComponent:ViewControllerComponent",
|
"//submodules/Components/ViewControllerComponent:ViewControllerComponent",
|
||||||
"//submodules/Components/MultilineTextComponent:MultilineTextComponent",
|
"//submodules/Components/MultilineTextComponent:MultilineTextComponent",
|
||||||
|
"//submodules/Components/MultilineTextWithEntitiesComponent:MultilineTextWithEntitiesComponent",
|
||||||
"//submodules/Components/BundleIconComponent:BundleIconComponent",
|
"//submodules/Components/BundleIconComponent:BundleIconComponent",
|
||||||
"//submodules/UndoUI:UndoUI",
|
"//submodules/UndoUI:UndoUI",
|
||||||
"//submodules/ActivityIndicator:ActivityIndicator",
|
"//submodules/ActivityIndicator:ActivityIndicator",
|
||||||
|
@ -11,6 +11,7 @@ import Speak
|
|||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
import ViewControllerComponent
|
import ViewControllerComponent
|
||||||
import MultilineTextComponent
|
import MultilineTextComponent
|
||||||
|
import MultilineTextWithEntitiesComponent
|
||||||
import BundleIconComponent
|
import BundleIconComponent
|
||||||
import UndoUI
|
import UndoUI
|
||||||
|
|
||||||
@ -35,15 +36,17 @@ private final class TranslateScreenComponent: CombinedComponent {
|
|||||||
|
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let text: String
|
let text: String
|
||||||
|
let entities: [MessageTextEntity]
|
||||||
let fromLanguage: String?
|
let fromLanguage: String?
|
||||||
let toLanguage: String
|
let toLanguage: String
|
||||||
let copyTranslation: ((String) -> Void)?
|
let copyTranslation: ((String) -> Void)?
|
||||||
let changeLanguage: (String, String, @escaping (String, String) -> Void) -> Void
|
let changeLanguage: (String, String, @escaping (String, String) -> Void) -> Void
|
||||||
let expand: () -> Void
|
let expand: () -> Void
|
||||||
|
|
||||||
init(context: AccountContext, text: String, fromLanguage: String?, toLanguage: String, copyTranslation: ((String) -> Void)?, changeLanguage: @escaping (String, String, @escaping (String, String) -> Void) -> Void, expand: @escaping () -> Void) {
|
init(context: AccountContext, text: String, entities: [MessageTextEntity], fromLanguage: String?, toLanguage: String, copyTranslation: ((String) -> Void)?, changeLanguage: @escaping (String, String, @escaping (String, String) -> Void) -> Void, expand: @escaping () -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.text = text
|
self.text = text
|
||||||
|
self.entities = entities
|
||||||
self.fromLanguage = fromLanguage
|
self.fromLanguage = fromLanguage
|
||||||
self.toLanguage = toLanguage
|
self.toLanguage = toLanguage
|
||||||
self.copyTranslation = copyTranslation
|
self.copyTranslation = copyTranslation
|
||||||
@ -58,6 +61,9 @@ private final class TranslateScreenComponent: CombinedComponent {
|
|||||||
if lhs.text != rhs.text {
|
if lhs.text != rhs.text {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.entities != rhs.entities {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.fromLanguage != rhs.fromLanguage {
|
if lhs.fromLanguage != rhs.fromLanguage {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -995,7 +1001,7 @@ public class TranslateScreen: ViewController {
|
|||||||
|
|
||||||
public var wasDismissed: (() -> Void)?
|
public var wasDismissed: (() -> Void)?
|
||||||
|
|
||||||
public convenience init(context: AccountContext, forceTheme: PresentationTheme? = nil, text: String, canCopy: Bool, fromLanguage: String?, toLanguage: String? = nil, isExpanded: Bool = false, ignoredLanguages: [String]? = nil) {
|
public convenience init(context: AccountContext, forceTheme: PresentationTheme? = nil, text: String, entities: [MessageTextEntity] = [], canCopy: Bool, fromLanguage: String?, toLanguage: String? = nil, isExpanded: Bool = false, ignoredLanguages: [String]? = nil) {
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
var baseLanguageCode = presentationData.strings.baseLanguageCode
|
var baseLanguageCode = presentationData.strings.baseLanguageCode
|
||||||
@ -1024,7 +1030,7 @@ public class TranslateScreen: ViewController {
|
|||||||
var copyTranslationImpl: ((String) -> Void)?
|
var copyTranslationImpl: ((String) -> Void)?
|
||||||
var changeLanguageImpl: ((String, String, @escaping (String, String) -> Void) -> Void)?
|
var changeLanguageImpl: ((String, String, @escaping (String, String) -> Void) -> Void)?
|
||||||
var expandImpl: (() -> Void)?
|
var expandImpl: (() -> Void)?
|
||||||
self.init(context: context, component: TranslateScreenComponent(context: context, text: text, fromLanguage: fromLanguage, toLanguage: toLanguage, copyTranslation: !canCopy ? nil : { text in
|
self.init(context: context, component: TranslateScreenComponent(context: context, text: text, entities: entities, fromLanguage: fromLanguage, toLanguage: toLanguage, copyTranslation: !canCopy ? nil : { text in
|
||||||
copyTranslationImpl?(text)
|
copyTranslationImpl?(text)
|
||||||
}, changeLanguage: { fromLang, toLang, completion in
|
}, changeLanguage: { fromLang, toLang, completion in
|
||||||
changeLanguageImpl?(fromLang, toLang, completion)
|
changeLanguageImpl?(fromLang, toLang, completion)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user