mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Camera and editor improvements
This commit is contained in:
parent
197e27d448
commit
803b887a2e
@ -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
|
||||
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -94,6 +94,7 @@ swift_library(
|
||||
"//submodules/TelegramNotices:TelegramNotices",
|
||||
"//submodules/FastBlur:FastBlur",
|
||||
"//submodules/TelegramUI/Components/MediaEditor",
|
||||
"//submodules/ChatPresentationInterfaceState:ChatPresentationInterfaceState",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user