diff --git a/submodules/DrawingUI/Sources/StickerPickerScreen.swift b/submodules/DrawingUI/Sources/StickerPickerScreen.swift index 5ec41118d3..368cd33e46 100644 --- a/submodules/DrawingUI/Sources/StickerPickerScreen.swift +++ b/submodules/DrawingUI/Sources/StickerPickerScreen.swift @@ -335,6 +335,16 @@ private final class StickerSelectionComponent: Component { return availableSize } + + public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + let result = super.hitTest(point, with: event) + + if self.searchVisible, let keyboardView = self.keyboardView.view, let keyboardResult = keyboardView.hitTest(self.convert(point, to: keyboardView), with: event) { + return keyboardResult + } + + return result + } } public func makeView() -> View { @@ -363,7 +373,7 @@ public class StickerPickerScreen: ViewController { private(set) var isExpanded = false private var panGestureRecognizer: UIPanGestureRecognizer? - private var panGestureArguments: (topInset: CGFloat, offset: CGFloat, scrollView: UIScrollView?)? + private var panGestureArguments: (topInset: CGFloat, offset: CGFloat, scrollView: UIScrollView?, listNode: ListView?)? private var currentIsVisible: Bool = false private var currentLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)? @@ -844,85 +854,6 @@ public class StickerPickerScreen: ViewController { stateContext: nil ) - content.masks?.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction( - performItemAction: { [weak self] _, item, _, _, _, _ in - guard let strongSelf = self, let file = item.itemFile else { - return - } - strongSelf.controller?.completion(.file(file)) - strongSelf.controller?.dismiss(animated: true) - }, - deleteBackwards: nil, - openStickerSettings: nil, - openFeatured: nil, - openSearch: {}, - addGroupAction: { [weak self] groupId, isPremiumLocked, _ in - guard let strongSelf = self, let controller = strongSelf.controller, let collectionId = groupId.base as? ItemCollectionId else { - return - } - let context = controller.context - let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedStickerPacks) - let _ = (context.account.postbox.combinedView(keys: [viewKey]) - |> take(1) - |> deliverOnMainQueue).start(next: { views in - guard let view = views.views[viewKey] as? OrderedItemListView else { - return - } - for featuredStickerPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { - if featuredStickerPack.info.id == collectionId { - let _ = (context.engine.stickers.loadedStickerPack(reference: .id(id: featuredStickerPack.info.id.id, accessHash: featuredStickerPack.info.accessHash), forceActualized: false) - |> mapToSignal { result -> Signal in - switch result { - case let .result(info, items, installed): - if installed { - return .complete() - } else { - return context.engine.stickers.addStickerPackInteractively(info: info, items: items) - } - case .fetching: - break - case .none: - break - } - return .complete() - } - |> deliverOnMainQueue).start(completed: { - }) - - break - } - } - }) - }, - clearGroup: { _ in - }, - pushController: { c in - }, - presentController: { c in - }, - presentGlobalOverlayController: { c in - }, - navigationController: { [weak self] in - return self?.controller?.navigationController as? NavigationController - }, - requestUpdate: { _ in - }, - updateSearchQuery: { _ in - }, - updateScrollingToItemGroup: { [weak self] in - self?.update(isExpanded: true, transition: .animated(duration: 0.4, curve: .spring)) - }, - onScroll: {}, - chatPeerId: nil, - peekBehavior: nil, - customLayout: nil, - externalBackground: nil, - externalExpansionView: nil, - useOpaqueTheme: false, - hideBackground: true, - stateContext: nil - ) - var stickerPeekBehavior: EmojiContentPeekBehaviorImpl? if let controller = self.controller { stickerPeekBehavior = EmojiContentPeekBehaviorImpl( @@ -951,6 +882,7 @@ public class StickerPickerScreen: ViewController { if let pagerView = componentView.keyboardView.view as? EntityKeyboardComponent.View { pagerView.openSearch() } + self.update(isExpanded: true, transition: .animated(duration: 0.4, curve: .spring)) } }, addGroupAction: { [weak self] groupId, isPremiumLocked, _ in @@ -1036,7 +968,13 @@ public class StickerPickerScreen: ViewController { navigationController: { [weak self] in return self?.controller?.navigationController as? NavigationController }, - requestUpdate: { _ in + requestUpdate: { [weak self] transition in + guard let strongSelf = self else { + return + } + if !transition.animation.isImmediate, let (layout, navigationHeight) = strongSelf.currentLayout { + strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: transition) + } }, updateSearchQuery: { [weak self] query in guard let strongSelf = self, let controller = strongSelf.controller else { @@ -1239,7 +1177,7 @@ public class StickerPickerScreen: ViewController { let edgeTopInset = isLandscape ? 0.0 : self.defaultTopInset let topInset: CGFloat var bottomInset = layout.intrinsicInsets.bottom - if let (panInitialTopInset, panOffset, _) = self.panGestureArguments { + if let (panInitialTopInset, panOffset, _, _) = self.panGestureArguments { if effectiveExpanded { topInset = min(edgeTopInset, panInitialTopInset + max(0.0, panOffset)) } else { @@ -1382,10 +1320,16 @@ public class StickerPickerScreen: ViewController { } } - private func findScrollView(view: UIView?) -> UIScrollView? { + private func findScrollView(view: UIView?) -> (UIScrollView, ListView?)? { if let view = view { if let view = view as? PagerExpandableScrollView { - return view + return (view, nil) + } + if let view = view as? GridNodeScrollerView { + return (view, nil) + } + if let node = view.asyncdisplaykit_node as? ListView { + return (node.scroller, node) } return findScrollView(view: view.superview) } else { @@ -1406,11 +1350,13 @@ public class StickerPickerScreen: ViewController { let point = recognizer.location(in: self.view) let currentHitView = self.hitTest(point, with: nil) - var scrollView = self.findScrollView(view: currentHitView) - if scrollView?.frame.height == self.frame.width { - scrollView = nil + var scrollViewAndListNode = self.findScrollView(view: currentHitView) + if scrollViewAndListNode?.0.frame.height == self.frame.width { + scrollViewAndListNode = nil } - + let scrollView = scrollViewAndListNode?.0 + let listNode = scrollViewAndListNode?.1 + let topInset: CGFloat if self.isExpanded { topInset = 0.0 @@ -1418,11 +1364,12 @@ public class StickerPickerScreen: ViewController { topInset = edgeTopInset } - self.panGestureArguments = (topInset, 0.0, scrollView) + self.panGestureArguments = (topInset, 0.0, scrollView, listNode) case .changed: - guard let (topInset, panOffset, scrollView) = self.panGestureArguments else { + guard let (topInset, panOffset, scrollView, listNode) = self.panGestureArguments else { return } + let visibleContentOffset = listNode?.visibleContentOffset() let contentOffset = scrollView?.contentOffset.y ?? 0.0 var translation = recognizer.translation(in: self.view).y @@ -1430,7 +1377,12 @@ public class StickerPickerScreen: ViewController { var currentOffset = topInset + translation let epsilon = 1.0 - if let scrollView = scrollView, contentOffset <= -scrollView.contentInset.top + epsilon { + if case let .known(value) = visibleContentOffset, value <= epsilon { + if let scrollView = scrollView { + scrollView.bounces = false + scrollView.setContentOffset(CGPoint(x: scrollView.contentOffset.x, y: 0.0), animated: false) + } + } else if let scrollView = scrollView, contentOffset <= -scrollView.contentInset.top + epsilon { scrollView.bounces = false scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false) } else if let scrollView = scrollView { @@ -1443,7 +1395,7 @@ public class StickerPickerScreen: ViewController { } } - self.panGestureArguments = (topInset, translation, scrollView) + self.panGestureArguments = (topInset, translation, scrollView, listNode) if !self.isExpanded { if currentOffset > 0.0, let scrollView = scrollView { @@ -1462,18 +1414,23 @@ public class StickerPickerScreen: ViewController { self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate) case .ended: - guard let (currentTopInset, panOffset, scrollView) = self.panGestureArguments else { + guard let (currentTopInset, panOffset, scrollView, listNode) = self.panGestureArguments else { return } self.panGestureArguments = nil + let visibleContentOffset = listNode?.visibleContentOffset() let contentOffset = scrollView?.contentOffset.y ?? 0.0 let translation = recognizer.translation(in: self.view).y var velocity = recognizer.velocity(in: self.view) if self.isExpanded { - if contentOffset > 0.1 { + if case let .known(value) = visibleContentOffset, value > 0.1 { + velocity = CGPoint() + } else if case .unknown = visibleContentOffset { + velocity = CGPoint() + } else if contentOffset > 0.1 { velocity = CGPoint() } } @@ -1499,7 +1456,9 @@ public class StickerPickerScreen: ViewController { } else if self.isExpanded { if velocity.y > 300.0 || offset > topInset / 2.0 { self.isExpanded = false - if let scrollView = scrollView { + if let listNode = listNode { + listNode.scroller.setContentOffset(CGPoint(), animated: false) + } else if let scrollView = scrollView { scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false) } @@ -1520,7 +1479,9 @@ public class StickerPickerScreen: ViewController { self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(transition)) } else { - if let scrollView = scrollView { + if let listNode = listNode { + listNode.scroller.setContentOffset(CGPoint(), animated: false) + } else if let scrollView = scrollView { scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false) } diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift index 7927d31ae7..06d3a04303 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift @@ -418,6 +418,22 @@ final class LockContentComponent: Component { } } +private func lastStateImage() -> UIImage { + let imagePath = NSTemporaryDirectory() + "galleryImage.jpg" + if let data = try? Data(contentsOf: URL(fileURLWithPath: imagePath)), let image = UIImage(data: data) { + return image + } else { + return UIImage(bundleImageName: "Camera/Placeholder")! + } +} + +private func saveLastStateImage(_ image: UIImage) { + let imagePath = NSTemporaryDirectory() + "galleryImage.jpg" + if let data = image.jpegData(compressionQuality: 0.6) { + try? data.write(to: URL(fileURLWithPath: imagePath)) + } +} + final class CaptureControlsComponent: Component { enum SwipeHint { case none @@ -505,8 +521,13 @@ final class CaptureControlsComponent: Component { self.assetDisposable.set((fetchPhotoLibraryImage(localIdentifier: lastGalleryAsset.localIdentifier, thumbnail: true) |> deliverOnMainQueue).start(next: { [weak self] imageAndDegraded in if let self, let (image, _) = imageAndDegraded { + let updated = self.cachedAssetImage?.0 != lastGalleryAsset.localIdentifier self.cachedAssetImage = (lastGalleryAsset.localIdentifier, image) self.updated(transition: .easeInOut(duration: 0.2)) + + if updated { + saveLastStateImage(image) + } } })) } @@ -514,6 +535,12 @@ final class CaptureControlsComponent: Component { } } + override init() { + self.cachedAssetImage = ("", lastStateImage()) + + super.init() + } + deinit { self.assetDisposable.dispose() } diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/PaneSearchBarNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/PaneSearchBarNode.swift index 64754e7388..3feba2a7aa 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/PaneSearchBarNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/PaneSearchBarNode.swift @@ -446,7 +446,7 @@ class PaneSearchBarNode: ASDisplayNode, UITextFieldDelegate { self.iconNode.layer.animateFrame(from: self.iconNode.frame, to: targetIconFrame, duration: duration, timingFunction: timingFunction, removeOnCompletion: false) let cancelButtonFrame = self.cancelButton.frame - self.cancelButton.layer.animatePosition(from: self.cancelButton.layer.position, to: CGPoint(x: self.bounds.size.width + cancelButtonFrame.size.width / 2.0, y: targetTextBackgroundFrame.minY + 2.0 + cancelButtonFrame.size.height / 2.0), duration: duration, timingFunction: timingFunction, removeOnCompletion: false) + self.cancelButton.layer.animatePosition(from: self.cancelButton.layer.position, to: CGPoint(x: self.bounds.size.width + cancelButtonFrame.size.width / 2.0, y: targetTextBackgroundFrame.minY + 4.0 + cancelButtonFrame.size.height / 2.0), duration: duration, timingFunction: timingFunction, removeOnCompletion: false) } func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/PaneSearchContainerNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/PaneSearchContainerNode.swift index 0c749591e7..b2971c836e 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/PaneSearchContainerNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/PaneSearchContainerNode.swift @@ -49,8 +49,6 @@ public final class PaneSearchContainerNode: ASDisplayNode, EntitySearchContainer public var openGifContextMenu: ((MultiplexedVideoNodeFile, ASDisplayNode, CGRect, ContextGesture, Bool) -> Void)? - public var addOnSupernode: Bool = true - public var ready: Signal { return self.contentNode.ready } diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift index 4724b56fec..a67b84d819 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift @@ -56,9 +56,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode private var itemGroups: [EmojiPagerContentComponent.ItemGroup] = [] public var onCancel: (() -> Void)? - - public var addOnSupernode: Bool = false - + private let emojiSearchDisposable = MetaDisposable() private let emojiSearchState = Promise(EmojiSearchState(result: nil, isSearching: false)) private var emojiSearchStateValue = EmojiSearchState(result: nil, isSearching: false) { diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntitySearchContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntitySearchContentComponent.swift index d009b49f0a..d6ccaed16b 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntitySearchContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntitySearchContentComponent.swift @@ -13,8 +13,6 @@ 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) diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index ae9d2a0954..22e28a75f7 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -1730,6 +1730,13 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate if case let .draft(draft, _) = subject, let privacy = draft.privacy { controller.state.privacy = privacy + } else if !controller.isEditingStory { + let _ = (mediaEditorStoredState(engine: controller.context.engine) + |> deliverOnMainQueue).start(next: { [weak controller] state in + if let controller, let privacy = state?.privacy { + controller.state.privacy = privacy + } + }) } let isSavingAvailable: Bool @@ -2910,18 +2917,18 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate } super.init(navigationBarPresentationData: nil) - - if let initialPrivacy { - self.state.privacy = MediaEditorResultPrivacy(privacy: initialPrivacy, timeout: 86400, archive: false) - } - + self.automaticallyControlPresentationContextLayout = false self.navigationPresentation = .flatModal - self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) - self.statusBar.statusBarStyle = .White + + if isEditing { + if let initialPrivacy { + self.state.privacy = MediaEditorResultPrivacy(privacy: initialPrivacy, timeout: 86400, archive: false) + } + } } required public init(coder aDecoder: NSCoder) { @@ -3332,6 +3339,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate return } + if !self.isEditingStory { + let _ = updateMediaEditorStoredStateInteractively(engine: self.context.engine, state: MediaEditorStoredState(privacy: self.state.privacy)).start() + } + if mediaEditor.resultIsVideo { var firstFrame: Signal let firstFrameTime = CMTime(seconds: mediaEditor.values.videoTrimRange?.lowerBound ?? 0.0, preferredTimescale: CMTimeScale(60)) diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorStoredState.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorStoredState.swift new file mode 100644 index 0000000000..f3b0bef743 --- /dev/null +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorStoredState.swift @@ -0,0 +1,63 @@ +import Foundation +import UIKit +import SwiftSignalKit +import TelegramCore +import TelegramUIPreferences +import MediaEditor + +public final class MediaEditorStoredState: Codable { + private enum CodingKeys: String, CodingKey { + case privacy + } + + public let privacy: MediaEditorResultPrivacy? + + public init(privacy: MediaEditorResultPrivacy?) { + self.privacy = privacy + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + if let data = try container.decodeIfPresent(Data.self, forKey: .privacy), let privacy = try? JSONDecoder().decode(MediaEditorResultPrivacy.self, from: data) { + self.privacy = privacy + } else { + self.privacy = nil + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + if let privacy = self .privacy { + if let data = try? JSONEncoder().encode(privacy) { + try container.encode(data, forKey: .privacy) + } else { + try container.encodeNil(forKey: .privacy) + } + } else { + try container.encodeNil(forKey: .privacy) + } + } +} + +func mediaEditorStoredState(engine: TelegramEngine) -> Signal { + let key = EngineDataBuffer(length: 4) + key.setInt32(0, value: 0) + + return engine.data.get(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: ApplicationSpecificItemCacheCollectionId.mediaEditorState, id: key)) + |> map { entry -> MediaEditorStoredState? in + return entry?.get(MediaEditorStoredState.self) + } +} + +func updateMediaEditorStoredStateInteractively(engine: TelegramEngine, state: MediaEditorStoredState?) -> Signal { + let key = EngineDataBuffer(length: 4) + key.setInt32(0, value: 0) + + if let state = state { + return engine.itemCache.put(collectionId: ApplicationSpecificItemCacheCollectionId.mediaEditorState, id: key, item: state) + } else { + return engine.itemCache.remove(collectionId: ApplicationSpecificItemCacheCollectionId.mediaEditorState, id: key) + } +} diff --git a/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift b/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift index a88995398a..9d2a07b477 100644 --- a/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift +++ b/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift @@ -77,6 +77,7 @@ private enum ApplicationSpecificItemCacheCollectionIdValues: Int8 { case pendingInAppPurchaseState = 7 case translationState = 10 case storySource = 11 + case mediaEditorState = 12 } public struct ApplicationSpecificItemCacheCollectionId { @@ -90,6 +91,7 @@ public struct ApplicationSpecificItemCacheCollectionId { public static let pendingInAppPurchaseState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.pendingInAppPurchaseState.rawValue) public static let translationState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.translationState.rawValue) public static let storySource = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.storySource.rawValue) + public static let mediaEditorState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.mediaEditorState.rawValue) } private enum ApplicationSpecificOrderedItemListCollectionIdValues: Int32 {