From 8ac9121c5ed5e406d2634062818df46b0c6a840e Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 27 Jun 2023 20:53:24 +0200 Subject: [PATCH] Stories improvements --- .../Sources/CalendarMessageScreen.swift | 2 +- submodules/Camera/Sources/VideoRecorder.swift | 12 +- .../Sources/ChatListController.swift | 16 +- .../Sources/ContactContextMenus.swift | 28 +- .../Sources/LocationViewControllerNode.swift | 2 +- .../QrCodeUI/Sources/QrCodeScanScreen.swift | 2 +- .../Themes/ThemePreviewController.swift | 2 +- .../Sources/Themes/WallpaperGalleryItem.swift | 2 +- .../MediaNavigationAccessoryHeaderNode.swift | 2 +- .../Sources/CallControllerNode.swift | 2 +- .../Sources/VoiceChatController.swift | 6 +- .../CameraScreen/Sources/CameraScreen.swift | 13 +- .../MediaEditor/Sources/MediaEditor.swift | 7 + .../Sources/MediaEditorValues.swift | 34 ++- .../Sources/MediaEditorVideoExport.swift | 10 +- .../Sources/MediaEditorScreen.swift | 46 +-- .../Sources/ShareWithPeersScreen.swift | 6 +- .../StoryItemSetContainerComponent.swift | 5 +- ...StoryItemSetContainerViewSendMessage.swift | 46 +-- .../Sources/ChatBotStartInputPanelNode.swift | 2 +- .../TelegramUI/Sources/ChatController.swift | 12 +- .../Sources/ChatTextInputPanelNode.swift | 2 +- .../TelegramUI/Sources/ChatThemeScreen.swift | 2 +- .../Sources/PeerInfo/PeerInfoScreen.swift | 2 +- submodules/TooltipUI/BUILD | 4 + .../TooltipUI/Sources/TooltipScreen.swift | 275 +++++++++++++++--- .../Sources/UndoOverlayController.swift | 2 +- .../Sources/UndoOverlayControllerNode.swift | 6 +- 28 files changed, 402 insertions(+), 148 deletions(-) diff --git a/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift b/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift index d07300b31f..49c401f87d 100644 --- a/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift +++ b/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift @@ -1369,7 +1369,7 @@ public final class CalendarMessageScreen: ViewController { if self.selectionState?.dayRange == nil { if let selectionToolbarNode = self.selectionToolbarNode { let toolbarFrame = selectionToolbarNode.view.convert(selectionToolbarNode.bounds, to: self.view) - self.controller?.present(TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: self.presentationData.strings.MessageCalendar_EmptySelectionTooltip, style: .default, icon: .none, location: .point(toolbarFrame.insetBy(dx: 0.0, dy: 10.0), .bottom), shouldDismissOnTouch: { point in + self.controller?.present(TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: self.presentationData.strings.MessageCalendar_EmptySelectionTooltip), style: .default, icon: .none, location: .point(toolbarFrame.insetBy(dx: 0.0, dy: 10.0), .bottom), shouldDismissOnTouch: { point in return .dismiss(consume: false) }), in: .current) } diff --git a/submodules/Camera/Sources/VideoRecorder.swift b/submodules/Camera/Sources/VideoRecorder.swift index 2d4247f953..6ddd1094c6 100644 --- a/submodules/Camera/Sources/VideoRecorder.swift +++ b/submodules/Camera/Sources/VideoRecorder.swift @@ -165,11 +165,13 @@ private final class VideoRecorderImpl { if !self.savedTransitionImage, let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) { self.savedTransitionImage = true - let ciImage = CIImage(cvPixelBuffer: pixelBuffer) - if let cgImage = self.imageContext.createCGImage(ciImage, from: ciImage.extent) { - self.transitionImage = UIImage(cgImage: cgImage, scale: 1.0, orientation: .right) - } else { - self.savedTransitionImage = false + Queue.concurrentDefaultQueue().async { + let ciImage = CIImage(cvPixelBuffer: pixelBuffer) + if let cgImage = self.imageContext.createCGImage(ciImage, from: ciImage.extent) { + self.transitionImage = UIImage(cgImage: cgImage, scale: 1.0, orientation: .right) + } else { + self.savedTransitionImage = false + } } } diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index d4092a9efe..9e2eaf2500 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -2356,7 +2356,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 8.0), size: CGSize()) - parentController.present(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: text, icon: .chatListPress, location: .point(location, .bottom), shouldDismissOnTouch: { point in + parentController.present(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: .plain(text: text), icon: .animation(name: "ChatListFoldersTooltip", delay: 0.6), location: .point(location, .bottom), shouldDismissOnTouch: { point in guard let strongSelf = self, let parentController = strongSelf.parent as? TabBarController else { return .dismiss(consume: false) } @@ -2697,11 +2697,21 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController return } - let location = CGRect(origin: CGPoint(x: sourceFrame.midX, y: sourceFrame.minY - 8.0), size: CGSize()) + let location = CGRect(origin: CGPoint(x: sourceFrame.midX, y: sourceFrame.minY + 1.0), size: CGSize()) let tooltipController = TooltipScreen( + context: self.context, account: self.context.account, sharedContext: self.context.sharedContext, - text: "Stories from \(peer.compactDisplayTitle) will now be shown in Contacts, not Chats.", + text: .markdown(text: "Stories from **\(peer.compactDisplayTitle)** will now be shown in Contacts, not Chats."), + icon: .peer(peer: peer, isStory: true), + action: TooltipScreen.Action( + title: "Undo", + action: { [weak self] in + if let self { + self.context.engine.peers.updatePeerStoriesHidden(id: peer.id, isHidden: false) + } + } + ), location: .point(location, .bottom), shouldDismissOnTouch: { _ in return .dismiss(consume: false) } ) diff --git a/submodules/ContactListUI/Sources/ContactContextMenus.swift b/submodules/ContactListUI/Sources/ContactContextMenus.swift index 404daf4337..982d46e5ef 100644 --- a/submodules/ContactListUI/Sources/ContactContextMenus.swift +++ b/submodules/ContactListUI/Sources/ContactContextMenus.swift @@ -96,15 +96,25 @@ func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, con return } - let location = CGRect(origin: CGPoint(x: sourceFrame.midX, y: sourceFrame.minY - 8.0), size: CGSize()) - let tooltipController = TooltipScreen( - account: context.account, - sharedContext: context.sharedContext, - text: "Stories from \(peer?.compactDisplayTitle ?? "") will now be shown in Chats, not Contacts.", - location: .point(location, .bottom), - shouldDismissOnTouch: { _ in return .dismiss(consume: false) } - ) - contactsController?.present(tooltipController, in: .window(.root)) + if let peer { + let location = CGRect(origin: CGPoint(x: sourceFrame.midX, y: sourceFrame.minY + 1.0), size: CGSize()) + let tooltipController = TooltipScreen( + context: context, + account: context.account, + sharedContext: context.sharedContext, + text: .markdown(text: "Stories from \(peer.compactDisplayTitle) will now be shown in Chats, not Contacts."), + icon: .peer(peer: peer, isStory: true), + action: TooltipScreen.Action( + title: "Undo", + action: { + context.engine.peers.updatePeerStoriesHidden(id: peer.id, isHidden: true) + } + ), + location: .point(location, .bottom), + shouldDismissOnTouch: { _ in return .dismiss(consume: false) } + ) + contactsController?.present(tooltipController, in: .window(.root)) + } }))) return items diff --git a/submodules/LocationUI/Sources/LocationViewControllerNode.swift b/submodules/LocationUI/Sources/LocationViewControllerNode.swift index f37d035755..644e7a7e20 100644 --- a/submodules/LocationUI/Sources/LocationViewControllerNode.swift +++ b/submodules/LocationUI/Sources/LocationViewControllerNode.swift @@ -828,7 +828,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan text = strongSelf.presentationData.strings.Location_ProximityTip(EnginePeer(peer).compactDisplayTitle).string } - strongSelf.interaction.present(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: text, icon: nil, location: .point(location.offsetBy(dx: -9.0, dy: 0.0), .right), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in + strongSelf.interaction.present(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: .plain(text: text), icon: nil, location: .point(location.offsetBy(dx: -9.0, dy: 0.0), .right), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in return .dismiss(consume: false) })) }) diff --git a/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift b/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift index 00de6f7e9f..9cfd90de3f 100644 --- a/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift +++ b/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift @@ -179,7 +179,7 @@ public final class QrCodeScanScreen: ViewController { return } if let navigationController = navigationController as? NavigationController { - self.present(UndoOverlayController(presentationData: self.presentationData, content: .actionSucceeded(title: self.presentationData.strings.AuthSessions_AddedDeviceTitle, text: session?.appName ?? "Telegram for macOS", cancel: self.presentationData.strings.AuthSessions_AddedDeviceTerminate), elevatedLayout: false, animateInAsReplacement: false, action: { value in + self.present(UndoOverlayController(presentationData: self.presentationData, content: .actionSucceeded(title: self.presentationData.strings.AuthSessions_AddedDeviceTitle, text: session?.appName ?? "Telegram for macOS", cancel: self.presentationData.strings.AuthSessions_AddedDeviceTerminate, destructive: true), elevatedLayout: false, animateInAsReplacement: false, action: { value in if value == .undo, let session = session { let _ = activeSessionsContext.remove(hash: session.hash).start() return true diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift index 49426bc211..102f847329 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift @@ -456,7 +456,7 @@ public final class ThemePreviewController: ViewController { guard let strongSelf = self, !displayed else { return } - strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .actionSucceeded(title: strongSelf.presentationData.strings.Theme_ThemeChanged, text: strongSelf.presentationData.strings.Theme_ThemeChangedText, cancel: strongSelf.presentationData.strings.Undo_Undo), elevatedLayout: true, animateInAsReplacement: false, action: { value in + strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .actionSucceeded(title: strongSelf.presentationData.strings.Theme_ThemeChanged, text: strongSelf.presentationData.strings.Theme_ThemeChangedText, cancel: strongSelf.presentationData.strings.Undo_Undo, destructive: false), elevatedLayout: true, animateInAsReplacement: false, action: { value in if value == .undo { Queue.mainQueue().after(0.2) { let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current -> PresentationThemeSettings in diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift index f2a602ebe7..69316fb000 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift @@ -1729,7 +1729,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { if let strongSelf = self, (count < 2 && currentTimestamp > timestamp + 24 * 60 * 60) { strongSelf.displayedPreviewTooltip = true - let controller = TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: isDark ? strongSelf.presentationData.strings.WallpaperPreview_PreviewInDayMode : strongSelf.presentationData.strings.WallpaperPreview_PreviewInNightMode, style: .customBlur(UIColor(rgb: 0x333333, alpha: 0.35)), icon: nil, location: .point(frame.offsetBy(dx: 1.0, dy: 6.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in + let controller = TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: .plain(text: isDark ? strongSelf.presentationData.strings.WallpaperPreview_PreviewInDayMode : strongSelf.presentationData.strings.WallpaperPreview_PreviewInNightMode), style: .customBlur(UIColor(rgb: 0x333333, alpha: 0.35)), icon: nil, location: .point(frame.offsetBy(dx: 1.0, dy: 6.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in return .dismiss(consume: false) }) strongSelf.galleryController()?.present(controller, in: .current) diff --git a/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift b/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift index 542aff1a1d..9a3a2b2582 100644 --- a/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift +++ b/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift @@ -529,7 +529,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi let _ = (ApplicationSpecificNotice.incrementAudioRateOptionsTip(accountManager: self.context.sharedContext.accountManager) |> deliverOnMainQueue).start(next: { [weak self] value in if let strongSelf = self, let controller = strongSelf.getController?(), value == 2 { - let tooltipController = TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: strongSelf.strings.Conversation_AudioRateOptionsTooltip, style: .default, icon: nil, location: .point(frame.offsetBy(dx: 0.0, dy: 4.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in + let tooltipController = TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: .plain(text: strongSelf.strings.Conversation_AudioRateOptionsTooltip), style: .default, icon: nil, location: .point(frame.offsetBy(dx: 0.0, dy: 4.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in return .dismiss(consume: false) }) controller.present(tooltipController, in: .window(.root)) diff --git a/submodules/TelegramCallsUI/Sources/CallControllerNode.swift b/submodules/TelegramCallsUI/Sources/CallControllerNode.swift index 25cc58eee2..2cca746b90 100644 --- a/submodules/TelegramCallsUI/Sources/CallControllerNode.swift +++ b/submodules/TelegramCallsUI/Sources/CallControllerNode.swift @@ -761,7 +761,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro return } - self.present?(TooltipScreen(account: self.account, sharedContext: self.sharedContext, text: self.presentationData.strings.Call_CameraOrScreenTooltip, style: .light, icon: nil, location: .point(location.offsetBy(dx: 0.0, dy: -14.0), .bottom), displayDuration: .custom(5.0), shouldDismissOnTouch: { _ in + self.present?(TooltipScreen(account: self.account, sharedContext: self.sharedContext, text: .plain(text: self.presentationData.strings.Call_CameraOrScreenTooltip), style: .light, icon: nil, location: .point(location.offsetBy(dx: 0.0, dy: -14.0), .bottom), displayDuration: .custom(5.0), shouldDismissOnTouch: { _ in return .dismiss(consume: false) })) } diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index f988c587e7..648b7580c2 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -2308,7 +2308,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController } else { text = presentationData.strings.VoiceChat_RecordingInProgress } - strongSelf.controller?.present(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: text, icon: nil, location: .point(location.offsetBy(dx: 1.0, dy: 0.0), .top), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in + strongSelf.controller?.present(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: .plain(text: text), icon: nil, location: .point(location.offsetBy(dx: 1.0, dy: 0.0), .top), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in return .dismiss(consume: true) }), in: .window(.root)) } @@ -3505,7 +3505,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController if !callState.subscribedToScheduled { let location = self.actionButton.view.convert(self.actionButton.bounds, to: self.view).center let point = CGRect(origin: CGPoint(x: location.x - 5.0, y: location.y - 5.0 - 68.0), size: CGSize(width: 10.0, height: 10.0)) - self.controller?.present(TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: self.presentationData.strings.VoiceChat_ReminderNotify, style: .gradient(UIColor(rgb: 0x262c5a), UIColor(rgb: 0x5d2835)), icon: nil, location: .point(point, .bottom), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in + self.controller?.present(TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: self.presentationData.strings.VoiceChat_ReminderNotify), style: .gradient(UIColor(rgb: 0x262c5a), UIColor(rgb: 0x5d2835)), icon: nil, location: .point(point, .bottom), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in return .dismiss(consume: false) }), in: .window(.root)) } @@ -6409,7 +6409,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController point.origin.y += 32.0 } } - self.controller?.present(TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: self.presentationData.strings.VoiceChat_UnmuteSuggestion, style: .gradient(UIColor(rgb: 0x1d446c), UIColor(rgb: 0x193e63)), icon: nil, location: .point(point, position), displayDuration: .custom(8.0), shouldDismissOnTouch: { _ in + self.controller?.present(TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: self.presentationData.strings.VoiceChat_UnmuteSuggestion), style: .gradient(UIColor(rgb: 0x1d446c), UIColor(rgb: 0x193e63)), icon: nil, location: .point(point, position), displayDuration: .custom(8.0), shouldDismissOnTouch: { _ in return .dismiss(consume: false) }), in: .window(.root)) } diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift index cbf01cc0c6..b39bb113e0 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift @@ -1589,7 +1589,7 @@ public class CameraScreen: ViewController { 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 controller = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: "Draft Saved", location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _ in + 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 }) self.controller?.present(controller, in: .current) @@ -1604,7 +1604,7 @@ public class CameraScreen: ViewController { 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.maxY + 3.0), size: CGSize()) - let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: "Enable Dual Camera Mode", location: .point(location, .top), displayDuration: .manual(false), inset: 16.0, shouldDismissOnTouch: { _ in + let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: "Enable Dual Camera Mode"), location: .point(location, .top), displayDuration: .manual(false), inset: 16.0, shouldDismissOnTouch: { _ in return .ignore }) self.controller?.present(tooltipController, in: .current) @@ -1617,9 +1617,9 @@ public class CameraScreen: ViewController { let parentFrame = self.view.convert(self.bounds, to: nil) let absoluteFrame = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0) - let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 3.0), size: CGSize()) + let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 1.0), size: CGSize()) - let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: "Take photos or videos to share with all your contacts or close friends at once.", location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _ in + let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: "Take photos or videos to share with all\nyour contacts or close friends at once."), textAlignment: .center, location: .point(location, .bottom), displayDuration: .custom(3.0), inset: 16.0, shouldDismissOnTouch: { _ in return .ignore }) self.controller?.present(tooltipController, in: .current) @@ -2106,6 +2106,11 @@ public class CameraScreen: ViewController { if let layout = self.validLayout, case .regular = layout.metrics.widthClass { return } + + if self.node.hasAppeared { + self.dismissAllTooltips() + } + let transitionFraction = max(0.0, min(1.0, transitionFraction)) let offsetX = floorToScreenPixels((1.0 - transitionFraction) * self.node.frame.width * -1.0) transition.updateTransform(layer: self.node.backgroundView.layer, transform: CGAffineTransform(translationX: offsetX, y: 0.0)) diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift index 568cd23ef8..808f100f2c 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift @@ -265,6 +265,7 @@ public final class MediaEditor { videoTrimRange: nil, videoIsMuted: false, videoIsFullHd: false, + videoIsMirrored: false, additionalVideoPath: nil, additionalVideoPosition: nil, additionalVideoScale: nil, @@ -582,6 +583,12 @@ public final class MediaEditor { } } + public func setVideoIsMirrored(_ videoIsMirrored: Bool) { + self.updateValues(mode: .skipRendering) { values in + return values.withUpdatedVideoIsMirrored(videoIsMirrored) + } + } + public func setVideoIsFullHd(_ videoIsFullHd: Bool) { self.updateValues(mode: .skipRendering) { values in return values.withUpdatedVideoIsFullHd(videoIsFullHd) diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift index e7c7c13e71..c1ec03817d 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift @@ -84,6 +84,9 @@ public final class MediaEditorValues: Codable, Equatable { if lhs.videoIsFullHd != rhs.videoIsFullHd { return false } + if lhs.videoIsMirrored != rhs.videoIsMirrored { + return false + } if lhs.additionalVideoPath != rhs.additionalVideoPath { return false } @@ -143,6 +146,7 @@ public final class MediaEditorValues: Codable, Equatable { case videoTrimRange case videoIsMuted case videoIsFullHd + case videoIsMirrored case additionalVideoPath case additionalVideoPosition @@ -167,6 +171,7 @@ public final class MediaEditorValues: Codable, Equatable { public let videoTrimRange: Range? public let videoIsMuted: Bool public let videoIsFullHd: Bool + public let videoIsMirrored: Bool public let additionalVideoPath: String? public let additionalVideoPosition: CGPoint? @@ -189,6 +194,7 @@ public final class MediaEditorValues: Codable, Equatable { videoTrimRange: Range?, videoIsMuted: Bool, videoIsFullHd: Bool, + videoIsMirrored: Bool, additionalVideoPath: String?, additionalVideoPosition: CGPoint?, additionalVideoScale: CGFloat?, @@ -208,6 +214,7 @@ public final class MediaEditorValues: Codable, Equatable { self.videoTrimRange = videoTrimRange self.videoIsMuted = videoIsMuted self.videoIsFullHd = videoIsFullHd + self.videoIsMirrored = videoIsMirrored self.additionalVideoPath = additionalVideoPath self.additionalVideoPosition = additionalVideoPosition self.additionalVideoScale = additionalVideoScale @@ -240,6 +247,7 @@ public final class MediaEditorValues: Codable, Equatable { self.videoTrimRange = try container.decodeIfPresent(Range.self, forKey: .videoTrimRange) self.videoIsMuted = try container.decode(Bool.self, forKey: .videoIsMuted) self.videoIsFullHd = try container.decodeIfPresent(Bool.self, forKey: .videoIsFullHd) ?? false + self.videoIsMirrored = try container.decodeIfPresent(Bool.self, forKey: .videoIsMirrored) ?? false self.additionalVideoPath = try container.decodeIfPresent(String.self, forKey: .additionalVideoPath) self.additionalVideoPosition = try container.decodeIfPresent(CGPoint.self, forKey: .additionalVideoPosition) @@ -283,6 +291,7 @@ public final class MediaEditorValues: Codable, Equatable { try container.encodeIfPresent(self.videoTrimRange, forKey: .videoTrimRange) try container.encode(self.videoIsMuted, forKey: .videoIsMuted) try container.encode(self.videoIsFullHd, forKey: .videoIsFullHd) + try container.encode(self.videoIsMirrored, forKey: .videoIsMirrored) try container.encodeIfPresent(self.additionalVideoPath, forKey: .additionalVideoPath) try container.encodeIfPresent(self.additionalVideoPosition, forKey: .additionalVideoPosition) @@ -306,43 +315,48 @@ public final class MediaEditorValues: Codable, Equatable { } public func makeCopy() -> MediaEditorValues { - return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues) + return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues) } func withUpdatedCrop(offset: CGPoint, scale: CGFloat, rotation: CGFloat, mirroring: Bool) -> MediaEditorValues { - return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: offset, cropSize: self.cropSize, cropScale: scale, cropRotation: rotation, cropMirroring: mirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues) + return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: offset, cropSize: self.cropSize, cropScale: scale, cropRotation: rotation, cropMirroring: mirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues) } func withUpdatedGradientColors(gradientColors: [UIColor]) -> MediaEditorValues { - return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues) + return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues) } func withUpdatedVideoIsMuted(_ videoIsMuted: Bool) -> MediaEditorValues { - return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: videoIsMuted, videoIsFullHd: self.videoIsFullHd, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues) + return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues) } func withUpdatedVideoIsFullHd(_ videoIsFullHd: Bool) -> MediaEditorValues { - return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: videoIsFullHd, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues) + return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues) + } + + + func withUpdatedVideoIsMirrored(_ videoIsMirrored: Bool) -> MediaEditorValues { + return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues) } func withUpdatedAdditionalVideo(path: String, positionChanges: [VideoPositionChange]) -> MediaEditorValues { - return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, additionalVideoPath: path, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: positionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues) + return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: path, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: positionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues) } func withUpdatedAdditionalVideo(position: CGPoint, scale: CGFloat, rotation: CGFloat) -> MediaEditorValues { - return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: position, additionalVideoScale: scale, additionalVideoRotation: rotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues) + return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: position, additionalVideoScale: scale, additionalVideoRotation: rotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues) } func withUpdatedVideoTrimRange(_ videoTrimRange: Range) -> MediaEditorValues { - return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues) + return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues) } func withUpdatedDrawingAndEntities(drawing: UIImage?, entities: [CodableDrawingEntity]) -> MediaEditorValues { - return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: drawing, entities: entities, toolValues: self.toolValues) + return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: drawing, entities: entities, toolValues: self.toolValues) } func withUpdatedToolValues(_ toolValues: [EditorToolKey: Any]) -> MediaEditorValues { - return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: toolValues) + return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, additionalVideoPath: self.additionalVideoPath, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, drawing: self.drawing, entities: self.entities, toolValues: toolValues) } public var resultDimensions: PixelDimensions { diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoExport.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoExport.swift index 275bc97794..56318cd4dc 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoExport.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoExport.swift @@ -345,11 +345,17 @@ public final class MediaEditorVideoExport { private func setupWithAsset(_ asset: AVAsset, additionalAsset: AVAsset?) { self.reader = try? AVAssetReader(asset: asset) - self.textureRotation = textureRotatonForAVAsset(asset) + + var mirror = false + if additionalAsset == nil, self.configuration.values.videoIsMirrored { + mirror = true + } + + self.textureRotation = textureRotatonForAVAsset(asset, mirror: mirror) if let additionalAsset { self.additionalReader = try? AVAssetReader(asset: additionalAsset) - self.additionalTextureRotation = textureRotatonForAVAsset(additionalAsset) + self.additionalTextureRotation = textureRotatonForAVAsset(additionalAsset, mirror: true) } guard let reader = self.reader else { return diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index edda7b3d13..ec9e7cffb7 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -696,7 +696,9 @@ final class MediaEditorScreenComponent: Component { guard let controller = environment.controller() as? MediaEditorScreen else { return } - controller.requestCompletion(animated: true) + controller.openPrivacySettings(completion: { [weak controller] in + controller?.requestCompletion(animated: true) + }) } )), environment: {}, @@ -1142,7 +1144,7 @@ final class MediaEditorScreenComponent: Component { } if let privacyButtonView = self.privacyButton.view { if privacyButtonView.superview == nil { - self.addSubview(privacyButtonView) + //self.addSubview(privacyButtonView) } transition.setPosition(view: privacyButtonView, position: privacyButtonFrame.center) transition.setBounds(view: privacyButtonView, bounds: CGRect(origin: .zero, size: privacyButtonFrame.size)) @@ -1841,19 +1843,22 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate imageEntity.scale = 1.49 imageEntity.position = position.getPosition(storyDimensions) self.entitiesView.add(imageEntity, announce: false) - } else if case let .video(_, _, _, additionalVideoPath, _, _, _, changes, position) = subject, let additionalVideoPath { - let videoEntity = DrawingStickerEntity(content: .dualVideoReference) - videoEntity.referenceDrawingSize = storyDimensions - videoEntity.scale = 1.49 - videoEntity.position = position.getPosition(storyDimensions) - self.entitiesView.add(videoEntity, announce: false) - - mediaEditor.setAdditionalVideo(additionalVideoPath, positionChanges: changes.map { VideoPositionChange(additional: $0.0, timestamp: $0.1) }) - 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) + } else if case let .video(_, _, mirror, additionalVideoPath, _, _, _, changes, position) = subject { + mediaEditor.setVideoIsMirrored(mirror) + if let additionalVideoPath { + let videoEntity = DrawingStickerEntity(content: .dualVideoReference) + videoEntity.referenceDrawingSize = storyDimensions + videoEntity.scale = 1.49 + videoEntity.position = position.getPosition(storyDimensions) + self.entitiesView.add(videoEntity, announce: false) + + mediaEditor.setAdditionalVideo(additionalVideoPath, positionChanges: changes.map { VideoPositionChange(additional: $0.0, timestamp: $0.1) }) + 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) + } } } } @@ -2504,7 +2509,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate 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.maxY + 3.0), size: CGSize()) - let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: "You can set who can view this story.", location: .point(location, .top), displayDuration: .manual(false), inset: 16.0, shouldDismissOnTouch: { _ in + let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: "You can set who can view this story."), location: .point(location, .top), displayDuration: .manual(false), inset: 16.0, shouldDismissOnTouch: { _ in return .ignore }) self.controller?.present(tooltipController, in: .current) @@ -2527,7 +2532,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate 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.maxY + 3.0), size: CGSize()) - let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: isMuted ? "The story will have no sound." : "The story will have sound." , location: .point(location, .top), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _ in + let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: isMuted ? "The story will have no sound." : "The story will have sound."), location: .point(location, .top), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _ in return .ignore }) self.muteTooltip = tooltipController @@ -2642,7 +2647,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate text = "Story will disappear in 24 hours." } - let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: text, location: .point(location, .bottom), displayDuration: .default, inset: 7.0, cornerRadius: 9.0, shouldDismissOnTouch: { _ in + let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: text), location: .point(location, .bottom), displayDuration: .default, inset: 7.0, cornerRadius: 9.0, shouldDismissOnTouch: { _ in return .ignore }) self.storyArchiveTooltip = tooltipController @@ -3133,7 +3138,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate self.displayNode.view.addInteraction(dropInteraction) } - func openPrivacySettings(_ privacy: MediaEditorResultPrivacy? = nil) { + func openPrivacySettings(_ privacy: MediaEditorResultPrivacy? = nil, completion: @escaping () -> Void = {}) { self.hapticFeedback.impact(.light) let privacy = privacy ?? self.state.privacy @@ -3157,6 +3162,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate return } self.state.privacy = MediaEditorResultPrivacy(privacy: privacy, timeout: timeout, archive: archive) + completion() }, editCategory: { [weak self] privacy in guard let self else { @@ -3166,7 +3172,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate guard let self else { return } - self.openPrivacySettings(MediaEditorResultPrivacy(privacy: privacy, timeout: timeout, archive: archive)) + self.openPrivacySettings(MediaEditorResultPrivacy(privacy: privacy, timeout: timeout, archive: archive), completion: completion) }) } ) diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift index 5df7cb1bb2..62e3f89687 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift @@ -458,8 +458,7 @@ final class ShareWithPeersScreenComponent: Component { let sectionTitle: String if section.id == 0 { - let hours = component.timeout / 3600 - sectionTitle = "WHO CAN VIEW FOR \(hours) HOURS" + sectionTitle = "WHO CAN VIEW" } else { if case .chats = component.stateContext.subject { sectionTitle = "CHATS" @@ -1063,10 +1062,12 @@ final class ShareWithPeersScreenComponent: Component { } navigationButtonsWidth += navigationLeftButtonSize.width + navigationSideInset + var actionButtonTitle = "Save Settings" let title: String switch component.stateContext.subject { case .stories: title = "Share Story" + actionButtonTitle = "Post Story" case .chats: title = "Send as a Message" case let .contacts(category): @@ -1125,7 +1126,6 @@ final class ShareWithPeersScreenComponent: Component { transition.setFrame(layer: self.navigationSeparatorLayer, frame: CGRect(origin: CGPoint(x: containerSideInset, y: navigationHeight), size: CGSize(width: containerWidth, height: UIScreenPixel))) - let actionButtonTitle: String = "Save Settings" let actionButtonSize = self.actionButton.update( transition: transition, component: AnyComponent(ButtonComponent( diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index f3729ea6f5..398661a073 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -712,6 +712,9 @@ public final class StoryItemSetContainerComponent: Component { if self.inputPanelExternalState.isEditing || component.isProgressPaused || self.actionSheet != nil || self.contextController != nil || self.sendMessageContext.audioRecorderValue != nil || self.sendMessageContext.videoRecorderValue != nil || self.displayViewList { return true } + if let reactionContextNode = self.reactionContextNode, reactionContextNode.isReactionSearchActive { + return true + } if self.privacyController != nil { return true } @@ -2235,7 +2238,7 @@ public final class StoryItemSetContainerComponent: Component { let tooltipScreen = TooltipScreen( account: component.context.account, sharedContext: component.context.sharedContext, - text: "You are seeing this story because you have\nbeen added to \(component.slice.peer.compactDisplayTitle)'s list of close friends.", style: .default, location: TooltipScreen.Location.point(closeFriendIconView.convert(closeFriendIconView.bounds, to: self).offsetBy(dx: 1.0, dy: 6.0), .top), displayDuration: .manual(true), shouldDismissOnTouch: { _ in + text: .markdown(text: "You are seeing this story because you have\nbeen added to **\(component.slice.peer.compactDisplayTitle)'s** list of close friends."), style: .default, location: TooltipScreen.Location.point(closeFriendIconView.convert(closeFriendIconView.bounds, to: self).offsetBy(dx: 1.0, dy: 6.0), .top), displayDuration: .manual(true), shouldDismissOnTouch: { _ in return .dismiss(consume: false) } ) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index 00552c3bc0..ecfe58f386 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -338,7 +338,7 @@ final class StoryItemSetContainerSendMessage { Queue.mainQueue().after(0.3) { controller.present(UndoOverlayController( presentationData: presentationData, - content: .actionSucceeded(title: "", text: "Message Sent", cancel: "View in Chat"), + content: .actionSucceeded(title: "", text: "Message Sent", cancel: "View in Chat", destructive: false), elevatedLayout: false, animateInAsReplacement: false, action: { [weak view] action in @@ -380,7 +380,7 @@ final class StoryItemSetContainerSendMessage { Queue.mainQueue().after(0.3) { controller.present(UndoOverlayController( presentationData: presentationData, - content: .actionSucceeded(title: "", text: "Message Sent", cancel: "View in Chat"), + content: .actionSucceeded(title: "", text: "Message Sent", cancel: "View in Chat", destructive: false), elevatedLayout: false, animateInAsReplacement: false, action: { [weak view] action in @@ -442,7 +442,7 @@ final class StoryItemSetContainerSendMessage { Queue.mainQueue().after(0.3) { controller.present(UndoOverlayController( presentationData: presentationData, - content: .actionSucceeded(title: "", text: "Message Sent", cancel: "View in Chat"), + content: .actionSucceeded(title: "", text: "Message Sent", cancel: "View in Chat", destructive: false), elevatedLayout: false, animateInAsReplacement: false, action: { [weak view] action in @@ -491,7 +491,7 @@ final class StoryItemSetContainerSendMessage { if isVideo { if self.videoRecorderValue == nil { if let currentInputPanelFrame = view.inputPanel.view?.frame { - self.videoRecorder.set(.single(legacyInstantVideoController(theme: component.theme, panelFrame: view.convert(currentInputPanelFrame, to: nil), context: component.context, peerId: peer.id, slowmodeState: nil, hasSchedule: peer.id.namespace != Namespaces.Peer.SecretChat, send: { [weak self, weak view] videoController, message in + self.videoRecorder.set(.single(legacyInstantVideoController(theme: component.theme, panelFrame: view.convert(currentInputPanelFrame, to: nil), context: component.context, peerId: peer.id, slowmodeState: nil, hasSchedule: true, send: { [weak self, weak view] videoController, message in guard let self, let view, let component = view.component else { return } @@ -2057,28 +2057,36 @@ final class StoryItemSetContainerSendMessage { } private func sendMessages(view: StoryItemSetContainerComponent.View, peer: EnginePeer, messages: [EnqueueMessage], media: Bool = false, commit: Bool = false) { - guard let component = view.component else { + guard let component = view.component, let controller = component.controller() else { return } + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + let _ = (enqueueMessages(account: component.context.account, peerId: peer.id, messages: self.transformEnqueueMessages(view: view, messages: messages, silentPosting: false)) - |> deliverOnMainQueue).start() + |> deliverOnMainQueue).start(next: { [weak controller] messageIds in + if let controller { + Queue.mainQueue().after(0.3) { + controller.present(UndoOverlayController( + presentationData: presentationData, + content: .actionSucceeded(title: "", text: "Message Sent", cancel: "View in Chat", destructive: false), + elevatedLayout: false, + animateInAsReplacement: false, + action: { [weak view] action in + if case .undo = action, let messageId = messageIds.first { + view?.navigateToPeer(peer: peer, messageId: messageId) + } + return false + } + ), in: .current) + } + } + }) donateSendMessageIntent(account: component.context.account, sharedContext: component.context.sharedContext, intentContext: .chat, peerIds: [peer.id]) if let attachmentController = self.attachmentController { attachmentController.dismiss(animated: true) } - - if let controller = component.controller() { - let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - controller.present(UndoOverlayController( - presentationData: presentationData, - content: .succeed(text: "Message Sent"), - elevatedLayout: false, - animateInAsReplacement: false, - action: { _ in return false } - ), in: .current) - } } private func enqueueMediaMessages(view: StoryItemSetContainerComponent.View, peer: EnginePeer, replyToMessageId: EngineMessage.Id?, replyToStoryId: StoryId?, signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil, getAnimatedTransitionSource: ((String) -> UIView?)? = nil, completion: @escaping () -> Void = {}) { @@ -2139,9 +2147,7 @@ final class StoryItemSetContainerSendMessage { strongSelf.sendMessages(view: view, peer: peer, messages: messages.map { $0.withUpdatedReplyToMessageId(replyToMessageId).withUpdatedReplyToStoryId(replyToStoryId) }, media: true) - if let _ = scheduleTime { - completion() - } + completion() } })) } diff --git a/submodules/TelegramUI/Sources/ChatBotStartInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatBotStartInputPanelNode.swift index 79689abe9f..0588929f3a 100644 --- a/submodules/TelegramUI/Sources/ChatBotStartInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatBotStartInputPanelNode.swift @@ -130,7 +130,7 @@ final class ChatBotStartInputPanelNode: ChatInputPanelNode { tooltipController.location = .point(location, .bottom) } } else { - let controller = TooltipScreen(account: context.account, sharedContext: context.sharedContext, text: self.strings.Bot_TapToUse, icon: .downArrows, location: .point(location, .bottom), displayDuration: .infinite, shouldDismissOnTouch: { _ in + let controller = TooltipScreen(account: context.account, sharedContext: context.sharedContext, text: .plain(text: self.strings.Bot_TapToUse), icon: .downArrows, location: .point(location, .bottom), displayDuration: .infinite, shouldDismissOnTouch: { _ in return .ignore }) controller.alwaysVisible = true diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 67c30eca03..670cb1e452 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -14490,7 +14490,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var found = false self.forEachController({ controller in if let controller = controller as? TooltipScreen { - if controller.text == solution.text && controller.textEntities == solution.entities { + if controller.text == .entities(text: solution.text, entities: solution.entities) { found = true controller.dismiss() return false @@ -14502,7 +14502,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } - let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: solution.text, textEntities: solution.entities, icon: .info, location: .top, shouldDismissOnTouch: { point in + let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .entities(text: solution.text, entities: solution.entities), icon: .animation(name: "anim_infotip", delay: 0.2), location: .top, shouldDismissOnTouch: { point in return .ignore }, openActiveTextItem: { [weak self] item, action in guard let strongSelf = self else { @@ -14583,7 +14583,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var found = false self.forEachController({ controller in if let controller = controller as? TooltipScreen { - if controller.text == psaText { + if controller.text == .plain(text: psaText) { found = true controller.dismiss() return false @@ -14595,7 +14595,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } - let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: psaText, textEntities: psaEntities, icon: .info, location: .top, displayDuration: .custom(10.0), shouldDismissOnTouch: { point in + let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .entities(text: psaText, entities: psaEntities), icon: .animation(name: "anim_infotip", delay: 0.2), location: .top, displayDuration: .custom(10.0), shouldDismissOnTouch: { point in return .ignore }, openActiveTextItem: { [weak self] item, action in guard let strongSelf = self else { @@ -14683,7 +14683,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var found = false self.forEachController({ controller in if let controller = controller as? TooltipScreen { - if controller.text == psaText { + if controller.text == .plain(text: psaText) { found = true controller.resetDismissTimeout() @@ -14709,7 +14709,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } - let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: psaText, textEntities: psaEntities, icon: .info, location: .top, displayDuration: .custom(10.0), shouldDismissOnTouch: { point in + let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .entities(text: psaText, entities: psaEntities), icon: .animation(name: "anim_infotip", delay: 0.2), location: .top, displayDuration: .custom(10.0), shouldDismissOnTouch: { point in return .ignore }, openActiveTextItem: { [weak self] item, action in guard let strongSelf = self else { diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index a4578a6bb6..2781cd9d00 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -1484,7 +1484,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { tooltipController.location = .point(location, .bottom) } } else { - let controller = TooltipScreen(account: context.account, sharedContext: context.sharedContext, text: interfaceState.strings.Bot_TapToUse, icon: .downArrows, location: .point(location, .bottom), displayDuration: .infinite, shouldDismissOnTouch: { _ in + let controller = TooltipScreen(account: context.account, sharedContext: context.sharedContext, text: .plain(text: interfaceState.strings.Bot_TapToUse), icon: .downArrows, location: .point(location, .bottom), displayDuration: .infinite, shouldDismissOnTouch: { _ in return .ignore }) controller.alwaysVisible = true diff --git a/submodules/TelegramUI/Sources/ChatThemeScreen.swift b/submodules/TelegramUI/Sources/ChatThemeScreen.swift index 93ff7acf41..91b573b6c3 100644 --- a/submodules/TelegramUI/Sources/ChatThemeScreen.swift +++ b/submodules/TelegramUI/Sources/ChatThemeScreen.swift @@ -1262,7 +1262,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega if let strongSelf = self, count < 2 && currentTimestamp > timestamp + 24 * 60 * 60 { strongSelf.displayedPreviewTooltip = true - strongSelf.present?(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: isDark ? strongSelf.presentationData.strings.Conversation_Theme_PreviewLightShort : strongSelf.presentationData.strings.Conversation_Theme_PreviewDarkShort, style: .default, icon: nil, location: .point(frame.offsetBy(dx: 3.0, dy: 6.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in + strongSelf.present?(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: .plain(text: isDark ? strongSelf.presentationData.strings.Conversation_Theme_PreviewLightShort : strongSelf.presentationData.strings.Conversation_Theme_PreviewDarkShort), style: .default, icon: nil, location: .point(frame.offsetBy(dx: 3.0, dy: 6.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in return .dismiss(consume: false) })) diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 68decbf26a..981df1a615 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -8691,7 +8691,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro return } let buttonFrame = buttonNode.view.convert(buttonNode.bounds, to: self.view) - controller.present(TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: self.presentationData.strings.SharedMedia_CalendarTooltip, style: .default, icon: .none, location: .point(buttonFrame.insetBy(dx: 0.0, dy: 5.0), .top), shouldDismissOnTouch: { point in + controller.present(TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: self.presentationData.strings.SharedMedia_CalendarTooltip), style: .default, icon: .none, location: .point(buttonFrame.insetBy(dx: 0.0, dy: 5.0), .top), shouldDismissOnTouch: { point in return .dismiss(consume: false) }), in: .current) } diff --git a/submodules/TooltipUI/BUILD b/submodules/TooltipUI/BUILD index cb1e093076..1d49fcf788 100644 --- a/submodules/TooltipUI/BUILD +++ b/submodules/TooltipUI/BUILD @@ -20,6 +20,10 @@ swift_library( "//submodules/TextFormat:TextFormat", "//submodules/UrlEscaping:UrlEscaping", "//submodules/AccountContext:AccountContext", + "//submodules/AvatarNode:AvatarNode", + "//submodules/ComponentFlow", + "//submodules/Markdown", + "//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TooltipUI/Sources/TooltipScreen.swift b/submodules/TooltipUI/Sources/TooltipScreen.swift index caedc905a1..8335675ef0 100644 --- a/submodules/TooltipUI/Sources/TooltipScreen.swift +++ b/submodules/TooltipUI/Sources/TooltipScreen.swift @@ -11,11 +11,11 @@ import TelegramCore import TextFormat import UrlEscaping import AccountContext - -public protocol TooltipCustomContentNode: ASDisplayNode { - func animateIn() - func updateLayout(size: CGSize) -> CGSize -} +import AvatarNode +import ComponentFlow +import AvatarStoryIndicatorComponent +import AccountContext +import Markdown public enum TooltipActiveTextItem { case url(String, Bool) @@ -109,7 +109,7 @@ private class DownArrowsIconNode: ASDisplayNode { private final class TooltipScreenNode: ViewControllerTracingNode { private let tooltipStyle: TooltipScreen.Style private let icon: TooltipScreen.Icon? - private let customContentNode: TooltipCustomContentNode? + private let action: TooltipScreen.Action? var location: TooltipScreen.Location { didSet { if let layout = self.validLayout { @@ -134,8 +134,11 @@ private final class TooltipScreenNode: ViewControllerTracingNode { private let arrowContainer: ASDisplayNode private let animatedStickerNode: AnimatedStickerNode private var downArrowsNode: DownArrowsIconNode? + private var avatarNode: AvatarNode? + private var avatarStoryIndicator: ComponentView? private let textNode: ImmediateTextNode - private let closeButtonNode: HighlightableButtonNode + private var closeButtonNode: HighlightableButtonNode? + private var actionButtonNode: HighlightableButtonNode? private var isArrowInverted: Bool = false @@ -143,10 +146,24 @@ private final class TooltipScreenNode: ViewControllerTracingNode { private var validLayout: ContainerViewLayout? - init(account: Account, sharedContext: SharedAccountContext, text: String, textEntities: [MessageTextEntity], style: TooltipScreen.Style, icon: TooltipScreen.Icon? = nil, customContentNode: TooltipCustomContentNode? = nil, location: TooltipScreen.Location, displayDuration: TooltipScreen.DisplayDuration, inset: CGFloat = 13.0, cornerRadius: CGFloat? = nil, shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, requestDismiss: @escaping () -> Void, openActiveTextItem: ((TooltipActiveTextItem, TooltipActiveTextAction) -> Void)?) { + init( + context: AccountContext?, + account: Account, + sharedContext: SharedAccountContext, + text: TooltipScreen.Text, + textAlignment: TooltipScreen.Alignment, + style: TooltipScreen.Style, + icon: TooltipScreen.Icon? = nil, + action: TooltipScreen.Action? = nil, + location: TooltipScreen.Location, + displayDuration: TooltipScreen.DisplayDuration, + inset: CGFloat = 13.0, + cornerRadius: CGFloat? = nil, + shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, requestDismiss: @escaping () -> Void, openActiveTextItem: ((TooltipActiveTextItem, TooltipActiveTextAction) -> Void)?) + { self.tooltipStyle = style self.icon = icon - self.customContentNode = customContentNode + self.action = action self.location = location self.displayDuration = displayDuration self.inset = inset @@ -164,6 +181,8 @@ private final class TooltipScreenNode: ViewControllerTracingNode { self.scrollingContainer = ASDisplayNode() + let theme = sharedContext.currentPresentationData.with { $0 }.theme + func svgPath(_ path: StaticString, scale: CGPoint = CGPoint(x: 1.0, y: 1.0), offset: CGPoint = CGPoint()) throws -> UIBezierPath { var index: UnsafePointer = path.utf8Start let end = path.utf8Start.advanced(by: path.utf8CodeUnitCount) @@ -210,7 +229,6 @@ private final class TooltipScreenNode: ViewControllerTracingNode { self.arrowContainer = ASDisplayNode() - let theme = sharedContext.currentPresentationData.with { $0 }.theme let fontSize: CGFloat if case .top = location { let backgroundColor: UIColor @@ -310,25 +328,56 @@ private final class TooltipScreenNode: ViewControllerTracingNode { self.textNode = ImmediateTextNode() self.textNode.displaysAsynchronously = false self.textNode.maximumNumberOfLines = 0 + + let baseFont = Font.regular(fontSize) + let boldFont = Font.semibold(14.0) + let italicFont = Font.italic(fontSize) + let boldItalicFont = Font.semiboldItalic(fontSize) + let fixedFont = Font.monospace(fontSize) - self.textNode.attributedText = stringWithAppliedEntities(text, entities: textEntities, baseColor: .white, linkColor: .white, baseFont: Font.regular(fontSize), linkFont: Font.regular(fontSize), boldFont: Font.semibold(14.0), italicFont: Font.italic(fontSize), boldItalicFont: Font.semiboldItalic(fontSize), fixedFont: Font.monospace(fontSize), blockQuoteFont: Font.regular(fontSize), underlineLinks: true, external: false, message: nil) + let textColor: UIColor = .white + + let attributedText: NSAttributedString + switch text { + case let .plain(text): + attributedText = NSAttributedString(string: text, font: baseFont, textColor: textColor) + case let .entities(text, entities): + attributedText = stringWithAppliedEntities(text, entities: entities, baseColor: textColor, linkColor: textColor, baseFont: baseFont, linkFont: baseFont, boldFont: boldFont, italicFont: italicFont, boldItalicFont: boldItalicFont, fixedFont: fixedFont, blockQuoteFont: baseFont, underlineLinks: true, external: false, message: nil) + case let .markdown(text): + let markdownAttributes = MarkdownAttributes( + body: MarkdownAttributeSet(font: baseFont, textColor: textColor), + bold: MarkdownAttributeSet(font: boldFont, textColor: textColor), + link: MarkdownAttributeSet(font: baseFont, textColor: textColor), + linkAttribute: { _ in + return nil + } + ) + attributedText = parseMarkdownIntoAttributedString(text, attributes: markdownAttributes) + } + + self.textNode.attributedText = attributedText + self.textNode.textAlignment = textAlignment == .center ? .center : .natural self.animatedStickerNode = DefaultAnimatedStickerNodeImpl() switch icon { case .none: break - case .chatListPress: - self.animatedStickerNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "ChatListFoldersTooltip"), width: Int(70 * UIScreenScale), height: Int(70 * UIScreenScale), playbackMode: .once, mode: .direct(cachePathPrefix: nil)) - self.animatedStickerNode.automaticallyLoadFirstFrame = true - case .info: - self.animatedStickerNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "anim_infotip"), width: Int(70 * UIScreenScale), height: Int(70 * UIScreenScale), playbackMode: .once, mode: .direct(cachePathPrefix: nil)) + case let .animation(animationName, _): + self.animatedStickerNode.setup(source: AnimatedStickerNodeLocalFileSource(name: animationName), width: Int(70 * UIScreenScale), height: Int(70 * UIScreenScale), playbackMode: .once, mode: .direct(cachePathPrefix: nil)) self.animatedStickerNode.automaticallyLoadFirstFrame = true case .downArrows: self.downArrowsNode = DownArrowsIconNode() + case let .peer(peer, _): + self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 15.0)) + if let context { + self.avatarNode?.setPeer(context: context, theme: defaultDarkPresentationTheme, peer: peer) + } } - self.closeButtonNode = HighlightableButtonNode() - self.closeButtonNode.setImage(UIImage(bundleImageName: "Components/Close"), for: .normal) + if case .manual = displayDuration { + self.closeButtonNode = HighlightableButtonNode() + self.closeButtonNode?.setImage(UIImage(bundleImageName: "Components/Close"), for: .normal) + } super.init() @@ -343,16 +392,28 @@ private final class TooltipScreenNode: ViewControllerTracingNode { self.containerNode.addSubnode(self.textNode) self.containerNode.addSubnode(self.animatedStickerNode) - if case .manual = displayDuration { - self.containerNode.addSubnode(self.closeButtonNode) + if let closeButtonNode = self.closeButtonNode { + self.containerNode.addSubnode(closeButtonNode) } if let downArrowsNode = self.downArrowsNode { self.containerNode.addSubnode(downArrowsNode) } + if let avatarNode = self.avatarNode { + self.containerNode.addSubnode(avatarNode) + } self.scrollingContainer.addSubnode(self.containerNode) self.addSubnode(self.scrollingContainer) + if let action { + let actionColor = theme.list.itemAccentColor.withMultiplied(hue: 1.0, saturation: 0.64, brightness: 1.08) + let actionButtonNode = HighlightableButtonNode() + actionButtonNode.hitTestSlop = UIEdgeInsets(top: -16.0, left: -16.0, bottom: -16.0, right: -16.0) + actionButtonNode.setAttributedTitle(NSAttributedString(string: action.title, font: Font.regular(17.0), textColor: actionColor), for: .normal) + self.containerNode.addSubnode(actionButtonNode) + self.actionButtonNode = actionButtonNode + } + self.textNode.linkHighlightColor = UIColor.white.withAlphaComponent(0.5) self.textNode.highlightAttributeAction = { attributes in let highlightedAttributes = [ @@ -412,7 +473,15 @@ private final class TooltipScreenNode: ViewControllerTracingNode { } } - self.closeButtonNode.addTarget(self, action: #selector(self.closePressed), forControlEvents: .touchUpInside) + self.actionButtonNode?.addTarget(self, action: #selector(self.actionPressed), forControlEvents: .touchUpInside) + self.closeButtonNode?.addTarget(self, action: #selector(self.closePressed), forControlEvents: .touchUpInside) + } + + @objc private func actionPressed() { + if let action = self.action { + action.action() + self.requestDismiss() + } } @objc private func closePressed() { @@ -441,11 +510,15 @@ private final class TooltipScreenNode: ViewControllerTracingNode { animationSize = CGSize(width: 24.0, height: 32.0) animationInset = (40.0 - animationSize.width) / 2.0 animationSpacing = 8.0 - case .chatListPress: + case let .animation(animationName, _): animationSize = CGSize(width: 32.0, height: 32.0) - animationInset = (70.0 - animationSize.width) / 2.0 + if animationName == "ChatListFoldersTooltip" { + animationInset = (70.0 - animationSize.width) / 2.0 + } else { + animationInset = 0.0 + } animationSpacing = 8.0 - case .info: + case .peer: animationSize = CGSize(width: 32.0, height: 32.0) animationInset = 0.0 animationSpacing = 8.0 @@ -453,24 +526,41 @@ private final class TooltipScreenNode: ViewControllerTracingNode { let containerWidth = max(100.0, min(layout.size.width, 614.0) - (sideInset + layout.safeInsets.left) * 2.0) - let textSize = self.textNode.updateLayout(CGSize(width: containerWidth - contentInset * 2.0 - animationSize.width - animationSpacing, height: .greatestFiniteMagnitude)) + var actionSize: CGSize = .zero + + var buttonInset: CGFloat = 0.0 + if let actionButtonNode = self.actionButtonNode { + actionSize = actionButtonNode.measure(CGSize(width: containerWidth, height: .greatestFiniteMagnitude)) + buttonInset += actionSize.width + 32.0 + } + if self.closeButtonNode != nil { + buttonInset += 24.0 + } + + let textSize = self.textNode.updateLayout(CGSize(width: containerWidth - contentInset * 2.0 - animationSize.width - animationSpacing - buttonInset, height: .greatestFiniteMagnitude)) var backgroundFrame: CGRect - let backgroundHeight: CGFloat + var backgroundHeight: CGFloat switch self.tooltipStyle { case .default, .gradient, .customBlur: - backgroundHeight = max(animationSize.height, textSize.height) + contentVerticalInset * 2.0 - case .light: - backgroundHeight = max(28.0, max(animationSize.height, textSize.height) + 4.0 * 2.0) + backgroundHeight = max(animationSize.height, textSize.height) + contentVerticalInset * 2.0 + if self.actionButtonNode != nil { + backgroundHeight += 2.0 + } + case .light: + backgroundHeight = max(28.0, max(animationSize.height, textSize.height) + 4.0 * 2.0) } var invertArrow = false switch self.location { case let .point(rect, arrowPosition): var backgroundWidth = textSize.width + contentInset * 2.0 + animationSize.width + animationSpacing - if self.closeButtonNode.supernode != nil { - backgroundWidth += 24.0 + if self.closeButtonNode != nil || self.actionButtonNode != nil { + backgroundWidth += buttonInset + } + if self.actionButtonNode != nil, case .compact = layout.metrics.widthClass { + backgroundWidth = containerWidth } switch arrowPosition { case .bottom, .top: @@ -554,8 +644,14 @@ private final class TooltipScreenNode: ViewControllerTracingNode { let textFrame = CGRect(origin: CGPoint(x: contentInset + animationSize.width + animationSpacing, y: floor((backgroundHeight - textSize.height) / 2.0)), size: textSize) transition.updateFrame(node: self.textNode, frame: textFrame) - let closeSize = CGSize(width: 44.0, height: 44.0) - transition.updateFrame(node: self.closeButtonNode, frame: CGRect(origin: CGPoint(x: textFrame.maxX - 6.0, y: floor((backgroundHeight - closeSize.height) / 2.0)), size: closeSize)) + if let closeButtonNode = self.closeButtonNode { + let closeSize = CGSize(width: 44.0, height: 44.0) + transition.updateFrame(node: closeButtonNode, frame: CGRect(origin: CGPoint(x: textFrame.maxX - 6.0, y: floor((backgroundHeight - closeSize.height) / 2.0)), size: closeSize)) + } + + if let actionButtonNode = self.actionButtonNode { + transition.updateFrame(node: actionButtonNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.width - actionSize.width - 16.0, y: floor((backgroundHeight - actionSize.height) / 2.0)), size: actionSize)) + } let animationFrame = CGRect(origin: CGPoint(x: contentInset - animationInset, y: contentVerticalInset - animationInset), size: CGSize(width: animationSize.width + animationInset * 2.0, height: animationSize.height + animationInset * 2.0)) transition.updateFrame(node: self.animatedStickerNode, frame: animationFrame) @@ -566,6 +662,59 @@ private final class TooltipScreenNode: ViewControllerTracingNode { transition.updateFrame(node: downArrowsNode, frame: CGRect(origin: CGPoint(x: animationFrame.midX - arrowsSize.width / 2.0, y: animationFrame.midY - arrowsSize.height / 2.0), size: arrowsSize)) downArrowsNode.setupAnimations() } + + if let avatarNode = self.avatarNode { + var avatarFrame = animationFrame + + if let icon, case let .peer(_, isStory) = icon, isStory { + let indicatorTransition: Transition = .immediate + let avatarStoryIndicator: ComponentView + if let current = self.avatarStoryIndicator { + avatarStoryIndicator = current + } else { + avatarStoryIndicator = ComponentView() + self.avatarStoryIndicator = avatarStoryIndicator + } + + let storyIndicatorScale: CGFloat = 1.0 + var indicatorFrame = CGRect(origin: CGPoint(x: avatarFrame.minX + 4.0, y: avatarFrame.minY + 4.0), size: CGSize(width: avatarFrame.width - 4.0 - 4.0, height: avatarFrame.height - 4.0 - 4.0)) + indicatorFrame.origin.x -= (avatarFrame.width - avatarFrame.width * storyIndicatorScale) * 0.5 + + let _ = avatarStoryIndicator.update( + transition: indicatorTransition, + component: AnyComponent(AvatarStoryIndicatorComponent( + hasUnseen: true, + hasUnseenCloseFriendsItems: false, + theme: defaultDarkPresentationTheme, + activeLineWidth: 1.0 + UIScreenPixel, + inactiveLineWidth: 1.0 + UIScreenPixel, + counters: nil + )), + environment: {}, + containerSize: indicatorFrame.size + ) + if let avatarStoryIndicatorView = avatarStoryIndicator.view { + if avatarStoryIndicatorView.superview == nil { + avatarStoryIndicatorView.isUserInteractionEnabled = false + self.containerNode.view.addSubview(avatarStoryIndicatorView) + } + + indicatorTransition.setPosition(view: avatarStoryIndicatorView, position: indicatorFrame.center) + indicatorTransition.setBounds(view: avatarStoryIndicatorView, bounds: CGRect(origin: CGPoint(), size: indicatorFrame.size)) + indicatorTransition.setScale(view: avatarStoryIndicatorView, scale: storyIndicatorScale) + } + + avatarFrame = avatarFrame.insetBy(dx: 4.0, dy: 4.0) + } else { + if let avatarStoryIndicator = self.avatarStoryIndicator { + self.avatarStoryIndicator = nil + avatarStoryIndicator.view?.removeFromSuperview() + } + } + + transition.updateFrame(node: avatarNode, frame: avatarFrame) + avatarNode.updateSize(size: avatarFrame.size) + } } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { @@ -587,6 +736,9 @@ private final class TooltipScreenNode: ViewControllerTracingNode { return nil } } + if let actionButtonNode = self.actionButtonNode, let result = actionButtonNode.hitTest(self.convert(point, to: actionButtonNode), with: event) { + return result + } switch self.shouldDismissOnTouch(point) { case .ignore: break @@ -628,12 +780,12 @@ private final class TooltipScreenNode: ViewControllerTracingNode { let animationDelay: Double switch self.icon { - case .chatListPress: - animationDelay = 0.6 - case .info: - animationDelay = 0.2 + case let .animation(_, delay): + animationDelay = delay case .none, .downArrows: animationDelay = 0.0 + case .peer: + animationDelay = 0.0 } DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + animationDelay, execute: { [weak self] in @@ -684,9 +836,28 @@ private final class TooltipScreenNode: ViewControllerTracingNode { } public final class TooltipScreen: ViewController { + public enum Text: Equatable { + case plain(text: String) + case entities(text: String, entities: [MessageTextEntity]) + case markdown(text: String) + } + + public class Action { + public let title: String + public let action: () -> Void + + public init( + title: String, + action: @escaping () -> Void + ) { + self.title = title + self.action = action + } + } + public enum Icon { - case info - case chatListPress + case animation(name: String, delay: Double) + case peer(peer: EnginePeer, isStory: Bool) case downArrows } @@ -720,13 +891,19 @@ public final class TooltipScreen: ViewController { case gradient(UIColor, UIColor) } + public enum Alignment { + case natural + case center + } + + private let context: AccountContext? private let account: Account private let sharedContext: SharedAccountContext - public let text: String - public let textEntities: [MessageTextEntity] + public let text: TooltipScreen.Text + public let textAlignment: TooltipScreen.Alignment private let style: TooltipScreen.Style private let icon: TooltipScreen.Icon? - private let customContentNode: TooltipCustomContentNode? + private let action: TooltipScreen.Action? public var location: TooltipScreen.Location { didSet { if self.isNodeLoaded { @@ -755,13 +932,14 @@ public final class TooltipScreen: ViewController { public var alwaysVisible = false public init( + context: AccountContext? = nil, account: Account, sharedContext: SharedAccountContext, - text: String, - textEntities: [MessageTextEntity] = [], + text: TooltipScreen.Text, + textAlignment: TooltipScreen.Alignment = .natural, style: TooltipScreen.Style = .default, icon: TooltipScreen.Icon? = nil, - customContentNode: TooltipCustomContentNode? = nil, + action: TooltipScreen.Action? = nil, location: TooltipScreen.Location, displayDuration: DisplayDuration = .default, inset: CGFloat = 13.0, @@ -769,13 +947,14 @@ public final class TooltipScreen: ViewController { shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, openActiveTextItem: ((TooltipActiveTextItem, TooltipActiveTextAction) -> Void)? = nil ) { + self.context = context self.account = account self.sharedContext = sharedContext self.text = text - self.textEntities = textEntities + self.textAlignment = textAlignment self.style = style self.icon = icon - self.customContentNode = customContentNode + self.action = action self.location = location self.displayDuration = displayDuration self.inset = inset @@ -839,7 +1018,7 @@ public final class TooltipScreen: ViewController { } override public func loadDisplayNode() { - self.displayNode = TooltipScreenNode(account: self.account, sharedContext: self.sharedContext, text: self.text, textEntities: self.textEntities, style: self.style, icon: self.icon, customContentNode: self.customContentNode, location: self.location, displayDuration: self.displayDuration, inset: self.inset, cornerRadius: self.cornerRadius, shouldDismissOnTouch: self.shouldDismissOnTouch, requestDismiss: { [weak self] in + self.displayNode = TooltipScreenNode(context: self.context, account: self.account, sharedContext: self.sharedContext, text: self.text, textAlignment: self.textAlignment, style: self.style, icon: self.icon, action: self.action, location: self.location, displayDuration: self.displayDuration, inset: self.inset, cornerRadius: self.cornerRadius, shouldDismissOnTouch: self.shouldDismissOnTouch, requestDismiss: { [weak self] in guard let strongSelf = self else { return } diff --git a/submodules/UndoUI/Sources/UndoOverlayController.swift b/submodules/UndoUI/Sources/UndoOverlayController.swift index 982b12556f..1b50d6b591 100644 --- a/submodules/UndoUI/Sources/UndoOverlayController.swift +++ b/submodules/UndoUI/Sources/UndoOverlayController.swift @@ -15,7 +15,7 @@ public enum UndoOverlayContent { case info(title: String?, text: String, timeout: Double?) case emoji(name: String, text: String) case swipeToReply(title: String, text: String) - case actionSucceeded(title: String, text: String, cancel: String) + case actionSucceeded(title: String, text: String, cancel: String, destructive: Bool) case stickersModified(title: String, text: String, undo: Bool, info: StickerPackCollectionInfo, topItem: StickerPackItem?, context: AccountContext) case dice(dice: TelegramMediaDice, context: AccountContext, text: String, action: String?) case chatAddedToFolder(chatTitle: String, folderTitle: String) diff --git a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift index f13da73112..3387014347 100644 --- a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift +++ b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift @@ -221,14 +221,16 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { isUserInteractionEnabled = true } - case let .actionSucceeded(title, text, cancel): + case let .actionSucceeded(title, text, cancel, destructive): self.avatarNode = nil self.iconNode = nil self.iconCheckNode = nil self.animationNode = AnimationNode(animation: "anim_success", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0) self.animatedStickerNode = nil - undoTextColor = UIColor(rgb: 0xff7b74) + if destructive { + undoTextColor = UIColor(rgb: 0xff7b74) + } let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white) let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)