Camera and editor improvements

This commit is contained in:
Ilya Laktyushin 2023-06-21 16:54:12 +04:00
parent 197e27d448
commit 803b887a2e
23 changed files with 527 additions and 193 deletions

View File

@ -895,7 +895,7 @@ public protocol SharedAccountContext: AnyObject {
func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack], parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?) -> ViewController
func makeMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping () -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController
func makeMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController
func makeProxySettingsController(sharedContext: SharedAccountContext, account: UnauthorizedAccount) -> ViewController

View File

@ -449,6 +449,10 @@ public extension CALayer {
self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: duration, mediaTimingFunction: mediaTimingFunction, additive: true)
}
func animateShapeLineWidth(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = CAMediaTimingFunctionName.easeInEaseOut.rawValue, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "lineWidth", timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion)
}
func animatePositionKeyframes(values: [CGPoint], duration: Double, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
self.animateKeyframes(values: values.map { NSValue(cgPoint: $0) }, duration: duration, keyPath: "position")
}

View File

@ -94,6 +94,7 @@ swift_library(
"//submodules/TelegramNotices:TelegramNotices",
"//submodules/FastBlur:FastBlur",
"//submodules/TelegramUI/Components/MediaEditor",
"//submodules/ChatPresentationInterfaceState:ChatPresentationInterfaceState",
],
visibility = [
"//visibility:public",

View File

@ -753,25 +753,12 @@ private final class DrawingScreenComponent: CombinedComponent {
hasTrending: true,
forceHasPremium: true
)
let maskItems = EmojiPagerContentComponent.stickerInputData(
context: context,
animationCache: context.animationCache,
animationRenderer: context.animationRenderer,
stickerNamespaces: [Namespaces.ItemCollection.CloudMaskPacks],
stickerOrderedItemListCollectionIds: [],
chatPeerId: context.account.peerId,
hasSearch: true,
hasTrending: false,
forceHasPremium: true
)
let signal = combineLatest(queue: .mainQueue(),
emojiItems,
stickerItems,
maskItems
) |> map { emoji, stickers, masks -> StickerPickerInputData in
return StickerPickerInputData(emoji: emoji, stickers: stickers, masks: masks)
stickerItems
) |> map { emoji, stickers -> StickerPickerInputData in
return StickerPickerInputData(emoji: emoji, stickers: stickers, masks: nil)
}
stickerPickerInputData.set(signal)

View File

@ -15,6 +15,7 @@ import FeaturedStickersScreen
import TelegramNotices
import ChatEntityKeyboardInputNode
import ContextUI
import ChatPresentationInterfaceState
public struct StickerPickerInputData: Equatable {
var emoji: EmojiPagerContentComponent
@ -90,7 +91,7 @@ private final class StickerSelectionComponent: Component {
}
public final class View: UIView {
private let keyboardView: ComponentView<Empty>
fileprivate let keyboardView: ComponentView<Empty>
private let keyboardClippingView: UIView
private let panelHostView: PagerExternalTopPanelContainer
private let panelBackgroundView: BlurredBackgroundView
@ -99,6 +100,12 @@ private final class StickerSelectionComponent: Component {
private var component: StickerSelectionComponent?
private weak var state: EmptyComponentState?
private var interaction: ChatEntityKeyboardInputNode.Interaction?
private var inputNodeInteraction: ChatMediaInputNodeInteraction?
private let trendingGifsPromise = Promise<ChatMediaInputGifPaneTrendingState?>(nil)
private var forceUpdate = false
override init(frame: CGRect) {
self.keyboardView = ComponentView<Empty>()
self.keyboardClippingView = UIView()
@ -112,6 +119,71 @@ private final class StickerSelectionComponent: Component {
self.addSubview(self.panelBackgroundView)
self.addSubview(self.panelSeparatorView)
self.addSubview(self.panelHostView)
self.interaction = ChatEntityKeyboardInputNode.Interaction(
sendSticker: { file, silent, schedule, query, clearInput, sourceView, sourceRect, sourceLayer, _ in
let _ = file
let _ = silent
let _ = schedule
let _ = query
let _ = clearInput
let _ = sourceView
let _ = sourceRect
let _ = sourceLayer
return false
},
sendEmoji: { _, _, _ in
},
sendGif: { _, _, _, _, _ in
return false
},
sendBotContextResultAsGif: { _, _, _, _, _, _ in
return false
},
updateChoosingSticker: { _ in },
switchToTextInput: {},
dismissTextInput: {},
insertText: { _ in
},
backwardsDeleteText: {},
presentController: { c, a in
let _ = c
let _ = a
},
presentGlobalOverlayController: { c, a in
let _ = c
let _ = a
},
getNavigationController: {
return nil
},
requestLayout: { transition in
let _ = transition
})
self.inputNodeInteraction = ChatMediaInputNodeInteraction(
navigateToCollectionId: { _ in
},
navigateBackToStickers: {
},
setGifMode: { _ in
},
openSettings: {
},
openTrending: { _ in
},
dismissTrendingPacks: { _ in
},
toggleSearch: { _, _, _ in
},
openPeerSpecificSettings: {
},
dismissPeerSpecificSettings: {
},
clearRecentlyUsedStickers: {
}
)
}
required init?(coder: NSCoder) {
@ -132,7 +204,19 @@ private final class StickerSelectionComponent: Component {
let topPanelHeight: CGFloat = 42.0
//let context = component.context
let context = component.context
let stickerPeekBehavior = EmojiContentPeekBehaviorImpl(
context: context,
interaction: nil,
chatPeerId: nil,
present: { c, a in
let _ = c
let _ = a
// controller?.presentInGlobalOverlay(c, with: a)
}
)
let trendingGifsPromise = self.trendingGifsPromise
let keyboardSize = self.keyboardView.update(
transition: transition.withUserData(EmojiPagerContentComponent.SynchronousLoadBehavior(isDisabled: true)),
component: AnyComponent(EntityKeyboardComponent(
@ -152,49 +236,52 @@ private final class StickerSelectionComponent: Component {
externalBottomPanelContainer: nil,
displayTopPanelBackground: .blur,
topPanelExtensionUpdated: { _, _ in },
hideInputUpdated: { _, _, _ in },
hideInputUpdated: { [weak self] _, _, transition in
guard let self else {
return
}
self.forceUpdate = true
self.state?.updated(transition: transition)
},
hideTopPanelUpdated: { _, _ in },
switchToTextInput: {},
switchToGifSubject: { _ in },
reorderItems: { _, _ in },
makeSearchContainerNode: { _ in
return nil
},
// makeSearchContainerNode: { [weak self, weak controllerInteraction] content in
// guard let self, let controllerInteraction = controllerInteraction else {
// return nil
// }
//
// let mappedMode: ChatMediaInputSearchMode
// switch content {
// case .stickers:
// mappedMode = .sticker
// case .gifs:
// mappedMode = .sticker
// }
//
// let presentationData = context.sharedContext.currentPresentationData.with { $0 }
// let searchContainerNode = PaneSearchContainerNode(
// context: context,
// theme: presentationData.theme,
// strings: presentationData.strings,
// controllerInteraction: controllerInteraction,
// inputNodeInteraction: inputNodeInteraction,
// mode: mappedMode,
// trendingGifsPromise: Promise(nil),
// cancel: {
// },
// peekBehavior: self.emojiInputInteraction?.peekBehavior
// )
makeSearchContainerNode: { [weak self] content in
guard let self, let interaction = self.interaction, let inputNodeInteraction = self.inputNodeInteraction else {
return nil
}
let mappedMode: ChatMediaInputSearchMode
switch content {
case .stickers:
mappedMode = .sticker
case .gifs:
mappedMode = .gif
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme)
let searchContainerNode = PaneSearchContainerNode(
context: context,
theme: presentationData.theme,
strings: presentationData.strings,
interaction: interaction,
inputNodeInteraction: inputNodeInteraction,
mode: mappedMode,
trendingGifsPromise: trendingGifsPromise,
cancel: {
},
peekBehavior: stickerPeekBehavior
)
// searchContainerNode.openGifContextMenu = { [weak self] item, sourceNode, sourceRect, gesture, isSaved in
// guard let self else {
// return
// }
// self.openGifContextMenu(file: item.file, contextResult: item.contextResult, sourceView: sourceNode.view, sourceRect: sourceRect, gesture: gesture, isSaved: isSaved)
// }
//
// return searchContainerNode
// },
return searchContainerNode
},
contentIdUpdated: { _ in },
deviceMetrics: component.deviceMetrics,
hiddenInputHeight: 0.0,
@ -204,8 +291,10 @@ private final class StickerSelectionComponent: Component {
clipContentToTopPanel: false
)),
environment: {},
forceUpdate: self.forceUpdate,
containerSize: availableSize
)
self.forceUpdate = false
if let keyboardComponentView = self.keyboardView.view {
if keyboardComponentView.superview == nil {
self.keyboardClippingView.addSubview(keyboardComponentView)
@ -818,7 +907,12 @@ public class StickerPickerScreen: ViewController {
deleteBackwards: nil,
openStickerSettings: nil,
openFeatured: nil,
openSearch: {
openSearch: { [weak self] in
if let self, let componentView = self.hostView.componentView as? StickerSelectionComponent.View {
if let pagerView = componentView.keyboardView.view as? EntityKeyboardComponent.View {
pagerView.openSearch()
}
}
},
addGroupAction: { [weak self] groupId, isPremiumLocked, _ in
guard let strongSelf = self, let controller = strongSelf.controller, let collectionId = groupId.base as? ItemCollectionId else {

View File

@ -235,6 +235,7 @@ final class MediaPickerGridItemNode: GridItemNode {
self.gradientNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
self.typeIconNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
self.durationNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
self.draftNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
if animateSpoilerNode {
self.spoilerNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
}
@ -254,7 +255,9 @@ final class MediaPickerGridItemNode: GridItemNode {
self.backgroundColor = theme.list.mediaPlaceholderColor
if self.currentDraftState == nil || self.currentDraftState?.0.path != draft.path || self.currentDraftState!.1 != index {
if self.currentDraftState == nil || self.currentDraftState?.0.path != draft.path || self.currentDraftState!.1 != index || self.currentState != nil {
self.currentState = nil
let imageSignal: Signal<UIImage?, NoError> = .single(draft.thumbnail)
self.imageNode.setSignal(imageSignal)
@ -262,10 +265,17 @@ final class MediaPickerGridItemNode: GridItemNode {
if self.draftNode.supernode == nil {
self.draftNode.attributedText = NSAttributedString(string: "Draft", font: Font.semibold(12.0), textColor: .white)
self.addSubnode(self.draftNode)
}
if self.typeIconNode.supernode != nil {
self.typeIconNode.removeFromSupernode()
}
if self.durationNode.supernode != nil {
self.durationNode.removeFromSupernode()
}
self.setNeedsLayout()
}
@ -286,10 +296,9 @@ final class MediaPickerGridItemNode: GridItemNode {
if self.backgroundNode.supernode == nil {
self.insertSubnode(self.backgroundNode, at: 0)
}
} else {
if self.draftNode.supernode != nil {
self.draftNode.removeFromSupernode()
}
}
if self.draftNode.supernode != nil {
self.draftNode.removeFromSupernode()
}
if self.currentMediaState == nil || self.currentMediaState!.0.uniqueIdentifier != media.identifier || self.currentMediaState!.1 != index {
@ -319,13 +328,14 @@ final class MediaPickerGridItemNode: GridItemNode {
if self.backgroundNode.supernode == nil {
self.insertSubnode(self.backgroundNode, at: 0)
}
} else {
if self.draftNode.supernode != nil {
self.draftNode.removeFromSupernode()
}
}
if self.draftNode.supernode != nil {
self.draftNode.removeFromSupernode()
}
if self.currentState == nil || self.currentState!.0 !== fetchResult || self.currentState!.1 != index {
if self.currentState == nil || self.currentState!.0 !== fetchResult || self.currentState!.1 != index || self.currentDraftState != nil {
self.currentDraftState = nil
self.backgroundNode.image = nil
let editingContext = interaction.editingState
let asset = fetchResult.object(at: index)

View File

@ -985,6 +985,17 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
}
}
fileprivate func defaultTransitionView() -> UIView? {
var transitionNode: MediaPickerGridItemNode?
if let itemNode = self.gridNode.itemNodeAtPoint(.zero) {
if let itemNode = itemNode as? MediaPickerGridItemNode {
transitionNode = itemNode
}
}
let transitionView = transitionNode?.transitionView(snapshot: false)
return transitionView
}
fileprivate func transitionView(for identifier: String, snapshot: Bool = true, hideSource: Bool = false) -> UIView? {
if let selectionNode = self.selectionNode, selectionNode.alpha > 0.0 {
return selectionNode.transitionView(for: identifier, hideSource: hideSource)
@ -1952,6 +1963,10 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
}
}
fileprivate func defaultTransitionView() -> UIView? {
return self.controllerNode.defaultTransitionView()
}
fileprivate func transitionView(for identifier: String, snapshot: Bool, hideSource: Bool = false) -> UIView? {
return self.controllerNode.transitionView(for: identifier, snapshot: snapshot, hideSource: hideSource)
}
@ -2172,7 +2187,7 @@ public func wallpaperMediaPickerController(
public func storyMediaPickerController(
context: AccountContext,
getSourceRect: @escaping () -> CGRect,
completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping () -> (UIView, CGRect)?, @escaping () -> Void) -> Void,
completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void,
dismissed: @escaping () -> Void
) -> ViewController {
let presentationData = context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkColorPresentationTheme)
@ -2188,9 +2203,18 @@ public func storyMediaPickerController(
if let result = result as? MediaEditorDraft {
controller.updateHiddenMediaId(result.path)
if let transitionView = controller.transitionView(for: result.path, snapshot: false) {
let transitionOut: () -> (UIView, CGRect)? = {
if let transitionView = controller.transitionView(for: result.path, snapshot: false) {
return (transitionView, transitionView.bounds)
let transitionOut: (Bool?) -> (UIView, CGRect)? = { isNew in
if let isNew {
controller.updateHiddenMediaId(result.path)
if isNew {
if let transitionView = controller.defaultTransitionView() {
return (transitionView, transitionView.bounds)
}
} else {
if let transitionView = controller.transitionView(for: result.path, snapshot: false) {
return (transitionView, transitionView.bounds)
}
}
}
return nil
}
@ -2201,9 +2225,16 @@ public func storyMediaPickerController(
} else if let result = result as? PHAsset {
controller.updateHiddenMediaId(result.localIdentifier)
if let transitionView = controller.transitionView(for: result.localIdentifier, snapshot: false) {
let transitionOut: () -> (UIView, CGRect)? = {
if let transitionView = controller.transitionView(for: result.localIdentifier, snapshot: false) {
return (transitionView, transitionView.bounds)
let transitionOut: (Bool?) -> (UIView, CGRect)? = { isNew in
if let isNew {
if isNew {
controller.updateHiddenMediaId(nil)
if let transitionView = controller.defaultTransitionView() {
return (transitionView, transitionView.bounds)
}
} else if let transitionView = controller.transitionView(for: result.localIdentifier, snapshot: false) {
return (transitionView, transitionView.bounds)
}
}
return nil
}

View File

@ -282,7 +282,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
private var genericReactionEffectDisposable: Disposable?
private var genericReactionEffect: String?
private var isReactionSearchActive: Bool = false
public var isReactionSearchActive: Bool = false
public static func randomGenericReactionEffect(context: AccountContext) -> Signal<String?, NoError> {
return context.engine.stickers.loadedStickerPack(reference: .emojiGenericAnimations, forceActualized: false)
@ -2016,17 +2016,15 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
}
strongSelf.hapticFeedback?.tap()
guard let targetView = targetView as? ReactionIconView else {
return
}
if switchToInlineImmediately {
targetView.updateIsAnimationHidden(isAnimationHidden: false, transition: .immediate)
itemNode.isHidden = true
} else {
targetView.updateIsAnimationHidden(isAnimationHidden: true, transition: .immediate)
targetView.addSubnode(itemNode)
itemNode.frame = selfTargetBounds
if let targetView = targetView as? ReactionIconView {
if switchToInlineImmediately {
targetView.updateIsAnimationHidden(isAnimationHidden: false, transition: .immediate)
itemNode.isHidden = true
} else {
targetView.updateIsAnimationHidden(isAnimationHidden: true, transition: .immediate)
targetView.addSubnode(itemNode)
itemNode.frame = selfTargetBounds
}
}
if switchToInlineImmediately {
@ -2071,7 +2069,11 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
targetView: targetView,
addStandaloneReactionAnimation: nil,
completion: { [weak standaloneReactionAnimation] in
standaloneReactionAnimation?.removeFromSupernode()
if let _ = standaloneReactionAnimation?.supernode {
standaloneReactionAnimation?.removeFromSupernode()
} else {
standaloneReactionAnimation?.view.removeFromSuperview()
}
}
)
}
@ -2085,7 +2087,11 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
targetView.isHidden = false
if let targetView = targetView as? ReactionIconView {
targetView.updateIsAnimationHidden(isAnimationHidden: false, transition: .immediate)
itemNode.removeFromSupernode()
if let _ = itemNode.supernode {
itemNode.removeFromSupernode()
} else {
itemNode.view.removeFromSuperview()
}
}
}
mainAnimationCompleted = true

View File

@ -4,7 +4,7 @@ import Postbox
import TelegramApi
public enum EngineOutgoingMessageContent {
case text(String)
case text(String, [MessageTextEntity])
}
public final class StoryPreloadInfo {
@ -222,10 +222,14 @@ public extension TelegramEngine {
content: EngineOutgoingMessageContent
) {
switch content {
case let .text(text):
case let .text(text, entities):
var attributes: [MessageAttribute] = []
if !entities.isEmpty {
attributes.append(TextEntitiesMessageAttribute(entities: entities))
}
let message: EnqueueMessage = .message(
text: text,
attributes: [],
attributes: attributes,
inlineStickers: [:],
mediaReference: nil,
replyToMessageId: replyToMessageId,

View File

@ -451,7 +451,7 @@ private final class CameraScreenComponent: CombinedComponent {
.disappear(.default(scale: true))
)
if #available(iOS 13.0, *), !isTablet && !"".isEmpty {
if #available(iOS 13.0, *), !isTablet {
let dualButton = dualButton.update(
component: CameraButton(
content: AnyComponentWithIdentity(
@ -1431,7 +1431,7 @@ public class CameraScreen: ViewController {
let parentFrame = self.view.convert(self.bounds, to: nil)
let absoluteFrame = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0)
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 25.0), size: CGSize())
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 4.0), size: CGSize())
let controller = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: "Draft Saved", location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _ in
return .ignore
@ -1682,13 +1682,13 @@ public class CameraScreen: ViewController {
public weak var sourceView: UIView?
public let sourceRect: CGRect
public let sourceImage: UIImage?
public let transitionOut: () -> (UIView, CGRect)?
public let transitionOut: (Bool?) -> (UIView, CGRect)?
public init(
sourceView: UIView,
sourceRect: CGRect,
sourceImage: UIImage?,
transitionOut: @escaping () -> (UIView, CGRect)?
transitionOut: @escaping (Bool?) -> (UIView, CGRect)?
) {
self.sourceView = sourceView
self.sourceRect = sourceRect
@ -1775,10 +1775,17 @@ public class CameraScreen: ViewController {
return
}
didStopCameraCapture = true
self.node.pauseCameraCapture()
}
let resumeCameraCapture = { [weak self] in
guard didStopCameraCapture, let self else {
return
}
didStopCameraCapture = false
self.node.resumeCameraCapture()
}
let controller: ViewController
if let current = self.galleryController {
controller = current
@ -1809,10 +1816,8 @@ public class CameraScreen: ViewController {
self.completion(.single(.draft(draft)), resultTransition, dismissed)
}
}
}, dismissed: { [weak self] in
if let self {
self.node.resumeCameraCapture()
}
}, dismissed: {
resumeCameraCapture()
})
self.galleryController = controller
}

View File

@ -49,6 +49,8 @@ public final class PaneSearchContainerNode: ASDisplayNode, EntitySearchContainer
public var openGifContextMenu: ((MultiplexedVideoNodeFile, ASDisplayNode, CGRect, ContextGesture, Bool) -> Void)?
public var addOnSupernode: Bool = true
public var ready: Signal<Void, NoError> {
return self.contentNode.ready
}

View File

@ -1718,6 +1718,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
let textFieldFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textFrame.minX, height: backgroundFrame.height))
let textField = EmojiSearchTextField(frame: textFieldFrame)
textField.keyboardAppearance = params.theme.rootController.keyboardColor.keyboardAppearance
textField.autocorrectionType = .no
textField.returnKeyType = .search
self.textField = textField

View File

@ -57,6 +57,8 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode
public var onCancel: (() -> Void)?
public var addOnSupernode: Bool = false
private let emojiSearchDisposable = MetaDisposable()
private let emojiSearchState = Promise<EmojiSearchState>(EmojiSearchState(result: nil, isSearching: false))
private var emojiSearchStateValue = EmojiSearchState(result: nil, isSearching: false) {

View File

@ -803,7 +803,7 @@ public final class EntityKeyboardComponent: Component {
searchView = ComponentHostView<EntitySearchContentEnvironment>()
self.searchView = searchView
self.addSubview(searchView)
animateIn = true
component.topPanelExtensionUpdated(0.0, transition)
}

View File

@ -13,6 +13,8 @@ import AsyncDisplayKit
import ComponentDisplayAdapters
public protocol EntitySearchContainerNode: ASDisplayNode {
var addOnSupernode: Bool { get set }
var onCancel: (() -> Void)? { get set }
func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, inputHeight: CGFloat, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition)

View File

@ -288,7 +288,7 @@ public final class MediaEditor {
let colors = mediaEditorGetGradientColors(from: image)
textureSource = .single((ImageTextureSource(image: image, renderTarget: renderTarget), image, nil, colors.0, colors.1))
case let .draft(draft):
guard let image = UIImage(contentsOfFile: draft.path) else {
guard let image = UIImage(contentsOfFile: draft.fullPath()) else {
return
}
let colors: (UIColor, UIColor)
@ -607,6 +607,11 @@ public final class MediaEditor {
}
}
public func requestRenderFrame() {
self.renderer.willRenderFrame()
self.renderer.renderFrame()
}
private func maybeGeneratePersonSegmentation(_ image: UIImage?) {
if #available(iOS 15.0, *), let cgImage = image?.cgImage {
let faceRequest = VNDetectFaceRectanglesRequest { [weak self] request, _ in

View File

@ -5,10 +5,52 @@ import TelegramCore
import TelegramUIPreferences
import PersistentStringHash
import Postbox
import AccountContext
public enum MediaEditorResultPrivacy: Equatable {
public enum MediaEditorResultPrivacy: Codable, Equatable {
case story(privacy: EngineStoryPrivacy, timeout: Int, archive: Bool)
case message(peers: [EnginePeer.Id], timeout: Int?)
private enum CodingKeys: String, CodingKey {
case type
case privacy
case peers
case timeout
case archive
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let privacy = try container.decodeIfPresent(EngineStoryPrivacy.self, forKey: .privacy) {
let timeout = try container.decode(Int32.self, forKey: .timeout)
let archive = try container.decode(Bool.self, forKey: .archive)
self = .story(privacy: privacy, timeout: Int(timeout), archive: archive)
} else if let peers = try container.decodeIfPresent([EnginePeer.Id].self, forKey: .peers) {
let timeout = try container.decodeIfPresent(Int32.self, forKey: .timeout)
self = .message(peers: peers, timeout: timeout.flatMap { Int($0) })
} else {
fatalError()
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case let .story(privacy, timeout, archive):
try container.encode(privacy, forKey: .privacy)
try container.encode(Int32(timeout), forKey: .timeout)
try container.encode(archive, forKey: .archive)
case let .message(peers, timeout):
try container.encode(peers, forKey: .peers)
if let timeout {
try container.encode(Int32(timeout), forKey: .timeout)
} else {
try container.encodeNil(forKey: .timeout)
}
}
}
}
public final class MediaEditorDraft: Codable, Equatable {
@ -66,8 +108,9 @@ public final class MediaEditorDraft: Codable, Equatable {
} else {
fatalError()
}
self.caption = NSAttributedString()
self.caption = ((try? container.decode(ChatTextInputStateText.self, forKey: .caption)) ?? ChatTextInputStateText()).attributedText()
self.privacy = nil
//self.privacy = try container.decode(MediaEditorResultPrivacy.self, forKey: .values)
}
public func encode(to encoder: Encoder) throws {
@ -85,6 +128,10 @@ public final class MediaEditorDraft: Codable, Equatable {
} else {
fatalError()
}
let chatInputText = ChatTextInputStateText(attributedText: self.caption)
try container.encode(chatInputText, forKey: .caption)
//try container.encode(self.privacy, forKey: .privacy)
}
}
@ -122,7 +169,7 @@ public func addStoryDraft(engine: TelegramEngine, item: MediaEditorDraft) {
public func removeStoryDraft(engine: TelegramEngine, path: String, delete: Bool) {
if delete {
try? FileManager.default.removeItem(atPath: path)
try? FileManager.default.removeItem(atPath: fullDraftPath(path))
}
let itemId = MediaEditorDraftItemId(path.persistentHashValue)
let _ = engine.orderedLists.removeItem(collectionId: ApplicationSpecificOrderedItemListCollectionId.storyDrafts, id: itemId.rawValue).start()
@ -144,3 +191,13 @@ public func storyDrafts(engine: TelegramEngine) -> Signal<[MediaEditorDraft], No
return result
}
}
public extension MediaEditorDraft {
func fullPath() -> String {
return fullDraftPath(self.path)
}
}
private func fullDraftPath(_ path: String) -> String {
return NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + "/storyDrafts/" + path
}

View File

@ -223,7 +223,7 @@ final class MediaEditorScreenComponent: Component {
private let fadeView = UIButton()
private let inputPanel = ComponentView<Empty>()
fileprivate let inputPanel = ComponentView<Empty>()
private let inputPanelExternalState = MessageInputPanelComponent.ExternalState()
private let inputPanelBackground = ComponentView<Empty>()
@ -584,6 +584,18 @@ final class MediaEditorScreenComponent: Component {
}
}
func getInputText() -> NSAttributedString {
guard let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View else {
return NSAttributedString()
}
var inputText = NSAttributedString()
switch inputPanelView.getSendMessageInput() {
case let .text(text):
inputText = text
}
return inputText
}
func update(component: MediaEditorScreenComponent, availableSize: CGSize, state: State, environment: Environment<ViewControllerComponentContainer.Environment>, transition: Transition) -> CGSize {
guard !self.isDismissed else {
return availableSize
@ -1000,6 +1012,9 @@ final class MediaEditorScreenComponent: Component {
self.isEditingCaption = isEditingCaption
if isEditingCaption {
if let controller = environment.controller() as? MediaEditorScreen {
controller.dismissAllTooltips()
}
mediaEditor?.stop()
} else {
mediaEditor?.play()
@ -1658,25 +1673,12 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
hasTrending: true,
forceHasPremium: true
)
let maskItems = EmojiPagerContentComponent.stickerInputData(
context: controller.context,
animationCache: controller.context.animationCache,
animationRenderer: controller.context.animationRenderer,
stickerNamespaces: [Namespaces.ItemCollection.CloudMaskPacks],
stickerOrderedItemListCollectionIds: [],
chatPeerId: controller.context.account.peerId,
hasSearch: false,
hasTrending: false,
forceHasPremium: true
)
let signal = combineLatest(queue: .mainQueue(),
emojiItems,
stickerItems,
maskItems
) |> map { emoji, stickers, masks -> StickerPickerInputData in
return StickerPickerInputData(emoji: emoji, stickers: stickers, masks: masks)
stickerItems
) |> map { emoji, stickers -> StickerPickerInputData in
return StickerPickerInputData(emoji: emoji, stickers: stickers, masks: nil)
}
stickerPickerInputData.set(signal)
@ -1752,7 +1754,6 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let imageEntity = DrawingStickerEntity(content: .image(image ?? additionalImage))
imageEntity.referenceDrawingSize = storyDimensions
imageEntity.scale = 1.49
imageEntity.mirrored = true
imageEntity.position = position.getPosition(storyDimensions)
self.entitiesView.add(imageEntity, announce: false)
} else if case let .video(_, _, additionalVideoPath, additionalVideoImage, _, position) = subject, let additionalVideoPath {
@ -2135,7 +2136,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
// }
}
func animateOut(finished: Bool, completion: @escaping () -> Void) {
func animateOut(finished: Bool, saveDraft: Bool, completion: @escaping () -> Void) {
guard let controller = self.controller else {
return
}
@ -2147,6 +2148,16 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.backgroundDimView.alpha = 0.0
self.backgroundDimView.layer.animateAlpha(from: previousDimAlpha, to: 0.0, duration: 0.15)
var isNew: Bool? = false
if let subject = self.subject {
if saveDraft {
isNew = true
}
if case .draft = subject, !saveDraft {
isNew = nil
}
}
if finished, case .message = controller.state.privacy {
if let view = self.componentHost.view as? MediaEditorScreenComponent.View {
view.animateOut(to: .camera)
@ -2158,10 +2169,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
view.previewView = nil
}
})
} else if let transitionOut = controller.transitionOut(finished), let destinationView = transitionOut.destinationView {
} else if let transitionOut = controller.transitionOut(finished, isNew), let destinationView = transitionOut.destinationView {
var destinationTransitionView: UIView?
if !finished {
if let transitionIn = controller.transitionIn, case let .gallery(galleryTransitionIn) = transitionIn, let sourceImage = galleryTransitionIn.sourceImage {
if let transitionIn = controller.transitionIn, case let .gallery(galleryTransitionIn) = transitionIn, let sourceImage = galleryTransitionIn.sourceImage, isNew != true {
let sourceSuperView = galleryTransitionIn.sourceView?.superview?.superview
let destinationTransitionOutView = UIImageView(image: sourceImage)
destinationTransitionOutView.clipsToBounds = true
@ -2179,22 +2190,34 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let destinationAspectRatio = destinationLocalFrame.height / destinationLocalFrame.width
var destinationSnapshotView: UIView?
if let destinationNode = destinationView.asyncdisplaykit_node, destinationNode is AvatarNode, let snapshotView = destinationView.snapshotView(afterScreenUpdates: false) {
if let destinationNode = destinationView.asyncdisplaykit_node as? AvatarNode {
let destinationTransitionView: UIView?
if let image = destinationNode.unroundedImage {
destinationTransitionView = UIImageView(image: image)
destinationTransitionView?.bounds = destinationNode.bounds
destinationTransitionView?.layer.cornerRadius = destinationNode.bounds.width / 2.0
} else if let snapshotView = destinationView.snapshotView(afterScreenUpdates: false) {
destinationTransitionView = snapshotView
} else {
destinationTransitionView = nil
}
destinationView.isHidden = true
snapshotView.layer.anchorPoint = CGPoint(x: 0.0, y: 0.5)
let snapshotScale = self.previewContainerView.bounds.width / snapshotView.frame.width
snapshotView.center = CGPoint(x: 0.0, y: self.previewContainerView.bounds.height / 2.0)
snapshotView.layer.transform = CATransform3DMakeScale(snapshotScale, snapshotScale, 1.0)
snapshotView.alpha = 0.0
Queue.mainQueue().after(0.15) {
snapshotView.alpha = 1.0
snapshotView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
if let destinationTransitionView {
destinationTransitionView.layer.anchorPoint = CGPoint(x: 0.0, y: 0.5)
let snapshotScale = self.previewContainerView.bounds.width / destinationTransitionView.frame.width
destinationTransitionView.center = CGPoint(x: 0.0, y: self.previewContainerView.bounds.height / 2.0)
destinationTransitionView.layer.transform = CATransform3DMakeScale(snapshotScale, snapshotScale, 1.0)
destinationTransitionView.alpha = 0.0
Queue.mainQueue().after(0.15) {
destinationTransitionView.alpha = 1.0
destinationTransitionView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
}
self.previewContainerView.addSubview(destinationTransitionView)
destinationSnapshotView = destinationTransitionView
}
self.previewContainerView.addSubview(snapshotView)
destinationSnapshotView = snapshotView
}
self.previewContainerView.layer.animatePosition(from: self.previewContainerView.center, to: destinationLocalFrame.center, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
@ -2258,7 +2281,11 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
completion()
})
} else {
completion()
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.4, removeOnCompletion: false)
self.layer.animateScale(from: 1.0, to: 0.8, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
self.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: self.bounds.height), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { _ in
completion()
})
}
}
@ -2820,7 +2847,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
fileprivate let context: AccountContext
fileprivate let subject: Signal<Subject?, NoError>
fileprivate let transitionIn: TransitionIn?
fileprivate let transitionOut: (Bool) -> TransitionOut?
fileprivate let transitionOut: (Bool, Bool?) -> TransitionOut?
public var cancelled: (Bool) -> Void = { _ in }
public var completion: (Int64, MediaEditorScreen.Result, MediaEditorResultPrivacy, @escaping (@escaping () -> Void) -> Void) -> Void = { _, _, _, _ in }
@ -2832,7 +2859,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
context: AccountContext,
subject: Signal<Subject?, NoError>,
transitionIn: TransitionIn?,
transitionOut: @escaping (Bool) -> TransitionOut?,
transitionOut: @escaping (Bool, Bool?) -> TransitionOut?,
completion: @escaping (Int64, MediaEditorScreen.Result, MediaEditorResultPrivacy, @escaping (@escaping () -> Void) -> Void) -> Void
) {
self.context = context
@ -3153,11 +3180,14 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
func maybePresentDiscardAlert() {
self.hapticFeedback.impact(.light)
if "".isEmpty {
self.requestDismiss(saveDraft: false, animated: true)
guard let mediaEditor = self.node.mediaEditor else {
return
}
let entities = self.node.entitiesView.entities.filter { !($0 is DrawingMediaEntity) }
let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView)
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
self.hapticFeedback.impact(.light)
if let subject = self.node.subject, case .asset = subject, self.node.mediaEditor?.values.hasChanges == false {
self.requestDismiss(saveDraft: false, animated: true)
return
@ -3203,9 +3233,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
if saveDraft {
self.saveDraft(id: nil)
} else {
// if case let .draft(draft, _) = self.node.subject {
// removeStoryDraft(engine: self.context.engine, path: draft.path, delete: true)
// }
if case let .draft(draft, _) = self.node.subject {
removeStoryDraft(engine: self.context.engine, path: draft.path, delete: true)
}
}
if let mediaEditor = self.node.mediaEditor {
@ -3215,7 +3245,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.cancelled(saveDraft)
self.node.animateOut(finished: false, completion: { [weak self] in
self.node.animateOut(finished: false, saveDraft: saveDraft, completion: { [weak self] in
self?.dismiss()
self?.dismissed()
})
@ -3228,6 +3258,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
try? FileManager.default.createDirectory(atPath: draftPath(), withIntermediateDirectories: true)
let privacy = self.state.privacy
let caption = (self.node.componentHost.view as? MediaEditorScreenComponent.View)?.getInputText() ?? NSAttributedString()
if let resultImage = self.node.mediaEditor?.resultImage {
self.node.mediaEditor?.seek(0.0, andPlay: false)
@ -3239,10 +3270,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let saveImageDraft: (UIImage, PixelDimensions) -> Void = { image, dimensions in
if let thumbnailImage = generateScaledImage(image: resultImage, size: fittedSize) {
let path = draftPath() + "/\(Int64.random(in: .min ... .max)).jpg"
let path = "\(Int64.random(in: .min ... .max)).jpg"
if let data = image.jpegData(compressionQuality: 0.87) {
try? data.write(to: URL(fileURLWithPath: path))
let draft = MediaEditorDraft(path: path, isVideo: false, thumbnail: thumbnailImage, dimensions: dimensions, values: values, caption: NSAttributedString(), privacy: privacy)
let draft = MediaEditorDraft(path: path, isVideo: false, thumbnail: thumbnailImage, dimensions: dimensions, values: values, caption: caption, privacy: privacy)
try? data.write(to: URL(fileURLWithPath: draft.fullPath()))
if let id {
saveStorySource(engine: self.context.engine, item: draft, id: id)
} else {
@ -3254,9 +3285,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let saveVideoDraft: (String, PixelDimensions) -> Void = { videoPath, dimensions in
if let thumbnailImage = generateScaledImage(image: resultImage, size: fittedSize) {
let path = draftPath() + "/\(Int64.random(in: .min ... .max)).mp4"
try? FileManager.default.moveItem(atPath: videoPath, toPath: path)
let draft = MediaEditorDraft(path: path, isVideo: true, thumbnail: thumbnailImage, dimensions: dimensions, values: values, caption: NSAttributedString(), privacy: privacy)
let path = "\(Int64.random(in: .min ... .max)).mp4"
let draft = MediaEditorDraft(path: path, isVideo: true, thumbnail: thumbnailImage, dimensions: dimensions, values: values, caption: caption, privacy: privacy)
try? FileManager.default.moveItem(atPath: videoPath, toPath: draft.fullPath())
if let id {
saveStorySource(engine: self.context.engine, item: draft, id: id)
} else {
@ -3288,15 +3319,11 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
case let .draft(draft, _):
if draft.isVideo {
saveVideoDraft(draft.path, draft.dimensions)
} else if let image = UIImage(contentsOfFile: draft.path) {
saveVideoDraft(draft.fullPath(), draft.dimensions)
} else if let image = UIImage(contentsOfFile: draft.fullPath()) {
saveImageDraft(image, draft.dimensions)
}
// if let thumbnailImage = generateScaledImage(image: resultImage, size: fittedSize) {
// removeStoryDraft(engine: self.context.engine, path: draft.path, delete: false)
// let draft = MediaEditorDraft(path: draft.path, isVideo: draft.isVideo, thumbnail: thumbnailImage, dimensions: draft.dimensions, values: values)
// addStoryDraft(engine: self.context.engine, item: draft)
// }
removeStoryDraft(engine: self.context.engine, path: draft.path, delete: false)
}
})
}
@ -3311,7 +3338,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.dismissAllTooltips()
mediaEditor.seek(0.0, andPlay: false)
mediaEditor.seek(mediaEditor.values.videoTrimRange?.lowerBound ?? 0.0, andPlay: false)
mediaEditor.requestRenderFrame()
mediaEditor.invalidate()
self.node.entitiesView.invalidate()
@ -3331,6 +3359,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
if mediaEditor.resultIsVideo {
var firstFrame: Signal<UIImage?, NoError>
let firstFrameTime = CMTime(seconds: mediaEditor.values.videoTrimRange?.lowerBound ?? 0.0, preferredTimescale: CMTimeScale(60))
let videoResult: Result.VideoResult
let duration: Double
switch subject {
@ -3341,6 +3372,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
videoResult = .imageFile(path: tempImagePath)
duration = 5.0
firstFrame = .single(image)
case let .video(path, _, _, _, _, _):
videoResult = .videoFile(path: path)
if let videoTrimRange = mediaEditor.values.videoTrimRange {
@ -3348,6 +3381,20 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
} else {
duration = 5.0
}
firstFrame = Signal<UIImage?, NoError> { subscriber in
let avAsset = AVURLAsset(url: URL(fileURLWithPath: path))
let avAssetGenerator = AVAssetImageGenerator(asset: avAsset)
avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: firstFrameTime)], completionHandler: { _, cgImage, _, _, _ in
if let cgImage {
subscriber.putNext(UIImage(cgImage: cgImage))
subscriber.putCompletion()
}
})
return ActionDisposable {
avAssetGenerator.cancelAllCGImageGeneration()
}
}
case let .asset(asset):
videoResult = .asset(localIdentifier: asset.localIdentifier)
if asset.mediaType == .video {
@ -3359,34 +3406,86 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
} else {
duration = 5.0
}
firstFrame = Signal<UIImage?, NoError> { subscriber in
if asset.mediaType == .video {
PHImageManager.default().requestAVAsset(forVideo: asset, options: nil) { avAsset, _, _ in
if let avAsset {
let avAssetGenerator = AVAssetImageGenerator(asset: avAsset)
avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: firstFrameTime)], completionHandler: { _, cgImage, _, _, _ in
if let cgImage {
subscriber.putNext(UIImage(cgImage: cgImage))
subscriber.putCompletion()
}
})
}
}
} else {
let options = PHImageRequestOptions()
options.deliveryMode = .highQualityFormat
PHImageManager.default().requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .default, options: options) { image, _ in
if let image {
subscriber.putNext(image)
subscriber.putCompletion()
}
}
}
return EmptyDisposable
}
case let .draft(draft, _):
if draft.isVideo {
videoResult = .videoFile(path: draft.path)
videoResult = .videoFile(path: draft.fullPath())
if let videoTrimRange = mediaEditor.values.videoTrimRange {
duration = videoTrimRange.upperBound - videoTrimRange.lowerBound
} else {
duration = 5.0
}
firstFrame = Signal<UIImage?, NoError> { subscriber in
let avAsset = AVURLAsset(url: URL(fileURLWithPath: draft.fullPath()))
let avAssetGenerator = AVAssetImageGenerator(asset: avAsset)
avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: firstFrameTime)], completionHandler: { _, cgImage, _, _, _ in
if let cgImage {
subscriber.putNext(UIImage(cgImage: cgImage))
subscriber.putCompletion()
}
})
return ActionDisposable {
avAssetGenerator.cancelAllCGImageGeneration()
}
}
} else {
videoResult = .imageFile(path: draft.path)
videoResult = .imageFile(path: draft.fullPath())
duration = 5.0
if let image = UIImage(contentsOfFile: draft.fullPath()) {
firstFrame = .single(image)
} else {
firstFrame = .single(UIImage())
}
}
}
if let resultImage = mediaEditor.resultImage {
firstFrame = .single(resultImage)
}
// makeEditorImageComposition(account: self.context.account, inputImage: image, dimensions: storyDimensions, values: mediaEditor.values, time: .zero, completion: { [weak self] coverImage in
// if let self {
self.completion(randomId, .video(video: videoResult, coverImage: nil, values: mediaEditor.values, duration: duration, dimensions: mediaEditor.values.resultDimensions, caption: caption), self.state.privacy, { [weak self] finished in
self?.node.animateOut(finished: true, completion: { [weak self] in
self?.dismiss()
Queue.mainQueue().justDispatch {
finished()
}
})
let _ = (firstFrame
|> deliverOnMainQueue).start(next: { [weak self] image in
if let self {
makeEditorImageComposition(account: self.context.account, inputImage: image ?? UIImage(), dimensions: storyDimensions, values: mediaEditor.values, time: .zero, completion: { [weak self] coverImage in
if let self {
self.completion(randomId, .video(video: videoResult, coverImage: coverImage, values: mediaEditor.values, duration: duration, dimensions: mediaEditor.values.resultDimensions, caption: caption), self.state.privacy, { [weak self] finished in
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
self?.dismiss()
Queue.mainQueue().justDispatch {
finished()
}
})
})
}
})
// }
// })
}
})
if case let .draft(draft, id) = subject, id == nil {
removeStoryDraft(engine: self.context.engine, path: draft.path, delete: true)
}
@ -3397,7 +3496,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
makeEditorImageComposition(account: self.context.account, inputImage: image, dimensions: storyDimensions, values: mediaEditor.values, time: .zero, completion: { [weak self] resultImage in
if let self, let resultImage {
self.completion(randomId, .image(image: resultImage, dimensions: PixelDimensions(resultImage.size), caption: caption), self.state.privacy, { [weak self] finished in
self?.node.animateOut(finished: true, completion: { [weak self] in
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
self?.dismiss()
Queue.mainQueue().justDispatch {
finished()
@ -3491,10 +3590,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
case let .draft(draft, _):
if draft.isVideo {
let asset = AVURLAsset(url: NSURL(fileURLWithPath: draft.path) as URL)
let asset = AVURLAsset(url: NSURL(fileURLWithPath: draft.fullPath()) as URL)
exportSubject = .single(.video(asset))
} else {
if let image = UIImage(contentsOfFile: draft.path) {
if let image = UIImage(contentsOfFile: draft.fullPath()) {
exportSubject = .single(.image(image))
} else {
fatalError()
@ -3577,7 +3676,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.node.updateEditProgress(progress)
}
private func dismissAllTooltips() {
fileprivate func dismissAllTooltips() {
self.window?.forEachController({ controller in
if let controller = controller as? TooltipScreen {
controller.dismiss()
@ -3724,11 +3823,6 @@ private final class HeaderContextReferenceContentSource: ContextReferenceContent
}
}
private func draftPath() -> String {
return NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + "/storyDrafts"
}
private final class ToolValueComponent: Component {
typealias EnvironmentType = Empty
@ -3927,3 +4021,7 @@ public final class BlurredGradientComponent: Component {
return view.update(component: self, availableSize: availableSize, transition: transition)
}
}
func draftPath() -> String {
return NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + "/storyDrafts"
}

View File

@ -1776,7 +1776,7 @@ public final class StoryItemSetContainerComponent: Component {
let reactionsAnchorRect = CGRect(origin: CGPoint(x: inputPanelFrame.maxX - 40.0, y: inputPanelFrame.minY + 9.0), size: CGSize(width: 32.0, height: 32.0)).insetBy(dx: -4.0, dy: -4.0)
var effectiveDisplayReactions = false
if self.inputPanelExternalState.isEditing && !self.inputPanelExternalState.hasText {
if self.inputPanelExternalState.isEditing && !self.inputPanelExternalState.hasText {
effectiveDisplayReactions = true
}
if self.sendMessageContext.audioRecorderValue != nil || self.sendMessageContext.videoRecorderValue != nil {
@ -1789,6 +1789,10 @@ public final class StoryItemSetContainerComponent: Component {
effectiveDisplayReactions = false
}
if let reactionContextNode = self.reactionContextNode, reactionContextNode.isReactionSearchActive {
effectiveDisplayReactions = true
}
if let reactionItems = self.reactionItems, effectiveDisplayReactions {
let reactionContextNode: ReactionContextNode
var reactionContextNodeTransition = transition
@ -2157,7 +2161,7 @@ public final class StoryItemSetContainerComponent: Component {
context: context,
subject: .single(.draft(source, Int64(id))),
transitionIn: nil,
transitionOut: { _ in return nil },
transitionOut: { _, _ in return nil },
completion: { [weak self] _, mediaResult, privacy, commit in
switch mediaResult {
case let .image(image, dimensions, caption):

View File

@ -101,11 +101,12 @@ final class StoryItemSetContainerSendMessage {
switch inputPanelView.getSendMessageInput() {
case let .text(text):
if !text.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
let entities = generateChatInputTextEntities(text)
component.context.engine.messages.enqueueOutgoingMessage(
to: peerId,
replyTo: nil,
storyId: StoryId(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id),
content: .text(text.string)
content: .text(text.string, entities)
)
inputPanelView.clearSendMessageInput()
view.endEditing(true)

View File

@ -696,7 +696,7 @@ public final class StoryPeerListItemComponent: Component {
}
var titleTransition = transition
if previousComponent?.ringAnimation != nil && component.ringAnimation == nil {
if let previousAnimation = previousComponent?.ringAnimation, case .progress = previousAnimation, component.ringAnimation == nil {
if let titleView = self.title.view, let snapshotView = titleView.snapshotView(afterScreenUpdates: false) {
self.button.addSubview(snapshotView)
snapshotView.frame = titleView.frame
@ -706,6 +706,20 @@ public final class StoryPeerListItemComponent: Component {
titleView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
}
titleTransition = .immediate
self.avatarContent.layer.transform = CATransform3DMakeScale(1.08, 1.08, 1.0)
self.avatarContent.layer.animateScale(from: 1.0, to: 1.08, duration: 0.2, completion: { [weak self] _ in
self?.avatarContent.layer.transform = CATransform3DMakeScale(1.0, 1.0, 1.0)
self?.avatarContent.layer.animateScale(from: 1.08, to: 1.0, duration: 0.15)
})
let initialLineWidth: CGFloat = 2.0
let targetLineWidth: CGFloat = 3.0
self.indicatorShapeLayer.lineWidth = targetLineWidth
self.indicatorShapeLayer.animateShapeLineWidth(from: initialLineWidth, to: targetLineWidth, duration: 0.2, completion: { [weak self] _ in
self?.indicatorShapeLayer.lineWidth = initialLineWidth
self?.indicatorShapeLayer.animateShapeLineWidth(from: targetLineWidth, to: initialLineWidth, duration: 0.15)
})
}
let titleSize = self.title.update(
@ -746,7 +760,13 @@ public final class StoryPeerListItemComponent: Component {
switch ringAnimation {
case let .progress(progress):
progressLayer.update(size: progressFrame.size, lineWidth: 4.0, value: .progress(progress), transition: transition)
let progressTransition: Transition
if abs(progress - 0.028) < 0.001 {
progressTransition = .immediate
} else {
progressTransition = .easeInOut(duration: 0.3)
}
progressLayer.update(size: progressFrame.size, lineWidth: 4.0, value: .progress(progress), transition: progressTransition)
case .loading:
progressLayer.update(size: progressFrame.size, lineWidth: 4.0, value: .indefinite, transition: transition)
}

View File

@ -1834,7 +1834,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: mainStickerPack, stickerPacks: stickerPacks, loadedStickerPacks: loadedStickerPacks, parentNavigationController: parentNavigationController, sendSticker: sendSticker)
}
public func makeMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping () -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController {
public func makeMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController {
return storyMediaPickerController(context: context, getSourceRect: getSourceRect, completion: completion, dismissed: dismissed)
}

View File

@ -331,14 +331,14 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
context: context,
subject: subject,
transitionIn: transitionIn,
transitionOut: { finished in
transitionOut: { finished, isNew in
if finished, let transitionOut = transitionOut(finished), let destinationView = transitionOut.destinationView {
return MediaEditorScreen.TransitionOut(
destinationView: destinationView,
destinationRect: transitionOut.destinationRect,
destinationCornerRadius: transitionOut.destinationCornerRadius
)
} else if !finished, let resultTransition, let (destinationView, destinationRect) = resultTransition.transitionOut() {
} else if !finished, let resultTransition, let (destinationView, destinationRect) = resultTransition.transitionOut(isNew) {
return MediaEditorScreen.TransitionOut(
destinationView: destinationView,
destinationRect: destinationRect,