diff --git a/submodules/ContactListUI/Sources/ContactsController.swift b/submodules/ContactListUI/Sources/ContactsController.swift index 3014b5126b..bfe2f1bf6c 100644 --- a/submodules/ContactListUI/Sources/ContactsController.swift +++ b/submodules/ContactListUI/Sources/ContactsController.swift @@ -689,48 +689,6 @@ public class ContactsController: ViewController { let controller = ContextController(account: self.context.account, presentationData: self.presentationData, source: .extracted(ContactsTabBarContextExtractedContentSource(controller: self, sourceNode: sourceNode)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture) self.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller) } - - private var storyCameraTransitionInCoordinator: StoryCameraTransitionInCoordinator? - var hasStoryCameraTransition: Bool { - return self.storyCameraTransitionInCoordinator != nil - } - func storyCameraPanGestureChanged(transitionFraction: CGFloat) { - guard let rootController = self.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface else { - return - } - - let coordinator: StoryCameraTransitionInCoordinator? - if let current = self.storyCameraTransitionInCoordinator { - coordinator = current - } else { - coordinator = rootController.openStoryCamera(transitionIn: nil, transitionedIn: {}, transitionOut: { [weak self] finished in - guard let self else { - return nil - } - - let _ = self -// if finished, let componentView = self.chatListHeaderView() { -// if let (transitionView, _) = componentView.storyPeerListView()?.transitionViewForItem(peerId: self.context.account.peerId) { -// return StoryCameraTransitionOut( -// destinationView: transitionView, -// destinationRect: transitionView.bounds, -// destinationCornerRadius: transitionView.bounds.height * 0.5 -// ) -// } -// } - return nil - }) - self.storyCameraTransitionInCoordinator = coordinator - } - coordinator?.updateTransitionProgress(transitionFraction) - } - - func storyCameraPanGestureEnded(transitionFraction: CGFloat, velocity: CGFloat) { - if let coordinator = self.storyCameraTransitionInCoordinator { - coordinator.completeWithTransitionProgressAndVelocity(transitionFraction, velocity) - self.storyCameraTransitionInCoordinator = nil - } - } } private final class ContactsTabBarContextExtractedContentSource: ContextExtractedContentSource { diff --git a/submodules/ContactListUI/Sources/ContactsControllerNode.swift b/submodules/ContactListUI/Sources/ContactsControllerNode.swift index 6466c45b61..eb49fab201 100644 --- a/submodules/ContactListUI/Sources/ContactsControllerNode.swift +++ b/submodules/ContactListUI/Sources/ContactsControllerNode.swift @@ -68,10 +68,7 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { private var presentationData: PresentationData private var presentationDataDisposable: Disposable? private let stringsPromise = Promise() - - private var isStoryPostingAvailable = false - private var storiesPostingAvailabilityDisposable: Disposable? - + weak var controller: ContactsController? private var initialScrollingOffset: CGFloat? @@ -254,80 +251,11 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { } self.openStories?(peer, sourceNode) } - - let storiesPostingAvailability = self.context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) - |> map { view -> AppConfiguration in - let appConfiguration: AppConfiguration = view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue - return appConfiguration - } - |> distinctUntilChanged - |> map { appConfiguration -> StoriesConfiguration.PostingAvailability in - let storiesConfiguration = StoriesConfiguration.with(appConfiguration: appConfiguration) - return storiesConfiguration.posting - } - - self.storiesPostingAvailabilityDisposable = combineLatest(queue: Queue.mainQueue(), - storiesPostingAvailability, - self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)) - |> map { peer -> Bool in - if case let .user(user) = peer, user.isPremium { - return true - } else { - return false - } - } - |> distinctUntilChanged - ).start(next: { [weak self] postingAvailability, isPremium in - if let self { - let isStoryPostingAvailable: Bool - switch postingAvailability { - case .enabled: - isStoryPostingAvailable = true - case .premium: - isStoryPostingAvailable = isPremium - case .disabled: - isStoryPostingAvailable = false - } - self.isStoryPostingAvailable = isStoryPostingAvailable - } - }) } deinit { self.presentationDataDisposable?.dispose() self.storySubscriptionsDisposable?.dispose() - self.storiesPostingAvailabilityDisposable?.dispose() - } - - override func didLoad() { - super.didLoad() - - let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { _ in - return [.rightCenter, .rightEdge] - }, edgeWidth: .widthMultiplier(factor: 1.0 / 6.0, min: 22.0, max: 80.0)) - panRecognizer.delegate = self - panRecognizer.delaysTouchesBegan = false - panRecognizer.cancelsTouchesInView = true - self.panRecognizer = panRecognizer - self.view.addGestureRecognizer(panRecognizer) - } - - public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { - return false - } - - public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool { - if let _ = otherGestureRecognizer as? InteractiveTransitionGestureRecognizer { - return false - } - if let _ = otherGestureRecognizer as? UIPanGestureRecognizer { - return true - } - return false - } - - override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { - return self.isStoryPostingAvailable } private func updateThemeAndStrings() { @@ -594,38 +522,6 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { placeholderNode.frame = previousFrame } } - - @objc private func panGesture(_ recognizer: UIPanGestureRecognizer) { - guard let (layout, _) = self.containerLayout else { - return - } - switch recognizer.state { - case .began: - break - case .changed: - let translation = recognizer.translation(in: self.view) - if case .compact = layout.metrics.widthClass { - let cameraIsAlreadyOpened = self.controller?.hasStoryCameraTransition ?? false - if translation.x > 0.0 { - self.controller?.storyCameraPanGestureChanged(transitionFraction: translation.x / layout.size.width) - } else if translation.x <= 0.0 && cameraIsAlreadyOpened { - self.controller?.storyCameraPanGestureChanged(transitionFraction: 0.0) - } - if cameraIsAlreadyOpened { - return - } - } - case .cancelled, .ended: - let translation = recognizer.translation(in: self.view) - let velocity = recognizer.velocity(in: self.view) - let hasStoryCameraTransition = self.controller?.hasStoryCameraTransition ?? false - if hasStoryCameraTransition { - self.controller?.storyCameraPanGestureEnded(transitionFraction: translation.x / layout.size.width, velocity: velocity.x) - } - default: - break - } - } } private final class ContactContextExtractedContentSource: ContextExtractedContentSource { diff --git a/submodules/DrawingUI/BUILD b/submodules/DrawingUI/BUILD index 0e40dd4048..35c361084d 100644 --- a/submodules/DrawingUI/BUILD +++ b/submodules/DrawingUI/BUILD @@ -96,6 +96,7 @@ swift_library( "//submodules/TelegramUI/Components/MediaEditor", "//submodules/ChatPresentationInterfaceState:ChatPresentationInterfaceState", "//submodules/StickerPackPreviewUI:StickerPackPreviewUI", + "//submodules/TelegramUI/Components/LottieComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift index e153a645b6..b23902023f 100644 --- a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift @@ -1242,7 +1242,7 @@ public class PremiumDemoScreen: ViewControllerComponentContainer { case other } - var disposed: () -> Void = {} + public var disposed: () -> Void = {} private var didSetReady = false private let _ready = Promise() diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 8d0fa3cfbb..a39d76544f 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -2093,6 +2093,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate } } self.isInteractingWithEntities = isInteracting + if !isInteracting { + self.controller?.isSavingAvailable = true + } self.requestUpdate(transition: .easeInOut(duration: 0.2)) } }, @@ -3853,11 +3856,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate 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) - - if let previousSavedValues = self.previousSavedValues, mediaEditor.values == previousSavedValues { - return - } - + self.hapticFeedback.impact(.light) self.previousSavedValues = mediaEditor.values diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 99664bccc5..8db8e72b84 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -583,6 +583,12 @@ public final class StoryItemSetContainerComponent: Component { } } + if let inputMediaView = self.sendMessageContext.inputMediaNode { + if inputMediaView.frame.contains(point) { + return false + } + } + if let centerInfoItemView = self.centerInfoItem?.view.view { if centerInfoItemView.convert(centerInfoItemView.bounds, to: self).contains(point) { return false @@ -2804,7 +2810,24 @@ public final class StoryItemSetContainerComponent: Component { } reactionContextNode.premiumReactionsSelected = { [weak self] file in - guard let self, let file, let component = self.component else { + guard let self, let component = self.component else { + return + } + + guard let file else { + let context = component.context + var replaceImpl: ((ViewController) -> Void)? + let controller = PremiumDemoScreen(context: context, subject: .uniqueReactions, action: { + let controller = PremiumIntroScreen(context: context, source: .reactions) + replaceImpl?(controller) + }) + controller.disposed = { [weak self] in + self?.updateIsProgressPaused() + } + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } + component.controller()?.push(controller) return } @@ -2820,6 +2843,9 @@ public final class StoryItemSetContainerComponent: Component { let controller = PremiumIntroScreen(context: context, source: .reactions) replaceImpl?(controller) }) + controller.disposed = { [weak self] in + self?.updateIsProgressPaused() + } replaceImpl = { [weak controller] c in controller?.replace(with: c) } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index 8de36f6d42..d22d9c1fa3 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -326,6 +326,35 @@ final class StoryItemSetContainerSendMessage { } } + private func presentMessageSentTooltip(view: StoryItemSetContainerComponent.View, peer: EnginePeer, messageId: EngineMessage.Id?) { + guard let component = view.component, let controller = component.controller() as? StoryContainerScreen else { + return + } + + if let tooltipScreen = self.tooltipScreen { + tooltipScreen.dismiss(animated: true) + } + + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + let tooltipScreen = UndoOverlayController( + presentationData: presentationData, + content: .actionSucceeded(title: "", text: "Message Sent", cancel: messageId != nil ? "View in Chat" : "", destructive: false), + elevatedLayout: false, + animateInAsReplacement: false, + action: { [weak view, weak self] action in + if case .undo = action, let messageId { + view?.navigateToPeer(peer: peer, chat: true, messageId: messageId) + } + self?.tooltipScreen = nil + view?.updateIsProgressPaused() + return false + } + ) + controller.present(tooltipScreen, in: .current) + self.tooltipScreen = tooltipScreen + view.updateIsProgressPaused() + } + func performSendMessageAction( view: StoryItemSetContainerComponent.View ) { @@ -342,8 +371,7 @@ final class StoryItemSetContainerSendMessage { } let peer = component.slice.peer - let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - let controller = component.controller() + let controller = component.controller() as? StoryContainerScreen if let recordedAudioPreview = self.recordedAudioPreview { self.recordedAudioPreview = nil @@ -370,27 +398,22 @@ final class StoryItemSetContainerSendMessage { replyTo: nil, storyId: focusedStoryId, content: .text(text.string, entities) - ) |> deliverOnMainQueue).start(next: { [weak controller, weak view] messageIds in - if let controller { - Queue.mainQueue().after(0.3) { - controller.present(UndoOverlayController( - presentationData: presentationData, - content: .actionSucceeded(title: "", text: "Message Sent", cancel: "View in Chat", destructive: false), - elevatedLayout: false, - animateInAsReplacement: false, - action: { [weak view] action in - if case .undo = action, let messageId = messageIds.first { - view?.navigateToPeer(peer: peer, chat: true, messageId: messageId) - } - return false - } - ), in: .current) + ) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in + Queue.mainQueue().after(0.3) { + if let self, let view { + self.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 }) } } }) inputPanelView.clearSendMessageInput() + self.currentInputMode = .text - view.endEditing(true) + if hasFirstResponder(view) { + view.endEditing(true) + } else { + view.state?.updated(transition: .spring(duration: 0.3)) + } + controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring)) } } } @@ -407,7 +430,6 @@ final class StoryItemSetContainerSendMessage { let focusedStoryId = StoryId(peerId: peerId, id: focusedItem.storyItem.id) let peer = component.slice.peer - let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } let controller = component.controller() as? StoryContainerScreen if let navigationController = controller?.navigationController as? NavigationController { @@ -433,21 +455,10 @@ final class StoryItemSetContainerSendMessage { replyTo: nil, storyId: focusedStoryId, content: .file(fileReference) - ) |> deliverOnMainQueue).start(next: { [weak controller, weak view] messageIds in - if let controller { - Queue.mainQueue().after(0.3) { - controller.present(UndoOverlayController( - presentationData: presentationData, - content: .actionSucceeded(title: "", text: "Message Sent", cancel: "View in Chat", destructive: false), - elevatedLayout: false, - animateInAsReplacement: false, - action: { [weak view] action in - if case .undo = action, let messageId = messageIds.first { - view?.navigateToPeer(peer: peer, chat: true, messageId: messageId) - } - return false - } - ), in: .current) + ) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in + Queue.mainQueue().after(0.3) { + if let self, let view { + self.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 }) } } }) @@ -457,8 +468,8 @@ final class StoryItemSetContainerSendMessage { view.endEditing(true) } else { view.state?.updated(transition: .spring(duration: 0.3)) - controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring)) } + controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring)) } func performSendContextResultAction(view: StoryItemSetContainerComponent.View, results: ChatContextResultCollection, result: ChatContextResult) { @@ -472,7 +483,6 @@ final class StoryItemSetContainerSendMessage { let focusedStoryId = StoryId(peerId: peerId, id: focusedItem.storyItem.id) let peer = component.slice.peer - let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } let controller = component.controller() as? StoryContainerScreen if let navigationController = controller?.navigationController as? NavigationController { @@ -498,21 +508,10 @@ final class StoryItemSetContainerSendMessage { replyTo: nil, storyId: focusedStoryId, content: .contextResult(results, result) - ) |> deliverOnMainQueue).start(next: { [weak controller, weak view] messageIds in - if let controller { - Queue.mainQueue().after(0.3) { - controller.present(UndoOverlayController( - presentationData: presentationData, - content: .actionSucceeded(title: "", text: "Message Sent", cancel: "View in Chat", destructive: false), - elevatedLayout: false, - animateInAsReplacement: false, - action: { [weak view] action in - if case .undo = action, let messageId = messageIds.first { - view?.navigateToPeer(peer: peer, chat: true, messageId: messageId) - } - return false - } - ), in: .current) + ) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in + Queue.mainQueue().after(0.3) { + if let self, let view { + self.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 }) } } }) @@ -522,8 +521,8 @@ final class StoryItemSetContainerSendMessage { view.endEditing(true) } else { view.state?.updated(transition: .spring(duration: 0.3)) - controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring)) } + controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring)) } func setMediaRecordingActive( @@ -1160,18 +1159,13 @@ final class StoryItemSetContainerSendMessage { } let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: mediaReference, replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) let _ = (enqueueMessages(account: component.context.account, peerId: peer.id, messages: [message.withUpdatedReplyToMessageId(nil)]) - |> deliverOnMainQueue).start() - - if let controller = component.controller() { - let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - controller.present(UndoOverlayController( - presentationData: presentationData, - content: .succeed(text: "Message Sent"), - elevatedLayout: false, - animateInAsReplacement: false, - action: { _ in return false } - ), in: .current) - } + |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in + if let self, let view { + Queue.mainQueue().after(0.3) { + self.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 }) + } + } + }) }) let _ = currentFilesController.swap(controller) if let controller = controller as? AttachmentContainable, let mediaPickerContext = controller.mediaPickerContext { @@ -1357,10 +1351,6 @@ final class StoryItemSetContainerSendMessage { })) } })) - case .poll: - let controller = self.configurePollCreation(view: view, peer: peer, targetMessageId: nil) - completion(controller, controller?.mediaPickerContext) - self.controllerNavigationDisposable.set(nil) case .gift: /*let premiumGiftOptions = strongSelf.presentationInterfaceState.premiumGiftOptions if !premiumGiftOptions.isEmpty { @@ -2002,56 +1992,6 @@ final class StoryItemSetContainerSendMessage { component.controller()?.present(controller, in: .window(.root)) } - private func configurePollCreation(view: StoryItemSetContainerComponent.View, peer: EnginePeer, targetMessageId: EngineMessage.Id?, isQuiz: Bool? = nil) -> CreatePollControllerImpl? { - guard let component = view.component else { - return nil - } - let focusedItem = component.slice.item - guard let peerId = focusedItem.peerId else { - return nil - } - let focusedStoryId = StoryId(peerId: peerId, id: focusedItem.storyItem.id) - - let theme = component.theme - return createPollController(context: component.context, updatedPresentationData: (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), peer: peer, isQuiz: isQuiz, completion: { [weak self, weak view] poll in - guard let self, let view else { - return - } - let replyMessageId = targetMessageId - /*strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ - if let strongSelf = self { - strongSelf.chatDisplayNode.collapseInput() - - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) } - }) - } - }, nil)*/ - let message: EnqueueMessage = .message( - text: "", - attributes: [], - inlineStickers: [:], - mediaReference: .standalone(media: TelegramMediaPoll( - pollId: EngineMedia.Id(namespace: Namespaces.Media.LocalPoll, id: Int64.random(in: Int64.min ... Int64.max)), - publicity: poll.publicity, - kind: poll.kind, - text: poll.text, - options: poll.options, - correctAnswers: poll.correctAnswers, - results: poll.results, - isClosed: false, - deadlineTimeout: poll.deadlineTimeout - )), - replyToMessageId: nil, - replyToStoryId: focusedStoryId, - localGroupingKey: nil, - correlationId: nil, - bubbleUpEmojiOrStickersets: [] - ) - self.sendMessages(view: view, peer: peer, messages: [message.withUpdatedReplyToMessageId(replyMessageId)]) - }) - } - private func transformEnqueueMessages(view: StoryItemSetContainerComponent.View, messages: [EnqueueMessage], silentPosting: Bool, scheduleTime: Int32? = nil) -> [EnqueueMessage] { var focusedStoryId: StoryId? if let component = view.component, let peerId = component.slice.item.peerId { @@ -2095,27 +2035,14 @@ final class StoryItemSetContainerSendMessage { } private func sendMessages(view: StoryItemSetContainerComponent.View, peer: EnginePeer, messages: [EnqueueMessage], media: Bool = false, commit: Bool = false) { - guard let component = view.component, let controller = component.controller() else { + guard let component = view.component else { return } - let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - let _ = (enqueueMessages(account: component.context.account, peerId: peer.id, messages: self.transformEnqueueMessages(view: view, messages: messages, silentPosting: false)) - |> deliverOnMainQueue).start(next: { [weak controller] messageIds in - if let controller { - Queue.mainQueue().after(0.3) { - controller.present(UndoOverlayController( - presentationData: presentationData, - content: .actionSucceeded(title: "", text: "Message Sent", cancel: "View in Chat", destructive: false), - elevatedLayout: false, - animateInAsReplacement: false, - action: { [weak view] action in - if case .undo = action, let messageId = messageIds.first { - view?.navigateToPeer(peer: peer, chat: true, messageId: messageId) - } - return false - } - ), in: .current) + |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in + Queue.mainQueue().after(0.3) { + if let view { + self?.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 }) } } })