From 882680ed4d01d8e217825e312e73d8ada9e83b4b Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 30 Jun 2023 00:42:06 +0200 Subject: [PATCH 1/6] Various fixes --- submodules/ContactListUI/Sources/ContactContextMenus.swift | 2 +- .../Components/CameraScreen/Sources/CameraScreen.swift | 7 +++++++ .../MediaEditorScreen/Sources/MediaEditorScreen.swift | 7 ++++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/submodules/ContactListUI/Sources/ContactContextMenus.swift b/submodules/ContactListUI/Sources/ContactContextMenus.swift index 2c499de1e9..fa60b7d37f 100644 --- a/submodules/ContactListUI/Sources/ContactContextMenus.swift +++ b/submodules/ContactListUI/Sources/ContactContextMenus.swift @@ -88,7 +88,7 @@ func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, con items.append(.action(ContextMenuActionItem(text: "Move to Chats", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MoveToChats"), color: theme.contextMenu.primaryColor) }, action: { _, f in - f(.default) + f(.dismissWithoutContent) context.engine.peers.updatePeerStoriesHidden(id: peerId, isHidden: false) diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift index 892b747940..0c93ca3d22 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift @@ -359,7 +359,14 @@ private final class CameraScreenComponent: CombinedComponent { action.invoke(Void()) } + private var lastDualCameraTimestamp: Double? func toggleDualCamera() { + let currentTimestamp = CACurrentMediaTime() + if let lastDualCameraTimestamp = self.lastDualCameraTimestamp, currentTimestamp - lastDualCameraTimestamp < 1.5 { + return + } + self.lastDualCameraTimestamp = currentTimestamp + let isEnabled = !self.cameraState.isDualCamEnabled self.camera.setDualCamEnabled(isEnabled) self.cameraState = self.cameraState.updatedIsDualCamEnabled(isEnabled) diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 3d1d2f1f3f..210e265bd8 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -694,13 +694,18 @@ final class MediaEditorScreenComponent: Component { transition.setAlpha(view: cancelButtonView, alpha: component.isDisplayingTool || component.isDismissing || component.isInteractingWithEntities ? 0.0 : 1.0) } + var doneButtonTitle = "NEXT" + if let controller = environment.controller() as? MediaEditorScreen, controller.isEditingStory { + doneButtonTitle = "DONE" + } + let doneButtonSize = self.doneButton.update( transition: transition, component: AnyComponent(Button( content: AnyComponent(DoneButtonComponent( backgroundColor: UIColor(rgb: 0x007aff), icon: UIImage(bundleImageName: "Media Editor/Next")!, - title: "NEXT")), + title: doneButtonTitle)), action: { guard let controller = environment.controller() as? MediaEditorScreen else { return From b6e9138ea7cff7bf489e476767fd7706a4aa6329 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 30 Jun 2023 03:28:34 +0200 Subject: [PATCH 2/6] Various improvements --- .../CameraScreen/Sources/CameraScreen.swift | 8 +- .../Sources/CaptureControlsComponent.swift | 3 + .../Sources/EmojiSuggestionsComponent.swift | 40 ++- .../Sources/MediaEditorScreen.swift | 6 +- .../MessageInputPanelComponent/BUILD | 2 + .../Sources/InputContextQueries.swift | 105 ++++++++ .../Sources/MessageInputPanelComponent.swift | 238 +++++++++++++++++- .../Sources/CategoryListItemComponent.swift | 2 +- .../Sources/ShareWithPeersScreen.swift | 57 ++++- .../Sources/TextFieldComponent.swift | 88 ++++++- .../Sources/ChatTextInputPanelNode.swift | 2 +- 11 files changed, 515 insertions(+), 36 deletions(-) diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift index 0c93ca3d22..14fa0a0a75 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift @@ -371,6 +371,8 @@ private final class CameraScreenComponent: CombinedComponent { self.camera.setDualCamEnabled(isEnabled) self.cameraState = self.cameraState.updatedIsDualCamEnabled(isEnabled) self.updated(transition: .easeInOut(duration: 0.1)) + + self.hapticFeedback.impact(.light) } func updateSwipeHint(_ hint: CaptureControlsComponent.SwipeHint) { @@ -1607,13 +1609,11 @@ public class CameraScreen: ViewController { } func presentDraftTooltip() { - guard let sourceView = self.componentHost.findTaggedView(tag: galleryButtonTag) else { + guard let sourceView = self.componentHost.findTaggedView(tag: galleryButtonTag), let absoluteLocation = sourceView.superview?.convert(sourceView.center, to: self.view) else { return } - 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 - 4.0), size: CGSize()) + let location = CGRect(origin: CGPoint(x: absoluteLocation.x, y: absoluteLocation.y - 29.0), size: CGSize()) let controller = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: "Draft Saved"), location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _ in return .ignore diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift index 92be146087..df6e5a93e7 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift @@ -772,6 +772,9 @@ final class CaptureControlsComponent: Component { self.component?.swipeHintUpdated(.flip) if location.x > self.frame.width / 2.0 + 60.0 { self.panBlobState = .transientToFlip + if self.didFlip && location.x < self.frame.width - 100.0 { + self.didFlip = false + } if !self.didFlip && location.x > self.frame.width - 70.0 { self.didFlip = true self.hapticFeedback.impact(.light) diff --git a/submodules/TelegramUI/Components/EmojiSuggestionsComponent/Sources/EmojiSuggestionsComponent.swift b/submodules/TelegramUI/Components/EmojiSuggestionsComponent/Sources/EmojiSuggestionsComponent.swift index 535b79b95d..143d96eff1 100644 --- a/submodules/TelegramUI/Components/EmojiSuggestionsComponent/Sources/EmojiSuggestionsComponent.swift +++ b/submodules/TelegramUI/Components/EmojiSuggestionsComponent/Sources/EmojiSuggestionsComponent.swift @@ -15,6 +15,22 @@ import TelegramUIPreferences public final class EmojiSuggestionsComponent: Component { public typealias EnvironmentType = Empty + public struct Theme: Equatable { + let backgroundColor: UIColor + let textColor: UIColor + let placeholderColor: UIColor + + public init( + backgroundColor: UIColor, + textColor: UIColor, + placeholderColor: UIColor + ) { + self.backgroundColor = backgroundColor + self.textColor = textColor + self.placeholderColor = placeholderColor + } + } + public static func suggestionData(context: AccountContext, isSavedMessages: Bool, query: String) -> Signal<[TelegramMediaFile], NoError> { let hasPremium: Signal if isSavedMessages { @@ -98,7 +114,7 @@ public final class EmojiSuggestionsComponent: Component { } public let context: AccountContext - public let theme: PresentationTheme + public let theme: Theme public let animationCache: AnimationCache public let animationRenderer: MultiAnimationRenderer public let files: [TelegramMediaFile] @@ -107,7 +123,7 @@ public final class EmojiSuggestionsComponent: Component { public init( context: AccountContext, userLocation: MediaResourceUserLocation, - theme: PresentationTheme, + theme: Theme, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, files: [TelegramMediaFile], @@ -125,7 +141,7 @@ public final class EmojiSuggestionsComponent: Component { if lhs.context !== rhs.context { return false } - if lhs.theme !== rhs.theme { + if lhs.theme != rhs.theme { return false } if lhs.animationCache !== rhs.animationCache { @@ -305,7 +321,7 @@ public final class EmojiSuggestionsComponent: Component { let itemLayer: InlineStickerItemLayer if let current = self.visibleLayers[item.fileId] { itemLayer = current - itemLayer.dynamicColor = component.theme.list.itemPrimaryTextColor + itemLayer.dynamicColor = component.theme.textColor } else { itemLayer = InlineStickerItemLayer( context: component.context, @@ -315,9 +331,9 @@ public final class EmojiSuggestionsComponent: Component { file: item, cache: component.animationCache, renderer: component.animationRenderer, - placeholderColor: component.theme.list.mediaPlaceholderColor, + placeholderColor: component.theme.placeholderColor, pointSize: itemFrame.size, - dynamicColor: component.theme.list.itemPrimaryTextColor + dynamicColor: component.theme.textColor ) self.visibleLayers[item.fileId] = itemLayer self.scrollView.layer.addSublayer(itemLayer) @@ -382,10 +398,10 @@ public final class EmojiSuggestionsComponent: Component { func update(component: EmojiSuggestionsComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { let height: CGFloat = 54.0 - if self.component?.theme !== component.theme { + if self.component?.theme.backgroundColor != component.theme.backgroundColor { //self.backgroundLayer.fillColor = component.theme.list.plainBackgroundColor.cgColor self.backgroundLayer.fillColor = UIColor.black.cgColor - self.blurView.updateColor(color: component.theme.list.plainBackgroundColor.withMultipliedAlpha(0.88), transition: .immediate) + self.blurView.updateColor(color: component.theme.backgroundColor, transition: .immediate) } var resetScrollingPosition = false if self.component?.files != component.files { @@ -427,3 +443,11 @@ public final class EmojiSuggestionsComponent: Component { return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } } + +public extension EmojiSuggestionsComponent.Theme { + init(theme: PresentationTheme) { + self.backgroundColor = theme.list.plainBackgroundColor.withMultipliedAlpha(0.88) + self.textColor = theme.list.itemPrimaryTextColor + self.placeholderColor = theme.list.mediaPlaceholderColor + } +} diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 210e265bd8..923a4da0b6 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -3338,6 +3338,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate func requestDismiss(saveDraft: Bool, animated: Bool) { self.dismissAllTooltips() + var showDraftTooltip = saveDraft + if let subject = self.node.subject, case .draft = subject { + showDraftTooltip = false + } if saveDraft { self.saveDraft(id: nil) } else { @@ -3351,7 +3355,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate } self.node.entitiesView.invalidate() - self.cancelled(saveDraft) + self.cancelled(showDraftTooltip) self.willDismiss() diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD b/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD index f48ad6e74c..354df30334 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD @@ -13,6 +13,7 @@ swift_library( "//submodules/Display", "//submodules/ComponentFlow", "//submodules/AppBundle", + "//submodules/TelegramCore", "//submodules/TelegramUI/Components/TextFieldComponent", "//submodules/Components/BundleIconComponent", "//submodules/TelegramUI/Components/ChatTextInputMediaRecordingButton", @@ -28,6 +29,7 @@ swift_library( "//submodules/TextFormat", "//submodules/TelegramUI/Components/Stories/PeerListItemComponent", "//submodules/TelegramUI/Components/MoreHeaderButton", + "//submodules/TelegramUI/Components/EmojiSuggestionsComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/InputContextQueries.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/InputContextQueries.swift index 4a36e6044c..584bd0d883 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/InputContextQueries.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/InputContextQueries.swift @@ -1,5 +1,6 @@ import Foundation import SwiftSignalKit +import TelegramCore import TextFieldComponent import ChatContextQuery import AccountContext @@ -129,6 +130,110 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, inp |> castError(ChatContextQueryError.self) return signal |> then(peers) + case let .emojiSearch(query, languageCode, range): + let hasPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + |> map { peer -> Bool in + guard case let .user(user) = peer else { + return false + } + return user.isPremium + } + |> distinctUntilChanged + + if query.isSingleEmoji { + return combineLatest( + context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000), + hasPremium + ) + |> map { view, hasPremium -> [(String, TelegramMediaFile?, String)] in + var result: [(String, TelegramMediaFile?, String)] = [] + + for entry in view.entries { + guard let item = entry.item as? StickerPackItem else { + continue + } + for attribute in item.file.attributes { + switch attribute { + case let .CustomEmoji(_, _, alt, _): + if alt == query { + if !item.file.isPremiumEmoji || hasPremium { + result.append((alt, item.file, alt)) + } + } + default: + break + } + } + } + return result + } + |> map { result -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in + return { _ in return .emojis(result, range) } + } + |> castError(ChatContextQueryError.self) + } else { + var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query, completeMatch: query.count < 2) + if !languageCode.lowercased().hasPrefix("en") { + signal = signal + |> mapToSignal { keywords in + return .single(keywords) + |> then( + context.engine.stickers.searchEmojiKeywords(inputLanguageCode: "en-US", query: query, completeMatch: query.count < 3) + |> map { englishKeywords in + return keywords + englishKeywords + } + ) + } + } + + return signal + |> castError(ChatContextQueryError.self) + |> mapToSignal { keywords -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> in + return combineLatest( + context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000), + hasPremium + ) + |> map { view, hasPremium -> [(String, TelegramMediaFile?, String)] in + var result: [(String, TelegramMediaFile?, String)] = [] + + var allEmoticons: [String: String] = [:] + for keyword in keywords { + for emoticon in keyword.emoticons { + allEmoticons[emoticon] = keyword.keyword + } + } + + for entry in view.entries { + guard let item = entry.item as? StickerPackItem else { + continue + } + for attribute in item.file.attributes { + switch attribute { + case let .CustomEmoji(_, _, alt, _): + if !alt.isEmpty, let keyword = allEmoticons[alt] { + if !item.file.isPremiumEmoji || hasPremium { + result.append((alt, item.file, keyword)) + } + } + default: + break + } + } + } + + for keyword in keywords { + for emoticon in keyword.emoticons { + result.append((emoticon, nil, keyword.keyword)) + } + } + return result + } + |> map { result -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in + return { _ in return .emojis(result, range) } + } + |> castError(ChatContextQueryError.self) + } + } default: return .complete() } diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift index f2c7d2dc88..06d13fe462 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift @@ -3,6 +3,7 @@ import UIKit import Display import ComponentFlow import SwiftSignalKit +import TelegramCore import AppBundle import TextFieldComponent import BundleIconComponent @@ -12,6 +13,8 @@ import ChatPresentationInterfaceState import LottieComponent import ChatContextQuery import TextFormat +import EmojiSuggestionsComponent +import AudioToolbox public final class MessageInputPanelComponent: Component { public enum Style { @@ -210,7 +213,7 @@ public final class MessageInputPanelComponent: Component { public enum SendMessageInput { case text(NSAttributedString) } - + public final class View: UIView { private let fieldBackgroundView: BlurredBackgroundView private let vibrancyEffectView: UIVisualEffectView @@ -240,13 +243,15 @@ public final class MessageInputPanelComponent: Component { private var currentMediaInputIsVoice: Bool = true private var mediaCancelFraction: CGFloat = 0.0 + private var currentInputMode: InputMode? + private var contextQueryStates: [ChatPresentationInputQueryKind: (ChatPresentationInputQuery, Disposable)] = [:] private var contextQueryResults: [ChatPresentationInputQueryKind: ChatPresentationInputQueryResult] = [:] - private var contextQueryResultPanel: ComponentView? private var contextQueryResultPanelExternalState: ContextResultPanelComponent.ExternalState? - private var currentInputMode: InputMode? + private var viewForOverlayContent: ViewForOverlayContent? + private var currentEmojiSuggestionView: ComponentHostView? private var component: MessageInputPanelComponent? private weak var state: EmptyComponentState? @@ -272,6 +277,28 @@ public final class MessageInputPanelComponent: Component { self.addSubview(self.gradientView) self.fieldBackgroundView.addSubview(self.vibrancyEffectView) self.addSubview(self.fieldBackgroundView) + + self.viewForOverlayContent = ViewForOverlayContent( + ignoreHit: { [weak self] view, point in + guard let self else { + return false + } + if self.hitTest(view.convert(point, to: self), with: nil) != nil { + return true + } + if view.convert(point, to: self).y > self.bounds.maxY { + return true + } + return false + }, + dismissSuggestions: { [weak self] in + guard let self else { + return + } + self.textFieldExternalState.dismissedEmojiSuggestionPosition = self.textFieldExternalState.currentEmojiSuggestion?.position + self.state?.updated() + } + ) } required init?(coder: NSCoder) { @@ -351,6 +378,17 @@ public final class MessageInputPanelComponent: Component { override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { let result = super.hitTest(point, with: event) + if let _ = self.textField.view, let currentEmojiSuggestion = self.textFieldExternalState.currentEmojiSuggestion, let currentEmojiSuggestionView = self.currentEmojiSuggestionView { + if let result = currentEmojiSuggestionView.hitTest(self.convert(point, to: currentEmojiSuggestionView), with: event) { + return result + } + self.textFieldExternalState.dismissedEmojiSuggestionPosition = currentEmojiSuggestion.position + if let textFieldView = self.textField.view as? TextFieldComponent.View { + textFieldView.updateEmojiSuggestion(transition: .immediate) + } + self.state?.updated() + } + if result == nil, let contextQueryResultPanel = self.contextQueryResultPanel?.view, let panelResult = contextQueryResultPanel.hitTest(self.convert(point, to: contextQueryResultPanel), with: event), panelResult !== contextQueryResultPanel { return panelResult } @@ -513,9 +551,18 @@ public final class MessageInputPanelComponent: Component { if let textFieldView = self.textField.view { if textFieldView.superview == nil { self.addSubview(textFieldView) + + if let viewForOverlayContent = self.viewForOverlayContent { + self.addSubview(viewForOverlayContent) + } } - transition.setFrame(view: textFieldView, frame: CGRect(origin: CGPoint(x: fieldBackgroundFrame.minX, y: fieldBackgroundFrame.maxY - textFieldSize.height), size: textFieldSize)) + let textFieldFrame = CGRect(origin: CGPoint(x: fieldBackgroundFrame.minX, y: fieldBackgroundFrame.maxY - textFieldSize.height), size: textFieldSize) + transition.setFrame(view: textFieldView, frame: textFieldFrame) transition.setAlpha(view: textFieldView, alpha: (hasMediaRecording || hasMediaEditing || component.disabledPlaceholder != nil) ? 0.0 : 1.0) + + if let viewForOverlayContent = self.viewForOverlayContent { + transition.setFrame(view: viewForOverlayContent, frame: textFieldFrame) + } } if let disabledPlaceholderText = component.disabledPlaceholder { @@ -1123,6 +1170,9 @@ public final class MessageInputPanelComponent: Component { } self.updateContextQueries() + + let panelLeftInset: CGFloat = max(insets.left, 7.0) + let panelRightInset: CGFloat = max(insets.right, 41.0) if let result = self.contextQueryResults[.mention], result.count > 0 && self.textFieldExternalState.isEditing { let availablePanelHeight: CGFloat = 413.0 @@ -1142,8 +1192,6 @@ public final class MessageInputPanelComponent: Component { animateIn = true transition = .immediate } - let panelLeftInset: CGFloat = max(insets.left, 7.0) - let panelRightInset: CGFloat = max(insets.right, 41.0) let panelSize = panel.update( transition: transition, component: AnyComponent(ContextResultPanelComponent( @@ -1209,6 +1257,143 @@ public final class MessageInputPanelComponent: Component { }) } + if let emojiSuggestion = self.textFieldExternalState.currentEmojiSuggestion, emojiSuggestion.disposable == nil { + emojiSuggestion.disposable = (EmojiSuggestionsComponent.suggestionData(context: component.context, isSavedMessages: false, query: emojiSuggestion.position.value) + |> deliverOnMainQueue).start(next: { [weak self, weak emojiSuggestion] result in + guard let self, let emojiSuggestion, self.textFieldExternalState.currentEmojiSuggestion === emojiSuggestion else { + return + } + + emojiSuggestion.value = result + self.state?.updated() + }) + } + + var hasTrackingView = self.textFieldExternalState.hasTrackingView + if let currentEmojiSuggestion = self.textFieldExternalState.currentEmojiSuggestion, let value = currentEmojiSuggestion.value as? [TelegramMediaFile], value.isEmpty { + hasTrackingView = false + } + if !self.textFieldExternalState.isEditing { + hasTrackingView = false + } + + if !hasTrackingView { + if let currentEmojiSuggestion = self.textFieldExternalState.currentEmojiSuggestion { + self.textFieldExternalState.currentEmojiSuggestion = nil + currentEmojiSuggestion.disposable?.dispose() + } + + if let currentEmojiSuggestionView = self.currentEmojiSuggestionView { + self.currentEmojiSuggestionView = nil + + currentEmojiSuggestionView.alpha = 0.0 + currentEmojiSuggestionView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak currentEmojiSuggestionView] _ in + currentEmojiSuggestionView?.removeFromSuperview() + }) + } + } + + if let currentEmojiSuggestion = self.textFieldExternalState.currentEmojiSuggestion, let value = currentEmojiSuggestion.value as? [TelegramMediaFile] { + let currentEmojiSuggestionView: ComponentHostView + if let current = self.currentEmojiSuggestionView { + currentEmojiSuggestionView = current + } else { + currentEmojiSuggestionView = ComponentHostView() + self.currentEmojiSuggestionView = currentEmojiSuggestionView + self.addSubview(currentEmojiSuggestionView) + + currentEmojiSuggestionView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + + //self.installEmojiSuggestionPreviewGesture(hostView: currentEmojiSuggestionView) + } + + + let globalPosition: CGPoint + if let textView = self.textField.view { + globalPosition = textView.convert(currentEmojiSuggestion.localPosition, to: self) + } else { + globalPosition = .zero + } + + let sideInset: CGFloat = 7.0 + + let viewSize = currentEmojiSuggestionView.update( + transition: .immediate, + component: AnyComponent(EmojiSuggestionsComponent( + context: component.context, + userLocation: .other, + theme: EmojiSuggestionsComponent.Theme( + backgroundColor: UIColor(white: 0.0, alpha: 0.5), + textColor: .white, + placeholderColor: UIColor(rgb: 0xffffff).mixedWith(UIColor(rgb: 0x1c1c1d), alpha: 0.9) + ), + animationCache: component.context.animationCache, + animationRenderer: component.context.animationRenderer, + files: value, + action: { [weak self] file in + guard let self, let textView = self.textField.view as? TextFieldComponent.View, let currentEmojiSuggestion = self.textFieldExternalState.currentEmojiSuggestion else { + return + } + + AudioServicesPlaySystemSound(0x450) + + let inputState = textView.getInputState() + let inputText = NSMutableAttributedString(attributedString: inputState.inputText) + + var text: String? + var emojiAttribute: ChatTextInputTextCustomEmojiAttribute? + loop: for attribute in file.attributes { + switch attribute { + case let .CustomEmoji(_, _, displayText, _): + text = displayText + emojiAttribute = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: file.fileId.id, file: file) + break loop + default: + break + } + } + + if let emojiAttribute = emojiAttribute, let text = text { + let replacementText = NSAttributedString(string: text, attributes: [ChatTextInputAttributes.customEmoji: emojiAttribute]) + + let range = currentEmojiSuggestion.position.range + let previousText = inputText.attributedSubstring(from: range) + inputText.replaceCharacters(in: range, with: replacementText) + + var replacedUpperBound = range.lowerBound + while true { + if inputText.attributedSubstring(from: NSRange(location: 0, length: replacedUpperBound)).string.hasSuffix(previousText.string) { + let replaceRange = NSRange(location: replacedUpperBound - previousText.length, length: previousText.length) + if replaceRange.location < 0 { + break + } + let adjacentString = inputText.attributedSubstring(from: replaceRange) + if adjacentString.string != previousText.string || adjacentString.attribute(ChatTextInputAttributes.customEmoji, at: 0, effectiveRange: nil) != nil { + break + } + inputText.replaceCharacters(in: replaceRange, with: NSAttributedString(string: text, attributes: [ChatTextInputAttributes.customEmoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: emojiAttribute.interactivelySelectedFromPackId, fileId: emojiAttribute.fileId, file: emojiAttribute.file)])) + replacedUpperBound = replaceRange.lowerBound + } else { + break + } + } + + let selectionPosition = range.lowerBound + (replacementText.string as NSString).length + textView.updateText(inputText, selectionRange: selectionPosition ..< selectionPosition) + } + } + )), + environment: {}, + containerSize: CGSize(width: self.bounds.width - panelLeftInset - panelRightInset, height: 100.0) + ) + + let viewFrame = CGRect(origin: CGPoint(x: min(self.bounds.width - sideInset - viewSize.width, max(panelLeftInset, floor(globalPosition.x - viewSize.width / 2.0))), y: globalPosition.y - 4.0 - viewSize.height), size: viewSize) + currentEmojiSuggestionView.frame = viewFrame + if let componentView = currentEmojiSuggestionView.componentView as? EmojiSuggestionsComponent.View { + componentView.adjustBackground(relativePositionX: floor(globalPosition.x - viewFrame.minX)) + } + } + return size } } @@ -1221,3 +1406,44 @@ public final class MessageInputPanelComponent: Component { return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } } + +final class ViewForOverlayContent: UIView { + let ignoreHit: (UIView, CGPoint) -> Bool + let dismissSuggestions: () -> Void + + init(ignoreHit: @escaping (UIView, CGPoint) -> Bool, dismissSuggestions: @escaping () -> Void) { + self.ignoreHit = ignoreHit + self.dismissSuggestions = dismissSuggestions + + super.init(frame: CGRect()) + } + + required init(coder: NSCoder) { + preconditionFailure() + } + + func maybeDismissContent(point: CGPoint) { + for subview in self.subviews.reversed() { + if let _ = subview.hitTest(self.convert(point, to: subview), with: nil) { + return + } + } + + self.dismissSuggestions() + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + for subview in self.subviews.reversed() { + if let result = subview.hitTest(self.convert(point, to: subview), with: event) { + return result + } + } + + if event == nil || self.ignoreHit(self, point) { + return nil + } + + self.dismissSuggestions() + return nil + } +} diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/CategoryListItemComponent.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/CategoryListItemComponent.swift index a1f23de673..48b059b18f 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/CategoryListItemComponent.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/CategoryListItemComponent.swift @@ -239,7 +239,7 @@ final class CategoryListItemComponent: Component { text: .plain(NSAttributedString(string: labelData.0, font: Font.regular(15.0), textColor: labelData.1 ? component.theme.list.itemAccentColor : component.theme.list.itemSecondaryTextColor)) )), environment: {}, - containerSize: CGSize(width: availableSize.width - leftInset - rightInset, height: 100.0) + containerSize: CGSize(width: availableSize.width - leftInset - rightInset - 14.0, height: 100.0) ) let labelArrowSize = self.labelArrow.update( diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift index 9a64aa4119..33ee596e91 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift @@ -1665,11 +1665,27 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer { switch subject { case .stories: - let state = State(peers: [], presences: [:]) - self.stateValue = state - self.stateSubject.set(.single(state)) - self.readySubject.set(true) - self.initialPeerIds = initialPeerIds + var signals: [Signal] = [] + if initialPeerIds.count < 3 { + for peerId in initialPeerIds { + signals.append(context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))) + } + } + self.stateDisposable = (combineLatest(signals) + |> deliverOnMainQueue).start(next: { [weak self] peers in + guard let self else { + return + } + + let state = State( + peers: peers.compactMap { $0 }, + presences: [:] + ) + self.stateValue = state + self.stateSubject.set(.single(state)) + + self.readySubject.set(true) + }) case .chats: self.stateDisposable = (context.engine.messages.chatList(group: .root, count: 200) |> deliverOnMainQueue).start(next: { [weak self] chatList in @@ -1811,6 +1827,8 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer { ) { self.context = context + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + var categoryItems: [ShareWithPeersScreenComponent.CategoryItem] = [] var optionItems: [ShareWithPeersScreenComponent.OptionItem] = [] if case let .stories(editing) = stateContext.subject { @@ -1822,12 +1840,25 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer { actionTitle: nil )) + var peerNames = "" + if let peers = stateContext.stateValue?.peers, !peers.isEmpty { + peerNames = String(peers.map { $0.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) }.joined(separator: ", ")) + } + var contactsSubtitle = "exclude people" if initialPrivacy.base == .contacts, initialPrivacy.additionallyIncludePeers.count > 0 { if initialPrivacy.additionallyIncludePeers.count == 1 { - contactsSubtitle = "except 1 person" + if !peerNames.isEmpty { + contactsSubtitle = "except \(peerNames)" + } else { + contactsSubtitle = "except 1 person" + } } else { - contactsSubtitle = "except \(initialPrivacy.additionallyIncludePeers.count) people" + if !peerNames.isEmpty { + contactsSubtitle = "except \(peerNames)" + } else { + contactsSubtitle = "except \(initialPrivacy.additionallyIncludePeers.count) people" + } } } categoryItems.append(ShareWithPeersScreenComponent.CategoryItem( @@ -1849,9 +1880,17 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer { var selectedContactsSubtitle = "choose" if initialPrivacy.base == .nobody, initialPrivacy.additionallyIncludePeers.count > 0 { if initialPrivacy.additionallyIncludePeers.count == 1 { - selectedContactsSubtitle = "1 person" + if !peerNames.isEmpty { + selectedContactsSubtitle = peerNames + } else { + selectedContactsSubtitle = "1 person" + } } else { - selectedContactsSubtitle = "\(initialPrivacy.additionallyIncludePeers.count) people" + if !peerNames.isEmpty { + selectedContactsSubtitle = peerNames + } else { + selectedContactsSubtitle = "\(initialPrivacy.additionallyIncludePeers.count) people" + } } } categoryItems.append(ShareWithPeersScreenComponent.CategoryItem( diff --git a/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift index 6cdce3f439..96a9a25363 100644 --- a/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift +++ b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift @@ -23,10 +23,34 @@ public final class TextFieldComponent: Component { public fileprivate(set) var hasText: Bool = false public var initialText: NSAttributedString? + public var hasTrackingView = false + + public var currentEmojiSuggestion: EmojiSuggestion? + public var dismissedEmojiSuggestionPosition: EmojiSuggestion.Position? + public init() { } } + public final class EmojiSuggestion { + public struct Position: Equatable { + public var range: NSRange + public var value: String + } + + public var localPosition: CGPoint + public var position: Position + public var disposable: Disposable? + public var value: Any? + + init(localPosition: CGPoint, position: Position) { + self.localPosition = localPosition + self.position = position + self.disposable = nil + self.value = nil + } + } + public final class AnimationHint { public enum Kind { case textChanged @@ -116,7 +140,7 @@ public final class TextFieldComponent: Component { private var spoilerView: InvisibleInkDustView? private var customEmojiContainerView: CustomEmojiContainerView? private var emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)? - + private var inputState: InputState { let selectionRange: Range = self.textView.selectedRange.location ..< (self.textView.selectedRange.location + self.textView.selectedRange.length) return InputState(inputText: stateAttributedStringForText(self.textView.attributedText ?? NSAttributedString()), selectionRange: selectionRange) @@ -223,6 +247,7 @@ public final class TextFieldComponent: Component { } self.updateSpoilersRevealed() + self.updateEmojiSuggestion(transition: .immediate) } public func textViewDidBeginEditing(_ textView: UITextView) { @@ -335,11 +360,6 @@ public final class TextFieldComponent: Component { } self.textView.becomeFirstResponder() } -// strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: true, { -// return $0.updatedInputMode({ _ in return inputMode }).updatedInterfaceState({ -// $0.withUpdatedEffectiveInputState(ChatTextInputState(inputText: $0.effectiveInputState.inputText, selectionRange: selectionRange.endIndex ..< selectionRange.endIndex)) -// }) -// }) } }) component.present(controller) @@ -547,6 +567,60 @@ public final class TextFieldComponent: Component { } } + public func updateEmojiSuggestion(transition: Transition) { + guard let component = self.component else { + return + } + + var hasTracking = false + var hasTrackingView = false + if self.textView.selectedRange.length == 0 && self.textView.selectedRange.location > 0 { + let selectedSubstring = self.textView.attributedText.attributedSubstring(from: NSRange(location: 0, length: self.textView.selectedRange.location)) + if let lastCharacter = selectedSubstring.string.last, String(lastCharacter).isSingleEmoji { + let queryLength = (String(lastCharacter) as NSString).length + if selectedSubstring.attribute(ChatTextInputAttributes.customEmoji, at: selectedSubstring.length - queryLength, effectiveRange: nil) == nil { + let beginning = self.textView.beginningOfDocument + + let characterRange = NSRange(location: selectedSubstring.length - queryLength, length: queryLength) + + let start = self.textView.position(from: beginning, offset: selectedSubstring.length - queryLength) + let end = self.textView.position(from: beginning, offset: selectedSubstring.length) + + if let start = start, let end = end, let textRange = self.textView.textRange(from: start, to: end) { + let selectionRects = self.textView.selectionRects(for: textRange) + let emojiSuggestionPosition = EmojiSuggestion.Position(range: characterRange, value: String(lastCharacter)) + + hasTracking = true + + if let trackingRect = selectionRects.first?.rect { + let trackingPosition = CGPoint(x: trackingRect.midX, y: trackingRect.minY) + if component.externalState.dismissedEmojiSuggestionPosition == emojiSuggestionPosition { + } else { + hasTrackingView = true + + let emojiSuggestion: EmojiSuggestion + if let current = component.externalState.currentEmojiSuggestion, current.position.value == emojiSuggestionPosition.value { + emojiSuggestion = current + } else { + + emojiSuggestion = EmojiSuggestion(localPosition: trackingPosition, position: emojiSuggestionPosition) + component.externalState.currentEmojiSuggestion = emojiSuggestion + } + emojiSuggestion.localPosition = trackingPosition + emojiSuggestion.position = emojiSuggestionPosition + component.externalState.dismissedEmojiSuggestionPosition = nil + } + } + } + } + } + } + if !hasTracking { + component.externalState.dismissedEmojiSuggestionPosition = nil + } + component.externalState.hasTrackingView = hasTrackingView + } + func update(component: TextFieldComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { self.component = component self.state = state @@ -584,6 +658,8 @@ public final class TextFieldComponent: Component { self.textView.frame = CGRect(origin: CGPoint(), size: size) self.textView.panGestureRecognizer.isEnabled = isEditing + self.updateEmojiSuggestion(transition: .immediate) + if refreshScrolling { if isEditing { if wasEditing { diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index 2781cd9d00..e98c6094ac 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -2859,7 +2859,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { component: AnyComponent(EmojiSuggestionsComponent( context: context, userLocation: .other, - theme: theme, + theme: EmojiSuggestionsComponent.Theme(theme: theme), animationCache: presentationContext.animationCache, animationRenderer: presentationContext.animationRenderer, files: value, From f918ce716e7e60be0eb50444bb62f9c7596e57e4 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 30 Jun 2023 12:39:09 +0200 Subject: [PATCH 3/6] Various improvements --- .../PendingMessageUploadedContent.swift | 2 +- .../TelegramEngine/Messages/Stories.swift | 13 +- .../Sources/VideoTextureSource.swift | 9 +- .../Sources/MediaEditorScreen.swift | 43 +++--- .../Sources/ShareWithPeersScreen.swift | 140 ++++++++++++++++-- 5 files changed, 171 insertions(+), 36 deletions(-) diff --git a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift index 855e7ec65e..37b9aa36ad 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift @@ -96,7 +96,7 @@ func messageContentToUpload(accountPeerId: PeerId, network: Network, postbox: Po return .content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaStory(userId: inputUser, id: media.storyId.id), ""), reuploadInfo: nil, cacheReferenceKey: nil)) } |> castError(PendingMessageUploadError.self), .text) - } else if let media = media.first, let mediaResult = mediaContentToUpload(accountPeerId: accountPeerId, network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: revalidationContext, forceReupload: forceReupload, isGrouped: isGrouped, passFetchProgress: false, peerId: peerId, media: media, text: text, autoremoveMessageAttribute: autoremoveMessageAttribute, autoclearMessageAttribute: autoclearMessageAttribute, messageId: messageId, attributes: attributes) { + } else if let media = media.first, let mediaResult = mediaContentToUpload(accountPeerId: accountPeerId, network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: revalidationContext, forceReupload: forceReupload, isGrouped: isGrouped, passFetchProgress: passFetchProgress, peerId: peerId, media: media, text: text, autoremoveMessageAttribute: autoremoveMessageAttribute, autoclearMessageAttribute: autoclearMessageAttribute, messageId: messageId, attributes: attributes) { return .signal(mediaResult, .media) } else { return .signal(.single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .text(text), reuploadInfo: nil, cacheReferenceKey: nil))), .text) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift index fd85989db6..2fcb4eff13 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift @@ -607,7 +607,7 @@ private func prepareUploadStoryContent(account: Account, media: EngineStoryInput } } -private func uploadedStoryContent(postbox: Postbox, network: Network, media: Media, accountPeerId: PeerId, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, auxiliaryMethods: AccountAuxiliaryMethods) -> (signal: Signal, media: Media) { +private func uploadedStoryContent(postbox: Postbox, network: Network, media: Media, accountPeerId: PeerId, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, auxiliaryMethods: AccountAuxiliaryMethods, passFetchProgress: Bool) -> (signal: Signal, media: Media) { let originalMedia: Media = media let contentToUpload: MessageContentToUpload @@ -621,7 +621,7 @@ private func uploadedStoryContent(postbox: Postbox, network: Network, media: Med revalidationContext: revalidationContext, forceReupload: true, isGrouped: false, - passFetchProgress: false, + passFetchProgress: passFetchProgress, peerId: accountPeerId, messageId: nil, attributes: [], @@ -758,7 +758,8 @@ private func _internal_putPendingStoryIdMapping(accountPeerId: PeerId, stableId: } func _internal_uploadStoryImpl(postbox: Postbox, network: Network, accountPeerId: PeerId, stateManager: AccountStateManager, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, auxiliaryMethods: AccountAuxiliaryMethods, stableId: Int32, media: Media, text: String, entities: [MessageTextEntity], pin: Bool, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, period: Int, randomId: Int64) -> Signal { - let (contentSignal, originalMedia) = uploadedStoryContent(postbox: postbox, network: network, media: media, accountPeerId: accountPeerId, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: revalidationContext, auxiliaryMethods: auxiliaryMethods) + let passFetchProgress = media is TelegramMediaFile + let (contentSignal, originalMedia) = uploadedStoryContent(postbox: postbox, network: network, media: media, accountPeerId: accountPeerId, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: revalidationContext, auxiliaryMethods: auxiliaryMethods, passFetchProgress: passFetchProgress) return contentSignal |> mapToSignal { result -> Signal in switch result { @@ -896,7 +897,11 @@ func _internal_editStory(account: Account, media: EngineStoryInputMedia?, id: In let contentSignal: Signal let originalMedia: Media? if let media = media { - (contentSignal, originalMedia) = uploadedStoryContent(postbox: account.postbox, network: account.network, media: prepareUploadStoryContent(account: account, media: media), accountPeerId: account.peerId, messageMediaPreuploadManager: account.messageMediaPreuploadManager, revalidationContext: account.mediaReferenceRevalidationContext, auxiliaryMethods: account.auxiliaryMethods) + var passFetchProgress = false + if case .video = media { + passFetchProgress = true + } + (contentSignal, originalMedia) = uploadedStoryContent(postbox: account.postbox, network: account.network, media: prepareUploadStoryContent(account: account, media: media), accountPeerId: account.peerId, messageMediaPreuploadManager: account.messageMediaPreuploadManager, revalidationContext: account.mediaReferenceRevalidationContext, auxiliaryMethods: account.auxiliaryMethods, passFetchProgress: passFetchProgress) } else { contentSignal = .single(nil) originalMedia = nil diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/VideoTextureSource.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/VideoTextureSource.swift index fb017e0585..480a78a79b 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/VideoTextureSource.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/VideoTextureSource.swift @@ -641,9 +641,13 @@ final class VideoInputScalePass: RenderPass { } func process(input: MTLTexture, secondInput: MTLTexture?, timestamp: CMTime, device: MTLDevice, commandBuffer: MTLCommandBuffer) -> MTLTexture? { +#if targetEnvironment(simulator) + +#else guard max(input.width, input.height) > 1920 || secondInput != nil else { return input } +#endif let scaledSize = CGSize(width: input.width, height: input.height).fitted(CGSize(width: 1920.0, height: 1920.0)) let width: Int @@ -691,8 +695,11 @@ final class VideoInputScalePass: RenderPass { renderCommandEncoder.setRenderPipelineState(self.mainPipelineState!) +#if targetEnvironment(simulator) + let secondInput = input +#endif + let (mainVideoState, additionalVideoState, transitionVideoState) = self.transitionState(for: timestamp, mainInput: input, additionalInput: secondInput) - if let transitionVideoState { self.encodeVideo( diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 923a4da0b6..2607702552 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -28,6 +28,7 @@ import CameraButtonComponent import UndoUI import ChatEntityKeyboardInputNode import ChatPresentationInterfaceState +import TextFormat enum DrawingScreenType { case drawing @@ -1862,25 +1863,25 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate } } } -//#if DEBUG -// if case let .asset(asset) = subject, asset.mediaType == .video { -// let videoEntity = DrawingStickerEntity(content: .dualVideoReference) -// videoEntity.referenceDrawingSize = storyDimensions -// videoEntity.scale = 1.49 -// videoEntity.position = PIPPosition.bottomRight.getPosition(storyDimensions) -// self.entitiesView.add(videoEntity, announce: false) -// -// mediaEditor.setAdditionalVideo("", positionChanges: [VideoPositionChange(additional: false, timestamp: 0.0), VideoPositionChange(additional: true, timestamp: 3.0)]) -// mediaEditor.setAdditionalVideoPosition(videoEntity.position, scale: videoEntity.scale, rotation: videoEntity.rotation) -// if let entityView = self.entitiesView.getView(for: videoEntity.uuid) as? DrawingStickerEntityView { -// entityView.updated = { [weak videoEntity, weak self] in -// if let self, let videoEntity { -// self.mediaEditor?.setAdditionalVideoPosition(videoEntity.position, scale: videoEntity.scale, rotation: videoEntity.rotation) -// } -// } -// } -// } -//#endif +#if targetEnvironment(simulator) + if case let .asset(asset) = subject, asset.mediaType == .video { + let videoEntity = DrawingStickerEntity(content: .dualVideoReference) + videoEntity.referenceDrawingSize = storyDimensions + videoEntity.scale = 1.49 + videoEntity.position = PIPPosition.bottomRight.getPosition(storyDimensions) + self.entitiesView.add(videoEntity, announce: false) + + mediaEditor.setAdditionalVideo("", positionChanges: [VideoPositionChange(additional: false, timestamp: 0.0), VideoPositionChange(additional: true, timestamp: 3.0)]) + mediaEditor.setAdditionalVideoPosition(videoEntity.position, scale: videoEntity.scale, rotation: videoEntity.rotation) + if let entityView = self.entitiesView.getView(for: videoEntity.uuid) as? DrawingStickerEntityView { + entityView.updated = { [weak videoEntity, weak self] in + if let self, let videoEntity { + self.mediaEditor?.setAdditionalVideoPosition(videoEntity.position, scale: videoEntity.scale, rotation: videoEntity.rotation) + } + } + } + } +#endif self.gradientColorsDisposable = mediaEditor.gradientColors.start(next: { [weak self] colors in if let self, let colors { @@ -3103,6 +3104,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate let privacy = privacy ?? self.state.privacy + let text = self.getCaption().string + let mentions = generateTextEntities(text, enabledTypes: [.mention], currentEntities: []).map { (text as NSString).substring(with: NSRange(location: $0.range.lowerBound + 1, length: $0.range.upperBound - $0.range.lowerBound - 1)) } + let stateContext = ShareWithPeersScreen.StateContext(context: self.context, subject: .stories(editing: false), initialPeerIds: Set(privacy.privacy.additionallyIncludePeers)) let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in guard let self else { @@ -3117,6 +3121,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate allowScreenshots: !privacy.isForwardingDisabled, pin: privacy.pin, timeout: privacy.timeout, + mentions: mentions, stateContext: stateContext, completion: { [weak self] privacy, allowScreenshots, pin in guard let self else { diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift index 33ee596e91..2735fc2978 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift @@ -9,6 +9,7 @@ import ComponentDisplayAdapters import TelegramPresentationData import AccountContext import TelegramCore +import Postbox import MultilineTextComponent import SolidRoundedButtonComponent import PresentationDataUtils @@ -31,6 +32,7 @@ final class ShareWithPeersScreenComponent: Component { let screenshot: Bool let pin: Bool let timeout: Int + let mentions: [String] let categoryItems: [CategoryItem] let optionItems: [OptionItem] let completion: (EngineStoryPrivacy, Bool, Bool) -> Void @@ -43,6 +45,7 @@ final class ShareWithPeersScreenComponent: Component { screenshot: Bool, pin: Bool, timeout: Int, + mentions: [String], categoryItems: [CategoryItem], optionItems: [OptionItem], completion: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void, @@ -54,6 +57,7 @@ final class ShareWithPeersScreenComponent: Component { self.screenshot = screenshot self.pin = pin self.timeout = timeout + self.mentions = mentions self.categoryItems = categoryItems self.optionItems = optionItems self.completion = completion @@ -79,6 +83,9 @@ final class ShareWithPeersScreenComponent: Component { if lhs.timeout != rhs.timeout { return false } + if lhs.mentions != rhs.mentions { + return false + } if lhs.categoryItems != rhs.categoryItems { return false } @@ -1508,7 +1515,7 @@ final class ShareWithPeersScreenComponent: Component { guard let self, let component = self.component, let controller = self.environment?.controller() as? ShareWithPeersScreen else { return } - + let base: EngineStoryPrivacy.Base if self.selectedCategories.contains(.everyone) { base = .everyone @@ -1522,17 +1529,126 @@ final class ShareWithPeersScreenComponent: Component { base = .nobody } - component.completion( - EngineStoryPrivacy( - base: base, - additionallyIncludePeers: self.selectedPeers - ), - self.selectedOptions.contains(.screenshot), - self.selectedOptions.contains(.pin) - ) + let proceed = { + component.completion( + EngineStoryPrivacy( + base: base, + additionallyIncludePeers: self.selectedPeers + ), + self.selectedOptions.contains(.screenshot), + self.selectedOptions.contains(.pin) + ) - controller.dismissAllTooltips() - controller.dismiss() + controller.dismissAllTooltips() + controller.dismiss() + } + + let presentAlert: ([String]) -> Void = { usernames in + let usernamesString = String(usernames.map { "@\($0)" }.joined(separator: ", ")) + let alertController = textAlertController( + context: component.context, + forceTheme: defaultDarkColorPresentationTheme, + title: "Privacy Restrictions", + text: "The privacy settings of your story will prevent some users you tagged (\( usernamesString )) from viewing it.", + actions: [ + TextAlertAction(type: .defaultAction, title: "Proceed Anyway", action: { + proceed() + }), + TextAlertAction(type: .genericAction, title: "Cancel", action: {}) + ], + actionLayout: .vertical + ) + controller.present(alertController, in: .window(.root)) + } + + func matchingUsername(user: TelegramUser, usernames: Set) -> String? { + for username in user.usernames { + if usernames.contains(username.username) { + return username.username + } + } + if let username = user.username { + if usernames.contains(username) { + return username + } + } + return nil + } + + let context = component.context + let selectedPeerIds = self.selectedPeers + + if case .stories = component.stateContext.subject { + if component.mentions.isEmpty { + proceed() + } else if case .nobody = base { + if selectedPeerIds.isEmpty { + presentAlert(component.mentions) + } else { + let _ = (context.account.postbox.transaction { transaction in + var filteredMentions = Set(component.mentions) + for peerId in selectedPeerIds { + if let user = transaction.getPeer(peerId) as? TelegramUser, let username = matchingUsername(user: user, usernames: filteredMentions) { + filteredMentions.remove(username) + } + } + return Array(filteredMentions) + } + |> deliverOnMainQueue).start(next: { mentions in + if mentions.isEmpty { + proceed() + } else { + presentAlert(mentions) + } + }) + } + } else if case .contacts = base { + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Contacts.List(includePresences: false)) + |> map { contacts -> [String] in + var filteredMentions = Set(component.mentions) + let peers = contacts.peers + for peer in peers { + if selectedPeerIds.contains(peer.id) { + continue + } + if case let .user(user) = peer, let username = matchingUsername(user: user, usernames: filteredMentions) { + filteredMentions.remove(username) + } + } + return Array(filteredMentions) + } + |> deliverOnMainQueue).start(next: { mentions in + if mentions.isEmpty { + proceed() + } else { + presentAlert(mentions) + } + }) + } else if case .closeFriends = base { + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Contacts.List(includePresences: false)) + |> map { contacts -> [String] in + var filteredMentions = Set(component.mentions) + let peers = contacts.peers + for peer in peers { + if case let .user(user) = peer, user.flags.contains(.isCloseFriend), let username = matchingUsername(user: user, usernames: filteredMentions) { + filteredMentions.remove(username) + } + } + return Array(filteredMentions) + } + |> deliverOnMainQueue).start(next: { mentions in + if mentions.isEmpty { + proceed() + } else { + presentAlert(mentions) + } + }) + } else { + proceed() + } + } else { + proceed() + } } )), environment: {}, @@ -1821,6 +1937,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer { allowScreenshots: Bool = true, pin: Bool = false, timeout: Int = 0, + mentions: [String] = [], stateContext: StateContext, completion: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void, editCategory: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void @@ -1921,6 +2038,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer { screenshot: allowScreenshots, pin: pin, timeout: timeout, + mentions: mentions, categoryItems: categoryItems, optionItems: optionItems, completion: completion, From 7b741d20a6601d75dbbf68a3e83d3c7f821abce2 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 30 Jun 2023 12:52:14 +0200 Subject: [PATCH 4/6] Various fixes --- .../Sources/MediaEditorScreen.swift | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 2607702552..a186c5894e 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -3987,8 +3987,10 @@ final class DoneButtonComponent: CombinedComponent { ) let backgroundHeight: CGFloat = 33.0 + var backgroundSize = CGSize(width: backgroundHeight, height: backgroundHeight) + + let textSpacing: CGFloat = 7.0 - var textWidth: CGFloat = 0.0 var title: _UpdatedChildComponent? if let titleText = context.component.title { title = text.update( @@ -4000,14 +4002,15 @@ final class DoneButtonComponent: CombinedComponent { availableSize: CGSize(width: 180.0, height: 100.0), transition: .immediate ) - textWidth = title!.size.width + + let updatedBackgroundWidth = backgroundSize.width + textSpacing + title!.size.width + if updatedBackgroundWidth < 126.0 { + backgroundSize.width = updatedBackgroundWidth + } else { + title = nil + } } - var backgroundSize = CGSize(width: 33.0, height: backgroundHeight) - if !textWidth.isZero { - backgroundSize.width += textWidth + 7.0 - } - let background = background.update( component: RoundedRectangle(color: context.component.backgroundColor, cornerRadius: backgroundHeight / 2.0), availableSize: backgroundSize, From 958e5d553740d2836891d1f4d79e9926e23c93d3 Mon Sep 17 00:00:00 2001 From: Mike Renoir <> Date: Fri, 30 Jun 2023 16:29:20 +0200 Subject: [PATCH 5/6] added some stuff --- .../TelegramEngine/Messages/StoryListContext.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift index 26c0ce6330..641d17d562 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift @@ -1077,6 +1077,16 @@ public final class PeerExpiringStoryListContext { return self.items.contains(where: { $0.id > self.maxReadId }) } + public var unseenCount: Int { + var count: Int = 0 + for item in items { + if item.id > maxReadId { + count += 1 + } + } + return count + } + public var hasUnseenCloseFriends: Bool { return self.items.contains(where: { $0.id > self.maxReadId && $0.isCloseFriends }) } From 7551bdc55e7536e5e8ec2251274fea0c2a5ec129 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 30 Jun 2023 18:25:53 +0200 Subject: [PATCH 6/6] Various fixes --- .../DeviceAccess/Sources/DeviceAccess.swift | 13 ++- .../Sources/State/UpdatesApiUtils.swift | 23 +++++ .../Messages/PendingStoryManager.swift | 16 ++- .../TelegramEngine/Messages/Stories.swift | 29 +++++- .../CameraScreen/Sources/CameraScreen.swift | 25 +++-- .../Sources/CaptureControlsComponent.swift | 5 +- .../Sources/MediaEditorComposer.swift | 10 +- .../Sources/MediaEditorVideoExport.swift | 26 +++-- .../Sources/VideoTextureSource.swift | 14 +-- .../Sources/MediaEditorScreen.swift | 24 ++++- .../StoryItemSetContainerComponent.swift | 16 ++- .../Media Editor/Next.imageset/Contents.json | 2 +- .../Media Editor/Next.imageset/arrow_left.pdf | Bin 4081 -> 0 bytes .../Media Editor/Next.imageset/ic_next.pdf | 92 ++++++++++++++++++ .../Sources/TelegramRootController.swift | 6 +- 15 files changed, 245 insertions(+), 56 deletions(-) delete mode 100644 submodules/TelegramUI/Images.xcassets/Media Editor/Next.imageset/arrow_left.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Media Editor/Next.imageset/ic_next.pdf diff --git a/submodules/DeviceAccess/Sources/DeviceAccess.swift b/submodules/DeviceAccess/Sources/DeviceAccess.swift index 4751691d08..eeab79c9d2 100644 --- a/submodules/DeviceAccess/Sources/DeviceAccess.swift +++ b/submodules/DeviceAccess/Sources/DeviceAccess.swift @@ -11,7 +11,6 @@ import AddressBook import UserNotifications import CoreTelephony import TelegramPresentationData -import LegacyComponents import AccountContext public enum DeviceAccessCameraSubject { @@ -88,7 +87,7 @@ public final class DeviceAccess { } public static func isCameraAccessAuthorized() -> Bool { - return PGCamera.cameraAuthorizationStatus() == PGCameraAuthorizationStatusAuthorized + return AVCaptureDevice.authorizationStatus(for: .video) == .authorized } public static func authorizationStatus(applicationInForeground: Signal? = nil, siriAuthorization: (() -> AccessType)? = nil, subject: DeviceAccessSubject) -> Signal { @@ -257,8 +256,8 @@ public final class DeviceAccess { public static func authorizeAccess(to subject: DeviceAccessSubject, onlyCheck: Bool = false, registerForNotifications: ((@escaping (Bool) -> Void) -> Void)? = nil, requestSiriAuthorization: ((@escaping (Bool) -> Void) -> Void)? = nil, locationManager: LocationManager? = nil, presentationData: PresentationData? = nil, present: @escaping (ViewController, Any?) -> Void = { _, _ in }, openSettings: @escaping () -> Void = { }, displayNotificationFromBackground: @escaping (String) -> Void = { _ in }, _ completion: @escaping (Bool) -> Void = { _ in }) { switch subject { case let .camera(cameraSubject): - let status = PGCamera.cameraAuthorizationStatus() - if status == PGCameraAuthorizationStatusNotDetermined { + let status = AVCaptureDevice.authorizationStatus(for: .video) + if case .notDetermined = status { if !onlyCheck { AVCaptureDevice.requestAccess(for: AVMediaType.video) { response in Queue.mainQueue().async { @@ -282,9 +281,9 @@ public final class DeviceAccess { } else { completion(true) } - } else if status == PGCameraAuthorizationStatusRestricted || status == PGCameraAuthorizationStatusDenied, let presentationData = presentationData { + } else if [.restricted, .denied].contains(status), let presentationData = presentationData { let text: String - if status == PGCameraAuthorizationStatusRestricted { + if case .restricted = status { text = presentationData.strings.AccessDenied_CameraRestricted } else { switch cameraSubject { @@ -300,7 +299,7 @@ public final class DeviceAccess { present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: { openSettings() })]), nil) - } else if status == PGCameraAuthorizationStatusAuthorized { + } else if case .authorized = status { completion(true) } else { assertionFailure() diff --git a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift index 7222906a27..5524e35bf6 100644 --- a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift +++ b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift @@ -611,3 +611,26 @@ extension Api.EncryptedMessage { } } } + +extension Api.InputMedia { + func withUpdatedStickers(_ stickers: [Api.InputDocument]?) -> Api.InputMedia { + switch self { + case let .inputMediaUploadedDocument(flags, file, thumb, mimeType, attributes, _, ttlSeconds): + var flags = flags + var attributes = attributes + if let _ = stickers { + flags |= (1 << 0) + attributes.append(.documentAttributeHasStickers) + } + return .inputMediaUploadedDocument(flags: flags, file: file, thumb: thumb, mimeType: mimeType, attributes: attributes, stickers: stickers, ttlSeconds: ttlSeconds) + case let .inputMediaUploadedPhoto(flags, file, _, ttlSeconds): + var flags = flags + if let _ = stickers { + flags |= (1 << 0) + } + return .inputMediaUploadedPhoto(flags: flags, file: file, stickers: stickers, ttlSeconds: ttlSeconds) + default: + return self + } + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/PendingStoryManager.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/PendingStoryManager.swift index c7cf01878f..445370d041 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/PendingStoryManager.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/PendingStoryManager.swift @@ -11,6 +11,7 @@ public extension Stories { case media case text case entities + case embeddedStickers case pin case privacy case isForwardingDisabled @@ -23,6 +24,7 @@ public extension Stories { public let media: Media public let text: String public let entities: [MessageTextEntity] + public let embeddedStickers: [TelegramMediaFile] public let pin: Bool public let privacy: EngineStoryPrivacy public let isForwardingDisabled: Bool @@ -35,6 +37,7 @@ public extension Stories { media: Media, text: String, entities: [MessageTextEntity], + embeddedStickers: [TelegramMediaFile], pin: Bool, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, @@ -46,6 +49,7 @@ public extension Stories { self.media = media self.text = text self.entities = entities + self.embeddedStickers = embeddedStickers self.pin = pin self.privacy = privacy self.isForwardingDisabled = isForwardingDisabled @@ -64,6 +68,11 @@ public extension Stories { self.text = try container.decode(String.self, forKey: .text) self.entities = try container.decode([MessageTextEntity].self, forKey: .entities) + + let stickersData = try container.decode(Data.self, forKey: .embeddedStickers) + let stickersDecoder = PostboxDecoder(buffer: MemoryBuffer(data: stickersData)) + self.embeddedStickers = (try? stickersDecoder.decodeObjectArrayWithCustomDecoderForKey("stickers", decoder: { TelegramMediaFile(decoder: $0) })) ?? [] + self.pin = try container.decode(Bool.self, forKey: .pin) self.privacy = try container.decode(EngineStoryPrivacy.self, forKey: .privacy) self.isForwardingDisabled = try container.decodeIfPresent(Bool.self, forKey: .isForwardingDisabled) ?? false @@ -83,6 +92,11 @@ public extension Stories { try container.encode(self.text, forKey: .text) try container.encode(self.entities, forKey: .entities) + + let stickersEncoder = PostboxEncoder() + stickersEncoder.encodeObjectArray(self.embeddedStickers, forKey: "stickers") + try container.encode(stickersEncoder.makeData(), forKey: .embeddedStickers) + try container.encode(self.pin, forKey: .pin) try container.encode(self.privacy, forKey: .privacy) try container.encode(self.isForwardingDisabled, forKey: .isForwardingDisabled) @@ -270,7 +284,7 @@ final class PendingStoryManager { self.currentPendingItemContext = pendingItemContext let stableId = firstItem.stableId - pendingItemContext.disposable = (_internal_uploadStoryImpl(postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, stateManager: self.stateManager, messageMediaPreuploadManager: self.messageMediaPreuploadManager, revalidationContext: self.revalidationContext, auxiliaryMethods: self.auxiliaryMethods, stableId: stableId, media: firstItem.media, text: firstItem.text, entities: firstItem.entities, pin: firstItem.pin, privacy: firstItem.privacy, isForwardingDisabled: firstItem.isForwardingDisabled, period: Int(firstItem.period), randomId: firstItem.randomId) + pendingItemContext.disposable = (_internal_uploadStoryImpl(postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, stateManager: self.stateManager, messageMediaPreuploadManager: self.messageMediaPreuploadManager, revalidationContext: self.revalidationContext, auxiliaryMethods: self.auxiliaryMethods, stableId: stableId, media: firstItem.media, text: firstItem.text, entities: firstItem.entities, embeddedStickers: firstItem.embeddedStickers, pin: firstItem.pin, privacy: firstItem.privacy, isForwardingDisabled: firstItem.isForwardingDisabled, period: Int(firstItem.period), randomId: firstItem.randomId) |> deliverOn(self.queue)).start(next: { [weak self] event in guard let `self` = self else { return diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift index 2fcb4eff13..a396de6e1c 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift @@ -4,8 +4,15 @@ import Postbox import TelegramApi public enum EngineStoryInputMedia { - case image(dimensions: PixelDimensions, data: Data) - case video(dimensions: PixelDimensions, duration: Double, resource: TelegramMediaResource, firstFrameImageData: Data?) + case image(dimensions: PixelDimensions, data: Data, stickers: [TelegramMediaFile]) + case video(dimensions: PixelDimensions, duration: Double, resource: TelegramMediaResource, firstFrameImageData: Data?, stickers: [TelegramMediaFile]) + + var embeddedStickers: [TelegramMediaFile] { + switch self { + case let .image(_, _, stickers), let .video(_, _, _, _, stickers): + return stickers + } + } } public struct EngineStoryPrivacy: Codable, Equatable { @@ -567,7 +574,7 @@ public enum StoryUploadResult { private func prepareUploadStoryContent(account: Account, media: EngineStoryInputMedia) -> Media { switch media { - case let .image(dimensions, data): + case let .image(dimensions, data, _): let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) account.postbox.mediaBox.storeResourceData(resource.id, data: data) @@ -580,7 +587,7 @@ private func prepareUploadStoryContent(account: Account, media: EngineStoryInput flags: [] ) return imageMedia - case let .video(dimensions, duration, resource, firstFrameImageData): + case let .video(dimensions, duration, resource, firstFrameImageData, _): var previewRepresentations: [TelegramMediaImageRepresentation] = [] if let firstFrameImageData = firstFrameImageData { let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) @@ -709,6 +716,7 @@ func _internal_uploadStory(account: Account, media: EngineStoryInputMedia, text: media: inputMedia, text: text, entities: entities, + embeddedStickers: media.embeddedStickers, pin: pin, privacy: privacy, isForwardingDisabled: isForwardingDisabled, @@ -757,7 +765,7 @@ private func _internal_putPendingStoryIdMapping(accountPeerId: PeerId, stableId: } } -func _internal_uploadStoryImpl(postbox: Postbox, network: Network, accountPeerId: PeerId, stateManager: AccountStateManager, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, auxiliaryMethods: AccountAuxiliaryMethods, stableId: Int32, media: Media, text: String, entities: [MessageTextEntity], pin: Bool, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, period: Int, randomId: Int64) -> Signal { +func _internal_uploadStoryImpl(postbox: Postbox, network: Network, accountPeerId: PeerId, stateManager: AccountStateManager, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, auxiliaryMethods: AccountAuxiliaryMethods, stableId: Int32, media: Media, text: String, entities: [MessageTextEntity], embeddedStickers: [TelegramMediaFile], pin: Bool, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, period: Int, randomId: Int64) -> Signal { let passFetchProgress = media is TelegramMediaFile let (contentSignal, originalMedia) = uploadedStoryContent(postbox: postbox, network: network, media: media, accountPeerId: accountPeerId, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: revalidationContext, auxiliaryMethods: auxiliaryMethods, passFetchProgress: passFetchProgress) return contentSignal @@ -802,6 +810,17 @@ func _internal_uploadStoryImpl(postbox: Postbox, network: Network, accountPeerId flags |= 1 << 4 } + var inputMedia = inputMedia + if !embeddedStickers.isEmpty { + var stickersValue: [Api.InputDocument] = [] + for file in embeddedStickers { + if let resource = file.resource as? CloudDocumentMediaResource, let fileReference = resource.fileReference { + stickersValue.append(Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: fileReference))) + } + } + inputMedia = inputMedia.withUpdatedStickers(stickersValue) + } + return network.request(Api.functions.stories.sendStory( flags: flags, media: inputMedia, diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift index 14fa0a0a75..17aeaab15e 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift @@ -98,7 +98,7 @@ private final class CameraScreenComponent: CombinedComponent { let hasAppeared: Bool let isVisible: Bool let panelWidth: CGFloat - let flipAnimationAction: ActionSlot + let animateFlipAction: ActionSlot let animateShutter: () -> Void let present: (ViewController) -> Void let push: (ViewController) -> Void @@ -112,7 +112,7 @@ private final class CameraScreenComponent: CombinedComponent { hasAppeared: Bool, isVisible: Bool, panelWidth: CGFloat, - flipAnimationAction: ActionSlot, + animateFlipAction: ActionSlot, animateShutter: @escaping () -> Void, present: @escaping (ViewController) -> Void, push: @escaping (ViewController) -> Void, @@ -125,7 +125,7 @@ private final class CameraScreenComponent: CombinedComponent { self.hasAppeared = hasAppeared self.isVisible = isVisible self.panelWidth = panelWidth - self.flipAnimationAction = flipAnimationAction + self.animateFlipAction = animateFlipAction self.animateShutter = animateShutter self.present = present self.push = push @@ -170,6 +170,10 @@ private final class CameraScreenComponent: CombinedComponent { } } + private var cameraAuthorizationStatus: AVAuthorizationStatus = .notDetermined + private var microphoneAuthorizationStatus: AVAuthorizationStatus = .notDetermined + private var galleryAuthorizationStatus: PHAuthorizationStatus = .notDetermined + private let context: AccountContext fileprivate let camera: Camera private let present: (ViewController) -> Void @@ -652,7 +656,7 @@ private final class CameraScreenComponent: CombinedComponent { } } - let flipAnimationAction = component.flipAnimationAction + let animateFlipAction = component.animateFlipAction let captureControlsAvailableSize: CGSize if isTablet { captureControlsAvailableSize = CGSize(width: panelWidth, height: availableSize.height) @@ -706,7 +710,7 @@ private final class CameraScreenComponent: CombinedComponent { guard let state else { return } - state.togglePosition(flipAnimationAction) + state.togglePosition(animateFlipAction) }, galleryTapped: { guard let controller = environment.controller() as? CameraScreen else { @@ -720,7 +724,7 @@ private final class CameraScreenComponent: CombinedComponent { zoomUpdated: { fraction in state.updateZoom(fraction: fraction) }, - flipAnimationAction: flipAnimationAction + flipAnimationAction: animateFlipAction ), availableSize: captureControlsAvailableSize, transition: context.transition @@ -743,14 +747,14 @@ private final class CameraScreenComponent: CombinedComponent { id: "flip", component: AnyComponent( FlipButtonContentComponent( - action: flipAnimationAction, + action: animateFlipAction, maskFrame: .zero ) ) ), minSize: CGSize(width: 44.0, height: 44.0), action: { - state.togglePosition(flipAnimationAction) + state.togglePosition(animateFlipAction) } ), availableSize: availableSize, @@ -1036,7 +1040,7 @@ public class CameraScreen: ViewController { private var pipPosition: PIPPosition = .bottomRight fileprivate var previewBlurPromise = ValuePromise(false) - private let flipAnimationAction = ActionSlot() + private let animateFlipAction = ActionSlot() fileprivate var cameraIsActive = true fileprivate var hasGallery = false @@ -1738,6 +1742,7 @@ public class CameraScreen: ViewController { self.hasAppeared = hasAppeared transition = transition.withUserData(CameraScreenTransition.finishedAnimateIn) + // self.presentCameraTooltip() // self.presentDualCameraTooltip() } @@ -1753,7 +1758,7 @@ public class CameraScreen: ViewController { hasAppeared: self.hasAppeared, isVisible: self.cameraIsActive && !self.hasGallery, panelWidth: panelWidth, - flipAnimationAction: self.flipAnimationAction, + animateFlipAction: self.animateFlipAction, animateShutter: { [weak self] in self?.mainPreviewContainerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) }, diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift index df6e5a93e7..e2ab97ef27 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift @@ -8,6 +8,7 @@ import LocalMediaResources import CameraButtonComponent enum ShutterButtonState: Equatable { + case disabled case generic case video case stopRecording @@ -162,7 +163,7 @@ private final class ShutterButtonContentComponent: Component { let ringWidth: CGFloat = 3.0 var recordingProgress: Float? switch component.shutterState { - case .generic: + case .generic, .disabled: innerColor = .white innerSize = CGSize(width: 60.0, height: 60.0) ringSize = CGSize(width: 68.0, height: 68.0) @@ -986,7 +987,7 @@ final class CaptureControlsComponent: Component { var blobState: ShutterBlobView.BlobState switch component.shutterState { - case .generic: + case .generic, .disabled: blobState = .generic case .video, .transition: blobState = .video diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposer.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposer.swift index 0e84964a99..cc6e774792 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposer.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposer.swift @@ -134,9 +134,9 @@ final class MediaEditorComposer { } private var filteredImage: CIImage? - func processImage(inputImage: UIImage, pool: CVPixelBufferPool?, time: CMTime, completion: @escaping (CVPixelBuffer?, CMTime) -> Void) { + func processImage(inputImage: UIImage, pool: CVPixelBufferPool?, time: CMTime, completion: @escaping (CVPixelBuffer?) -> Void) { guard let pool else { - completion(nil, time) + completion(nil) return } if self.filteredImage == nil, let device = self.device { @@ -161,15 +161,15 @@ final class MediaEditorComposer { compositedImage = compositedImage.samplingLinear().transformed(by: CGAffineTransform(scaleX: scale, y: scale)) self.ciContext?.render(compositedImage, to: pixelBuffer) - completion(pixelBuffer, time) + completion(pixelBuffer) } else { - completion(nil, time) + completion(nil) } }) return } } - completion(nil, time) + completion(nil) } func processImage(inputImage: CIImage, time: CMTime, completion: @escaping (CIImage?) -> Void) { diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoExport.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoExport.swift index 4d8160470d..0b0ef2fc7d 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoExport.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoExport.swift @@ -569,18 +569,30 @@ public final class MediaEditorVideoExport { let progress = (position - .zero).seconds / duration self.statusValue = .progress(Float(progress)) - composer.processImage(inputImage: image, pool: writer.pixelBufferPool, time: position, completion: { pixelBuffer, timestamp in + composer.processImage(inputImage: image, pool: writer.pixelBufferPool, time: position, completion: { pixelBuffer in if let pixelBuffer { - if !writer.appendPixelBuffer(pixelBuffer, at: timestamp) { - Logger.shared.log("VideoExport", "Failed to append pixelbuffer") - writer.markVideoAsFinished() - appendFailed = true + if !writer.appendPixelBuffer(pixelBuffer, at: position) { + Logger.shared.log("VideoExport", "Failed to append pixelbuffer at \(position.seconds), trying to wait") + Queue.concurrentDefaultQueue().after(1.0, { + if !writer.appendPixelBuffer(pixelBuffer, at: position) { + Logger.shared.log("VideoExport", "Failed to append pixelbuffer at \(position.seconds), complete failure") + writer.markVideoAsFinished() + appendFailed = true + self.semaphore.signal() + } + }) + } else { + Logger.shared.log("VideoExport", "Appended pixelbuffer at \(position.seconds)") + + Thread.sleep(forTimeInterval: 0.01) + self.semaphore.signal() } } else { Logger.shared.log("VideoExport", "No pixelbuffer from composer") + + Thread.sleep(forTimeInterval: 0.01) + self.semaphore.signal() } - Thread.sleep(forTimeInterval: 0.001) - self.semaphore.signal() }) self.semaphore.wait() diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/VideoTextureSource.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/VideoTextureSource.swift index 480a78a79b..cd9c61ebf0 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/VideoTextureSource.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/VideoTextureSource.swift @@ -641,13 +641,13 @@ final class VideoInputScalePass: RenderPass { } func process(input: MTLTexture, secondInput: MTLTexture?, timestamp: CMTime, device: MTLDevice, commandBuffer: MTLCommandBuffer) -> MTLTexture? { -#if targetEnvironment(simulator) - -#else +//#if targetEnvironment(simulator) +// +//#else guard max(input.width, input.height) > 1920 || secondInput != nil else { return input } -#endif +//#endif let scaledSize = CGSize(width: input.width, height: input.height).fitted(CGSize(width: 1920.0, height: 1920.0)) let width: Int @@ -695,9 +695,9 @@ final class VideoInputScalePass: RenderPass { renderCommandEncoder.setRenderPipelineState(self.mainPipelineState!) -#if targetEnvironment(simulator) - let secondInput = input -#endif +//#if targetEnvironment(simulator) +// let secondInput = input +//#endif let (mainVideoState, additionalVideoState, transitionVideoState) = self.transitionState(for: timestamp, mainInput: input, additionalInput: secondInput) diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index a186c5894e..6c8024f4d9 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -3016,7 +3016,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate fileprivate let transitionOut: (Bool, Bool?) -> TransitionOut? public var cancelled: (Bool) -> Void = { _ in } - public var completion: (Int64, MediaEditorScreen.Result?, NSAttributedString, MediaEditorResultPrivacy , @escaping (@escaping () -> Void) -> Void) -> Void = { _, _, _, _, _ in } + public var completion: (Int64, MediaEditorScreen.Result?, NSAttributedString, MediaEditorResultPrivacy, [TelegramMediaFile], @escaping (@escaping () -> Void) -> Void) -> Void = { _, _, _, _, _, _ in } public var dismissed: () -> Void = { } public var willDismiss: () -> Void = { } @@ -3031,7 +3031,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate initialVideoPosition: Double? = nil, transitionIn: TransitionIn?, transitionOut: @escaping (Bool, Bool?) -> TransitionOut?, - completion: @escaping (Int64, MediaEditorScreen.Result?, NSAttributedString, MediaEditorResultPrivacy, @escaping (@escaping () -> Void) -> Void) -> Void + completion: @escaping (Int64, MediaEditorScreen.Result?, NSAttributedString, MediaEditorResultPrivacy, [TelegramMediaFile], @escaping (@escaping () -> Void) -> Void) -> Void ) { self.context = context self.subject = subject @@ -3484,8 +3484,22 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate randomId = Int64.random(in: .min ... .max) } + var stickers: [TelegramMediaFile] = [] + for entity in codableEntities { + if case let .sticker(stickerEntity) = entity, case let .file(file) = stickerEntity.content { + stickers.append(file) + if let subEntities = stickerEntity.renderSubEntities { + for entity in subEntities { + if let stickerEntity = entity as? DrawingStickerEntity, case let .file(file) = stickerEntity.content { + stickers.append(file) + } + } + } + } + } + if self.isEditingStory && !self.node.hasAnyChanges { - self.completion(randomId, nil, caption, self.state.privacy, { [weak self] finished in + self.completion(randomId, nil, caption, self.state.privacy, stickers, { [weak self] finished in self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in self?.dismiss() Queue.mainQueue().justDispatch { @@ -3616,7 +3630,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate if let self { makeEditorImageComposition(context: self.node.ciContext, 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, self.state.privacy, { [weak self] finished in + self.completion(randomId, .video(video: videoResult, coverImage: coverImage, values: mediaEditor.values, duration: duration, dimensions: mediaEditor.values.resultDimensions), caption, self.state.privacy, stickers, { [weak self] finished in self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in self?.dismiss() Queue.mainQueue().justDispatch { @@ -3638,7 +3652,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate makeEditorImageComposition(context: self.node.ciContext, 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, self.state.privacy, { [weak self] finished in + self.completion(randomId, .image(image: resultImage, dimensions: PixelDimensions(resultImage.size)), caption, self.state.privacy, stickers, { [weak self] finished in self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in self?.dismiss() Queue.mainQueue().justDispatch { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index e31aadb307..e9e10b54d3 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -2809,7 +2809,7 @@ public final class StoryItemSetContainerComponent: Component { initialVideoPosition: videoPlaybackPosition, transitionIn: nil, transitionOut: { _, _ in return nil }, - completion: { [weak self] _, mediaResult, caption, privacy, commit in + completion: { [weak self] _, mediaResult, caption, privacy, stickers, commit in guard let self else { return } @@ -2831,7 +2831,7 @@ public final class StoryItemSetContainerComponent: Component { updateProgressImpl?(0.0) if let imageData = compressImageToJPEG(image, quality: 0.7) { - let _ = (context.engine.messages.editStory(media: .image(dimensions: dimensions, data: imageData), id: id, text: updatedText, entities: updatedEntities, privacy: updatedPrivacy) + let _ = (context.engine.messages.editStory(media: .image(dimensions: dimensions, data: imageData, stickers: stickers), id: id, text: updatedText, entities: updatedEntities, privacy: updatedPrivacy) |> deliverOnMainQueue).start(next: { [weak self] result in guard let self else { return @@ -2870,7 +2870,7 @@ public final class StoryItemSetContainerComponent: Component { } let firstFrameImageData = firstFrameImage.flatMap { compressImageToJPEG($0, quality: 0.6) } - let _ = (context.engine.messages.editStory(media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameImageData: firstFrameImageData), id: id, text: updatedText, entities: updatedEntities, privacy: updatedPrivacy) + let _ = (context.engine.messages.editStory(media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameImageData: firstFrameImageData, stickers: stickers), id: id, text: updatedText, entities: updatedEntities, privacy: updatedPrivacy) |> deliverOnMainQueue).start(next: { [weak self] result in guard let self else { return @@ -2990,6 +2990,14 @@ public final class StoryItemSetContainerComponent: Component { let additionalCount = component.slice.item.storyItem.privacy?.additionallyIncludePeers.count ?? 0 + var hasLinkedStickers = false + let media = component.slice.item.storyItem.media._asMedia() + if let image = media as? TelegramMediaImage { + hasLinkedStickers = image.flags.contains(.hasStickers) + } else if let file = media as? TelegramMediaFile { + hasLinkedStickers = file.hasLinkedStickers + } + let privacyText: String switch component.slice.item.storyItem.privacy?.base { case .closeFriends: @@ -3126,6 +3134,8 @@ public final class StoryItemSetContainerComponent: Component { self.sendMessageContext.performShareAction(view: self) }))) } + + let _ = hasLinkedStickers let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) let contextController = ContextController(account: component.context.account, presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/Next.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Media Editor/Next.imageset/Contents.json index ffc2f05f85..89b3ac9ee6 100644 --- a/submodules/TelegramUI/Images.xcassets/Media Editor/Next.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Media Editor/Next.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "arrow_left.pdf", + "filename" : "ic_next.pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/Next.imageset/arrow_left.pdf b/submodules/TelegramUI/Images.xcassets/Media Editor/Next.imageset/arrow_left.pdf deleted file mode 100644 index 7b20434672a16424c35740a555a96dd0fa6ae0d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4081 zcmai%c|25Y8^~DvoFP1BB3l{BwK?*_Uwux8nP!I zW0wkvv1DH!yS$_2eR`hf{e0f@IiGXxbKTdu&VBCR?~m^VHc;1;fJ(xE;6~~)btZS^ zNoQj-5DtQX+%S&73l~6AdU#hmf;|X93mJi=G##7?cz4>}8BM^eSBtU^BOi~x=BVmq zTC#T%lk7H>MhKG)+3-}o{JM54l?p4`QTAr@$Irj*ufnX1zcY0(y-ta?dGFQil zJO{qqwGy1F8kGB^?xk`ITJ+hQPd%Yq$|uhlav4u%t{nF{!}l=fCCVqSp*^^8x#<81 zHwcTqaJjJ@Kw7*SR*=z75bGd3c8%0B>Eyu1rT}%=R){9+fiKsvk!^Ebeu+Exi#0F~ zsXZh|r$b6b*-eG!=U#Nyk|~Ar5|6=)UYx-5*jIJ^_3OBGO=nie#lX2*t&&w$iqcd{ zhf!Qd-9=c6PP}437wn`DfZW|fk)QZ6seVn|%=o+P4ZCmY zK>6Y)C`qj^3x~{ZO;@UPyH)MbmuUrV0`ac6pW8|s?W?gb!Tucvlir`)A4>lw)a9Gk z@gOM!wB7fsJKmK5g8dN92=C$M>5j#FfMk9I)ZAPNw0RHEzR*Vd0{*PGKmSX319vy9 zF`fXjpcPfu1X+Qk)ZCoi+>NiJv3StFGt@kwAlaV84!sGj*0nj+3mDDZtrSCk?Toq^V?VCb9j}4&S-PP@EX*d z+#Fr?_I5UQ5(T%r&bc@C2I=reYlHaXv^Ts~-aoKy?(D{cT4Jx&n-nuE!shwdIa#iwW@Sm~ZgaY5zytXfjDe;Jw_Wd@w zb&Az8!gW+nMbLO+jV&7I4$ctg%Nz-Iu46dzfQj$~2oI8kFo<6}BoGL@&NvLB3l7xc zWPHddsm0)+40x`h_?`}^0)0>Kb=UDaU2l+YC*aZ@nd=8F0axD~y3WcLh*AcI*6Bj% zhl3oL?<{h1XQ<>q;K|}hQtjtFZq9sDO~pWti?K;9`~hdI@@A*-(_`oE=BKL}D6F0# z@K-Tg-Q9Y^)Cqzg+@<#qKAgzVgFf`0?q%R$sk{I)s=n>Bx)1kbCf+w6M|CQB6xl24 zc}I^g(us%I)WRX~on@t@=fKNYS~7Ip1Ss$<|kIAnCp|({dN4MP;oHQ3TdphD6SyJ zDPe}>2iA`tAbFE)B-2D}P6z0G%h{=9FsxIqGY-luy^^juT3)XZqOFmfk$|~}Nyda@ z?tVcu8G>KO2E|q!(a%$!F6{|+@D%M> z?Wop^#_xNFFJG&olvCK8ChUdb1&HdFjM7hLW`&ylPmm24-rmZ*`Djy&RUy(eGKFV| z$3}ck+*o2p{FIrf*#T3R@~@>^4Idhlovg6_EuoHM*s_kCp@|noc@iUDwR8UR$yTL9 zJk^(~DQoO&g4@E|P$q>ipD;4}`g?-c+3H~bzQZ$9qm>;?#4#ojKapoW+PNbsW*wp( zhdMG@*?1RudnJy!cikAR6mXA`#7Poue6FYWntV(cGoRUNzc>&z>DB1cIP_j&aJO<> zj6;_r1{K*7pBp#HPhqvbM=H4*bG4)*b{(_exR9%OUNKxTO7UooS51Mx)CPK&Vb^PG zXk}n=a%*5)A0P)PWSIhH0W<+um=sy1S&kg=1T@sq*Hzy+CDm;sWZBEZpt_^x6>@99}3qd$ZES=eCbGKmwzouGUzs75%pREh;jKN0-N2vlZ2((TBRI;io~; z&?T9Qo0W}g63vOA90i_J$GQE4yxjBFyN7hc#%8|&$L1wrmJiEjKe)f3=Cx6ZAK28H z-8gsh_^A^koO|I%>+`63#j7^2ys3bJxazR&*vV$G$*3i(4g5|FfE4^ZxJ#YXXcdmt zF4pGO9%Il~dGXovxy^FWcwYR2_)Jk)URT~94#DLcmMR}*cT0Rccj<^K$&_^*gF)5zp5_lc(cHO|K zNX4ZI{b{tYfijcvwW`)Gz$QR_PHQQKf(k)px70^__R04S$Uc>gm#L46ZGJX(qs49a z^Pg&dM+>G6V41Hr0CK01OJ7;h#S(vYWQ{veP`mJiY%@fB4HR83#W- zzme&{o|F?ECnS^}?cG`>t|cXxy|dG$#B^{!euPZ(zP+s49I_M{&Kq|ADnYTTe7rlQ zxU_h2dfD#7 zv7-Am2mB|uthS|w*Q)%f_xDncGe#cVnwPBc+q3?pFzr9Q+r^Z}VXf5eE4OREi=7?I z5CQ6+OU+j*+U?sIu+1Z{&ORBqMk%{z7adxuCbgHaS)$)?fRaZM(=6BgiYh`;h?~SA z-)E~$i)}@XGeT`FWp0zeBSO^uGYT z&#(J28suuLsi~qp@Ho&uz#4(9elhVrME_#qzZlyCBy|ywb3m)Q`G71Sv=k`ql=%+H zo;31?f~0gEa2_;x-bZv=wch}lmif<$YG?x5+0E_;zI*)S_P?+k3jO)U*^6i_NZ%M_ zO8cB76ecMHGW0~d6MR85LjMl+J_ONU>xTYn4$i8dmxe%*5GV`^g+q`~ggFEvLOcJ; z{B1on>c@FvX)WIOZ_+wGHCks!aBy=~-$(ZEHEF?%Zn)o{|5~Y!JKh#Z+X)yD^4|j_ zgFwI#AY0JS7z`mtlVbk>x∇BcB0e~-anGPJ$_dkg}Rrv27GVsL4iWdDdEr2j{~ z|CNWNwN-zA7Y2j;s~!vvr}awz$b%#Q9Yg&79|U)_gEQX!dmnA=;7i*-S_5R{=0@|! zz6WR1 XZXN`hHNFc1laYY~!C(!2P2hh33O4!5 diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/Next.imageset/ic_next.pdf b/submodules/TelegramUI/Images.xcassets/Media Editor/Next.imageset/ic_next.pdf new file mode 100644 index 0000000000..58537192a4 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Media Editor/Next.imageset/ic_next.pdf @@ -0,0 +1,92 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +0.000000 -1.000000 1.000000 0.000000 -6.195312 14.000000 cm +1.000000 1.000000 1.000000 scn +-0.707107 8.902419 m +-1.097631 8.511895 -1.097631 7.878730 -0.707107 7.488206 c +-0.316583 7.097682 0.316583 7.097682 0.707107 7.488206 c +-0.707107 8.902419 l +h +6.000000 14.195312 m +6.707107 14.902419 l +6.316583 15.292944 5.683417 15.292944 5.292893 14.902419 c +6.000000 14.195312 l +h +11.292893 7.488206 m +11.683417 7.097682 12.316583 7.097682 12.707107 7.488206 c +13.097631 7.878730 13.097631 8.511895 12.707107 8.902419 c +11.292893 7.488206 l +h +0.707107 7.488206 m +6.707107 13.488206 l +5.292893 14.902419 l +-0.707107 8.902419 l +0.707107 7.488206 l +h +5.292893 13.488206 m +11.292893 7.488206 l +12.707107 8.902419 l +6.707107 14.902419 l +5.292893 13.488206 l +h +f +n +Q + +endstream +endobj + +3 0 obj + 785 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 10.000000 16.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000000875 00000 n +0000000897 00000 n +0000001070 00000 n +0000001144 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1203 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Sources/TelegramRootController.swift b/submodules/TelegramUI/Sources/TelegramRootController.swift index b5d2010422..4e10eb5517 100644 --- a/submodules/TelegramUI/Sources/TelegramRootController.swift +++ b/submodules/TelegramUI/Sources/TelegramRootController.swift @@ -354,7 +354,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon } else { return nil } - }, completion: { [weak self] randomId, mediaResult, caption, privacy, commit in + }, completion: { [weak self] randomId, mediaResult, caption, privacy, stickers, commit in guard let self, let mediaResult else { dismissCameraImpl?() commit({}) @@ -373,7 +373,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon case let .image(image, dimensions): if let imageData = compressImageToJPEG(image, quality: 0.7) { let entities = generateChatInputTextEntities(caption) - self.context.engine.messages.uploadStory(media: .image(dimensions: dimensions, data: imageData), text: caption.string, entities: entities, pin: privacy.pin, privacy: privacy.privacy, isForwardingDisabled: privacy.isForwardingDisabled, period: privacy.timeout, randomId: randomId) + self.context.engine.messages.uploadStory(media: .image(dimensions: dimensions, data: imageData, stickers: stickers), text: caption.string, entities: entities, pin: privacy.pin, privacy: privacy.privacy, isForwardingDisabled: privacy.isForwardingDisabled, period: privacy.timeout, randomId: randomId) Queue.mainQueue().justDispatch { commit({}) } @@ -396,7 +396,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon } let imageData = firstFrameImage.flatMap { compressImageToJPEG($0, quality: 0.6) } let entities = generateChatInputTextEntities(caption) - self.context.engine.messages.uploadStory(media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameImageData: imageData), text: caption.string, entities: entities, pin: privacy.pin, privacy: privacy.privacy, isForwardingDisabled: privacy.isForwardingDisabled, period: privacy.timeout, randomId: randomId) + self.context.engine.messages.uploadStory(media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameImageData: imageData, stickers: stickers), text: caption.string, entities: entities, pin: privacy.pin, privacy: privacy.privacy, isForwardingDisabled: privacy.isForwardingDisabled, period: privacy.timeout, randomId: randomId) Queue.mainQueue().justDispatch { commit({}) }