From 77df1cf45ae8fc77db2cfb721c4490d70db6ed45 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 17 Dec 2022 15:35:00 +0400 Subject: [PATCH] Various improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 10 +- .../Sources/AccountContext.swift | 9 + submodules/AppLock/Sources/AppLock.swift | 14 +- .../AppLockState/Sources/AppLockState.swift | 4 +- .../Sources/ChatListSearchListPaneNode.swift | 2 +- .../ChatPresentationInterfaceState/BUILD | 1 + .../ChatMediaInputNodeInteraction.swift | 61 + .../Sources/PagerComponent.swift | 13 +- .../Sources/ContactAddItem.swift | 7 +- .../Sources/ContactsSearchContainerNode.swift | 2 +- submodules/DrawingUI/BUILD | 4 + .../DrawingUI/Sources/ColorPickerScreen.swift | 17 +- .../Sources/DrawingBubbleEntity.swift | 50 +- .../Sources/DrawingEntitiesView.swift | 144 +- .../DrawingUI/Sources/DrawingGesture.swift | 17 +- .../DrawingUI/Sources/DrawingMetalView.swift | 296 +- .../DrawingUI/Sources/DrawingScreen.swift | 274 +- .../Sources/DrawingSimpleShapeEntity.swift | 49 +- .../Sources/DrawingStickerEntity.swift | 45 +- .../DrawingUI/Sources/DrawingTextEntity.swift | 338 +- .../DrawingUI/Sources/DrawingTools.swift | 532 +- .../DrawingUI/Sources/DrawingUtils.swift | 178 +- .../Sources/DrawingVectorEntity.swift | 50 +- .../DrawingUI/Sources/DrawingView.swift | 116 +- submodules/DrawingUI/Sources/PenTool.swift | 579 +- .../Sources/StickerPickerScreen.swift | 392 +- .../Sources/TextSettingsComponent.swift | 172 +- submodules/FeaturedStickersScreen/BUILD | 39 + .../Sources/ChatMediaInputPane.swift | 19 + .../Sources/ChatMediaInputTrendingPane.swift | 63 +- .../Sources/FeaturedStickersScreen.swift | 17 +- .../Sources/MediaInputPaneTrendingItem.swift | 0 .../PaneSearchBarPlaceholderItem.swift | 40 +- .../Sources/StickerPaneSearchGlobaltem.swift | 76 +- .../StickerPaneSearchStickerItem.swift | 49 +- .../GalleryData/Sources/GalleryData.swift | 6 +- .../Sources/ItemListAvatarAndNameItem.swift | 8 +- .../Sources/ItemListPeerActionItem.swift | 21 +- .../LegacyComponents/TGMediaEditingContext.h | 2 +- .../TGMediaPickerGalleryVideoItemView.h | 4 +- .../LegacyComponents/TGPaintingData.h | 15 +- .../LegacyComponents/TGPhotoAvatarCropView.h | 4 +- .../TGPhotoEditorController.h | 6 +- .../TGPhotoPaintStickersContext.h | 6 +- .../LegacyComponents/TGPhotoVideoEditor.h | 2 +- .../Sources/TGCameraController.m | 9 +- .../Sources/TGMediaAssetsController.m | 21 +- .../Sources/TGMediaEditingContext.m | 7 +- .../Sources/TGMediaPickerGalleryModel.m | 6 +- .../TGMediaPickerGalleryPhotoItemView.m | 35 +- .../TGMediaPickerGalleryVideoItemView.m | 50 +- .../LegacyComponents/Sources/TGPaintingData.m | 66 +- .../Sources/TGPhotoAvatarCropView.m | 5 +- .../Sources/TGPhotoAvatarPreviewController.h | 5 +- .../Sources/TGPhotoAvatarPreviewController.m | 53 +- .../Sources/TGPhotoDrawingController.h | 12 +- .../Sources/TGPhotoDrawingController.m | 63 +- .../Sources/TGPhotoEditorController.m | 15 +- .../Sources/TGPhotoEntitiesContainerView.m | 8 +- .../Sources/TGPhotoPaintController.h | 56 +- .../Sources/TGPhotoPaintController.m | 5265 ++++++++--------- .../Sources/TGPhotoToolsController.h | 4 +- .../Sources/TGPhotoToolsController.m | 7 +- .../Sources/TGPhotoVideoEditor.m | 3 +- .../Sources/TGVideoEditAdjustments.m | 67 +- .../Sources/LegacyAvatarPicker.swift | 4 +- .../Sources/LegacyPaintStickersContext.swift | 37 +- .../LocalizedPeerData/Sources/PeerTitle.swift | 4 +- .../Sources/MediaPickerScreen.swift | 4 +- .../Sources/AvatarGalleryController.swift | 9 +- .../Sources/PeerInfoAvatarListNode.swift | 4 +- .../QrCodeUI/Sources/QrCodeScanScreen.swift | 17 +- .../Sources/ChangePhoneNumberController.swift | 182 - .../PrivacyIntroController.swift | 4 +- .../PrivacyIntroControllerNode.swift | 2 +- .../SelectivePrivacySettingsController.swift | 9 +- .../InstalledStickerPacksController.swift | 7 - .../Themes/ThemeGridSearchContentNode.swift | 2 +- .../Sources/ApiUtils/ChatContextResult.swift | 45 + submodules/TelegramUI/BUILD | 4 + .../ChatControllerInteraction/BUILD | 33 + .../Sources/ChatControllerInteraction.swift | 279 +- .../ChatEntityKeyboardInputNode/BUILD | 45 + .../Sources/ChatEntityKeyboardInputNode.swift | 206 +- .../Sources/GifPaneSearchContentNode.swift | 113 +- .../Sources/PaneSearchBarNode.swift | 1 + .../Sources/PaneSearchContainerNode.swift | 29 +- .../StickerPaneSearchContentNode.swift | 18 +- .../TelegramUI/Components/ChatInputNode/BUILD | 21 + .../ChatInputNode/Sources/ChatInputNode.swift | 34 + .../EmojiStatusSelectionComponent.swift | 3 + .../Sources/EmojiTextAttachmentView.swift | 66 + .../Sources/EmojiPagerContentComponent.swift | 41 +- .../Sources/EntityKeyboard.swift | 203 +- .../Sources/ForumCreateTopicScreen.swift | 3 + .../Components/MultiplexedVideoNode/BUILD | 26 + .../Sources/MultiplexedVideoNode.swift | 63 +- .../Sources/SoftwareVideoThumbnailLayer.swift | 12 +- .../Contents.json | 15 + .../EntityInputMasksIcon.imageset/mask.pdf | Bin 0 -> 5801 bytes .../TelegramUI/Sources/ChatBotInfoItem.swift | 1 + .../Sources/ChatButtonKeyboardInputNode.swift | 2 + .../ChatContextResultPeekContentNode.swift | 1 + .../TelegramUI/Sources/ChatController.swift | 24 +- .../Sources/ChatControllerNode.swift | 3 + .../Sources/ChatHistoryListNode.swift | 1 + .../ChatHistorySearchContainerNode.swift | 1 + .../Sources/ChatInputContextPanelNode.swift | 1 + .../TelegramUI/Sources/ChatInputNode.swift | 34 - .../ChatInterfaceInputContextPanels.swift | 1 + .../Sources/ChatInterfaceInputNodes.swift | 3 + .../ChatInterfaceStateAccessoryPanels.swift | 1 + .../ChatInterfaceStateContextMenus.swift | 1 + .../ChatInterfaceTitlePanelNodes.swift | 1 + .../Sources/ChatMediaInputGifPane.swift | 14 +- .../Sources/ChatMediaInputGridEntries.swift | 2 + .../ChatMediaInputMetaSectionItemNode.swift | 1 + .../Sources/ChatMediaInputNode.swift | 47 +- .../Sources/ChatMediaInputPane.swift | 1 + .../Sources/ChatMediaInputPanelEntries.swift | 1 + .../ChatMediaInputPeerSpecificItem.swift | 1 + .../ChatMediaInputRecentGifsItem.swift | 1 + .../Sources/ChatMediaInputSettingsItem.swift | 1 + .../ChatMediaInputStickerGridItem.swift | 2 + .../ChatMediaInputStickerPackItem.swift | 1 + .../Sources/ChatMediaInputStickerPane.swift | 2 + .../Sources/ChatMediaInputTrendingItem.swift | 1 + .../ChatMessageAnimatedStickerItemNode.swift | 5 +- .../ChatMessageAttachedContentNode.swift | 1 + .../ChatMessageBubbleContentNode.swift | 1 + .../Sources/ChatMessageBubbleItemNode.swift | 14 +- .../Sources/ChatMessageDateHeader.swift | 1 + .../Sources/ChatMessageGiftItemNode.swift | 1 + .../ChatMessageInstantVideoItemNode.swift | 1 + .../ChatMessageInteractiveFileNode.swift | 1 + .../ChatMessageInteractiveMediaNode.swift | 1 + .../TelegramUI/Sources/ChatMessageItem.swift | 1 + .../Sources/ChatMessageItemView.swift | 1 + .../ChatMessageMediaBubbleContentNode.swift | 1 + ...hatMessageReactionsFooterContentNode.swift | 1 + .../Sources/ChatMessageStickerItemNode.swift | 1 + .../Sources/ChatMessageSwipeToReplyNode.swift | 1 + .../Sources/ChatMessageThreadInfoNode.swift | 1 + .../Sources/ChatMessageTransitionNode.swift | 4 +- .../ChatRecentActionsControllerNode.swift | 2 + .../ChatRecentActionsHistoryTransition.swift | 1 + .../Sources/ChatReplyCountItem.swift | 1 + .../ChatTextInputActionButtonsNode.swift | 1 + .../Sources/ChatTextInputPanelNode.swift | 67 +- .../TelegramUI/Sources/ChatUnreadItem.swift | 1 + .../CommandChatInputContextPanelNode.swift | 1 + ...CommandMenuChatInputContextPanelNode.swift | 1 + ...textResultsChatInputContextPanelNode.swift | 1 + .../Sources/DrawingStickersScreen.swift | 3 + .../EmojisChatInputContextPanelNode.swift | 1 + .../TelegramUI/Sources/GridMessageItem.swift | 1 + .../HashtagChatInputContextPanelNode.swift | 1 + ...textResultsChatInputContextPanelNode.swift | 1 + ...ListContextResultsChatInputPanelItem.swift | 1 + ...rizontalStickersChatContextPanelNode.swift | 1 + .../Sources/InlineReactionSearchPanel.swift | 1 + .../MentionChatInputContextPanelNode.swift | 1 + .../OverlayAudioPlayerControllerNode.swift | 1 + .../PeerInfoGroupsInCommonPaneNode.swift | 1 + .../PeerInfo/Panes/PeerInfoListPaneNode.swift | 1 + .../Panes/PeerInfoVisualMediaPaneNode.swift | 1 + .../Sources/PeerInfo/PeerInfoHeaderNode.swift | 5 + .../PeerInfo/PeerInfoPaneContainerNode.swift | 1 + .../Sources/PeerInfo/PeerInfoScreen.swift | 100 +- .../Sources/PeerInfoGifPaneNode.swift | 1 + .../PreparedChatHistoryViewTransition.swift | 1 + .../Sources/SharedAccountContext.swift | 6 + .../StickerPaneTrendingListGridItem.swift | 1 + .../StickersChatInputContextPanelNode.swift | 1 + .../Sources/TelegramRootController.swift | 3 +- ...textResultsChatInputContextPanelNode.swift | 1 + .../Sources/ChatTextInputAttributes.swift | 35 +- .../Sources/UndoOverlayController.swift | 2 +- .../Sources/UndoOverlayControllerNode.swift | 6 +- .../Sources/LegacyWebSearchGallery.swift | 11 +- .../Sources/WebSearchController.swift | 47 +- .../WebUI/Sources/WebAppController.swift | 14 +- 182 files changed, 6119 insertions(+), 5449 deletions(-) create mode 100644 submodules/ChatPresentationInterfaceState/Sources/ChatMediaInputNodeInteraction.swift create mode 100644 submodules/FeaturedStickersScreen/BUILD create mode 100644 submodules/FeaturedStickersScreen/Sources/ChatMediaInputPane.swift rename submodules/{TelegramUI => FeaturedStickersScreen}/Sources/ChatMediaInputTrendingPane.swift (88%) rename submodules/{TelegramUI => FeaturedStickersScreen}/Sources/FeaturedStickersScreen.swift (98%) rename submodules/{TelegramUI => FeaturedStickersScreen}/Sources/MediaInputPaneTrendingItem.swift (100%) rename submodules/{TelegramUI => FeaturedStickersScreen}/Sources/PaneSearchBarPlaceholderItem.swift (79%) rename submodules/{TelegramUI => FeaturedStickersScreen}/Sources/StickerPaneSearchGlobaltem.swift (90%) rename submodules/{TelegramUI => FeaturedStickersScreen}/Sources/StickerPaneSearchStickerItem.swift (86%) create mode 100644 submodules/TelegramUI/Components/ChatControllerInteraction/BUILD rename submodules/TelegramUI/{ => Components/ChatControllerInteraction}/Sources/ChatControllerInteraction.swift (58%) create mode 100644 submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/BUILD rename submodules/TelegramUI/{ => Components/ChatEntityKeyboardInputNode}/Sources/ChatEntityKeyboardInputNode.swift (91%) rename submodules/TelegramUI/{ => Components/ChatEntityKeyboardInputNode}/Sources/GifPaneSearchContentNode.swift (65%) rename submodules/TelegramUI/{ => Components/ChatEntityKeyboardInputNode}/Sources/PaneSearchBarNode.swift (99%) rename submodules/TelegramUI/{ => Components/ChatEntityKeyboardInputNode}/Sources/PaneSearchContainerNode.swift (83%) rename submodules/TelegramUI/{ => Components/ChatEntityKeyboardInputNode}/Sources/StickerPaneSearchContentNode.swift (97%) create mode 100644 submodules/TelegramUI/Components/ChatInputNode/BUILD create mode 100644 submodules/TelegramUI/Components/ChatInputNode/Sources/ChatInputNode.swift create mode 100644 submodules/TelegramUI/Components/MultiplexedVideoNode/BUILD rename submodules/TelegramUI/{ => Components/MultiplexedVideoNode}/Sources/MultiplexedVideoNode.swift (93%) rename submodules/TelegramUI/{ => Components/MultiplexedVideoNode}/Sources/SoftwareVideoThumbnailLayer.swift (90%) create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputMasksIcon.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputMasksIcon.imageset/mask.pdf delete mode 100644 submodules/TelegramUI/Sources/ChatInputNode.swift diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 5226eaafc6..75490fae3d 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -8478,11 +8478,13 @@ Sorry for the inconvenience."; "Conversation.SuggestedPhotoText" = "%@ suggests you to use this photo for your Telegram account."; "Conversation.SuggestedPhotoTextYou" = "You suggested %@ to use this photo for their Telegram account."; "Conversation.SuggestedPhotoView" = "View"; +"Conversation.SuggestedPhotoSuccess" = "You have updated your Telegram account photo."; "Conversation.SuggestedVideoTitle" = "Suggested Video"; "Conversation.SuggestedVideoText" = "%@ suggests you to use this video for your Telegram account."; "Conversation.SuggestedVideoTextYou" = "You suggested %@ to use this video for their Telegram account."; "Conversation.SuggestedVideoView" = "View"; +"Conversation.SuggestedVideoSuccess" = "You have updated your Telegram account video."; "CacheEvictionMenu.CategoryExceptions_1" = "%@ Exception"; "CacheEvictionMenu.CategoryExceptions_any" = "%@ Exceptions"; @@ -8493,9 +8495,6 @@ Sorry for the inconvenience."; "Notification.SuggestedProfilePhoto" = "Suggested Profile Photo"; "Notification.SuggestedProfileVideo" = "Suggested Profile Video"; -"PhotoEditor.SetProfilePhoto" = "Set Profile Photo"; -"PhotoEditor.SetProfileVideo" = "Set Profile Video"; - "PhotoEditor.SetAsMyPhoto" = "Set as My Photo"; "PhotoEditor.SetAsMyVideo" = "Set as My Video"; @@ -8504,8 +8503,13 @@ Sorry for the inconvenience."; "Privacy.ProfilePhoto.SetPublicPhoto" = "Set Public Photo"; "Privacy.ProfilePhoto.UpdatePublicPhoto" = "Update Public Photo"; "Privacy.ProfilePhoto.RemovePublicPhoto" = "Remove Public Photo"; +"Privacy.ProfilePhoto.RemovePublicVideo" = "Remove Public Video"; "Privacy.ProfilePhoto.PublicPhotoInfo" = "You can upload a public photo for those who are restricted from viewing your real profile photo."; +"Privacy.ProfilePhoto.PublicPhotoSuccess" = "This photo is now set for those who are restricted from viewing your main photo."; +"Privacy.ProfilePhoto.PublicVideoSuccess" = "This video is now set for those who are restricted from viewing your main photo."; "WebApp.AddToAttachmentAllowMessages" = "Allow **%@** to send me messages"; "Common.Paste" = "Paste"; + +"PhotoEditor.SelectCoverFrameSuggestion" = "Choose a cover for profile video"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 3257a7d587..0d139dc0fd 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -706,6 +706,13 @@ public enum ChatListSearchFilter: Equatable { } } +public enum InstalledStickerPacksControllerMode { + case general + case modal + case masks + case emoji +} + public let defaultContactLabel: String = "_$!!$_" public enum CreateGroupMode { @@ -812,6 +819,8 @@ public protocol SharedAccountContext: AnyObject { func makeProxySettingsController(sharedContext: SharedAccountContext, account: UnauthorizedAccount) -> ViewController + func makeInstalledStickerPacksController(context: AccountContext, mode: InstalledStickerPacksControllerMode) -> ViewController + func navigateToCurrentCall() var hasOngoingCall: ValuePromise { get } var immediateHasOngoingCall: Bool { get } diff --git a/submodules/AppLock/Sources/AppLock.swift b/submodules/AppLock/Sources/AppLock.swift index 917ddb7537..11bd653501 100644 --- a/submodules/AppLock/Sources/AppLock.swift +++ b/submodules/AppLock/Sources/AppLock.swift @@ -316,8 +316,8 @@ public final class AppLockContextImpl: AppLockContext { public var invalidAttempts: Signal { return self.currentState.get() |> map { state in - return state.unlockAttemts.flatMap { unlockAttemts in - return AccessChallengeAttempts(count: unlockAttemts.count, bootTimestamp: unlockAttemts.timestamp.bootTimestamp, uptime: unlockAttemts.timestamp.uptime) + return state.unlockAttempts.flatMap { unlockAttempts in + return AccessChallengeAttempts(count: unlockAttempts.count, bootTimestamp: unlockAttempts.timestamp.bootTimestamp, uptime: unlockAttempts.timestamp.uptime) } } } @@ -346,7 +346,7 @@ public final class AppLockContextImpl: AppLockContext { self.updateLockState { state in var state = state - state.unlockAttemts = nil + state.unlockAttempts = nil state.isManuallyLocked = false @@ -362,16 +362,16 @@ public final class AppLockContextImpl: AppLockContext { public func failedUnlockAttempt() { self.updateLockState { state in var state = state - var unlockAttemts = state.unlockAttemts ?? UnlockAttempts(count: 0, timestamp: MonotonicTimestamp(bootTimestamp: 0, uptime: 0)) + var unlockAttempts = state.unlockAttempts ?? UnlockAttempts(count: 0, timestamp: MonotonicTimestamp(bootTimestamp: 0, uptime: 0)) - unlockAttemts.count += 1 + unlockAttempts.count += 1 var bootTimestamp: Int32 = 0 let uptime = getDeviceUptimeSeconds(&bootTimestamp) let timestamp = MonotonicTimestamp(bootTimestamp: bootTimestamp, uptime: uptime) - unlockAttemts.timestamp = timestamp - state.unlockAttemts = unlockAttemts + unlockAttempts.timestamp = timestamp + state.unlockAttempts = unlockAttempts return state } } diff --git a/submodules/AppLockState/Sources/AppLockState.swift b/submodules/AppLockState/Sources/AppLockState.swift index c632725ab1..3db956b630 100644 --- a/submodules/AppLockState/Sources/AppLockState.swift +++ b/submodules/AppLockState/Sources/AppLockState.swift @@ -24,13 +24,13 @@ public struct UnlockAttempts: Codable, Equatable { public struct LockState: Codable, Equatable { public var isManuallyLocked: Bool public var autolockTimeout: Int32? - public var unlockAttemts: UnlockAttempts? + public var unlockAttempts: UnlockAttempts? public var applicationActivityTimestamp: MonotonicTimestamp? public init(isManuallyLocked: Bool = false, autolockTimeout: Int32? = nil, unlockAttemts: UnlockAttempts? = nil, applicationActivityTimestamp: MonotonicTimestamp? = nil) { self.isManuallyLocked = isManuallyLocked self.autolockTimeout = autolockTimeout - self.unlockAttemts = unlockAttemts + self.unlockAttempts = unlockAttemts self.applicationActivityTimestamp = applicationActivityTimestamp } } diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index e2f2e6a228..322e30e6f6 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -781,7 +781,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { )), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) } case let .addContact(phoneNumber, theme, strings): - return ContactsAddItem(theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { + return ContactsAddItem(context: context, theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { interaction.addContact(phoneNumber) }) } diff --git a/submodules/ChatPresentationInterfaceState/BUILD b/submodules/ChatPresentationInterfaceState/BUILD index a6c4d04ec5..2462d80ce2 100644 --- a/submodules/ChatPresentationInterfaceState/BUILD +++ b/submodules/ChatPresentationInterfaceState/BUILD @@ -19,6 +19,7 @@ swift_library( "//submodules/ChatInterfaceState:ChatInterfaceState", "//submodules/TelegramUIPreferences:TelegramUIPreferences", "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/StickerPeekUI:StickerPeekUI", ], visibility = [ "//visibility:public", diff --git a/submodules/ChatPresentationInterfaceState/Sources/ChatMediaInputNodeInteraction.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatMediaInputNodeInteraction.swift new file mode 100644 index 0000000000..62abff0ed4 --- /dev/null +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatMediaInputNodeInteraction.swift @@ -0,0 +1,61 @@ +import Foundation +import Postbox +import StickerPeekUI +import TelegramUIPreferences + +public struct ChatInterfaceStickerSettings: Equatable { + public let loopAnimatedStickers: Bool + + public init(loopAnimatedStickers: Bool) { + self.loopAnimatedStickers = loopAnimatedStickers + } + + public init(stickerSettings: StickerSettings) { + self.loopAnimatedStickers = stickerSettings.loopAnimatedStickers + } + + public static func ==(lhs: ChatInterfaceStickerSettings, rhs: ChatInterfaceStickerSettings) -> Bool { + return lhs.loopAnimatedStickers == rhs.loopAnimatedStickers + } +} + +public enum ChatMediaInputGifMode: Equatable { + case recent + case trending + case emojiSearch(String) +} + +public final class ChatMediaInputNodeInteraction { + public let navigateToCollectionId: (ItemCollectionId) -> Void + public let navigateBackToStickers: () -> Void + public let setGifMode: (ChatMediaInputGifMode) -> Void + public let openSettings: () -> Void + public let openTrending: (ItemCollectionId?) -> Void + public let dismissTrendingPacks: ([ItemCollectionId]) -> Void + public let toggleSearch: (Bool, ChatMediaInputSearchMode?, String) -> Void + public let openPeerSpecificSettings: () -> Void + public let dismissPeerSpecificSettings: () -> Void + public let clearRecentlyUsedStickers: () -> Void + + public var stickerSettings: ChatInterfaceStickerSettings? + public var highlightedStickerItemCollectionId: ItemCollectionId? + public var highlightedItemCollectionId: ItemCollectionId? + public var highlightedGifMode: ChatMediaInputGifMode = .recent + public var previewedStickerPackItem: StickerPreviewPeekItem? + public var appearanceTransition: CGFloat = 1.0 + public var displayStickerPlaceholder = true + public var displayStickerPackManageControls = true + + public init(navigateToCollectionId: @escaping (ItemCollectionId) -> Void, navigateBackToStickers: @escaping () -> Void, setGifMode: @escaping (ChatMediaInputGifMode) -> Void, openSettings: @escaping () -> Void, openTrending: @escaping (ItemCollectionId?) -> Void, dismissTrendingPacks: @escaping ([ItemCollectionId]) -> Void, toggleSearch: @escaping (Bool, ChatMediaInputSearchMode?, String) -> Void, openPeerSpecificSettings: @escaping () -> Void, dismissPeerSpecificSettings: @escaping () -> Void, clearRecentlyUsedStickers: @escaping () -> Void) { + self.navigateToCollectionId = navigateToCollectionId + self.navigateBackToStickers = navigateBackToStickers + self.setGifMode = setGifMode + self.openSettings = openSettings + self.openTrending = openTrending + self.dismissTrendingPacks = dismissTrendingPacks + self.toggleSearch = toggleSearch + self.openPeerSpecificSettings = openPeerSpecificSettings + self.dismissPeerSpecificSettings = dismissPeerSpecificSettings + self.clearRecentlyUsedStickers = clearRecentlyUsedStickers + } +} diff --git a/submodules/Components/PagerComponent/Sources/PagerComponent.swift b/submodules/Components/PagerComponent/Sources/PagerComponent.swift index ebe517155a..6a6eafe102 100644 --- a/submodules/Components/PagerComponent/Sources/PagerComponent.swift +++ b/submodules/Components/PagerComponent/Sources/PagerComponent.swift @@ -181,6 +181,7 @@ public final class PagerComponent>? public let externalTopPanelContainer: PagerExternalTopPanelContainer? public let bottomPanel: AnyComponent>? + public let externalBottomPanelContainer: PagerExternalTopPanelContainer? public let panelStateUpdated: ((PagerComponentPanelState, Transition) -> Void)? public let isTopPanelExpandedUpdated: (Bool, Transition) -> Void public let isTopPanelHiddenUpdated: (Bool, Transition) -> Void @@ -198,6 +199,7 @@ public final class PagerComponent>?, externalTopPanelContainer: PagerExternalTopPanelContainer?, bottomPanel: AnyComponent>?, + externalBottomPanelContainer: PagerExternalTopPanelContainer?, panelStateUpdated: ((PagerComponentPanelState, Transition) -> Void)?, isTopPanelExpandedUpdated: @escaping (Bool, Transition) -> Void, isTopPanelHiddenUpdated: @escaping (Bool, Transition) -> Void, @@ -214,6 +216,7 @@ public final class PagerComponent>() self.bottomPanelView = bottomPanelView - self.addSubview(bottomPanelView) } + + let bottomPanelSuperview = component.externalBottomPanelContainer ?? self + if bottomPanelView.superview !== bottomPanelSuperview { + bottomPanelSuperview.addSubview(bottomPanelView) + } + let bottomPanelSize = bottomPanelView.update( transition: bottomPanelTransition, component: bottomPanel, diff --git a/submodules/ContactListUI/Sources/ContactAddItem.swift b/submodules/ContactListUI/Sources/ContactAddItem.swift index ec494117f8..8af09937ad 100644 --- a/submodules/ContactListUI/Sources/ContactAddItem.swift +++ b/submodules/ContactListUI/Sources/ContactAddItem.swift @@ -7,10 +7,12 @@ import TelegramCore import TelegramPresentationData import AppBundle import PhoneNumberFormat +import AccountContext private let titleFont = Font.regular(17.0) public class ContactsAddItem: ListViewItem { + let context: AccountContext let theme: PresentationTheme let strings: PresentationStrings let phoneNumber: String @@ -18,7 +20,8 @@ public class ContactsAddItem: ListViewItem { public let header: ListViewItemHeader? - public init(theme: PresentationTheme, strings: PresentationStrings, phoneNumber: String, header: ListViewItemHeader?, action: @escaping () -> Void) { + public init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, phoneNumber: String, header: ListViewItemHeader?, action: @escaping () -> Void) { + self.context = context self.theme = theme self.strings = strings self.phoneNumber = phoneNumber @@ -187,7 +190,7 @@ class ContactsAddItemNode: ListViewItemNode { let leftInset: CGFloat = 65.0 + params.leftInset let rightInset: CGFloat = 10.0 + params.rightInset - let titleAttributedString = NSAttributedString(string: item.strings.Contacts_AddPhoneNumber(formatPhoneNumber(item.phoneNumber)).string, font: titleFont, textColor: item.theme.list.itemAccentColor) + let titleAttributedString = NSAttributedString(string: item.strings.Contacts_AddPhoneNumber(formatPhoneNumber(context: item.context, number: item.phoneNumber)).string, font: titleFont, textColor: item.theme.list.itemAccentColor) let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(0.0, params.width - leftInset - rightInset), height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) diff --git a/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift b/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift index c01bd6b402..3333f44ac5 100644 --- a/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift +++ b/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift @@ -124,7 +124,7 @@ private enum ContactListSearchEntry: Comparable, Identifiable { func item(context: AccountContext, presentationData: PresentationData, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, timeFormat: PresentationDateTimeFormat, addContact: ((String) -> Void)?, openPeer: @escaping (ContactListPeer) -> Void, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?) -> ListViewItem { switch self { case let .addContact(theme, strings, phoneNumber): - return ContactsAddItem(theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { + return ContactsAddItem(context: context, theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { addContact?(phoneNumber) }) case let .peer(_, theme, strings, peer, presence, group, enabled): diff --git a/submodules/DrawingUI/BUILD b/submodules/DrawingUI/BUILD index 693fae0b44..aa23b25d28 100644 --- a/submodules/DrawingUI/BUILD +++ b/submodules/DrawingUI/BUILD @@ -86,6 +86,10 @@ swift_library( "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode", "//submodules/StickerResources:StickerResources", "//submodules/ImageBlur:ImageBlur", + "//submodules/TextFormat:TextFormat", + "//submodules/TelegramUI/Components/EmojiTextAttachmentView:EmojiTextAttachmentView", + "//submodules/TelegramUI/Components/ChatEntityKeyboardInputNode:ChatEntityKeyboardInputNode", + "//submodules/FeaturedStickersScreen:FeaturedStickersScreen", ], visibility = [ "//visibility:public", diff --git a/submodules/DrawingUI/Sources/ColorPickerScreen.swift b/submodules/DrawingUI/Sources/ColorPickerScreen.swift index 1e4ea084a7..4be2876086 100644 --- a/submodules/DrawingUI/Sources/ColorPickerScreen.swift +++ b/submodules/DrawingUI/Sources/ColorPickerScreen.swift @@ -2274,10 +2274,10 @@ private final class ColorPickerContent: CombinedComponent { let swatch1Button = swatch1Button.update( component: ColorSwatchComponent( - type: .pallete(false), + type: .pallete(state.selectedColor == DrawingColor(color: .black)), color: DrawingColor(color: .black), action: { - + state.updateColor(DrawingColor(color: .black)) } ), availableSize: CGSize(width: 30.0, height: 30.0), @@ -2290,10 +2290,10 @@ private final class ColorPickerContent: CombinedComponent { let swatch2Button = swatch2Button.update( component: ColorSwatchComponent( - type: .pallete(false), + type: .pallete(state.selectedColor == DrawingColor(rgb: 0x0161fd)), color: DrawingColor(rgb: 0x0161fd), action: { - + state.updateColor(DrawingColor(rgb: 0x0161fd)) } ), availableSize: CGSize(width: 30.0, height: 30.0), @@ -2306,9 +2306,10 @@ private final class ColorPickerContent: CombinedComponent { let swatch3Button = swatch3Button.update( component: ColorSwatchComponent( - type: .pallete(false), + type: .pallete(state.selectedColor == DrawingColor(rgb: 0x32c759)), color: DrawingColor(rgb: 0x32c759), action: { + state.updateColor(DrawingColor(rgb: 0x32c759)) } ), availableSize: CGSize(width: 30.0, height: 30.0), @@ -2321,9 +2322,10 @@ private final class ColorPickerContent: CombinedComponent { let swatch4Button = swatch4Button.update( component: ColorSwatchComponent( - type: .pallete(false), + type: .pallete(state.selectedColor == DrawingColor(rgb: 0xffcc02)), color: DrawingColor(rgb: 0xffcc02), action: { + state.updateColor(DrawingColor(rgb: 0xffcc02)) } ), availableSize: CGSize(width: 30.0, height: 30.0), @@ -2336,9 +2338,10 @@ private final class ColorPickerContent: CombinedComponent { let swatch5Button = swatch5Button.update( component: ColorSwatchComponent( - type: .pallete(false), + type: .pallete(state.selectedColor == DrawingColor(rgb: 0xff3a30)), color: DrawingColor(rgb: 0xff3a30), action: { + state.updateColor(DrawingColor(rgb: 0xff3a30)) } ), availableSize: CGSize(width: 30.0, height: 30.0), diff --git a/submodules/DrawingUI/Sources/DrawingBubbleEntity.swift b/submodules/DrawingUI/Sources/DrawingBubbleEntity.swift index c453ee95fa..025f6b9f18 100644 --- a/submodules/DrawingUI/Sources/DrawingBubbleEntity.swift +++ b/submodules/DrawingUI/Sources/DrawingBubbleEntity.swift @@ -3,8 +3,20 @@ import UIKit import Display import AccountContext -final class DrawingBubbleEntity: DrawingEntity { - public enum DrawType { +final class DrawingBubbleEntity: DrawingEntity, Codable { + private enum CodingKeys: String, CodingKey { + case uuid + case drawType + case color + case lineWidth + case referenceDrawingSize + case position + case size + case rotation + case tailPosition + } + + public enum DrawType: Codable { case fill case stroke } @@ -22,6 +34,10 @@ final class DrawingBubbleEntity: DrawingEntity { var rotation: CGFloat var tailPosition: CGPoint + var center: CGPoint { + return self.position + } + init(drawType: DrawType, color: DrawingColor, lineWidth: CGFloat) { self.uuid = UUID() self.isAnimated = false @@ -37,10 +53,33 @@ final class DrawingBubbleEntity: DrawingEntity { self.tailPosition = CGPoint(x: 0.16, y: 0.18) } - var center: CGPoint { - return self.position + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.uuid = try container.decode(UUID.self, forKey: .uuid) + self.isAnimated = false + self.drawType = try container.decode(DrawType.self, forKey: .drawType) + self.color = try container.decode(DrawingColor.self, forKey: .color) + self.lineWidth = try container.decode(CGFloat.self, forKey: .lineWidth) + self.referenceDrawingSize = try container.decode(CGSize.self, forKey: .referenceDrawingSize) + self.position = try container.decode(CGPoint.self, forKey: .position) + self.size = try container.decode(CGSize.self, forKey: .size) + self.rotation = try container.decode(CGFloat.self, forKey: .rotation) + self.tailPosition = try container.decode(CGPoint.self, forKey: .tailPosition) } + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.uuid, forKey: .uuid) + try container.encode(self.drawType, forKey: .drawType) + try container.encode(self.color, forKey: .color) + try container.encode(self.lineWidth, forKey: .lineWidth) + try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize) + try container.encode(self.position, forKey: .position) + try container.encode(self.size, forKey: .size) + try container.encode(self.rotation, forKey: .rotation) + try container.encode(self.tailPosition, forKey: .tailPosition) + } + func duplicate() -> DrawingEntity { let newEntity = DrawingBubbleEntity(drawType: self.drawType, color: self.color, lineWidth: self.lineWidth) newEntity.referenceDrawingSize = self.referenceDrawingSize @@ -138,9 +177,6 @@ final class DrawingBubbleEntityView: DrawingEntityView { return } -// let scale = self.superview?.superview?.layer.value(forKeyPath: "transform.scale.x") as? CGFloat ?? 1.0 -// selectionView.scale = scale - selectionView.transform = CGAffineTransformMakeRotation(self.bubbleEntity.rotation) selectionView.setNeedsLayout() } diff --git a/submodules/DrawingUI/Sources/DrawingEntitiesView.swift b/submodules/DrawingUI/Sources/DrawingEntitiesView.swift index d3b374f176..2e97341dc3 100644 --- a/submodules/DrawingUI/Sources/DrawingEntitiesView.swift +++ b/submodules/DrawingUI/Sources/DrawingEntitiesView.swift @@ -17,10 +17,102 @@ protocol DrawingEntity: AnyObject { func makeView(context: AccountContext) -> DrawingEntityView } +enum CodableDrawingEntity { + case sticker(DrawingStickerEntity) + case text(DrawingTextEntity) + case simpleShape(DrawingSimpleShapeEntity) + case bubble(DrawingBubbleEntity) + case vector(DrawingVectorEntity) + + init?(entity: DrawingEntity) { + if let entity = entity as? DrawingStickerEntity { + self = .sticker(entity) + } else if let entity = entity as? DrawingTextEntity { + self = .text(entity) + } else if let entity = entity as? DrawingSimpleShapeEntity { + self = .simpleShape(entity) + } else if let entity = entity as? DrawingBubbleEntity { + self = .bubble(entity) + } else if let entity = entity as? DrawingVectorEntity { + self = .vector(entity) + } else { + return nil + } + } + + var entity: DrawingEntity { + switch self { + case let .sticker(entity): + return entity + case let .text(entity): + return entity + case let .simpleShape(entity): + return entity + case let .bubble(entity): + return entity + case let .vector(entity): + return entity + } + } +} + +extension CodableDrawingEntity: Codable { + private enum CodingKeys: String, CodingKey { + case type + case entity + } + + private enum EntityType: Int, Codable { + case sticker + case text + case simpleShape + case bubble + case vector + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let type = try container.decode(EntityType.self, forKey: .type) + switch type { + case .sticker: + self = .sticker(try container.decode(DrawingStickerEntity.self, forKey: .entity)) + case .text: + self = .text(try container.decode(DrawingTextEntity.self, forKey: .entity)) + case .simpleShape: + self = .simpleShape(try container.decode(DrawingSimpleShapeEntity.self, forKey: .entity)) + case .bubble: + self = .bubble(try container.decode(DrawingBubbleEntity.self, forKey: .entity)) + case .vector: + self = .vector(try container.decode(DrawingVectorEntity.self, forKey: .entity)) + } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case let .sticker(payload): + try container.encode(EntityType.sticker, forKey: .type) + try container.encode(payload, forKey: .entity) + case let .text(payload): + try container.encode(EntityType.text, forKey: .type) + try container.encode(payload, forKey: .entity) + case let .simpleShape(payload): + try container.encode(EntityType.simpleShape, forKey: .type) + try container.encode(payload, forKey: .entity) + case let .bubble(payload): + try container.encode(EntityType.bubble, forKey: .type) + try container.encode(payload, forKey: .entity) + case let .vector(payload): + try container.encode(EntityType.vector, forKey: .type) + try container.encode(payload, forKey: .entity) + } + } +} + public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView { private let context: AccountContext private let size: CGSize - + weak var selectionContainerView: DrawingSelectionContainerView? private var tapGestureRecognizer: UITapGestureRecognizer! @@ -30,16 +122,12 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView { var selectionChanged: (DrawingEntity?) -> Void = { _ in } var requestedMenuForEntityView: (DrawingEntityView, Bool) -> Void = { _, _ in } - init(context: AccountContext, size: CGSize, entities: [DrawingEntity] = []) { + public init(context: AccountContext, size: CGSize) { self.context = context self.size = size super.init(frame: CGRect(origin: .zero, size: size)) - for entity in entities { - self.add(entity) - } - let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:))) self.addGestureRecognizer(tapGestureRecognizer) self.tapGestureRecognizer = tapGestureRecognizer @@ -48,7 +136,7 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView { required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + var entities: [DrawingEntity] { var entities: [DrawingEntity] = [] for case let view as DrawingEntityView in self.subviews { @@ -57,6 +145,26 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView { return entities } + public func setup(withEntitiesData entitiesData: Data!) { + self.clear() + + if let entitiesData = entitiesData, let codableEntities = try? JSONDecoder().decode([CodableDrawingEntity].self, from: entitiesData) { + let entities = codableEntities.map { $0.entity } + for entity in entities { + self.add(entity) + } + } + } + + var entitiesData: Data? { + let entities = self.entities.compactMap({ CodableDrawingEntity(entity: $0) }) + if let data = try? JSONEncoder().encode(entities) { + return data + } else { + return nil + } + } + private func startPosition(relativeTo entity: DrawingEntity?) -> CGPoint { let offsetLength = round(self.size.width * 0.1) let offset = CGPoint(x: offsetLength, y: offsetLength) @@ -118,7 +226,11 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView { sticker.position = center if setup { sticker.referenceDrawingSize = self.size - sticker.scale = 1.0 + if !sticker.file.isVideoSticker && sticker.file.mimeType.hasSuffix("webm") { + sticker.scale = 4.0 + } else { + sticker.scale = 1.0 + } } } else if let bubble = entity as? DrawingBubbleEntity { bubble.position = center @@ -171,11 +283,15 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView { } func removeAll() { + self.clear() + self.selectionChanged(nil) + self.hasSelectionChanged(false) + } + + private func clear() { for case let view as DrawingEntityView in self.subviews { view.removeFromSuperview() } - self.selectionChanged(nil) - self.hasSelectionChanged(false) } func bringToFront(uuid: UUID) { @@ -222,7 +338,7 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView { view.updateVisibility(visibility) } } - + @objc private func handleTap(_ gestureRecognzier: UITapGestureRecognizer) { let location = gestureRecognzier.location(in: self) @@ -256,7 +372,7 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView { } } } - + if let entity = entity, let entityView = self.getView(for: entity.uuid) { self.selectedEntityView = entityView @@ -298,7 +414,7 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView { } return result } - + public func clearSelection() { self.selectEntity(nil) } @@ -382,7 +498,7 @@ public class DrawingEntityView: UIView { return } self.pushIdentityTransformForMeasurement() - + selectionView.transform = .identity let bounds = self.selectionBounds let center = bounds.center diff --git a/submodules/DrawingUI/Sources/DrawingGesture.swift b/submodules/DrawingUI/Sources/DrawingGesture.swift index 0096844959..c80ce44233 100644 --- a/submodules/DrawingUI/Sources/DrawingGesture.swift +++ b/submodules/DrawingUI/Sources/DrawingGesture.swift @@ -969,7 +969,7 @@ struct Polyline { self.azimuth = azimuth self.touchPoint = touchPoint } - + init(touchPoint: TouchPath.Point) { self.location = touchPoint.event.location self.force = touchPoint.event.force @@ -989,6 +989,17 @@ struct Polyline { touchPoint: self.touchPoint ) } + + func withLocation(_ point: CGPoint) -> Polyline.Point { + return Point( + location: point, + force: self.force, + altitudeAngle: self.altitudeAngle, + azimuth: self.azimuth, + velocity: self.velocity, + touchPoint: self.touchPoint + ) + } } public internal(set) var isComplete: Bool @@ -1023,9 +1034,8 @@ struct Polyline { } init(points: [Point]) { - assert(!points.isEmpty) self.isComplete = true - self.touchIdentifier = points.first!.event.touchIdentifier + self.touchIdentifier = points.first?.event.touchIdentifier ?? "" self.points = points } @@ -1063,7 +1073,6 @@ struct Polyline { } } - // Remove points from the end of the list toward the beginning for index in indexesToRemove.reversed() { guard index < points.count else { print("Error: unknown polyline index \(index)") diff --git a/submodules/DrawingUI/Sources/DrawingMetalView.swift b/submodules/DrawingUI/Sources/DrawingMetalView.swift index 1c548b6262..f181aeda4b 100644 --- a/submodules/DrawingUI/Sources/DrawingMetalView.swift +++ b/submodules/DrawingUI/Sources/DrawingMetalView.swift @@ -2,9 +2,10 @@ import Foundation import UIKit import QuartzCore import MetalKit +import Display import AppBundle -public final class DrawingMetalView: MTKView { +final class DrawingMetalView: MTKView { private let size: CGSize private let commandQueue: MTLCommandQueue @@ -20,7 +21,7 @@ public final class DrawingMetalView: MTKView { private var markerBrush: Brush? private var pencilBrush: Brush? - public init?(size: CGSize) { + init?(size: CGSize) { var size = size if Int(size.width) % 16 != 0 { size.width = round(size.width / 16.0) * 16.0 @@ -74,12 +75,22 @@ public final class DrawingMetalView: MTKView { } func drawInContext(_ cgContext: CGContext) { - guard let texture = self.drawable?.texture, let ciImage = CIImage(mtlTexture: texture, options: [.colorSpace: CGColorSpaceCreateDeviceRGB()])?.oriented(forExifOrientation: 1) else { +// guard let texture = self.drawable?.texture, let ciImage = CIImage(mtlTexture: texture, options: [.colorSpace: CGColorSpaceCreateDeviceRGB()])?.oriented(forExifOrientation: 1) else { +// return +// } +// let context = CIContext(cgContext: cgContext) +// let rect = CGRect(origin: .zero, size: ciImage.extent.size) +// context.draw(ciImage, in: rect, from: rect) + guard let texture = self.drawable?.texture, let image = texture.createCGImage() else { return } - let context = CIContext(cgContext: cgContext) - let rect = CGRect(origin: .zero, size: ciImage.extent.size) - context.draw(ciImage, in: rect, from: rect) + let rect = CGRect(origin: .zero, size: CGSize(width: image.width, height: image.height)) + cgContext.saveGState() + cgContext.translateBy(x: rect.midX, y: rect.midY) + cgContext.scaleBy(x: 1.0, y: -1.0) + cgContext.translateBy(x: -rect.midX, y: -rect.midY) + cgContext.draw(image, in: rect) + cgContext.restoreGState() } private func setup() { @@ -116,7 +127,7 @@ public final class DrawingMetalView: MTKView { self.penBrush = Brush(texture: nil, target: self, rotation: .ahead) if let url = getAppBundle().url(forResource: "marker", withExtension: "png"), let data = try? Data(contentsOf: url) { - self.markerBrush = Brush(texture: self.makeTexture(with: data), target: self, rotation: .fixed(0.0)) + self.markerBrush = Brush(texture: self.makeTexture(with: data), target: self, rotation: .fixed(-0.55)) } if let url = getAppBundle().url(forResource: "pencil", withExtension: "png"), let data = try? Data(contentsOf: url) { @@ -124,10 +135,11 @@ public final class DrawingMetalView: MTKView { } } - override public func draw(_ rect: CGRect) { + var clearOnce = false + override func draw(_ rect: CGRect) { super.draw(rect) - guard let drawable = self.drawable, let texture = drawable.texture else { + guard let drawable = self.drawable, let texture = drawable.texture?.texture else { return } @@ -138,6 +150,10 @@ public final class DrawingMetalView: MTKView { attachment?.loadAction = .clear attachment?.storeAction = .store + guard let _ = attachment?.texture else { + return + } + let commandBuffer = self.commandQueue.makeCommandBuffer() let commandEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: renderPassDescriptor) @@ -160,10 +176,11 @@ public final class DrawingMetalView: MTKView { return } + self.clearOnce = true drawable.updateBuffer(with: self.size) drawable.clear() - drawable.commit() + drawable.commit(wait: true) } enum BrushType { @@ -182,10 +199,21 @@ public final class DrawingMetalView: MTKView { self.pencilBrush?.updated(point, color: color, state: state, size: size) } } + + func setup(_ points: [CGPoint], brush: BrushType, color: DrawingColor, size: CGFloat) { + switch brush { + case .pen: + self.penBrush?.setup(points, color: color, size: size) + case .marker: + self.markerBrush?.setup(points, color: color, size: size) + case .pencil: + self.pencilBrush?.setup(points, color: color, size: size) + } + } } private class Drawable { - public private(set) var texture: MTLTexture? + public private(set) var texture: Texture? internal var pixelFormat: MTLPixelFormat = .bgra8Unorm internal var size: CGSize @@ -199,12 +227,12 @@ private class Drawable { self.size = size self.pixelFormat = pixelFormat self.device = device - self.texture = self.makeColorTexture(DrawingColor.clear) + self.texture = self.makeTexture() self.commandQueue = device?.makeCommandQueue() self.renderPassDescriptor = MTLRenderPassDescriptor() let attachment = self.renderPassDescriptor?.colorAttachments[0] - attachment?.texture = self.texture + attachment?.texture = self.texture?.texture attachment?.loadAction = .load attachment?.storeAction = .store @@ -212,9 +240,8 @@ private class Drawable { } func clear() { - self.texture = self.makeColorTexture(DrawingColor.clear) - self.renderPassDescriptor?.colorAttachments[0].texture = self.texture - self.commit() + self.texture?.clear() + self.commit(wait: true) } internal func updateBuffer(with size: CGSize) { @@ -226,49 +253,40 @@ private class Drawable { internal func prepareForDraw() { if self.commandBuffer == nil { - self.commandBuffer = commandQueue?.makeCommandBuffer() + self.commandBuffer = self.commandQueue?.makeCommandBuffer() } } internal func makeCommandEncoder() -> MTLRenderCommandEncoder? { - guard let commandBuffer = self.commandBuffer, let rpd = renderPassDescriptor else { + guard let commandBuffer = self.commandBuffer, let renderPassDescriptor = self.renderPassDescriptor else { return nil } - return commandBuffer.makeRenderCommandEncoder(descriptor: rpd) + return commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) } - internal func commit() { + internal func commit(wait: Bool = false) { self.commandBuffer?.commit() + if wait { + self.commandBuffer?.waitUntilCompleted() + } self.commandBuffer = nil } - internal func makeColorTexture(_ color: DrawingColor) -> MTLTexture? { - guard self.size.width * self.size.height > 0 else { + internal func makeTexture() -> Texture? { + guard self.size.width * self.size.height > 0, let device = self.device else { return nil } - let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor( - pixelFormat: self.pixelFormat, - width: Int(self.size.width), - height: Int(self.size.height), - mipmapped: false - ) - textureDescriptor.usage = [.renderTarget, .shaderRead] - guard let texture = device?.makeTexture(descriptor: textureDescriptor) else { - return nil - } - let region = MTLRegion( - origin: MTLOrigin(x: 0, y: 0, z: 0), - size: MTLSize(width: texture.width, height: texture.height, depth: 1) - ) - let bytesPerRow = 4 * texture.width - let data = Data(capacity: Int(bytesPerRow * texture.height)) - if let bytes = data.withUnsafeBytes({ $0.baseAddress }) { - texture.replace(region: region, mipmapLevel: 0, withBytes: bytes, bytesPerRow: bytesPerRow) - } - return texture + return Texture(device: device, width: Int(self.size.width), height: Int(self.size.height)) } } +private func alignUp(size: Int, align: Int) -> Int { + precondition(((align - 1) & align) == 0, "Align must be a power of two") + + let alignmentMask = align - 1 + return (size + alignmentMask) & ~alignmentMask +} + private class Brush { private(set) var texture: MTLTexture? private(set) var pipelineState: MTLRenderPipelineState! @@ -356,7 +374,7 @@ private class Brush { switch state { case .began: self.bezier.begin(with: point) - self.pushPoint(point, color: color, size: size, isEnd: false) + let _ = self.pushPoint(point, color: color, size: size, isEnd: false) case .changed: if self.bezier.points.count > 0 && point != lastRenderedPoint { self.pushPoint(point, color: color, size: size, isEnd: false) @@ -370,6 +388,45 @@ private class Brush { } } + func setup(_ inputPoints: [CGPoint], color: DrawingColor, size: CGFloat) { + guard inputPoints.count >= 2 else { + return + } + var pointStep: CGFloat + if case .random = self.rotation { + pointStep = size * 0.1 + } else { + pointStep = 2.0 + } + + var lines: [Line] = [] + + var previousPoint = inputPoints[0] + + var points: [CGPoint] = [] + self.bezier.begin(with: inputPoints.first!) + for point in inputPoints { + let smoothPoints = self.bezier.pushPoint(point) + points.append(contentsOf: smoothPoints) + } + self.bezier.finish() + + for i in 1 ..< points.count { + let p = points[i] + if (i == points.count - 1) || pointStep <= 1 || (pointStep > 1 && previousPoint.distance(to: p) >= pointStep) { + let line = Line(start: previousPoint, end: p, pointSize: size, pointStep: pointStep) + lines.append(line) + previousPoint = p + } + } + + if let drawable = self.target?.drawable { + let stroke = Stroke(color: color, lines: lines, target: drawable) + self.render(stroke: stroke, in: drawable) + drawable.commit(wait: true) + } + } + private var lastRenderedPoint: CGPoint? func pushPoint(_ point: CGPoint, color: DrawingColor, size: CGFloat, isEnd: Bool) { var pointStep: CGFloat @@ -396,9 +453,7 @@ private class Brush { if let drawable = self.target?.drawable { let stroke = Stroke(color: color, lines: lines, target: drawable) - self.render(stroke: stroke, in: drawable) - drawable.commit() } } @@ -489,8 +544,7 @@ class BezierGenerator { return [] } step += 1 - let result = genericPathPoints() - return result + return self.generateSmoothPathPoints() } func finish() { @@ -501,7 +555,7 @@ class BezierGenerator { var points: [CGPoint] = [] private var step = 0 - private func genericPathPoints() -> [CGPoint] { + private func generateSmoothPathPoints() -> [CGPoint] { var begin: CGPoint var control: CGPoint let end = CGPoint.middle(p1: points[step], p2: points[step + 1]) @@ -552,3 +606,149 @@ private struct Line { return self.end.angle(to: self.start) } } + +final class Texture { +#if !targetEnvironment(simulator) + let buffer: MTLBuffer +#endif + + let width: Int + let height: Int + let bytesPerRow: Int + let texture: MTLTexture + + init?(device: MTLDevice, width: Int, height: Int) { + let bytesPerPixel = 4 + let pixelRowAlignment = device.minimumLinearTextureAlignment(for: .bgra8Unorm) + let bytesPerRow = alignUp(size: width * bytesPerPixel, align: pixelRowAlignment) + + self.width = width + self.height = height + self.bytesPerRow = bytesPerRow + + if #available(iOS 12.0, *) { +#if targetEnvironment(simulator) + let textureDescriptor = MTLTextureDescriptor() + textureDescriptor.textureType = .type2D + textureDescriptor.pixelFormat = .bgra8Unorm + textureDescriptor.width = width + textureDescriptor.height = height + textureDescriptor.usage = [.renderTarget, .shaderRead] + textureDescriptor.storageMode = .shared + + guard let texture = device.makeTexture(descriptor: textureDescriptor) else { + return nil + } +#else + guard let buffer = device.makeBuffer(length: bytesPerRow * height, options: MTLResourceOptions.storageModeShared) else { + return nil + } + self.buffer = buffer + + let textureDescriptor = MTLTextureDescriptor() + textureDescriptor.textureType = .type2D + textureDescriptor.pixelFormat = .bgra8Unorm + textureDescriptor.width = width + textureDescriptor.height = height + textureDescriptor.usage = [.renderTarget] + textureDescriptor.storageMode = buffer.storageMode + + guard let texture = buffer.makeTexture(descriptor: textureDescriptor, offset: 0, bytesPerRow: bytesPerRow) else { + return nil + } +#endif + + self.texture = texture + } else { + let textureDescriptor = MTLTextureDescriptor() + textureDescriptor.textureType = .type2D + textureDescriptor.pixelFormat = .bgra8Unorm + textureDescriptor.width = width + textureDescriptor.height = height + textureDescriptor.usage = [.renderTarget, .shaderRead] + textureDescriptor.storageMode = .shared + + guard let texture = device.makeTexture(descriptor: textureDescriptor) else { + return nil + } + + self.texture = texture + } + self.clear() + +// let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor( + // pixelFormat: self.pixelFormat, + // width: Int(self.size.width), + // height: Int(self.size.height), + // mipmapped: false + // ) + // textureDescriptor.usage = [.renderTarget, .shaderRead] + // guard let texture = device?.makeTexture(descriptor: textureDescriptor) else { + // return nil + // } + // let region = MTLRegion( + // origin: MTLOrigin(x: 0, y: 0, z: 0), + // size: MTLSize(width: texture.width, height: texture.height, depth: 1) + // ) + // let bytesPerRow = 4 * texture.width + // let data = Data(capacity: Int(bytesPerRow * texture.height)) + // if let bytes = data.withUnsafeBytes({ $0.baseAddress }) { + // texture.replace(region: region, mipmapLevel: 0, withBytes: bytes, bytesPerRow: bytesPerRow) + // } + // return texture + } + + func clear() { + let region = MTLRegion( + origin: MTLOrigin(x: 0, y: 0, z: 0), + size: MTLSize(width: self.width, height: self.height, depth: 1) + ) + let data = Data(capacity: Int(self.bytesPerRow * self.height)) + if let bytes = data.withUnsafeBytes({ $0.baseAddress }) { + self.texture.replace(region: region, mipmapLevel: 0, withBytes: bytes, bytesPerRow: self.bytesPerRow) + } + } + + func createCGImage() -> CGImage? { + #if targetEnvironment(simulator) + guard let data = NSMutableData(capacity: self.bytesPerRow * self.height) else { + return nil + } + data.length = self.bytesPerRow * self.height + self.texture.getBytes(data.mutableBytes, bytesPerRow: self.bytesPerRow, bytesPerImage: self.bytesPerRow * self.height, from: MTLRegion(origin: MTLOrigin(), size: MTLSize(width: self.width, height: self.height, depth: 1)), mipmapLevel: 0, slice: 0) + + guard let dataProvider = CGDataProvider(data: data as CFData) else { + return nil + } + #else + let content = self.content + let pool = self.pool + guard let dataProvider = CGDataProvider(data: Data(bytesNoCopy: self.buffer.contents(), count: self.buffer.length, deallocator: .custom { [weak pool] _, _ in +// guard let pool = pool else { +// return +// } +// pool.recycle(content: content) + }) as CFData) else { + return nil + } + #endif + + guard let image = CGImage( + width: Int(self.width), + height: Int(self.height), + bitsPerComponent: 8, + bitsPerPixel: 8 * 4, + bytesPerRow: self.bytesPerRow, + space: DeviceGraphicsContextSettings.shared.colorSpace, + bitmapInfo: DeviceGraphicsContextSettings.shared.transparentBitmapInfo, + provider: dataProvider, + decode: nil, + shouldInterpolate: true, + intent: .defaultIntent + ) else { + return nil + } + + return image + } +} diff --git a/submodules/DrawingUI/Sources/DrawingScreen.swift b/submodules/DrawingUI/Sources/DrawingScreen.swift index 4742b40c09..44f165c222 100644 --- a/submodules/DrawingUI/Sources/DrawingScreen.swift +++ b/submodules/DrawingUI/Sources/DrawingScreen.swift @@ -15,6 +15,7 @@ import ComponentDisplayAdapters import LottieAnimationComponent import ViewControllerComponent import ContextUI +import ChatEntityKeyboardInputNode enum DrawingToolState: Equatable { enum Key: CaseIterable { @@ -279,10 +280,10 @@ struct DrawingState: Equatable { return DrawingState( selectedTool: .pen, tools: [ - .pen(DrawingToolState.BrushState(color: DrawingColor(rgb: 0xe22400), size: 0.25, mode: .round)), - .marker(DrawingToolState.BrushState(color: DrawingColor(rgb: 0xfee21b), size: 0.5, mode: .round)), - .neon(DrawingToolState.BrushState(color: DrawingColor(rgb: 0x34ffab), size: 0.5, mode: .round)), - .pencil(DrawingToolState.BrushState(color: DrawingColor(rgb: 0x2570f0), size: 0.5, mode: .round)), + .pen(DrawingToolState.BrushState(color: DrawingColor(rgb: 0xe22400), size: 0.2, mode: .round)), + .marker(DrawingToolState.BrushState(color: DrawingColor(rgb: 0xfee21b), size: 0.75, mode: .round)), + .neon(DrawingToolState.BrushState(color: DrawingColor(rgb: 0x34ffab), size: 0.4, mode: .round)), + .pencil(DrawingToolState.BrushState(color: DrawingColor(rgb: 0x2570f0), size: 0.3, mode: .round)), .lasso, .eraser(DrawingToolState.EraserState(size: 0.5, mode: .bitmap)) ] @@ -308,6 +309,7 @@ enum DrawingScreenTransition { } private let undoButtonTag = GenericComponentViewTag() +private let redoButtonTag = GenericComponentViewTag() private let clearAllButtonTag = GenericComponentViewTag() private let colorButtonTag = GenericComponentViewTag() private let addButtonTag = GenericComponentViewTag() @@ -699,7 +701,7 @@ private final class DrawingScreenComponent: CombinedComponent { } func addTextEntity() { - let textEntity = DrawingTextEntity(text: "", style: .regular, font: .sanFrancisco, alignment: .center, fontSize: 1.0, color: self.currentColor) + let textEntity = DrawingTextEntity(text: NSAttributedString(), style: .regular, font: .sanFrancisco, alignment: .center, fontSize: 1.0, color: DrawingColor(color: .white)) self.insertEntity.invoke(textEntity) } @@ -811,6 +813,7 @@ private final class DrawingScreenComponent: CombinedComponent { style: DrawingTextStyle(style: textEntity.style), alignment: DrawingTextAlignment(alignment: textEntity.alignment), font: DrawingTextFont(font: textEntity.font), + isEmojiKeyboard: false, toggleStyle: { [weak state, weak textEntity] in guard let textEntity = textEntity else { return @@ -860,7 +863,8 @@ private final class DrawingScreenComponent: CombinedComponent { entityView.update() } state?.updated(transition: .easeInOut(duration: 0.2)) - } + }, + toggleKeyboard: nil ), availableSize: CGSize(width: context.availableSize.width - 84.0, height: 44.0), transition: context.transition @@ -1200,7 +1204,7 @@ private final class DrawingScreenComponent: CombinedComponent { action: { performAction.invoke(.redo) } - ).minSize(CGSize(width: 44.0, height: 44.0)), + ).minSize(CGSize(width: 44.0, height: 44.0)).tagged(redoButtonTag), availableSize: CGSize(width: 24.0, height: 24.0), transition: context.transition ) @@ -1368,37 +1372,40 @@ private final class DrawingScreenComponent: CombinedComponent { image = nil } - let brushModeButton = brushModeButton.update( - component: Button( - content: AnyComponent( - BrushButtonContent( - title: title, - image: image ?? UIImage() - ) - ), - action: { [weak state] in - guard let controller = controller() as? DrawingScreen else { - return - } - if let buttonView = controller.node.componentHost.findTaggedView(tag: brushModeButtonTag) as? Button.View { - if isEraser { - state?.presentEraserModePicker(buttonView) - } else { - state?.presentBrushModePicker(buttonView) + if [.pen, .eraser].contains(state.drawingState.selectedTool) { + let brushModeButton = brushModeButton.update( + component: Button( + content: AnyComponent( + BrushButtonContent( + title: title, + image: image ?? UIImage() + ) + ), + action: { [weak state] in + guard let controller = controller() as? DrawingScreen else { + return + } + if let buttonView = controller.node.componentHost.findTaggedView(tag: brushModeButtonTag) as? Button.View { + if isEraser { + state?.presentEraserModePicker(buttonView) + } else { + state?.presentBrushModePicker(buttonView) + } } } - } - ).minSize(CGSize(width: 44.0, height: 44.0)).tagged(brushModeButtonTag), - availableSize: CGSize(width: 75.0, height: 33.0), - transition: .immediate - ) - context.add(brushModeButton - .position(CGPoint(x: context.availableSize.width - environment.safeInsets.right - brushModeButton.size.width / 2.0 - 5.0, y: context.availableSize.height - environment.safeInsets.bottom - brushModeButton.size.height / 2.0 - 2.0 - UIScreenPixel)) - .appear(.default(alpha: true)) - .disappear(.default(alpha: true)) - ) - - modeRightInset += 35.0 + ).minSize(CGSize(width: 44.0, height: 44.0)).tagged(brushModeButtonTag), + availableSize: CGSize(width: 75.0, height: 33.0), + transition: .immediate + ) + context.add(brushModeButton + .position(CGPoint(x: context.availableSize.width - environment.safeInsets.right - brushModeButton.size.width / 2.0 - 5.0, y: context.availableSize.height - environment.safeInsets.bottom - brushModeButton.size.height / 2.0 - 2.0 - UIScreenPixel)) + .appear(.default(alpha: true)) + .disappear(.default(alpha: true)) + ) + modeRightInset += 35.0 + } else { + modeRightInset = 16.0 + } } else { var isFilled = false if let entity = state.selectedEntity as? DrawingSimpleShapeEntity, case .fill = entity.drawType { @@ -1662,7 +1669,7 @@ private final class DrawingScreenComponent: CombinedComponent { } public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { - fileprivate final class Node: ViewControllerTracingNode, FPSCounterDelegate { + fileprivate final class Node: ViewControllerTracingNode { private weak var controller: DrawingScreen? private let context: AccountContext private let updateState: ActionSlot @@ -1685,10 +1692,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { private var presentationData: PresentationData private let hapticFeedback = HapticFeedback() private var validLayout: ContainerViewLayout? - - private let fpsCounter = FPSCounter() - private var fpsLabel: UILabel? - + private var _drawingView: DrawingView? var drawingView: DrawingView { if self._drawingView == nil, let controller = self.controller { @@ -1741,7 +1745,26 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { } self.performAction.connect { [weak self] action in if let strongSelf = self { - strongSelf._drawingView?.performAction(action) + if action == .clear { + let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData) + actionSheet.setItemGroups([ + ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: strongSelf.presentationData.strings.Paint_ClearConfirm, color: .destructive, action: { [weak actionSheet, weak self] in + actionSheet?.dismissAnimated() + + self?._drawingView?.performAction(action) + }) + ]), + ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ]) + ]) + strongSelf.controller?.present(actionSheet, in: .window(.root)) + } else { + strongSelf._drawingView?.performAction(action) + } } } self.updateToolState.connect { [weak self] state in @@ -1762,7 +1785,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { private var _entitiesView: DrawingEntitiesView? var entitiesView: DrawingEntitiesView { if self._entitiesView == nil, let controller = self.controller { - self._entitiesView = DrawingEntitiesView(context: self.context, size: controller.size, entities: []) + self._entitiesView = DrawingEntitiesView(context: self.context, size: controller.size) self._drawingView?.entitiesView = self._entitiesView let entitiesLayer = self.entitiesView.layer self._drawingView?.getFullImage = { [weak self, weak entitiesLayer] withDrawing in @@ -1917,10 +1940,33 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { super.init() self.apply.connect { [weak self] _ in - self?.controller?.requestApply() + if let strongSelf = self { + strongSelf.controller?.requestApply() + } } self.dismiss.connect { [weak self] _ in - self?.controller?.requestDismiss() + if let strongSelf = self { + if !strongSelf.drawingView.isEmpty { + let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData) + actionSheet.setItemGroups([ + ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: strongSelf.presentationData.strings.PhotoEditor_DiscardChanges, color: .accent, action: { [weak actionSheet, weak self] in + actionSheet?.dismissAnimated() + + self?.controller?.requestDismiss() + }) + ]), + ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ]) + ]) + strongSelf.controller?.present(actionSheet, in: .window(.root)) + } else { + strongSelf.controller?.requestDismiss() + } + } } } @@ -1929,23 +1975,8 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { self.view.disablesInteractiveKeyboardGestureRecognizer = true self.view.disablesInteractiveTransitionGestureRecognizer = true - - if self.fpsLabel == nil { - let fpsLabel = UILabel(frame: CGRect(origin: CGPoint(x: 30.0, y: 10.0), size: CGSize(width: 120.0, height: 44.0))) - fpsLabel.alpha = 0.1 - fpsLabel.textColor = .white -// self.view.addSubview(fpsLabel) - self.fpsLabel = fpsLabel + } - self.fpsCounter.delegate = self - self.fpsCounter.startTracking() - } - } - - func fpsCounter(_ counter: FPSCounter, didUpdateFramesPerSecond fps: Int) { - self.fpsLabel?.text = "\(fps)" - } - func presentEyedropper(dismissed: @escaping () -> Void) { guard let controller = self.controller else { return @@ -2064,6 +2095,11 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { buttonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) buttonView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.3) } + if let buttonView = self.componentHost.findTaggedView(tag: redoButtonTag), buttonView.alpha > 0.0 { + buttonView.alpha = 0.0 + buttonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) + buttonView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.3) + } if let buttonView = self.componentHost.findTaggedView(tag: clearAllButtonTag) { buttonView.alpha = 0.0 buttonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) @@ -2190,6 +2226,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { style: DrawingTextStyle(style: textEntity.style), alignment: DrawingTextAlignment(alignment: textEntity.alignment), font: DrawingTextFont(font: textEntity.font), + isEmojiKeyboard: entityView.textView.inputView != nil, presentColorPicker: { [weak self] in guard let strongSelf = self, let entityView = strongSelf.entitiesView.selectedEntityView as? DrawingTextEntityView, let textEntity = entityView.entity as? DrawingTextEntity else { return @@ -2262,6 +2299,12 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { if let layout = strongSelf.validLayout { strongSelf.containerLayoutUpdated(layout: layout, transition: .immediate) } + }, + toggleKeyboard: { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.toggleInputMode() } ) ), @@ -2280,6 +2323,58 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { } } } + + private func toggleInputMode() { + guard let entityView = self.entitiesView.selectedEntityView as? DrawingTextEntityView else { + return + } + + let textView = entityView.textView + var shouldHaveInputView = false + if textView.isFirstResponder { + if textView.inputView == nil { + shouldHaveInputView = true + } + } else { + shouldHaveInputView = true + } + + if shouldHaveInputView { + let inputView = EntityInputView(context: self.context, isDark: true, areCustomEmojiEnabled: true) + inputView.insertText = { [weak entityView] text in + entityView?.insertText(text) + } + inputView.deleteBackwards = { [weak textView] in + textView?.deleteBackward() + } + inputView.switchToKeyboard = { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.toggleInputMode() + } +// inputView?.presentController = { [weak self] c in +// guard let strongSelf = self else { +// return +// } +// strongSelf.presentController(c) +// } + + textView.inputView = inputView + } else { + textView.inputView = nil + } + + if textView.isFirstResponder { + textView.reloadInputViews() + } else { + textView.becomeFirstResponder() + } + + if let layout = self.validLayout { + self.containerLayoutUpdated(layout: layout, animateOut: false, transition: .immediate) + } + } } fileprivate var node: Node { @@ -2288,14 +2383,16 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { private let context: AccountContext private let size: CGSize + private let originalSize: CGSize public var requestDismiss: (() -> Void)! public var requestApply: (() -> Void)! public var getCurrentImage: (() -> UIImage?)! - public init(context: AccountContext, size: CGSize) { + public init(context: AccountContext, size: CGSize, originalSize: CGSize) { self.context = context self.size = size + self.originalSize = originalSize super.init(navigationBarPresentationData: nil) @@ -2342,36 +2439,36 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { }, opaque: false, scale: 1.0) var hasAnimatedEntities = false - var legacyEntities: [TGPhotoPaintEntity] = [] for entity in self.entitiesView.entities { if entity.isAnimated { hasAnimatedEntities = true + break } - if let entity = entity as? DrawingStickerEntity { - let coder = PostboxEncoder() - coder.encodeRootObject(entity.file) - - let baseSize = max(10.0, min(entity.referenceDrawingSize.width, entity.referenceDrawingSize.height) * 0.38) - if let stickerEntity = TGPhotoPaintStickerEntity(document: coder.makeData(), baseSize: CGSize(width: baseSize, height: baseSize), animated: entity.isAnimated) { - stickerEntity.position = entity.position - stickerEntity.scale = entity.scale - stickerEntity.angle = entity.rotation - legacyEntities.append(stickerEntity) - } - } else if let entity = entity as? DrawingTextEntity, let view = self.entitiesView.getView(for: entity.uuid) as? DrawingTextEntityView { - let textEntity = TGPhotoPaintStaticEntity() - textEntity.position = entity.position - textEntity.angle = entity.rotation - textEntity.renderImage = view.getRenderImage() - legacyEntities.append(textEntity) - } else if let _ = entity as? DrawingSimpleShapeEntity { - - } else if let _ = entity as? DrawingBubbleEntity { - - } else if let _ = entity as? DrawingVectorEntity { - - } +// if let entity = entity as? DrawingStickerEntity { +// let coder = PostboxEncoder() +// coder.encodeRootObject(entity.file) +// +// let baseSize = max(10.0, min(entity.referenceDrawingSize.width, entity.referenceDrawingSize.height) * 0.38) +// if let stickerEntity = TGPhotoPaintStickerEntity(document: coder.makeData(), baseSize: CGSize(width: baseSize, height: baseSize), animated: entity.isAnimated) { +// stickerEntity.position = entity.position +// stickerEntity.scale = entity.scale +// stickerEntity.angle = entity.rotation +// legacyEntities.append(stickerEntity) +// } +// } else if let entity = entity as? DrawingTextEntity, let view = self.entitiesView.getView(for: entity.uuid) as? DrawingTextEntityView { +// let textEntity = TGPhotoPaintStaticEntity() +// textEntity.position = entity.position +// textEntity.angle = entity.rotation +// textEntity.renderImage = view.getRenderImage() +// legacyEntities.append(textEntity) +// } else if let _ = entity as? DrawingSimpleShapeEntity { +// +// } else if let _ = entity as? DrawingBubbleEntity { +// +// } else if let _ = entity as? DrawingVectorEntity { +// +// } } let finalImage = generateImage(self.drawingView.imageSize, contextGenerator: { size, context in @@ -2396,7 +2493,8 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController { image = finalImage } - return TGPaintingData(painting: nil, image: image, stillImage: stillImage, entities: legacyEntities, undoManager: nil) + return TGPaintingData(drawing: nil, entitiesData: self.entitiesView.entitiesData, image: image, stillImage: stillImage, hasAnimation: hasAnimatedEntities) +// return TGPaintingData(painting: nil, image: image, stillImage: stillImage, entities: legacyEntities, undoManager: nil) } public func resultImage() -> UIImage! { diff --git a/submodules/DrawingUI/Sources/DrawingSimpleShapeEntity.swift b/submodules/DrawingUI/Sources/DrawingSimpleShapeEntity.swift index 99d8ac9af9..7fb7d65abb 100644 --- a/submodules/DrawingUI/Sources/DrawingSimpleShapeEntity.swift +++ b/submodules/DrawingUI/Sources/DrawingSimpleShapeEntity.swift @@ -3,14 +3,26 @@ import UIKit import Display import AccountContext -final class DrawingSimpleShapeEntity: DrawingEntity { - public enum ShapeType { +final class DrawingSimpleShapeEntity: DrawingEntity, Codable { + private enum CodingKeys: String, CodingKey { + case uuid + case shapeType + case drawType + case color + case lineWidth + case referenceDrawingSize + case position + case size + case rotation + } + + public enum ShapeType: Codable { case rectangle case ellipse case star } - public enum DrawType { + public enum DrawType: Codable { case fill case stroke } @@ -28,6 +40,10 @@ final class DrawingSimpleShapeEntity: DrawingEntity { var size: CGSize var rotation: CGFloat + var center: CGPoint { + return self.position + } + init(shapeType: ShapeType, drawType: DrawType, color: DrawingColor, lineWidth: CGFloat) { self.uuid = UUID() self.isAnimated = false @@ -43,10 +59,33 @@ final class DrawingSimpleShapeEntity: DrawingEntity { self.rotation = 0.0 } - var center: CGPoint { - return self.position + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.uuid = try container.decode(UUID.self, forKey: .uuid) + self.isAnimated = false + self.shapeType = try container.decode(ShapeType.self, forKey: .shapeType) + self.drawType = try container.decode(DrawType.self, forKey: .drawType) + self.color = try container.decode(DrawingColor.self, forKey: .color) + self.lineWidth = try container.decode(CGFloat.self, forKey: .lineWidth) + self.referenceDrawingSize = try container.decode(CGSize.self, forKey: .referenceDrawingSize) + self.position = try container.decode(CGPoint.self, forKey: .position) + self.size = try container.decode(CGSize.self, forKey: .size) + self.rotation = try container.decode(CGFloat.self, forKey: .rotation) } + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.uuid, forKey: .uuid) + try container.encode(self.shapeType, forKey: .shapeType) + try container.encode(self.drawType, forKey: .drawType) + try container.encode(self.color, forKey: .color) + try container.encode(self.lineWidth, forKey: .lineWidth) + try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize) + try container.encode(self.position, forKey: .position) + try container.encode(self.size, forKey: .size) + try container.encode(self.rotation, forKey: .rotation) + } + func duplicate() -> DrawingEntity { let newEntity = DrawingSimpleShapeEntity(shapeType: self.shapeType, drawType: self.drawType, color: self.color, lineWidth: self.lineWidth) newEntity.referenceDrawingSize = self.referenceDrawingSize diff --git a/submodules/DrawingUI/Sources/DrawingStickerEntity.swift b/submodules/DrawingUI/Sources/DrawingStickerEntity.swift index f591097401..8cd589271c 100644 --- a/submodules/DrawingUI/Sources/DrawingStickerEntity.swift +++ b/submodules/DrawingUI/Sources/DrawingStickerEntity.swift @@ -8,7 +8,18 @@ import TelegramAnimatedStickerNode import StickerResources import AccountContext -final class DrawingStickerEntity: DrawingEntity { +final class DrawingStickerEntity: DrawingEntity, Codable { + private enum CodingKeys: String, CodingKey { + case uuid + case isAnimated + case file + case referenceDrawingSize + case position + case scale + case rotation + case mirrored + } + let uuid: UUID let isAnimated: Bool let file: TelegramMediaFile @@ -22,6 +33,10 @@ final class DrawingStickerEntity: DrawingEntity { var color: DrawingColor = DrawingColor.clear var lineWidth: CGFloat = 0.0 + var center: CGPoint { + return self.position + } + init(file: TelegramMediaFile) { self.uuid = UUID() self.isAnimated = file.isAnimatedSticker @@ -35,10 +50,30 @@ final class DrawingStickerEntity: DrawingEntity { self.mirrored = false } - var center: CGPoint { - return self.position + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.uuid = try container.decode(UUID.self, forKey: .uuid) + self.isAnimated = try container.decode(Bool.self, forKey: .isAnimated) + self.file = try container.decode(TelegramMediaFile.self, forKey: .file) + self.referenceDrawingSize = try container.decode(CGSize.self, forKey: .referenceDrawingSize) + self.position = try container.decode(CGPoint.self, forKey: .position) + self.scale = try container.decode(CGFloat.self, forKey: .scale) + self.rotation = try container.decode(CGFloat.self, forKey: .rotation) + self.mirrored = try container.decode(Bool.self, forKey: .mirrored) } + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.uuid, forKey: .uuid) + try container.encode(self.isAnimated, forKey: .isAnimated) + try container.encode(self.file, forKey: .file) + try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize) + try container.encode(self.position, forKey: .position) + try container.encode(self.scale, forKey: .scale) + try container.encode(self.rotation, forKey: .rotation) + try container.encode(self.mirrored, forKey: .mirrored) + } + func duplicate() -> DrawingEntity { let newEntity = DrawingStickerEntity(file: self.file) newEntity.referenceDrawingSize = self.referenceDrawingSize @@ -102,7 +137,7 @@ final class DrawingStickerEntityView: DrawingEntityView { private func setup() { if let dimensions = self.file.dimensions { - if self.file.isAnimatedSticker || self.file.isVideoSticker { + if self.file.isAnimatedSticker || self.file.isVideoSticker || self.file.mimeType == "video/webm" { if self.animationNode == nil { let animationNode = DefaultAnimatedStickerNodeImpl() animationNode.autoplay = false @@ -176,7 +211,7 @@ final class DrawingStickerEntityView: DrawingEntityView { self.didSetUpAnimationNode = true let dimensions = self.file.dimensions ?? PixelDimensions(width: 512, height: 512) let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 384.0, height: 384.0)) - let source = AnimatedStickerResourceSource(account: self.context.account, resource: self.file.resource, isVideo: self.file.isVideoSticker) + let source = AnimatedStickerResourceSource(account: self.context.account, resource: self.file.resource, isVideo: self.file.isVideoSticker || self.file.mimeType == "video/webm") self.animationNode?.setup(source: source, width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: .loop, mode: .direct(cachePathPrefix: nil)) self.cachedDisposable.set((source.cachedDataPath(width: 384, height: 384) diff --git a/submodules/DrawingUI/Sources/DrawingTextEntity.swift b/submodules/DrawingUI/Sources/DrawingTextEntity.swift index 9582748134..5c4798ad34 100644 --- a/submodules/DrawingUI/Sources/DrawingTextEntity.swift +++ b/submodules/DrawingUI/Sources/DrawingTextEntity.swift @@ -1,10 +1,60 @@ import Foundation import UIKit import Display +import SwiftSignalKit import AccountContext +import TextFormat +import EmojiTextAttachmentView -final class DrawingTextEntity: DrawingEntity { - enum Style { +final class DrawingTextEntity: DrawingEntity, Codable { + final class CustomEmojiAttribute: Codable { + private enum CodingKeys: String, CodingKey { + case attribute + case rangeOrigin + case rangeLength + } + let attribute: ChatTextInputTextCustomEmojiAttribute + let range: NSRange + + init(attribute: ChatTextInputTextCustomEmojiAttribute, range: NSRange) { + self.attribute = attribute + self.range = range + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.attribute = try container.decode(ChatTextInputTextCustomEmojiAttribute.self, forKey: .attribute) + + let rangeOrigin = try container.decode(Int.self, forKey: .rangeOrigin) + let rangeLength = try container.decode(Int.self, forKey: .rangeLength) + self.range = NSMakeRange(rangeOrigin, rangeLength) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.attribute, forKey: .attribute) + try container.encode(self.range.location, forKey: .rangeOrigin) + try container.encode(self.range.length, forKey: .rangeLength) + } + } + + private enum CodingKeys: String, CodingKey { + case uuid + case text + case textAttributes + case style + case font + case alignment + case fontSize + case color + case referenceDrawingSize + case position + case width + case scale + case rotation + } + + enum Style: Codable { case regular case filled case semi @@ -24,7 +74,7 @@ final class DrawingTextEntity: DrawingEntity { } } - enum Font { + enum Font: Codable { case sanFrancisco case newYork case monospaced @@ -44,7 +94,7 @@ final class DrawingTextEntity: DrawingEntity { } } - enum Alignment { + enum Alignment: Codable { case left case center case right @@ -73,9 +123,17 @@ final class DrawingTextEntity: DrawingEntity { } var uuid: UUID - let isAnimated: Bool + var isAnimated: Bool { + var isAnimated = false + self.text.enumerateAttributes(in: NSMakeRange(0, self.text.length), options: [], using: { attributes, range, _ in + if let _ = attributes[ChatTextInputAttributes.customEmoji] as? ChatTextInputTextCustomEmojiAttribute { + isAnimated = true + } + }) + return isAnimated + } - var text: String + var text: NSAttributedString var style: Style var font: Font var alignment: Alignment @@ -86,11 +144,15 @@ final class DrawingTextEntity: DrawingEntity { var referenceDrawingSize: CGSize var position: CGPoint var width: CGFloat + var scale: CGFloat var rotation: CGFloat - init(text: String, style: Style, font: Font, alignment: Alignment, fontSize: CGFloat, color: DrawingColor) { + var center: CGPoint { + return self.position + } + + init(text: NSAttributedString, style: Style, font: Font, alignment: Alignment, fontSize: CGFloat, color: DrawingColor) { self.uuid = UUID() - self.isAnimated = false self.text = text self.style = style @@ -102,18 +164,65 @@ final class DrawingTextEntity: DrawingEntity { self.referenceDrawingSize = .zero self.position = .zero self.width = 100.0 + self.scale = 1.0 self.rotation = 0.0 } - var center: CGPoint { - return self.position + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.uuid = try container.decode(UUID.self, forKey: .uuid) + let text = try container.decode(String.self, forKey: .text) + + let attributedString = NSMutableAttributedString(string: text) + let textAttributes = try container.decode([CustomEmojiAttribute].self, forKey: .textAttributes) + for attribute in textAttributes { + attributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: attribute.attribute, range: attribute.range) + } + self.text = attributedString + + self.style = try container.decode(Style.self, forKey: .style) + self.font = try container.decode(Font.self, forKey: .font) + self.alignment = try container.decode(Alignment.self, forKey: .alignment) + self.fontSize = try container.decode(CGFloat.self, forKey: .fontSize) + self.color = try container.decode(DrawingColor.self, forKey: .color) + self.referenceDrawingSize = try container.decode(CGSize.self, forKey: .referenceDrawingSize) + self.position = try container.decode(CGPoint.self, forKey: .position) + self.width = try container.decode(CGFloat.self, forKey: .width) + self.scale = try container.decode(CGFloat.self, forKey: .scale) + self.rotation = try container.decode(CGFloat.self, forKey: .rotation) } + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.uuid, forKey: .uuid) + try container.encode(self.text.string, forKey: .text) + + var textAttributes: [CustomEmojiAttribute] = [] + self.text.enumerateAttributes(in: NSMakeRange(0, self.text.length), options: [], using: { attributes, range, _ in + if let value = attributes[ChatTextInputAttributes.customEmoji] as? ChatTextInputTextCustomEmojiAttribute { + textAttributes.append(CustomEmojiAttribute(attribute: value, range: range)) + } + }) + try container.encode(textAttributes, forKey: .textAttributes) + + try container.encode(self.style, forKey: .style) + try container.encode(self.font, forKey: .font) + try container.encode(self.alignment, forKey: .alignment) + try container.encode(self.fontSize, forKey: .fontSize) + try container.encode(self.color, forKey: .color) + try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize) + try container.encode(self.position, forKey: .position) + try container.encode(self.width, forKey: .width) + try container.encode(self.scale, forKey: .scale) + try container.encode(self.rotation, forKey: .rotation) + } + func duplicate() -> DrawingEntity { let newEntity = DrawingTextEntity(text: self.text, style: self.style, font: self.font, alignment: self.alignment, fontSize: self.fontSize, color: self.color) newEntity.referenceDrawingSize = self.referenceDrawingSize newEntity.position = self.position newEntity.width = self.width + newEntity.scale = self.scale newEntity.rotation = self.rotation return newEntity } @@ -131,14 +240,15 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate { return self.entity as! DrawingTextEntity } - private let textView: DrawingTextView + let textView: DrawingTextView + var customEmojiContainerView: CustomEmojiContainerView? + var emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)? init(context: AccountContext, entity: DrawingTextEntity) { self.textView = DrawingTextView(frame: .zero) self.textView.clipsToBounds = false self.textView.backgroundColor = .clear - self.textView.text = entity.text self.textView.isEditable = false self.textView.isSelectable = false self.textView.contentInset = .zero @@ -159,6 +269,15 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate { self.addSubview(self.textView) self.update(animated: false) + + self.emojiViewProvider = { [weak self] emoji in + guard let strongSelf = self else { + return UIView() + } + + let pointSize: CGFloat = 128.0 + return EmojiTextAttachmentView(context: context, emoji: emoji, file: emoji.file, cache: strongSelf.context.animationCache, renderer: strongSelf.context.animationRenderer, placeholderColor: UIColor.white.withAlphaComponent(0.12), pointSize: CGSize(width: pointSize, height: pointSize)) + } } required init?(coder: NSCoder) { @@ -178,9 +297,62 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate { self.endEditing() } + func updateEntities() { + self.textView.drawingLayoutManager.ensureLayout(for: self.textView.textContainer) + + var customEmojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute)] = [] + + if let attributedText = self.textView.attributedText { + let beginning = self.textView.beginningOfDocument + attributedText.enumerateAttributes(in: NSMakeRange(0, attributedText.length), options: [], using: { attributes, range, _ in + if let value = attributes[ChatTextInputAttributes.customEmoji] as? ChatTextInputTextCustomEmojiAttribute { + if let start = self.textView.position(from: beginning, offset: range.location), let end = self.textView.position(from: start, offset: range.length), let textRange = self.textView.textRange(from: start, to: end) { + let rect = self.textView.firstRect(for: textRange) + customEmojiRects.append((rect, value)) + } + } + }) + } + + let color = self.textEntity.color.toUIColor() + let textColor: UIColor + switch self.textEntity.style { + case .regular: + textColor = color + case .filled: + textColor = color.lightness > 0.99 ? UIColor.black : UIColor.white + case .semi: + textColor = color + case .stroke: + textColor = color.lightness > 0.99 ? UIColor.black : UIColor.white + } + + if !customEmojiRects.isEmpty { + let customEmojiContainerView: CustomEmojiContainerView + if let current = self.customEmojiContainerView { + customEmojiContainerView = current + } else { + customEmojiContainerView = CustomEmojiContainerView(emojiViewProvider: { [weak self] emoji in + guard let strongSelf = self, let emojiViewProvider = strongSelf.emojiViewProvider else { + return nil + } + return emojiViewProvider(emoji) + }) + customEmojiContainerView.isUserInteractionEnabled = false + self.textView.addSubview(customEmojiContainerView) + self.customEmojiContainerView = customEmojiContainerView + } + + customEmojiContainerView.update(fontSize: self.displayFontSize, textColor: textColor, emojiRects: customEmojiRects) + } else if let customEmojiContainerView = self.customEmojiContainerView { + customEmojiContainerView.removeFromSuperview() + self.customEmojiContainerView = nil + } + } + func beginEditing(accessoryView: UIView?) { self._isEditing = true - if !self.textEntity.text.isEmpty { + if !self.textEntity.text.string.isEmpty { let previousEntity = self.textEntity.duplicate() as? DrawingTextEntity previousEntity?.uuid = self.textEntity.uuid self.previousEntity = previousEntity @@ -214,7 +386,7 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate { if let selectionView = self.selectionView as? DrawingTextEntititySelectionView { selectionView.alpha = 0.0 - if !self.textEntity.text.isEmpty { + if !self.textEntity.text.string.isEmpty { selectionView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) } } @@ -223,6 +395,8 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate { func endEditing(reset: Bool = false) { self._isEditing = false self.textView.resignFirstResponder() + self.textView.inputView = nil + self.textView.inputAccessoryView = nil self.textView.isEditable = false self.textView.isSelectable = false @@ -240,14 +414,12 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate { self.containerView?.remove(uuid: self.textEntity.uuid) } } else { - self.textEntity.text = self.textView.text.trimmingCharacters(in: .whitespacesAndNewlines) - if self.textEntity.text.isEmpty { +// self.textEntity.text = self.textView.text.trimmingCharacters(in: .whitespacesAndNewlines) + if self.textEntity.text.string.isEmpty { self.containerView?.remove(uuid: self.textEntity.uuid) } } - - self.textView.text = self.textEntity.text - + if let fadeView = self.fadeView { self.fadeView = nil fadeView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak fadeView] _ in @@ -288,8 +460,33 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate { } func textViewDidChange(_ textView: UITextView) { + guard let updatedText = self.textView.attributedText.mutableCopy() as? NSMutableAttributedString else { + return + } + let range = NSMakeRange(0, updatedText.length) + updatedText.removeAttribute(.font, range: range) + updatedText.removeAttribute(.paragraphStyle, range: range) + updatedText.removeAttribute(.foregroundColor, range: range) + + self.textEntity.text = updatedText + self.sizeToFit() - self.textView.setNeedsDisplay() + self.update() + } + + func insertText(_ text: NSAttributedString) { + guard let updatedText = self.textView.attributedText.mutableCopy() as? NSMutableAttributedString else { + return + } + let range = NSMakeRange(0, updatedText.length) + updatedText.removeAttribute(.font, range: range) + updatedText.removeAttribute(.paragraphStyle, range: range) + updatedText.removeAttribute(.foregroundColor, range: range) + updatedText.replaceCharacters(in: self.textView.selectedRange, with: text) + + self.textEntity.text = updatedText + + self.update() } override func sizeThatFits(_ size: CGSize) -> CGSize { @@ -321,27 +518,65 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate { self.textView.frame = rect } - override func update(animated: Bool = false) { - if !self.isEditing { - self.center = self.textEntity.position - self.transform = CGAffineTransformMakeRotation(self.textEntity.rotation) - } - + private var displayFontSize: CGFloat { let minFontSize = max(10.0, min(self.textEntity.referenceDrawingSize.width, self.textEntity.referenceDrawingSize.height) * 0.05) let maxFontSize = max(10.0, min(self.textEntity.referenceDrawingSize.width, self.textEntity.referenceDrawingSize.height) * 0.45) let fontSize = minFontSize + (maxFontSize - minFontSize) * self.textEntity.fontSize - + return fontSize + } + + private func updateText() { + guard let text = self.textEntity.text.mutableCopy() as? NSMutableAttributedString else { + return + } + let range = NSMakeRange(0, text.length) + let fontSize = self.displayFontSize + + let font: UIFont switch self.textEntity.font { case .sanFrancisco: - self.textView.font = Font.with(size: fontSize, design: .regular, weight: .semibold) + font = Font.with(size: fontSize, design: .regular, weight: .semibold) case .newYork: - self.textView.font = Font.with(size: fontSize, design: .serif, weight: .semibold) + font = Font.with(size: fontSize, design: .serif, weight: .semibold) case .monospaced: - self.textView.font = Font.with(size: fontSize, design: .monospace, weight: .semibold) + font = Font.with(size: fontSize, design: .monospace, weight: .semibold) case .round: - self.textView.font = Font.with(size: fontSize, design: .round, weight: .semibold) + font = Font.with(size: fontSize, design: .round, weight: .semibold) + } + text.addAttribute(.font, value: font, range: range) + + let color = self.textEntity.color.toUIColor() + let textColor: UIColor + switch self.textEntity.style { + case .regular: + textColor = color + case .filled: + textColor = color.lightness > 0.99 ? UIColor.black : UIColor.white + case .semi: + textColor = color + case .stroke: + textColor = color.lightness > 0.99 ? UIColor.black : UIColor.white + } + text.addAttribute(.foregroundColor, value: textColor, range: range) + + text.enumerateAttributes(in: range) { attributes, subrange, _ in + if let _ = attributes[ChatTextInputAttributes.customEmoji] { + text.addAttribute(.foregroundColor, value: UIColor.clear, range: subrange) + } + } + + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.alignment = self.textEntity.alignment.alignment + text.addAttribute(.paragraphStyle, value: paragraphStyle, range: range) + + self.textView.attributedText = text + } + + override func update(animated: Bool = false) { + if !self.isEditing { + self.center = self.textEntity.position + self.transform = CGAffineTransformScale(CGAffineTransformMakeRotation(self.textEntity.rotation), self.textEntity.scale, self.textEntity.scale) } - self.textView.textAlignment = self.textEntity.alignment.alignment let color = self.textEntity.color.toUIColor() switch self.textEntity.style { @@ -358,7 +593,7 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate { self.textView.strokeColor = nil self.textView.frameColor = UIColor(rgb: 0xffffff, alpha: 0.75) case .stroke: - self.textView.textColor = color.lightness > 0.99 ? UIColor.black : UIColor.white + self.textView.textColor = color.lightness > 0.99 ? UIColor.black : UIColor.white self.textView.strokeColor = color self.textView.frameColor = nil } @@ -375,22 +610,35 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate { self.textView.layer.shadowRadius = 0.0 } + self.updateText() + self.sizeToFit() + Queue.mainQueue().after(0.001) { + self.updateEntities() + } + + super.update(animated: animated) } override func updateSelectionView() { - super.updateSelectionView() - guard let selectionView = self.selectionView as? DrawingTextEntititySelectionView else { return } + self.pushIdentityTransformForMeasurement() + + selectionView.transform = .identity + let bounds = self.selectionBounds + let center = bounds.center -// let scale = self.superview?.superview?.layer.value(forKeyPath: "transform.scale.x") as? CGFloat ?? 1.0 -// selectionView.scale = scale + let scale = self.superview?.superview?.layer.value(forKeyPath: "transform.scale.x") as? CGFloat ?? 1.0 + selectionView.center = self.convert(center, to: selectionView.superview) + selectionView.bounds = CGRect(origin: .zero, size: CGSize(width: (bounds.width * self.textEntity.scale) * scale + selectionView.selectionInset * 2.0, height: (bounds.height * self.textEntity.scale) * scale + selectionView.selectionInset * 2.0)) selectionView.transform = CGAffineTransformMakeRotation(self.textEntity.rotation) + + self.popIdentityTransformForMeasurement() } override func makeSelectionView() -> DrawingEntitySelectionView { @@ -492,7 +740,7 @@ final class DrawingTextEntititySelectionView: DrawingEntitySelectionView, UIGest let delta = gestureRecognizer.translation(in: entityView.superview) let parentLocation = gestureRecognizer.location(in: self.superview) - var updatedFontSize = entity.fontSize + var updatedScale = entity.scale var updatedPosition = entity.position var updatedRotation = entity.rotation @@ -502,7 +750,7 @@ final class DrawingTextEntititySelectionView: DrawingEntitySelectionView, UIGest deltaX *= -1.0 } let scaleDelta = (self.bounds.size.width + deltaX * 2.0) / self.bounds.size.width - updatedFontSize = max(0.0, min(1.0, updatedFontSize * scaleDelta)) + updatedScale = max(0.01, updatedScale * scaleDelta) let deltaAngle: CGFloat if self.currentHandle === self.leftHandle { @@ -516,7 +764,7 @@ final class DrawingTextEntititySelectionView: DrawingEntitySelectionView, UIGest updatedPosition.y += delta.y } - entity.fontSize = updatedFontSize + entity.scale = updatedScale entity.position = updatedPosition entity.rotation = updatedRotation entityView.update() @@ -537,7 +785,7 @@ final class DrawingTextEntititySelectionView: DrawingEntitySelectionView, UIGest switch gestureRecognizer.state { case .began, .changed: let scale = gestureRecognizer.scale - entity.fontSize = max(0.0, min(1.0, entity.fontSize * scale)) + entity.fontSize = max(0.1, entity.scale * scale) entityView.update() gestureRecognizer.scale = 1.0 @@ -823,8 +1071,8 @@ private class DrawingTextStorage: NSTextStorage { } } -private class DrawingTextView: UITextView { - var drawingLayoutManager: DrawingTextLayoutManager { +class DrawingTextView: UITextView { + fileprivate var drawingLayoutManager: DrawingTextLayoutManager { return self.layoutManager as! DrawingTextLayoutManager } @@ -912,14 +1160,14 @@ private class DrawingTextView: UITextView { super.insertText(text) self.fixTypingAttributes() } - + override func paste(_ sender: Any?) { self.fixTypingAttributes() super.paste(sender) self.fixTypingAttributes() } - private func fixTypingAttributes() { + fileprivate func fixTypingAttributes() { var attributes: [NSAttributedString.Key: Any] = [:] if let font = self.font { attributes[NSAttributedString.Key.font] = font diff --git a/submodules/DrawingUI/Sources/DrawingTools.swift b/submodules/DrawingUI/Sources/DrawingTools.swift index b4d28cb97a..870b187f84 100644 --- a/submodules/DrawingUI/Sources/DrawingTools.swift +++ b/submodules/DrawingUI/Sources/DrawingTools.swift @@ -6,468 +6,6 @@ protocol DrawingRenderLayer: CALayer { } -//final class PenTool: DrawingElement { -// class RenderLayer: SimpleLayer, DrawingRenderLayer { -// var lineWidth: CGFloat = 0.0 -// -// func setup(size: CGSize, color: DrawingColor, lineWidth: CGFloat, strokeWidth: CGFloat, shadowRadius: CGFloat) { -// self.contentsScale = 1.0 -// self.lineWidth = lineWidth -// -// -// } -// } -// -// let uuid = UUID() -// -// let drawingSize: CGSize -// let color: DrawingColor -// let lineWidth: CGFloat -// let arrow: Bool -// -// var path: BezierPath? -// var boundingBox: CGRect? -// -// var renderPath: CGPath? -// let renderStrokeWidth: CGFloat -// let renderShadowRadius: CGFloat -// let renderLineWidth: CGFloat -// -// var translation = CGPoint() -// -// private var currentRenderLayer: DrawingRenderLayer? -// -// var bounds: CGRect { -// return self.path?.path.bounds.offsetBy(dx: self.translation.x, dy: self.translation.y) ?? .zero -// } -// -// var points: [Polyline.Point] { -// guard let linePath = self.path else { -// return [] -// } -// var points: [Polyline.Point] = [] -// for element in linePath.elements { -// if case .moveTo = element.type { -// points.append(element.startPoint.offsetBy(self.translation)) -// } else { -// points.append(element.endPoint.offsetBy(self.translation)) -// } -// } -// return points -// } -// -// func containsPoint(_ point: CGPoint) -> Bool { -// return self.renderPath?.contains(point.offsetBy(CGPoint(x: -self.translation.x, y: -self.translation.y))) ?? false -// } -// -// func hasPointsInsidePath(_ path: UIBezierPath) -> Bool { -// if let linePath = self.path { -// let pathBoundingBox = path.bounds -// if self.bounds.intersects(pathBoundingBox) { -// for element in linePath.elements { -// if case .moveTo = element.type { -// if path.contains(element.startPoint.location.offsetBy(self.translation)) { -// return true -// } -// } else { -// if path.contains(element.startPoint.location.offsetBy(self.translation)) { -// return true -// } -// if path.contains(element.endPoint.location.offsetBy(self.translation)) { -// return true -// } -// if case .cubicCurve = element.type { -// if path.contains(element.controlPoints[0].offsetBy(self.translation)) { -// return true -// } -// if path.contains(element.controlPoints[1].offsetBy(self.translation)) { -// return true -// } -// } else if case .quadCurve = element.type { -// if path.contains(element.controlPoints[0].offsetBy(self.translation)) { -// return true -// } -// } -// } -// } -// } -// } -// return false -// } -// -// required init(drawingSize: CGSize, color: DrawingColor, lineWidth: CGFloat, arrow: Bool) { -// self.drawingSize = drawingSize -// self.color = color -// self.lineWidth = lineWidth -// self.arrow = arrow -// -// let strokeWidth = min(drawingSize.width, drawingSize.height) * 0.008 -// let shadowRadius = min(drawingSize.width, drawingSize.height) * 0.03 -// -// let minLineWidth = max(1.0, min(drawingSize.width, drawingSize.height) * 0.003) -// let maxLineWidth = max(10.0, min(drawingSize.width, drawingSize.height) * 0.09) -// let lineWidth = minLineWidth + (maxLineWidth - minLineWidth) * lineWidth -// -// self.renderStrokeWidth = strokeWidth -// self.renderShadowRadius = shadowRadius -// self.renderLineWidth = lineWidth -// } -// -// func setupRenderLayer() -> DrawingRenderLayer? { -// let layer = RenderLayer() -// layer.setup(size: self.drawingSize, color: self.color, lineWidth: self.renderLineWidth, strokeWidth: self.renderStrokeWidth, shadowRadius: self.renderShadowRadius) -// self.currentRenderLayer = layer -// return layer -// } -// -// func updatePath(_ path: DrawingGesturePipeline.DrawingResult, state: DrawingGesturePipeline.DrawingGestureState) { -// guard case let .smoothCurve(bezierPath) = path else { -// return -// } -// -// self.path = bezierPath -// -//// if self.arrow && polyline.isComplete, polyline.points.count > 2 { -//// let lastPoint = lastPosition -//// var secondPoint = polyline.points[polyline.points.count - 2] -//// if secondPoint.location.distance(to: lastPoint) < self.renderArrowLineWidth { -//// secondPoint = polyline.points[polyline.points.count - 3] -//// } -//// let angle = lastPoint.angle(to: secondPoint.location) -//// let point1 = lastPoint.pointAt(distance: self.renderArrowLength, angle: angle - CGFloat.pi * 0.15) -//// let point2 = lastPoint.pointAt(distance: self.renderArrowLength, angle: angle + CGFloat.pi * 0.15) -//// -//// let arrowPath = UIBezierPath() -//// arrowPath.move(to: point2) -//// arrowPath.addLine(to: lastPoint) -//// arrowPath.addLine(to: point1) -//// let arrowThickPath = arrowPath.cgPath.copy(strokingWithWidth: self.renderArrowLineWidth, lineCap: .round, lineJoin: .round, miterLimit: 0.0) -//// -//// combinedPath.usesEvenOddFillRule = false -//// combinedPath.append(UIBezierPath(cgPath: arrowThickPath)) -//// } -// -// -// let cgPath = bezierPath.path.cgPath.copy(strokingWithWidth: self.renderLineWidth, lineCap: .round, lineJoin: .round, miterLimit: 0.0) -// self.renderPath = cgPath -// -// if let currentRenderLayer = self.currentRenderLayer as? RenderLayer { -// currentRenderLayer.updatePath(cgPath) -// } -// } -// -// func draw(in context: CGContext, size: CGSize) { -// guard let path = self.renderPath else { -// return -// } -// context.saveGState() -// -// context.translateBy(x: self.translation.x, y: self.translation.y) -// -// context.setShouldAntialias(true) -// -// context.setBlendMode(.normal) -// -// context.addPath(path) -// context.setFillColor(UIColor.white.cgColor) -// context.setStrokeColor(UIColor.white.cgColor) -// context.setLineWidth(self.renderStrokeWidth * 0.5) -// context.setShadow(offset: .zero, blur: self.renderShadowRadius * 3.0, color: self.color.toCGColor()) -// context.drawPath(using: .fillStroke) -// -// context.addPath(path) -// context.setShadow(offset: .zero, blur: 0.0, color: UIColor.clear.cgColor) -// context.setLineCap(.round) -// context.setLineWidth(self.renderStrokeWidth) -// context.setStrokeColor(UIColor.white.mixedWith(self.color.toUIColor(), alpha: 0.25).cgColor) -// context.strokePath() -// -// context.addPath(path) -// context.setFillColor(UIColor.white.cgColor) -// -// context.fillPath() -// -// context.restoreGState() -// } -//} - -//final class PenTool: DrawingElement { -// class RenderLayer: SimpleLayer, DrawingRenderLayer { -// var lineWidth: CGFloat = 0.0 -// -// let fillLayer = SimpleShapeLayer() -// -// func setup(size: CGSize, color: DrawingColor, lineWidth: CGFloat) { -// self.contentsScale = 1.0 -// -// let minLineWidth = max(1.0, min(size.width, size.height) * 0.003) -// let maxLineWidth = max(10.0, min(size.width, size.height) * 0.055) -// let lineWidth = minLineWidth + (maxLineWidth - minLineWidth) * lineWidth -// -// self.lineWidth = lineWidth -// -// let bounds = CGRect(origin: .zero, size: size) -// self.frame = bounds -// -// self.fillLayer.frame = bounds -// self.fillLayer.contentsScale = 1.0 -// self.fillLayer.fillColor = color.toCGColor() -// self.fillLayer.strokeColor = color.toCGColor() -// self.fillLayer.lineWidth = 1.0 -// -// self.addSublayer(self.fillLayer) -// } -// -// func updatePath(_ path: CGPath) { -// self.fillLayer.path = path -// } -// } -// -// let uuid = UUID() -// -// let drawingSize: CGSize -// let color: DrawingColor -// let lineWidth: CGFloat -// let arrow: Bool -// -// var polyline: Polyline? -//// var path = BezierPath() -// let renderArrowLength: CGFloat -// let renderArrowLineWidth: CGFloat -// var renderLineWidth: CGFloat = 0.0 -// -// var translation = CGPoint() -// -// private var currentRenderLayer: DrawingRenderLayer? -// -// var bounds: CGRect { -// return .zero -// } -// -// var points: [CGPoint] { -// guard let polyline = self.polyline else { -// return [] -// } -// var points: [CGPoint] = [] -// for point in polyline.points { -// points.append(point.location) -// } -// return points -// } -// -// func containsPoint(_ point: CGPoint) -> Bool { -// return false -// } -// -// func hasPointsInsidePath(_ path: UIBezierPath) -> Bool { -// if let polyline = self.polyline { -// let pathBoundingBox = path.bounds -// if self.bounds.intersects(pathBoundingBox) { -// for point in polyline.points { -// if path.contains(point.location) { -// return true -// } -// } -// } -// } -// return false -// } -// -// required init(drawingSize: CGSize, color: DrawingColor, lineWidth: CGFloat, arrow: Bool) { -// self.drawingSize = drawingSize -// self.color = color -// self.lineWidth = lineWidth -// self.arrow = arrow -// -// self.renderArrowLength = min(drawingSize.width, drawingSize.height) * 0.04 -// self.renderArrowLineWidth = min(drawingSize.width, drawingSize.height) * 0.01 -// } -// -// func setupRenderLayer() -> DrawingRenderLayer? { -// let layer = RenderLayer() -// layer.setup(size: self.drawingSize, color: self.color, lineWidth: self.lineWidth) -// self.currentRenderLayer = layer -// return layer -// } -// -// func updatePath(_ path: DrawingGesturePipeline.DrawingResult, state: DrawingGesturePipeline.DrawingGestureState) { -// guard case let .polyline(polyline) = path else { -// return -// } -// -// self.polyline = polyline -// -// struct LineSegment { -// var firstPoint: CGPoint -// var secondPoint: CGPoint -// } -// -// func lineSegmentPerpendicularTo(_ pp: LineSegment, fraction: CGFloat) -> LineSegment { -// let x0: CGFloat = pp.firstPoint.x -// let y0: CGFloat = pp.firstPoint.y -// -// let x1: CGFloat = pp.secondPoint.x -// let y1: CGFloat = pp.secondPoint.y -// -// let dx = x1 - x0 -// let dy = y1 - y0 -// -// var xa: CGFloat -// var ya: CGFloat -// var xb: CGFloat -// var yb: CGFloat -// -// xa = x1 + fraction * 0.5 * dy -// ya = y1 - fraction * 0.5 * dx -// xb = x1 - fraction * 0.5 * dy -// yb = y1 + fraction * 0.5 * dx -// -// return LineSegment(firstPoint: CGPoint(x: xa, y: ya), secondPoint: CGPoint(x: xb, y: yb)) -// } -// -// -// func len_sq(_ p1: CGPoint, p2: CGPoint) -> CGFloat { -// let dx: CGFloat = p2.x - p1.x -// let dy: CGFloat = p2.y - p1.y -// return dx * dx + dy * dy -// } -// -// func clamp(_ value: T, min: T, max: T) -> T { -// if (value < min) { -// return min -// } -// if (value > max) { -// return max -// } -// return value -// } -// -// var ls: [LineSegment] = [] -// var isFirst = true -// -// let combinedPath = UIBezierPath() -// var segmentPath = UIBezierPath() -// -// var pts: [CGPoint] = [] -// var ctr: Int = 0 -// -// let ff: CGFloat = 0.2 -// let lower: CGFloat = 0.01 -// let upper: CGFloat = 1.0 -// -// var lastUsedIndex = 0 -// var index = 0 -// for point in polyline.points { -// pts.insert(point.location, at: ctr) -// ctr += 1 -// -// if ctr == 5 { -// pts[3] = CGPoint(x: (pts[2].x + pts[4].x) / 2.0, y: (pts[2].y + pts[4].y) / 2.0) -// -// if isFirst { -// isFirst = false -// let segment = LineSegment(firstPoint: pts[0], secondPoint: pts[0]) -// ls.append(segment) -// segmentPath.move(to: pts[0]) -// } -// -// let frac1: CGFloat = ff/clamp(len_sq(pts[0], p2: pts[1]), min: lower, max: upper) -// let frac2: CGFloat = ff/clamp(len_sq(pts[1], p2: pts[2]), min: lower, max: upper) -// let frac3: CGFloat = ff/clamp(len_sq(pts[2], p2: pts[3]), min: lower, max: upper) -// -// ls.insert(lineSegmentPerpendicularTo(LineSegment(firstPoint: pts[0], secondPoint: pts[1]), fraction: frac1), at: 1) -// ls.insert(lineSegmentPerpendicularTo(LineSegment(firstPoint: pts[1], secondPoint: pts[2]), fraction: frac2), at: 2) -// ls.insert(lineSegmentPerpendicularTo(LineSegment(firstPoint: pts[2], secondPoint: pts[3]), fraction: frac3), at: 3) -// -// segmentPath.move(to: ls[0].firstPoint) -// segmentPath.addCurve(to: ls[3].firstPoint, controlPoint1: ls[1].firstPoint, controlPoint2: ls[2].firstPoint) -// segmentPath.addLine(to: ls[3].secondPoint) -// segmentPath.addCurve(to: ls[0].secondPoint, controlPoint1: ls[2].secondPoint, controlPoint2: ls[1].secondPoint) -// segmentPath.close() -// combinedPath.append(segmentPath) -// -// let last = ls[3] -// ls.removeAll() -// ls.append(last) -// -// pts[0] = pts[3] -// pts[1] = pts[4] -// ctr = 2 -// -// combinedPath.append(segmentPath) -// segmentPath = UIBezierPath() -// -// lastUsedIndex = index -// } -// index += 1 -// } -// -// var lastPosition = polyline.points.last?.location ?? CGPoint() -// if let lastPoint = polyline.points.last, ls.count > 0 { -// if lastUsedIndex < polyline.points.count - 1 { -// let frac1: CGFloat = ff/clamp(len_sq(pts[0], p2: pts[1]), min: lower, max: upper) -// let frac2: CGFloat = ff/clamp(len_sq(pts[1], p2: lastPoint.location), min: lower, max: upper) -// ls.insert(lineSegmentPerpendicularTo(LineSegment(firstPoint: pts[0], secondPoint: pts[1]), fraction: frac1), at: 1) -// ls.insert(lineSegmentPerpendicularTo(LineSegment(firstPoint: pts[1], secondPoint: lastPoint.location), fraction: frac2), at: 2) -// -// segmentPath.move(to: ls[0].firstPoint) -// segmentPath.addQuadCurve(to: ls[2].firstPoint, controlPoint: ls[1].firstPoint) -// segmentPath.addLine(to: ls[2].secondPoint) -// segmentPath.addQuadCurve(to: ls[0].secondPoint, controlPoint: ls[1].secondPoint) -// segmentPath.addLine(to: ls[0].secondPoint) -// segmentPath.close() -// combinedPath.append(segmentPath) -// -// let diameter = ls[2].firstPoint.distance(to: ls[2].secondPoint) -// combinedPath.append(UIBezierPath(ovalIn: CGRect(origin: CGPoint(x: lastPoint.x - diameter * 0.5, y: lastPoint.y - diameter * 0.5), size: CGSize(width: diameter, height: diameter)))) -// -// lastPosition = lastPoint.location -// } else { -// let diameter = ls[0].firstPoint.distance(to: ls[0].secondPoint) -// let center = ls[0].firstPoint.point(to: ls[0].secondPoint, t: 0.5) -// combinedPath.append(UIBezierPath(ovalIn: CGRect(origin: CGPoint(x: center.x - diameter * 0.5, y: center.y - diameter * 0.5), size: CGSize(width: diameter, height: diameter)))) -// -// lastPosition = center -// } -// } -// -// if self.arrow && polyline.isComplete, polyline.points.count > 2 { -// let lastPoint = lastPosition -// var secondPoint = polyline.points[polyline.points.count - 2] -// if secondPoint.location.distance(to: lastPoint) < self.renderArrowLineWidth { -// secondPoint = polyline.points[polyline.points.count - 3] -// } -// let angle = lastPoint.angle(to: secondPoint.location) -// let point1 = lastPoint.pointAt(distance: self.renderArrowLength, angle: angle - CGFloat.pi * 0.15) -// let point2 = lastPoint.pointAt(distance: self.renderArrowLength, angle: angle + CGFloat.pi * 0.15) -// -// let arrowPath = UIBezierPath() -// arrowPath.move(to: point2) -// arrowPath.addLine(to: lastPoint) -// arrowPath.addLine(to: point1) -// let arrowThickPath = arrowPath.cgPath.copy(strokingWithWidth: self.renderArrowLineWidth, lineCap: .round, lineJoin: .round, miterLimit: 0.0) -// -// combinedPath.usesEvenOddFillRule = false -// combinedPath.append(UIBezierPath(cgPath: arrowThickPath)) -// } -// -// if let currentRenderLayer = self.currentRenderLayer as? RenderLayer { -// currentRenderLayer.updatePath(combinedPath.cgPath) -// } -// } -// -// func draw(in context: CGContext, size: CGSize) { -// let renderLayer: DrawingRenderLayer? -// if let currentRenderLayer = self.currentRenderLayer { -// renderLayer = currentRenderLayer -// } else { -// renderLayer = self.setupRenderLayer() -//// (renderLayer as? RenderLayer)?.updatePath(self.path.path.cgPath) -// } -// renderLayer?.render(in: context) -// } -//} - final class MarkerTool: DrawingElement { let uuid = UUID() @@ -483,22 +21,25 @@ final class MarkerTool: DrawingElement { var translation = CGPoint() var bounds: CGRect { - return self.renderPath.bounds + return self.renderPath.bounds.offsetBy(dx: self.translation.x, dy: self.translation.y) } - var points: [Polyline.Point] = [] - + var _points: [Polyline.Point] = [] + var points: [Polyline.Point] { + return self._points.map { $0.offsetBy(self.translation) } + } + weak var metalView: DrawingMetalView? func containsPoint(_ point: CGPoint) -> Bool { - return self.renderPath.contains(point) + return self.renderPath.contains(point.offsetBy(CGPoint(x: -self.translation.x, y: -self.translation.y))) } func hasPointsInsidePath(_ path: UIBezierPath) -> Bool { let pathBoundingBox = path.bounds if self.bounds.intersects(pathBoundingBox) { - for point in self.points { - if path.contains(point.location) { + for point in self._points { + if path.contains(point.location.offsetBy(self.translation)) { return true } } @@ -523,27 +64,43 @@ final class MarkerTool: DrawingElement { return nil } + private var hot = false func updatePath(_ path: DrawingGesturePipeline.DrawingResult, state: DrawingGesturePipeline.DrawingGestureState) { guard case let .location(point) = path else { return } - - if self.points.isEmpty { + + if self._points.isEmpty { self.renderPath.move(to: point.location) } else { self.renderPath.addLine(to: point.location) } - self.points.append(point) + self._points.append(point) + self.hot = true self.metalView?.updated(point, state: state, brush: .marker, color: self.color, size: self.renderLineWidth) } func draw(in context: CGContext, size: CGSize) { - guard !self.points.isEmpty else { + guard !self._points.isEmpty else { return } + context.saveGState() + context.translateBy(x: self.translation.x, y: self.translation.y) + + let hot = self.hot + if hot { + self.hot = false + } else { + self.metalView?.setup(self._points.map { $0.location }, brush: .marker, color: self.color, size: self.renderLineWidth) + } self.metalView?.drawInContext(context) + if !hot { + self.metalView?.clear() + } + + context.restoreGState() } } @@ -789,10 +346,13 @@ final class PencilTool: DrawingElement { var renderAngle: CGFloat = 0.0 var bounds: CGRect { - return self.renderPath.bounds + return self.renderPath.bounds.offsetBy(dx: self.translation.x, dy: self.translation.y) } - var points: [Polyline.Point] = [] + var _points: [Polyline.Point] = [] + var points: [Polyline.Point] { + return self._points.map { $0.offsetBy(self.translation) } + } weak var metalView: DrawingMetalView? @@ -803,7 +363,7 @@ final class PencilTool: DrawingElement { func hasPointsInsidePath(_ path: UIBezierPath) -> Bool { let pathBoundingBox = path.bounds if self.bounds.intersects(pathBoundingBox) { - for point in self.points { + for point in self._points { if path.contains(point.location.offsetBy(self.translation)) { return true } @@ -829,27 +389,43 @@ final class PencilTool: DrawingElement { return nil } + private var hot = false func updatePath(_ path: DrawingGesturePipeline.DrawingResult, state: DrawingGesturePipeline.DrawingGestureState) { guard case let .location(point) = path else { return } - if self.points.isEmpty { + if self._points.isEmpty { self.renderPath.move(to: point.location) } else { self.renderPath.addLine(to: point.location) } - self.points.append(point) + self._points.append(point) + self.hot = true self.metalView?.updated(point, state: state, brush: .pencil, color: self.color, size: self.renderLineWidth) } func draw(in context: CGContext, size: CGSize) { - guard !self.points.isEmpty else { + guard !self._points.isEmpty else { return } + context.saveGState() + context.translateBy(x: self.translation.x, y: self.translation.y) + + let hot = self.hot + if hot { + self.hot = false + } else { + self.metalView?.setup(self._points.map { $0.location }, brush: .pencil, color: self.color, size: self.renderLineWidth) + } self.metalView?.drawInContext(context) + if !hot { + self.metalView?.clear() + } + + context.restoreGState() } } diff --git a/submodules/DrawingUI/Sources/DrawingUtils.swift b/submodules/DrawingUI/Sources/DrawingUtils.swift index a47b8c680d..82912a0bd0 100644 --- a/submodules/DrawingUI/Sources/DrawingUtils.swift +++ b/submodules/DrawingUI/Sources/DrawingUtils.swift @@ -3,7 +3,15 @@ import UIKit import QuartzCore import simd -struct DrawingColor: Equatable { +struct DrawingColor: Equatable, Codable { + private enum CodingKeys: String, CodingKey { + case red + case green + case blue + case alpha + case position + } + public static var clear = DrawingColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0) public var red: CGFloat @@ -48,6 +56,24 @@ struct DrawingColor: Equatable { public init(rgb: UInt32) { self.init(color: UIColor(rgb: rgb)) } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.red = try container.decode(CGFloat.self, forKey: .red) + self.green = try container.decode(CGFloat.self, forKey: .green) + self.blue = try container.decode(CGFloat.self, forKey: .blue) + self.alpha = try container.decode(CGFloat.self, forKey: .alpha) + self.position = try container.decodeIfPresent(CGPoint.self, forKey: .position) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.red, forKey: .red) + try container.encode(self.green, forKey: .green) + try container.encode(self.blue, forKey: .blue) + try container.encode(self.alpha, forKey: .alpha) + try container.encodeIfPresent(self.position, forKey: .position) + } func withUpdatedRed(_ red: CGFloat) -> DrawingColor { return DrawingColor( @@ -561,156 +587,6 @@ private func configureControlPoints(data: [CGPoint]) -> [(ctrl1: CGPoint, ctrl2: return [] } - - - - -class FPSCounter: NSObject { - - /// Helper class that relays display link updates to the FPSCounter - /// - /// This is necessary because CADisplayLink retains its target. Thus - /// if the FPSCounter class would be the target of the display link - /// it would create a retain cycle. The delegate has a weak reference - /// to its parent FPSCounter, thus preventing this. - /// - internal class DisplayLinkProxy: NSObject { - - /// A weak ref to the parent FPSCounter instance. - @objc weak var parentCounter: FPSCounter? - - /// Notify the parent FPSCounter of a CADisplayLink update. - /// - /// This method is automatically called by the CADisplayLink. - /// - /// - Parameters: - /// - displayLink: The display link that updated - /// - @objc func updateFromDisplayLink(_ displayLink: CADisplayLink) { - parentCounter?.updateFromDisplayLink(displayLink) - } - } - - - // MARK: - Initialization - - private let displayLink: CADisplayLink - private let displayLinkProxy: DisplayLinkProxy - - /// Create a new FPSCounter. - /// - /// To start receiving FPS updates you need to start tracking with the - /// `startTracking(inRunLoop:mode:)` method. - /// - public override init() { - self.displayLinkProxy = DisplayLinkProxy() - self.displayLink = CADisplayLink( - target: self.displayLinkProxy, - selector: #selector(DisplayLinkProxy.updateFromDisplayLink(_:)) - ) - - super.init() - - self.displayLinkProxy.parentCounter = self - } - - deinit { - self.displayLink.invalidate() - } - - - // MARK: - Configuration - - /// The delegate that should receive FPS updates. - public weak var delegate: FPSCounterDelegate? - - /// Delay between FPS updates. Longer delays mean more averaged FPS numbers. - @objc public var notificationDelay: TimeInterval = 1.0 - - - // MARK: - Tracking - - private var runloop: RunLoop? - private var mode: RunLoop.Mode? - - /// Start tracking FPS updates. - /// - /// You can specify wich runloop to use for tracking, as well as the runloop modes. - /// Usually you'll want the main runloop (default), and either the common run loop modes - /// (default), or the tracking mode (`RunLoop.Mode.tracking`). - /// - /// When the counter is already tracking, it's stopped first. - /// - /// - Parameters: - /// - runloop: The runloop to start tracking in - /// - mode: The mode(s) to track in the runloop - /// - @objc public func startTracking(inRunLoop runloop: RunLoop = .main, mode: RunLoop.Mode = .common) { - self.stopTracking() - - self.runloop = runloop - self.mode = mode - self.displayLink.add(to: runloop, forMode: mode) - } - - /// Stop tracking FPS updates. - /// - /// This method does nothing if the counter is not currently tracking. - /// - @objc public func stopTracking() { - guard let runloop = self.runloop, let mode = self.mode else { return } - - self.displayLink.remove(from: runloop, forMode: mode) - self.runloop = nil - self.mode = nil - } - - - // MARK: - Handling Frame Updates - - private var lastNotificationTime: CFAbsoluteTime = 0.0 - private var numberOfFrames = 0 - - private func updateFromDisplayLink(_ displayLink: CADisplayLink) { - if self.lastNotificationTime == 0.0 { - self.lastNotificationTime = CFAbsoluteTimeGetCurrent() - return - } - - self.numberOfFrames += 1 - - let currentTime = CFAbsoluteTimeGetCurrent() - let elapsedTime = currentTime - self.lastNotificationTime - - if elapsedTime >= self.notificationDelay { - self.notifyUpdateForElapsedTime(elapsedTime) - self.lastNotificationTime = 0.0 - self.numberOfFrames = 0 - } - } - - private func notifyUpdateForElapsedTime(_ elapsedTime: CFAbsoluteTime) { - let fps = Int(round(Double(self.numberOfFrames) / elapsedTime)) - self.delegate?.fpsCounter(self, didUpdateFramesPerSecond: fps) - } -} - - -/// The delegate protocol for the FPSCounter class. -/// -/// Implement this protocol if you want to receive updates from a `FPSCounter`. -/// -protocol FPSCounterDelegate: NSObjectProtocol { - - /// Called in regular intervals while the counter is tracking FPS. - /// - /// - Parameters: - /// - counter: The FPSCounter that sent the update - /// - fps: The current FPS of the application - /// - func fpsCounter(_ counter: FPSCounter, didUpdateFramesPerSecond fps: Int) -} - class BezierPath { struct Element { enum ElementType { diff --git a/submodules/DrawingUI/Sources/DrawingVectorEntity.swift b/submodules/DrawingUI/Sources/DrawingVectorEntity.swift index 5e9d319af2..ce06b50cb8 100644 --- a/submodules/DrawingUI/Sources/DrawingVectorEntity.swift +++ b/submodules/DrawingUI/Sources/DrawingVectorEntity.swift @@ -3,8 +3,20 @@ import UIKit import Display import AccountContext -final class DrawingVectorEntity: DrawingEntity { - public enum VectorType { +final class DrawingVectorEntity: DrawingEntity, Codable { + private enum CodingKeys: String, CodingKey { + case uuid + case type + case color + case lineWidth + case drawingSize + case referenceDrawingSize + case start + case mid + case end + } + + public enum VectorType: Codable { case line case oneSidedArrow case twoSidedArrow @@ -34,6 +46,10 @@ final class DrawingVectorEntity: DrawingEntity { } } + var center: CGPoint { + return self.start + } + init(type: VectorType, color: DrawingColor, lineWidth: CGFloat) { self.uuid = UUID() self.isAnimated = false @@ -49,8 +65,34 @@ final class DrawingVectorEntity: DrawingEntity { self.end = CGPoint() } - var center: CGPoint { - return self.start + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.uuid = try container.decode(UUID.self, forKey: .uuid) + self.isAnimated = false + self.type = try container.decode(VectorType.self, forKey: .type) + self.color = try container.decode(DrawingColor.self, forKey: .color) + self.lineWidth = try container.decode(CGFloat.self, forKey: .lineWidth) + self.drawingSize = try container.decode(CGSize.self, forKey: .drawingSize) + self.referenceDrawingSize = try container.decode(CGSize.self, forKey: .referenceDrawingSize) + self.start = try container.decode(CGPoint.self, forKey: .start) + + let mid = try container.decode(CGPoint.self, forKey: .mid) + self.mid = (mid.x, mid.y) + + self.end = try container.decode(CGPoint.self, forKey: .end) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.uuid, forKey: .uuid) + try container.encode(self.type, forKey: .type) + try container.encode(self.color, forKey: .color) + try container.encode(self.lineWidth, forKey: .lineWidth) + try container.encode(self.drawingSize, forKey: .drawingSize) + try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize) + try container.encode(self.start, forKey: .start) + try container.encode(CGPoint(x: self.mid.0, y: self.mid.1), forKey: .mid) + try container.encode(self.end, forKey: .end) } func duplicate() -> DrawingEntity { diff --git a/submodules/DrawingUI/Sources/DrawingView.swift b/submodules/DrawingUI/Sources/DrawingView.swift index 1d007cac59..39dafe2253 100644 --- a/submodules/DrawingUI/Sources/DrawingView.swift +++ b/submodules/DrawingUI/Sources/DrawingView.swift @@ -90,7 +90,6 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw private var currentDrawingView: UIView private var currentDrawingLayer: DrawingRenderLayer? - private var selectionImage: UIImage? private var pannedSelectionView: UIView var lassoView: DrawingLassoView @@ -155,6 +154,7 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw self.lassoView.isHidden = true self.metalView = DrawingMetalView(size: size)! + self.metalView.isHidden = true self.brushSizePreviewLayer = SimpleShapeLayer() self.brushSizePreviewLayer.bounds = CGRect(origin: .zero, size: CGSize(width: 100.0, height: 100.0)) @@ -258,6 +258,11 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw guard let newElement = strongSelf.prepareNewElement() else { return } + + if newElement is MarkerTool || newElement is PencilTool { + self?.metalView.isHidden = false + } + if let renderLayer = newElement.setupRenderLayer() { strongSelf.currentDrawingView.layer.addSublayer(renderLayer) strongSelf.currentDrawingLayer = renderLayer @@ -366,7 +371,7 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw if let strongSelf = self { strongSelf.skipDrawing = Set(elements) strongSelf.commit(reset: true) - strongSelf.updateSelectionImage() + strongSelf.updateSelectionContent() } } @@ -386,12 +391,12 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw } } strongSelf.skipDrawing = Set() - strongSelf.commit(reset: true) - strongSelf.selectionImage = nil - strongSelf.pannedSelectionView.layer.contents = nil - - strongSelf.lassoView.bounds = CGRect(origin: .zero, size: strongSelf.lassoView.bounds.size) - strongSelf.lassoView.translate(offset) + strongSelf.commit(reset: true, completion: { + strongSelf.pannedSelectionView.layer.contents = nil + + strongSelf.lassoView.bounds = CGRect(origin: .zero, size: strongSelf.lassoView.bounds.size) + strongSelf.lassoView.translate(offset) + }) } } } @@ -468,46 +473,66 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw } } + private let queue = Queue() private var skipDrawing = Set() - private func commit(reset: Bool = false) { + private func commit(reset: Bool = false, interactive: Bool = false, synchronous: Bool = false, completion: @escaping () -> Void = {}) { let currentImage = self.drawingImage - self.drawingImage = self.renderer.image { context in - if !reset { - context.cgContext.clear(CGRect(origin: .zero, size: self.imageSize)) - if let image = currentImage { - image.draw(at: .zero) - } - if let uncommitedElement = self.uncommitedElement { - uncommitedElement.draw(in: context.cgContext, size: self.imageSize) - } - } else { - context.cgContext.clear(CGRect(origin: .zero, size: self.imageSize)) - for element in self.elements { - if !self.skipDrawing.contains(element.uuid) { - element.draw(in: context.cgContext, size: self.imageSize) + let uncommitedElement = self.uncommitedElement + let imageSize = self.imageSize + let skipDrawing = self.skipDrawing + + let action = { + let updatedImage = self.renderer.image { context in + if !reset { + context.cgContext.clear(CGRect(origin: .zero, size: imageSize)) + if let image = currentImage { + image.draw(at: .zero) + } + if let uncommitedElement = uncommitedElement { + uncommitedElement.draw(in: context.cgContext, size: imageSize) + } + } else { + context.cgContext.clear(CGRect(origin: .zero, size: imageSize)) + for element in self.elements { + if !skipDrawing.contains(element.uuid) { + element.draw(in: context.cgContext, size: imageSize) + } } } } + Queue.mainQueue().async { + self.drawingImage = updatedImage + self.layer.contents = updatedImage.cgImage + + if let currentDrawingLayer = self.currentDrawingLayer { + self.currentDrawingLayer = nil + currentDrawingLayer.removeFromSuperlayer() + } + + self.metalView.clear() + self.metalView.isHidden = true + + completion() + } } - self.layer.contents = self.drawingImage?.cgImage - - if let currentDrawingLayer = self.currentDrawingLayer { - self.currentDrawingLayer = nil - currentDrawingLayer.removeFromSuperlayer() + if synchronous { + action() + } else { + self.queue.async { + action() + } } - - self.metalView.clear() } - private func updateSelectionImage() { - self.selectionImage = self.renderer.image { context in + private func updateSelectionContent() { + let selectionImage = self.renderer.image { context in for element in self.elements { if self.skipDrawing.contains(element.uuid) { element.draw(in: context.cgContext, size: self.imageSize) } } } - self.pannedSelectionView.layer.contents = self.selectionImage?.cgImage + self.pannedSelectionView.layer.contents = selectionImage.cgImage } fileprivate func cancelDrawing() { @@ -520,15 +545,24 @@ public final class DrawingView: UIView, UIGestureRecognizerDelegate, TGPhotoDraw } fileprivate func finishDrawing() { - self.commit() - - self.redoElements.removeAll() - if let uncommitedElement = self.uncommitedElement { - self.elements.append(uncommitedElement) - self.uncommitedElement = nil + let complete: (Bool) -> Void = { synchronous in + self.commit(interactive: true, synchronous: synchronous) + + self.redoElements.removeAll() + if let uncommitedElement = self.uncommitedElement { + self.elements.append(uncommitedElement) + self.uncommitedElement = nil + } + + self.updateInternalState() + } + if let uncommitedElement = self.uncommitedElement as? PenTool, uncommitedElement.arrow { + uncommitedElement.finishArrow({ + complete(true) + }) + } else { + complete(false) } - - self.updateInternalState() } weak var entitiesView: DrawingEntitiesView? diff --git a/submodules/DrawingUI/Sources/PenTool.swift b/submodules/DrawingUI/Sources/PenTool.swift index 763cc25f4a..82c0079f84 100644 --- a/submodules/DrawingUI/Sources/PenTool.swift +++ b/submodules/DrawingUI/Sources/PenTool.swift @@ -91,6 +91,32 @@ final class PenTool: DrawingElement { self.setNeedsDisplay(rect.insetBy(dx: -50.0, dy: -50.0)) } + func animateArrowPaths(leftArrowPath: UIBezierPath, rightArrowPath: UIBezierPath, lineWidth: CGFloat, completion: @escaping () -> Void) { + let leftArrowShape = CAShapeLayer() + leftArrowShape.path = leftArrowPath.cgPath + leftArrowShape.lineWidth = lineWidth + leftArrowShape.strokeColor = self.color?.cgColor + leftArrowShape.lineCap = .round + leftArrowShape.frame = self.bounds + self.addSublayer(leftArrowShape) + + let rightArrowShape = CAShapeLayer() + rightArrowShape.path = rightArrowPath.cgPath + rightArrowShape.lineWidth = lineWidth + rightArrowShape.strokeColor = self.color?.cgColor + rightArrowShape.lineCap = .round + rightArrowShape.frame = self.bounds + self.addSublayer(rightArrowShape) + + leftArrowShape.animate(from: 0.0 as NSNumber, to: 1.0 as NSNumber, keyPath: "strokeEnd", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2) + rightArrowShape.animate(from: 0.0 as NSNumber, to: 1.0 as NSNumber, keyPath: "strokeEnd", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2, completion: { [weak leftArrowShape, weak rightArrowShape] _ in + completion() + + leftArrowShape?.removeFromSuperlayer() + rightArrowShape?.removeFromSuperlayer() + }) + } + override func draw(in ctx: CGContext) { guard let color = self.color else { return @@ -122,10 +148,17 @@ final class PenTool: DrawingElement { var didSetupArrow = false let renderLineWidth: CGFloat + let renderArrowLength: CGFloat + let renderArrowLineWidth: CGFloat var bezierPaths: [UIBezierPath] = [] var tempBezierPath: UIBezierPath? + var arrowLeftPath: UIBezierPath? + var arrowLeftPoint: CGPoint? + var arrowRightPath: UIBezierPath? + var arrowRightPoint: CGPoint? + var translation = CGPoint() private var currentRenderLayer: DrawingRenderLayer? @@ -138,8 +171,16 @@ final class PenTool: DrawingElement { var points: [Polyline.Point] { var points: [Polyline.Point] = [] + var lastPoint: Polyline.Point? for point in self._points { points.append(point.offsetBy(self.translation)) + lastPoint = point + } + if let arrowLeftPoint, let lastPoint { + points.append(lastPoint.withLocation(arrowLeftPoint.offsetBy(self.translation))) + } + if let arrowRightPoint, let lastPoint { + points.append(lastPoint.withLocation(arrowRightPoint.offsetBy(self.translation))) } return points } @@ -148,9 +189,15 @@ final class PenTool: DrawingElement { private var nextPointIndex: Int = 0 private var drawPoints = [PointWeighted](repeating: PointWeighted.zero, count: 4) + private var arrowParams: (CGPoint, CGFloat)? + func containsPoint(_ point: CGPoint) -> Bool { + for path in self.bezierPaths { + if path.contains(point.offsetBy(CGPoint(x: -self.translation.x, y: -self.translation.y))) { + return true + } + } return false - // return self.renderPath?.contains(point.offsetBy(CGPoint(x: -self.translation.x, y: -self.translation.y))) ?? false } func hasPointsInsidePath(_ path: UIBezierPath) -> Bool { @@ -178,12 +225,20 @@ final class PenTool: DrawingElement { let lineWidth = minLineWidth + (maxLineWidth - minLineWidth) * lineWidth self.renderLineWidth = lineWidth + self.renderArrowLength = lineWidth * 7.0 + self.renderArrowLineWidth = lineWidth * 2.0 -// self.renderLine = StrokeLine(color: color.toUIColor(), minLineWidth: minLineWidth, lineWidth: lineWidth) -// if arrow { -// self.renderLineArrow1 = StrokeLine(color: color.toUIColor(), minLineWidth: minLineWidth, lineWidth: lineWidth * 0.8) -// self.renderLineArrow2 = StrokeLine(color: color.toUIColor(), minLineWidth: minLineWidth, lineWidth: lineWidth * 0.8) -// } + self.path = Polyline(points: []) + } + + func finishArrow(_ completion: @escaping () -> Void) { + if let arrowLeftPath, let arrowRightPath { + (self.currentRenderLayer as? RenderLayer)?.animateArrowPaths(leftArrowPath: arrowLeftPath, rightArrowPath: arrowRightPath, lineWidth: self.renderArrowLineWidth, completion: { + completion() + }) + } else { + completion() + } } func setupRenderLayer() -> DrawingRenderLayer? { @@ -211,6 +266,7 @@ final class PenTool: DrawingElement { } self._points.append(point) + self.path?.points.append(point) switch state { case .began: @@ -222,6 +278,41 @@ final class PenTool: DrawingElement { } case .ended: self.updateWithLocation(point.location, ended: true) + + if self.arrow { + let points = self.path?.points ?? [] + var direction: CGFloat? + + + let p2 = points[points.count - 1].location + for i in 1 ..< min(points.count - 2, 12) { + let p1 = points[points.count - 1 - i].location + if p1.distance(to: p2) > renderArrowLength * 0.5 { + direction = p2.angle(to: p1) + break + } + } + + if let point = points.last?.location, let direction { + self.arrowParams = (point, direction) + + let arrowLeftPath = UIBezierPath() + arrowLeftPath.move(to: point) + let leftPoint = point.pointAt(distance: self.renderArrowLength, angle: direction - 0.45) + arrowLeftPath.addLine(to: leftPoint) + + let arrowRightPath = UIBezierPath() + arrowRightPath.move(to: point) + let rightPoint = point.pointAt(distance: self.renderArrowLength, angle: direction + 0.45) + arrowRightPath.addLine(to: rightPoint) + + self.arrowLeftPath = arrowLeftPath + self.arrowLeftPoint = leftPoint + + self.arrowRightPath = arrowRightPath + self.arrowRightPoint = rightPoint + } + } case .cancelled: break } @@ -229,41 +320,6 @@ final class PenTool: DrawingElement { if let currentRenderLayer = self.currentRenderLayer as? RenderLayer { currentRenderLayer.draw(paths: self.bezierPaths, tempPath: self.tempBezierPath, color: self.color.toUIColor(), rect: CGRect(origin: .zero, size: self.drawingSize)) } - -// self.path = line -// -// let rect = self.renderLine.draw(at: point) -// if let currentRenderLayer = self.currentRenderLayer as? RenderLayer { -// currentRenderLayer.draw(line: self.renderLine, rect: rect) -// } - // self.path = bezierPath - - // if self.arrow && polyline.isComplete, polyline.points.count > 2 { - // let lastPoint = lastPosition - // var secondPoint = polyline.points[polyline.points.count - 2] - // if secondPoint.location.distance(to: lastPoint) < self.renderArrowLineWidth { - // secondPoint = polyline.points[polyline.points.count - 3] - // } - // let angle = lastPoint.angle(to: secondPoint.location) - // let point1 = lastPoint.pointAt(distance: self.renderArrowLength, angle: angle - CGFloat.pi * 0.15) - // let point2 = lastPoint.pointAt(distance: self.renderArrowLength, angle: angle + CGFloat.pi * 0.15) - // - // let arrowPath = UIBezierPath() - // arrowPath.move(to: point2) - // arrowPath.addLine(to: lastPoint) - // arrowPath.addLine(to: point1) - // let arrowThickPath = arrowPath.cgPath.copy(strokingWithWidth: self.renderArrowLineWidth, lineCap: .round, lineJoin: .round, miterLimit: 0.0) - // - // combinedPath.usesEvenOddFillRule = false - // combinedPath.append(UIBezierPath(cgPath: arrowThickPath)) - // } - - // let cgPath = bezierPath.path.cgPath.copy(strokingWithWidth: self.renderLineWidth, lineCap: .round, lineJoin: .round, miterLimit: 0.0) - // self.renderPath = cgPath - - // if let currentRenderLayer = self.currentRenderLayer as? RenderLayer { - // currentRenderLayer.updatePath(cgPath) - // } } private let minDistance: CGFloat = 2 @@ -289,7 +345,6 @@ final class PenTool: DrawingElement { self.tempBezierPath = newBezier } - private var isFirstPoint: Bool { return nextPointIndex == 0 } @@ -401,6 +456,18 @@ final class PenTool: DrawingElement { context.fillPath() } + if let arrowLeftPath, let arrowRightPath { + context.setStrokeColor(self.color.toCGColor()) + context.setLineWidth(self.renderArrowLineWidth) + context.setLineCap(.round) + + context.addPath(arrowLeftPath.cgPath) + context.strokePath() + + context.addPath(arrowRightPath.cgPath) + context.strokePath() + } + context.restoreGState() } } @@ -503,431 +570,3 @@ extension UIBezierPath { return (line.normalLine(from: pointA), line.normalLine(from: pointB)) } } - - - -// -//final class PenTool: DrawingElement { -// class RenderLayer: SimpleLayer, DrawingRenderLayer { -// func setup(size: CGSize) { -// self.shouldRasterize = true -// self.contentsScale = 1.0 -// -// let bounds = CGRect(origin: .zero, size: size) -// self.frame = bounds -// } -// -// private var line: StrokeLine? -// fileprivate func draw(line: StrokeLine, rect: CGRect) { -// self.line = line -// self.setNeedsDisplay(rect.insetBy(dx: -50.0, dy: -50.0)) -// } -// -// override func draw(in ctx: CGContext) { -// self.line?.drawInContext(ctx) -// } -// } -// -// let uuid = UUID() -// -// let drawingSize: CGSize -// let color: DrawingColor -// let lineWidth: CGFloat -// let arrow: Bool -// -// var path: Polyline? -// var boundingBox: CGRect? -// -// private var renderLine: StrokeLine -// var didSetupArrow = false -// private var renderLineArrow1: StrokeLine? -// private var renderLineArrow2: StrokeLine? -// let renderLineWidth: CGFloat -// -// var translation = CGPoint() -// -// private var currentRenderLayer: DrawingRenderLayer? -// -// var bounds: CGRect { -// return self.path?.bounds.offsetBy(dx: self.translation.x, dy: self.translation.y) ?? .zero -// } -// -// var points: [Polyline.Point] { -// guard let linePath = self.path else { -// return [] -// } -// var points: [Polyline.Point] = [] -// for point in linePath.points { -// points.append(point.offsetBy(self.translation)) -// } -// return points -// } -// -// func containsPoint(_ point: CGPoint) -> Bool { -// return false -// // return self.renderPath?.contains(point.offsetBy(CGPoint(x: -self.translation.x, y: -self.translation.y))) ?? false -// } -// -// func hasPointsInsidePath(_ path: UIBezierPath) -> Bool { -// if let linePath = self.path { -// let pathBoundingBox = path.bounds -// if self.bounds.intersects(pathBoundingBox) { -// for point in linePath.points { -// if path.contains(point.location.offsetBy(self.translation)) { -// return true -// } -// } -// } -// } -// return false -// } -// -// required init(drawingSize: CGSize, color: DrawingColor, lineWidth: CGFloat, arrow: Bool) { -// self.drawingSize = drawingSize -// self.color = color -// self.lineWidth = lineWidth -// self.arrow = arrow -// -// let minLineWidth = max(1.0, min(drawingSize.width, drawingSize.height) * 0.003) -// let maxLineWidth = max(10.0, min(drawingSize.width, drawingSize.height) * 0.09) -// let lineWidth = minLineWidth + (maxLineWidth - minLineWidth) * lineWidth -// -// self.renderLineWidth = lineWidth -// -// self.renderLine = StrokeLine(color: color.toUIColor(), minLineWidth: minLineWidth, lineWidth: lineWidth) -// if arrow { -// self.renderLineArrow1 = StrokeLine(color: color.toUIColor(), minLineWidth: minLineWidth, lineWidth: lineWidth * 0.8) -// self.renderLineArrow2 = StrokeLine(color: color.toUIColor(), minLineWidth: minLineWidth, lineWidth: lineWidth * 0.8) -// } -// } -// -// func setupRenderLayer() -> DrawingRenderLayer? { -// let layer = RenderLayer() -// layer.setup(size: self.drawingSize) -// self.currentRenderLayer = layer -// return layer -// } -// -// func updatePath(_ path: DrawingGesturePipeline.DrawingResult, state: DrawingGesturePipeline.DrawingGestureState) { -// guard case let .polyline(line) = path, let point = line.points.last else { -// return -// } -// self.path = line -// -// let rect = self.renderLine.draw(at: point) -// if let currentRenderLayer = self.currentRenderLayer as? RenderLayer { -// currentRenderLayer.draw(line: self.renderLine, rect: rect) -// } -// // self.path = bezierPath -// -// // if self.arrow && polyline.isComplete, polyline.points.count > 2 { -// // let lastPoint = lastPosition -// // var secondPoint = polyline.points[polyline.points.count - 2] -// // if secondPoint.location.distance(to: lastPoint) < self.renderArrowLineWidth { -// // secondPoint = polyline.points[polyline.points.count - 3] -// // } -// // let angle = lastPoint.angle(to: secondPoint.location) -// // let point1 = lastPoint.pointAt(distance: self.renderArrowLength, angle: angle - CGFloat.pi * 0.15) -// // let point2 = lastPoint.pointAt(distance: self.renderArrowLength, angle: angle + CGFloat.pi * 0.15) -// // -// // let arrowPath = UIBezierPath() -// // arrowPath.move(to: point2) -// // arrowPath.addLine(to: lastPoint) -// // arrowPath.addLine(to: point1) -// // let arrowThickPath = arrowPath.cgPath.copy(strokingWithWidth: self.renderArrowLineWidth, lineCap: .round, lineJoin: .round, miterLimit: 0.0) -// // -// // combinedPath.usesEvenOddFillRule = false -// // combinedPath.append(UIBezierPath(cgPath: arrowThickPath)) -// // } -// -// // let cgPath = bezierPath.path.cgPath.copy(strokingWithWidth: self.renderLineWidth, lineCap: .round, lineJoin: .round, miterLimit: 0.0) -// // self.renderPath = cgPath -// -// // if let currentRenderLayer = self.currentRenderLayer as? RenderLayer { -// // currentRenderLayer.updatePath(cgPath) -// // } -// } -// -// func draw(in context: CGContext, size: CGSize) { -// context.saveGState() -// -// context.translateBy(x: self.translation.x, y: self.translation.y) -// -// context.setShouldAntialias(true) -// -// if self.arrow, let path = self.path, let lastPoint = path.points.last { -// var lastPointWithVelocity: Polyline.Point? -// for point in path.points.reversed() { -// if point.velocity > 0.0 { -// lastPointWithVelocity = point -// break -// } -// } -// if !self.didSetupArrow, let lastPointWithVelocity = lastPointWithVelocity { -// let w = self.renderLineWidth -// var dist: CGFloat = 18.0 * sqrt(w) -// let spread: CGFloat = .pi * max(0.05, 0.03 * sqrt(w)) -// -// let suffix = path.points.suffix(100).reversed() -// -// var p0 = suffix.first! -// -// var p2 = suffix.last! -// var d: CGFloat = 0 -// for p in suffix { -// d += hypot(p0.location.x - p.location.x, p0.location.y - p.location.y) -// if d >= dist { -// p2 = p -// break -// } -// p0 = p -// } -// -// p0 = suffix.first! -// dist = min(dist, hypot(p0.location.x - p2.location.x, p0.location.y - p2.location.y)) -// -// var i = 0 -// for spread in [-spread, spread] { -// var points: [CGPoint] = [] -// points.append(lastPoint.location) -// -// p0 = suffix.first! -// var prev = p0.location -// d = 0 -// for p in suffix { -// let d1 = hypot(p0.location.x - p.location.x, p0.location.y - p.location.y) -// d += d1 -// if d >= dist { -// break -// } -// let d2 = d1 / cos(spread) -// let angle = atan2(p.location.y - p0.location.y, p.location.x - p0.location.x) -// let cur = CGPoint(x: prev.x + d2 * cos(angle + spread), y: prev.y + d2 * sin(angle + spread)) -// -// points.append( -// cur -// ) -// -// p0 = p -// prev = cur -// } -// -// for point in points { -// if i == 0 { -// let _ = self.renderLineArrow1?.draw(at: Polyline.Point(location: point, force: 0.0, altitudeAngle: 0.0, azimuth: 0.0, velocity: lastPointWithVelocity.velocity, touchPoint: lastPointWithVelocity.touchPoint)) -// } else if i == 1 { -// let _ = self.renderLineArrow2?.draw(at: Polyline.Point(location: point, force: 0.0, altitudeAngle: 0.0, azimuth: 0.0, velocity: lastPointWithVelocity.velocity, touchPoint: lastPointWithVelocity.touchPoint)) -// } -// } -// i += 1 -// } -// self.didSetupArrow = true -// } -// self.renderLineArrow1?.drawInContext(context) -// self.renderLineArrow2?.drawInContext(context) -// } -// -// self.renderLine.drawInContext(context) -// -// context.restoreGState() -// } -//} -// -//private class StrokeLine { -// struct Segment { -// let a: CGPoint -// let b: CGPoint -// let c: CGPoint -// let d: CGPoint -// let abWidth: CGFloat -// let cdWidth: CGFloat -// } -// -// struct Point { -// let position: CGPoint -// let width: CGFloat -// -// init(position: CGPoint, width: CGFloat) { -// self.position = position -// self.width = width -// } -// } -// -// private(set) var points: [Point] = [] -// private var smoothPoints: [Point] = [] -// private var segments: [Segment] = [] -// private var lastWidth: CGFloat? -// -// private let minLineWidth: CGFloat -// let lineWidth: CGFloat -// -// let color: UIColor -// -// init(color: UIColor, minLineWidth: CGFloat, lineWidth: CGFloat) { -// self.color = color -// self.minLineWidth = minLineWidth -// self.lineWidth = lineWidth -// } -// -// func draw(at point: Polyline.Point) -> CGRect { -// let width = extractLineWidth(from: point.velocity) -// self.lastWidth = width -// -// let point = Point(position: point.location, width: width) -// return appendPoint(point) -// } -// -// func drawInContext(_ context: CGContext) { -// self.drawSegments(self.segments, inContext: context) -// } -// -// func extractLineWidth(from velocity: CGFloat) -> CGFloat { -// let minValue = self.minLineWidth -// let maxValue = self.lineWidth -// -// var size = max(minValue, min(maxValue + 1 - (velocity / 150), maxValue)) -// if let lastWidth = self.lastWidth { -// size = size * 0.2 + lastWidth * 0.8 -// } -// return size -// } -// -// func appendPoint(_ point: Point) -> CGRect { -// self.points.append(point) -// -// guard self.points.count > 2 else { return .null } -// -// let index = self.points.count - 1 -// let point0 = self.points[index - 2] -// let point1 = self.points[index - 1] -// let point2 = self.points[index] -// -// let newSmoothPoints = smoothPoints( -// fromPoint0: point0, -// point1: point1, -// point2: point2 -// ) -// -// let lastOldSmoothPoint = smoothPoints.last -// smoothPoints.append(contentsOf: newSmoothPoints) -// -// guard smoothPoints.count > 1 else { return .null } -// -// let newSegments: ([Segment], CGRect) = { -// guard let lastOldSmoothPoint = lastOldSmoothPoint else { -// return segments(fromSmoothPoints: newSmoothPoints) -// } -// return segments(fromSmoothPoints: [lastOldSmoothPoint] + newSmoothPoints) -// }() -// segments.append(contentsOf: newSegments.0) -// -// return newSegments.1 -// } -// -// func smoothPoints(fromPoint0 point0: Point, point1: Point, point2: Point) -> [Point] { -// var smoothPoints = [Point]() -// -// let midPoint1 = (point0.position + point1.position) * 0.5 -// let midPoint2 = (point1.position + point2.position) * 0.5 -// -// let segmentDistance = 2.0 -// let distance = midPoint1.distance(to: midPoint2) -// let numberOfSegments = min(128, max(floor(distance/segmentDistance), 32)) -// -// let step = 1.0 / numberOfSegments -// for t in stride(from: 0, to: 1, by: step) { -// let position = midPoint1 * pow(1 - t, 2) + point1.position * 2 * (1 - t) * t + midPoint2 * t * t -// let size = pow(1 - t, 2) * ((point0.width + point1.width) * 0.5) + 2 * (1 - t) * t * point1.width + t * t * ((point1.width + point2.width) * 0.5) -// let point = Point(position: position, width: size) -// smoothPoints.append(point) -// } -// -// let finalPoint = Point(position: midPoint2, width: (point1.width + point2.width) * 0.5) -// smoothPoints.append(finalPoint) -// -// return smoothPoints -// } -// -// func segments(fromSmoothPoints smoothPoints: [Point]) -> ([Segment], CGRect) { -// var segments = [Segment]() -// var updateRect = CGRect.null -// for i in 1 ..< smoothPoints.count { -// let previousPoint = smoothPoints[i - 1].position -// let previousWidth = smoothPoints[i - 1].width -// let currentPoint = smoothPoints[i].position -// let currentWidth = smoothPoints[i].width -// let direction = currentPoint - previousPoint -// -// guard !currentPoint.isEqual(to: previousPoint, epsilon: 0.0001) else { -// continue -// } -// -// var perpendicular = CGPoint(x: -direction.y, y: direction.x) -// let length = perpendicular.length -// if length > 0.0 { -// perpendicular = perpendicular / length -// } -// -// let a = previousPoint + perpendicular * previousWidth / 2 -// let b = previousPoint - perpendicular * previousWidth / 2 -// let c = currentPoint + perpendicular * currentWidth / 2 -// let d = currentPoint - perpendicular * currentWidth / 2 -// -// let ab: CGPoint = { -// let center = (b + a)/2 -// let radius = center - b -// return .init(x: center.x - radius.y, y: center.y + radius.x) -// }() -// let cd: CGPoint = { -// let center = (c + d)/2 -// let radius = center - c -// return .init(x: center.x + radius.y, y: center.y - radius.x) -// }() -// -// let minX = min(a.x, b.x, c.x, d.x, ab.x, cd.x) -// let minY = min(a.y, b.y, c.y, d.y, ab.y, cd.y) -// let maxX = max(a.x, b.x, c.x, d.x, ab.x, cd.x) -// let maxY = max(a.y, b.y, c.y, d.y, ab.y, cd.y) -// -// updateRect = updateRect.union(CGRect(x: minX, y: minY, width: maxX - minX, height: maxY - minY)) -// -// segments.append(Segment(a: a, b: b, c: c, d: d, abWidth: previousWidth, cdWidth: currentWidth)) -// } -// return (segments, updateRect) -// } -// -// func drawSegments(_ segments: [Segment], inContext context: CGContext) { -// for segment in segments { -// context.beginPath() -// -// context.setStrokeColor(color.cgColor) -// context.setFillColor(color.cgColor) -// -// context.move(to: segment.b) -// -// let abStartAngle = atan2(segment.b.y - segment.a.y, segment.b.x - segment.a.x) -// context.addArc( -// center: (segment.a + segment.b)/2, -// radius: segment.abWidth/2, -// startAngle: abStartAngle, -// endAngle: abStartAngle + .pi, -// clockwise: true -// ) -// context.addLine(to: segment.c) -// -// let cdStartAngle = atan2(segment.c.y - segment.d.y, segment.c.x - segment.d.x) -// context.addArc( -// center: (segment.c + segment.d)/2, -// radius: segment.cdWidth/2, -// startAngle: cdStartAngle, -// endAngle: cdStartAngle + .pi, -// clockwise: true -// ) -// context.closePath() -// -// context.fillPath() -// context.strokePath() -// } -// } -//} -// diff --git a/submodules/DrawingUI/Sources/StickerPickerScreen.swift b/submodules/DrawingUI/Sources/StickerPickerScreen.swift index ff8707f5c5..92da0222ff 100644 --- a/submodules/DrawingUI/Sources/StickerPickerScreen.swift +++ b/submodules/DrawingUI/Sources/StickerPickerScreen.swift @@ -11,29 +11,49 @@ import ComponentFlow import ViewControllerComponent import EntityKeyboard import PagerComponent +import FeaturedStickersScreen + +struct InputData: Equatable { + var emoji: EmojiPagerContentComponent + var stickers: EmojiPagerContentComponent? + var masks: EmojiPagerContentComponent? + + init( + emoji: EmojiPagerContentComponent, + stickers: EmojiPagerContentComponent?, + masks: EmojiPagerContentComponent? + ) { + self.emoji = emoji + self.stickers = stickers + self.masks = masks + } +} private final class StickerSelectionComponent: Component { - public typealias EnvironmentType = Empty + typealias EnvironmentType = Empty - public let theme: PresentationTheme - public let strings: PresentationStrings - public let deviceMetrics: DeviceMetrics - public let stickerContent: EmojiPagerContentComponent - public let backgroundColor: UIColor - public let separatorColor: UIColor + let theme: PresentationTheme + let strings: PresentationStrings + let deviceMetrics: DeviceMetrics + let bottomInset: CGFloat + let content: InputData + let backgroundColor: UIColor + let separatorColor: UIColor - public init( + init( theme: PresentationTheme, strings: PresentationStrings, deviceMetrics: DeviceMetrics, - stickerContent: EmojiPagerContentComponent, + bottomInset: CGFloat, + content: InputData, backgroundColor: UIColor, separatorColor: UIColor ) { self.theme = theme self.strings = strings self.deviceMetrics = deviceMetrics - self.stickerContent = stickerContent + self.bottomInset = bottomInset + self.content = content self.backgroundColor = backgroundColor self.separatorColor = separatorColor } @@ -48,7 +68,10 @@ private final class StickerSelectionComponent: Component { if lhs.deviceMetrics != rhs.deviceMetrics { return false } - if lhs.stickerContent != rhs.stickerContent { + if lhs.bottomInset != rhs.bottomInset { + return false + } + if lhs.content != rhs.content { return false } if lhs.backgroundColor != rhs.backgroundColor { @@ -96,7 +119,7 @@ private final class StickerSelectionComponent: Component { self.backgroundColor = component.backgroundColor let panelBackgroundColor = component.backgroundColor.withMultipliedAlpha(0.85) self.panelBackgroundView.updateColor(color: panelBackgroundColor, transition: .immediate) - self.panelSeparatorView.backgroundColor = component.separatorColor + self.panelSeparatorView.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.1) self.component = component self.state = state @@ -109,15 +132,18 @@ private final class StickerSelectionComponent: Component { theme: component.theme, strings: component.strings, isContentInFocus: true, - containerInsets: UIEdgeInsets(top: topPanelHeight - 34.0, left: 0.0, bottom: 0.0, right: 0.0), + containerInsets: UIEdgeInsets(top: topPanelHeight - 34.0, left: 0.0, bottom: component.bottomInset, right: 0.0), topPanelInsets: UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0), - emojiContent: nil, - stickerContent: component.stickerContent, + emojiContent: component.content.emoji, + stickerContent: component.content.stickers, + maskContent: component.content.masks, gifContent: nil, hasRecentGifs: false, availableGifSearchEmojies: [], defaultToEmojiTab: false, externalTopPanelContainer: self.panelHostView, + externalBottomPanelContainer: nil, + displayTopPanelBackground: true, topPanelExtensionUpdated: { _, _ in }, hideInputUpdated: { _, _, _ in }, hideTopPanelUpdated: { _, _ in }, @@ -128,7 +154,7 @@ private final class StickerSelectionComponent: Component { deviceMetrics: component.deviceMetrics, hiddenInputHeight: 0.0, inputHeight: 0.0, - displayBottomPanel: false, + displayBottomPanel: true, isExpanded: true )), environment: {}, @@ -182,8 +208,8 @@ public class StickerPickerScreen: ViewController { let scrollView: UIScrollView let hostView: ComponentHostView - private var stickerContent: EmojiPagerContentComponent? - private let stickerContentDisposable = MetaDisposable() + private var content: InputData? + private let contentDisposable = MetaDisposable() private var scheduledEmojiContentAnimationHint: EmojiPagerContentComponent.ContentAnimation? private(set) var isExpanded = false @@ -224,31 +250,63 @@ public class StickerPickerScreen: ViewController { self.containerView.addSubview(self.scrollView) self.scrollView.addSubview(self.hostView) - self.stickerContentDisposable.set(( - EmojiPagerContentComponent.stickerInputData( - context: context, - animationCache: context.animationCache, - animationRenderer: context.animationRenderer, - stickerNamespaces: [Namespaces.ItemCollection.CloudStickerPacks], - stickerOrderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.CloudAllPremiumStickers], - chatPeerId: context.account.peerId - ) - |> deliverOnMainQueue).start(next: { [weak self] content in - if let strongSelf = self { - strongSelf.updateContent(content) - } - }) + let emojiItems = EmojiPagerContentComponent.emojiInputData( + context: context, + animationCache: context.animationCache, + animationRenderer: context.animationRenderer, + isStandalone: false, + isStatusSelection: false, + isReactionSelection: false, + isEmojiSelection: true, + topReactionItems: [], + areUnicodeEmojiEnabled: true, + areCustomEmojiEnabled: true, + chatPeerId: context.account.peerId, + hasSearch: false ) + + let stickerItems = EmojiPagerContentComponent.stickerInputData( + context: context, + animationCache: context.animationCache, + animationRenderer: context.animationRenderer, + stickerNamespaces: [Namespaces.ItemCollection.CloudStickerPacks], + stickerOrderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.CloudAllPremiumStickers], + chatPeerId: context.account.peerId, + hasSearch: false, + hasTrending: true + ) + + let maskItems = EmojiPagerContentComponent.stickerInputData( + context: context, + animationCache: context.animationCache, + animationRenderer: context.animationRenderer, + stickerNamespaces: [Namespaces.ItemCollection.CloudMaskPacks], + stickerOrderedItemListCollectionIds: [], + chatPeerId: context.account.peerId, + hasSearch: false, + hasTrending: false + ) + + let signal = combineLatest(queue: .mainQueue(), + emojiItems, + stickerItems, + maskItems + ) + self.contentDisposable.set(signal.start(next: { [weak self] emoji, stickers, masks in + if let strongSelf = self { + strongSelf.updateContent(InputData(emoji: emoji, stickers: stickers, masks: masks)) + } + })) } deinit { - self.stickerContentDisposable.dispose() + self.contentDisposable.dispose() } - - func updateContent(_ content: EmojiPagerContentComponent) { - self.stickerContent = content - content.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction( + func updateContent(_ content: InputData) { + self.content = content + + content.emoji.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction( performItemAction: { [weak self] _, item, _, _, _, _ in guard let strongSelf = self, let file = item.itemFile else { return @@ -256,30 +314,229 @@ public class StickerPickerScreen: ViewController { strongSelf.controller?.completion(file) strongSelf.controller?.dismiss(animated: true) }, - deleteBackwards: {}, - openStickerSettings: { -// guard let controllerInteraction = controllerInteraction else { -// return -// } -// let controller = installedStickerPacksController(context: context, mode: .modal) -// controller.navigationPresentation = .modal -// controllerInteraction.navigationController()?.pushViewController(controller) - }, + deleteBackwards: nil, + openStickerSettings: nil, openFeatured: { -// guard let controllerInteraction = controllerInteraction else { -// return -// } -// -// controllerInteraction.navigationController()?.pushViewController(FeaturedStickersScreen( -// context: context, -// highlightedPackId: nil, -// sendSticker: { [weak controllerInteraction] fileReference, sourceNode, sourceRect in -// guard let controllerInteraction = controllerInteraction else { -// return false -// } -// return controllerInteraction.sendSticker(fileReference, false, false, nil, false, sourceNode, sourceRect, nil, []) -// } -// )) + }, + openSearch: {}, + addGroupAction: { [weak self] groupId, isPremiumLocked in + guard let strongSelf = self, let controller = strongSelf.controller, let collectionId = groupId.base as? ItemCollectionId else { + return + } + let context = controller.context + + if isPremiumLocked { +// let controller = PremiumIntroScreen(context: context, source: .stickers) +// controllerInteraction.navigationController()?.pushViewController(controller) + + return + } + + let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedStickerPacks) + let _ = (context.account.postbox.combinedView(keys: [viewKey]) + |> take(1) + |> deliverOnMainQueue).start(next: { views in + guard let view = views.views[viewKey] as? OrderedItemListView else { + return + } + for featuredStickerPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { + if featuredStickerPack.info.id == collectionId { + let _ = (context.engine.stickers.loadedStickerPack(reference: .id(id: featuredStickerPack.info.id.id, accessHash: featuredStickerPack.info.accessHash), forceActualized: false) + |> mapToSignal { result -> Signal in + switch result { + case let .result(info, items, installed): + if installed { + return .complete() + } else { + return context.engine.stickers.addStickerPackInteractively(info: info, items: items) + } + case .fetching: + break + case .none: + break + } + return .complete() + } + |> deliverOnMainQueue).start(completed: { + }) + + break + } + } + }) + }, + clearGroup: { [weak self] groupId in + guard let strongSelf = self, let controller = strongSelf.controller else { + return + } + if groupId == AnyHashable("popular") { + let presentationData = controller.context.sharedContext.currentPresentationData.with { $0 } + let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme, fontSize: presentationData.listsFontSize)) + var items: [ActionSheetItem] = [] + let context = controller.context + items.append(ActionSheetTextItem(title: presentationData.strings.Chat_ClearReactionsAlertText, parseMarkdown: true)) + items.append(ActionSheetButtonItem(title: presentationData.strings.Chat_ClearReactionsAlertAction, color: .destructive, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + guard let strongSelf = self else { + return + } + + strongSelf.scheduledEmojiContentAnimationHint = EmojiPagerContentComponent.ContentAnimation(type: .groupRemoved(id: "popular")) + let _ = context.engine.stickers.clearRecentlyUsedReactions().start() + })) + actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ])]) + context.sharedContext.mainWindow?.presentInGlobalOverlay(actionSheet) + } + }, + pushController: { c in }, + presentController: { c in }, + presentGlobalOverlayController: { c in }, + navigationController: { [weak self] in + return self?.controller?.navigationController as? NavigationController + }, + requestUpdate: { _ in }, + updateSearchQuery: { _, _ in }, + chatPeerId: nil, + peekBehavior: nil, + customLayout: nil, + externalBackground: nil, + externalExpansionView: nil, + useOpaqueTheme: false + ) + + content.masks?.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction( + performItemAction: { [weak self] _, item, _, _, _, _ in + guard let strongSelf = self, let file = item.itemFile else { + return + } + strongSelf.controller?.completion(file) + strongSelf.controller?.dismiss(animated: true) + }, + deleteBackwards: nil, + openStickerSettings: nil, + openFeatured: { + }, + openSearch: {}, + addGroupAction: { [weak self] groupId, isPremiumLocked in + guard let strongSelf = self, let controller = strongSelf.controller, let collectionId = groupId.base as? ItemCollectionId else { + return + } + let context = controller.context + + if isPremiumLocked { +// let controller = PremiumIntroScreen(context: context, source: .stickers) +// controllerInteraction.navigationController()?.pushViewController(controller) + + return + } + + let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedStickerPacks) + let _ = (context.account.postbox.combinedView(keys: [viewKey]) + |> take(1) + |> deliverOnMainQueue).start(next: { views in + guard let view = views.views[viewKey] as? OrderedItemListView else { + return + } + for featuredStickerPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { + if featuredStickerPack.info.id == collectionId { + let _ = (context.engine.stickers.loadedStickerPack(reference: .id(id: featuredStickerPack.info.id.id, accessHash: featuredStickerPack.info.accessHash), forceActualized: false) + |> mapToSignal { result -> Signal in + switch result { + case let .result(info, items, installed): + if installed { + return .complete() + } else { + return context.engine.stickers.addStickerPackInteractively(info: info, items: items) + } + case .fetching: + break + case .none: + break + } + return .complete() + } + |> deliverOnMainQueue).start(completed: { + }) + + break + } + } + }) + }, + clearGroup: { [weak self] groupId in + guard let strongSelf = self, let controller = strongSelf.controller else { + return + } + if groupId == AnyHashable("popular") { + let presentationData = controller.context.sharedContext.currentPresentationData.with { $0 } + let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme, fontSize: presentationData.listsFontSize)) + var items: [ActionSheetItem] = [] + let context = controller.context + items.append(ActionSheetTextItem(title: presentationData.strings.Chat_ClearReactionsAlertText, parseMarkdown: true)) + items.append(ActionSheetButtonItem(title: presentationData.strings.Chat_ClearReactionsAlertAction, color: .destructive, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + guard let strongSelf = self else { + return + } + + strongSelf.scheduledEmojiContentAnimationHint = EmojiPagerContentComponent.ContentAnimation(type: .groupRemoved(id: "popular")) + let _ = context.engine.stickers.clearRecentlyUsedReactions().start() + })) + actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ])]) + context.sharedContext.mainWindow?.presentInGlobalOverlay(actionSheet) + } + }, + pushController: { c in }, + presentController: { c in }, + presentGlobalOverlayController: { c in }, + navigationController: { [weak self] in + return self?.controller?.navigationController as? NavigationController + }, + requestUpdate: { _ in }, + updateSearchQuery: { _, _ in }, + chatPeerId: nil, + peekBehavior: nil, + customLayout: nil, + externalBackground: nil, + externalExpansionView: nil, + useOpaqueTheme: false + ) + + content.stickers?.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction( + performItemAction: { [weak self] _, item, _, _, _, _ in + guard let strongSelf = self, let file = item.itemFile else { + return + } + strongSelf.controller?.completion(file) + strongSelf.controller?.dismiss(animated: true) + }, + deleteBackwards: nil, + openStickerSettings: nil, + openFeatured: { [weak self] in + guard let strongSelf = self, let controller = strongSelf.controller else { + return + } + strongSelf.controller?.push( + FeaturedStickersScreen( + context: controller.context, + highlightedPackId: nil, + sendSticker: { _, _, _ in +// guard let controllerInteraction = controllerInteraction else { +// return false +// } + return false +// return controllerInteraction.sendSticker(fileReference, false, false, nil, false, sourceNode, sourceRect, nil, []) + } + ) + ) }, openSearch: {}, addGroupAction: { [weak self] groupId, isPremiumLocked in @@ -477,6 +734,7 @@ public class StickerPickerScreen: ViewController { self.controller?.updateModalStyleOverlayTransitionFactor(modalProgress, transition: transition.containedViewLayoutTransition) let clipFrame: CGRect + let contentFrame: CGRect if layout.metrics.widthClass == .compact { self.dim.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.25) if isLandscape { @@ -495,6 +753,7 @@ public class StickerPickerScreen: ViewController { if isLandscape { clipFrame = CGRect(origin: CGPoint(), size: layout.size) + contentFrame = clipFrame } else { let coveredByModalTransition: CGFloat = 0.0 var containerTopInset: CGFloat = 10.0 @@ -510,6 +769,7 @@ public class StickerPickerScreen: ViewController { let containerFrame = unscaledFrame.offsetBy(dx: 0.0, dy: scaledTopInset - (unscaledFrame.midY - containerScale * unscaledFrame.height / 2.0)) clipFrame = CGRect(x: containerFrame.minX, y: containerFrame.minY, width: containerFrame.width, height: containerFrame.height) + contentFrame = CGRect(x: containerFrame.minX, y: containerFrame.minY, width: containerFrame.width, height: containerFrame.height - topInset) } } else { self.dim.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.4) @@ -521,12 +781,13 @@ public class StickerPickerScreen: ViewController { let minSide = min(layout.size.width, layout.size.height) let containerSize = CGSize(width: min(layout.size.width - 20.0, floor(maxSide / 2.0)), height: min(layout.size.height, minSide) - verticalInset * 2.0) clipFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - containerSize.width) / 2.0), y: floor((layout.size.height - containerSize.height) / 2.0)), size: containerSize) + contentFrame = clipFrame } transition.setFrame(view: self.containerView, frame: clipFrame) transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: clipFrame.size), completion: nil) - if let stickerContent = self.stickerContent { + if let content = self.content { var stickersTransition: Transition = transition if let scheduledEmojiContentAnimationHint = self.scheduledEmojiContentAnimationHint { self.scheduledEmojiContentAnimationHint = nil @@ -541,14 +802,15 @@ public class StickerPickerScreen: ViewController { theme: self.theme, strings: self.presentationData.strings, deviceMetrics: layout.deviceMetrics, - stickerContent: stickerContent, + bottomInset: layout.intrinsicInsets.bottom, + content: content, backgroundColor: self.theme.list.itemBlocksBackgroundColor, separatorColor: self.theme.list.blocksBackgroundColor ) ), environment: {}, forceUpdate: true, - containerSize: CGSize(width: clipFrame.size.width, height: clipFrame.height) + containerSize: CGSize(width: contentFrame.size.width, height: contentFrame.height) ) contentSize.height = max(layout.size.height - navigationHeight, contentSize.height) transition.setFrame(view: self.hostView, frame: CGRect(origin: CGPoint(), size: contentSize), completion: nil) @@ -816,10 +1078,6 @@ public class StickerPickerScreen: ViewController { fatalError("init(coder:) has not been implemented") } - @objc private func cancelPressed() { - self.dismiss(animated: true, completion: nil) - } - override open func loadDisplayNode() { self.displayNode = Node(context: self.context, controller: self, theme: self.theme) self.displayNodeDidLoad() diff --git a/submodules/DrawingUI/Sources/TextSettingsComponent.swift b/submodules/DrawingUI/Sources/TextSettingsComponent.swift index c2707e73a5..63b21a4735 100644 --- a/submodules/DrawingUI/Sources/TextSettingsComponent.swift +++ b/submodules/DrawingUI/Sources/TextSettingsComponent.swift @@ -5,6 +5,7 @@ import ComponentFlow import LegacyComponents import TelegramCore import Postbox +import LottieAnimationComponent enum DrawingTextStyle: Equatable { case regular @@ -175,21 +176,29 @@ final class TextAlignmentComponent: Component { } final class TextFontComponent: Component { + let styleButton: AnyComponent + let alignmentButton: AnyComponent + let values: [DrawingTextFont] let selectedValue: DrawingTextFont let updated: (DrawingTextFont) -> Void - init(values: [DrawingTextFont], selectedValue: DrawingTextFont, updated: @escaping (DrawingTextFont) -> Void) { + init(styleButton: AnyComponent, alignmentButton: AnyComponent, values: [DrawingTextFont], selectedValue: DrawingTextFont, updated: @escaping (DrawingTextFont) -> Void) { + self.styleButton = styleButton + self.alignmentButton = alignmentButton self.values = values self.selectedValue = selectedValue self.updated = updated } static func == (lhs: TextFontComponent, rhs: TextFontComponent) -> Bool { - return lhs.values == rhs.values && lhs.selectedValue == rhs.selectedValue + return lhs.styleButton == rhs.styleButton && lhs.alignmentButton == rhs.alignmentButton && lhs.values == rhs.values && lhs.selectedValue == rhs.selectedValue } public final class View: UIView { + private let styleButtonHost: ComponentView + private let alignmentButtonHost: ComponentView + private var buttons: [DrawingTextFont: HighlightableButton] = [:] private let scrollView = UIScrollView() private let scrollMask = UIView() @@ -207,6 +216,9 @@ final class TextFontComponent: Component { self.scrollView.showsVerticalScrollIndicator = false self.scrollView.decelerationRate = .fast + self.styleButtonHost = ComponentView() + self.alignmentButtonHost = ComponentView() + super.init(frame: frame) self.mask = self.scrollMask @@ -251,6 +263,36 @@ final class TextFontComponent: Component { var contentWidth: CGFloat = 0.0 + let styleSize = self.styleButtonHost.update( + transition: transition, + component: component.styleButton, + environment: {}, + containerSize: CGSize(width: 30.0, height: 30.0) + ) + if let view = self.styleButtonHost.view { + if view.superview == nil { + self.scrollView.addSubview(view) + } + view.frame = CGRect(origin: CGPoint(x: -7.0, y: -7.0), size: styleSize) + } + + contentWidth += 44.0 + + let alignmentSize = self.alignmentButtonHost.update( + transition: transition, + component: component.alignmentButton, + environment: {}, + containerSize: CGSize(width: 30.0, height: 30.0) + ) + if let view = self.alignmentButtonHost.view { + if view.superview == nil { + self.scrollView.addSubview(view) + } + view.frame = CGRect(origin: CGPoint(x: contentWidth - 7.0, y: -7.0), size: alignmentSize) + } + + contentWidth += 32.0 + for value in component.values { contentWidth += 12.0 let button: HighlightableButton @@ -319,6 +361,7 @@ final class TextSettingsComponent: CombinedComponent { let style: DrawingTextStyle let alignment: DrawingTextAlignment let font: DrawingTextFont + let isEmojiKeyboard: Bool let presentColorPicker: () -> Void let presentFastColorPicker: (GenericComponentViewTag) -> Void @@ -327,24 +370,28 @@ final class TextSettingsComponent: CombinedComponent { let toggleStyle: () -> Void let toggleAlignment: () -> Void let updateFont: (DrawingTextFont) -> Void - + let toggleKeyboard: (() -> Void)? + init( color: DrawingColor?, style: DrawingTextStyle, alignment: DrawingTextAlignment, font: DrawingTextFont, + isEmojiKeyboard: Bool, presentColorPicker: @escaping () -> Void = {}, presentFastColorPicker: @escaping (GenericComponentViewTag) -> Void = { _ in }, updateFastColorPickerPan: @escaping (CGPoint) -> Void = { _ in }, dismissFastColorPicker: @escaping () -> Void = {}, toggleStyle: @escaping () -> Void, toggleAlignment: @escaping () -> Void, - updateFont: @escaping (DrawingTextFont) -> Void + updateFont: @escaping (DrawingTextFont) -> Void, + toggleKeyboard: (() -> Void)? ) { self.color = color self.style = style self.alignment = alignment self.font = font + self.isEmojiKeyboard = isEmojiKeyboard self.presentColorPicker = presentColorPicker self.presentFastColorPicker = presentFastColorPicker self.updateFastColorPickerPan = updateFastColorPickerPan @@ -352,6 +399,7 @@ final class TextSettingsComponent: CombinedComponent { self.toggleStyle = toggleStyle self.toggleAlignment = toggleAlignment self.updateFont = updateFont + self.toggleKeyboard = toggleKeyboard } static func ==(lhs: TextSettingsComponent, rhs: TextSettingsComponent) -> Bool { @@ -367,6 +415,9 @@ final class TextSettingsComponent: CombinedComponent { if lhs.font != rhs.font { return false } + if lhs.isEmojiKeyboard != rhs.isEmojiKeyboard { + return false + } return true } @@ -376,6 +427,8 @@ final class TextSettingsComponent: CombinedComponent { case filled case semi case stroke + case keyboard + case emoji } private var cachedImages: [ImageKey: UIImage] = [:] func image(_ key: ImageKey) -> UIImage { @@ -392,6 +445,10 @@ final class TextSettingsComponent: CombinedComponent { image = UIImage(bundleImageName: "Media Editor/TextSemi")! case .stroke: image = UIImage(bundleImageName: "Media Editor/TextStroke")! + case .keyboard: + image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/AccessoryIconKeyboard"), color: .white)! + case .emoji: + image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputEmojiIcon"), color: .white)! } cachedImages[key] = image return image @@ -407,8 +464,9 @@ final class TextSettingsComponent: CombinedComponent { let colorButton = Child(ColorSwatchComponent.self) let colorButtonTag = GenericComponentViewTag() - let alignmentButton = Child(Button.self) - let styleButton = Child(Button.self) +// let styleButton = Child(Button.self) +// let alignmentButton = Child(Button.self) + let keyboardButton = Child(Button.self) let font = Child(TextFontComponent.self) return { context in @@ -465,69 +523,71 @@ final class TextSettingsComponent: CombinedComponent { styleImage = state.image(.stroke) } - let styleButton = styleButton.update( - component: Button( - content: AnyComponent( - Image( - image: styleImage - ) - ), - action: { - toggleStyle() - } - ).minSize(CGSize(width: 44.0, height: 44.0)), - availableSize: CGSize(width: 30.0, height: 30.0), - transition: .easeInOut(duration: 0.2) - ) - context.add(styleButton - .position(CGPoint(x: offset + styleButton.size.width / 2.0, y: context.availableSize.height / 2.0)) - .update(Transition.Update { _, view, transition in - if let snapshot = view.snapshotView(afterScreenUpdates: false) { - transition.setAlpha(view: snapshot, alpha: 0.0, completion: { [weak snapshot] _ in - snapshot?.removeFromSuperview() - }) - snapshot.frame = view.frame - transition.animateAlpha(view: view, from: 0.0, to: 1.0) - view.superview?.addSubview(snapshot) - } - }) - ) - offset += 44.0 - - let alignmentButton = alignmentButton.update( - component: Button( - content: AnyComponent( - TextAlignmentComponent( - alignment: component.alignment - ) - ), - action: { - toggleAlignment() - } - ).minSize(CGSize(width: 44.0, height: 44.0)), - availableSize: context.availableSize, - transition: .easeInOut(duration: 0.2) - ) - context.add(alignmentButton - .position(CGPoint(x: offset + alignmentButton.size.width / 2.0, y: context.availableSize.height / 2.0)) - ) - offset += 44.0 - + var fontAvailableWidth: CGFloat = context.availableSize.width + if component.color != nil { + fontAvailableWidth -= 88.0 + } + let font = font.update( component: TextFontComponent( + styleButton: AnyComponent( + Button( + content: AnyComponent( + Image( + image: styleImage + ) + ), + action: { + toggleStyle() + } + ).minSize(CGSize(width: 44.0, height: 44.0)) + ), + alignmentButton: AnyComponent( + Button( + content: AnyComponent( + TextAlignmentComponent( + alignment: component.alignment + ) + ), + action: { + toggleAlignment() + } + ).minSize(CGSize(width: 44.0, height: 44.0)) + ), values: DrawingTextFont.allCases, selectedValue: component.font, updated: { font in updateFont(font) } ), - availableSize: CGSize(width: context.availableSize.width - offset, height: 30.0), + availableSize: CGSize(width: fontAvailableWidth, height: 30.0), transition: .easeInOut(duration: 0.2) ) context.add(font .position(CGPoint(x: offset + font.size.width / 2.0, y: context.availableSize.height / 2.0)) ) - offset += 44.0 + + if let toggleKeyboard = component.toggleKeyboard { + let keyboardButton = keyboardButton.update( + component: Button( + content: AnyComponent( + LottieAnimationComponent( + animation: LottieAnimationComponent.AnimationItem(name: !component.isEmojiKeyboard ? "input_anim_smileToKey" : "input_anim_keyToSmile" , mode: .animateTransitionFromPrevious), + colors: ["__allcolors__": UIColor.white], + size: CGSize(width: 32.0, height: 32.0) + ) + ), + action: { + toggleKeyboard() + } + ).minSize(CGSize(width: 44.0, height: 44.0)), + availableSize: CGSize(width: 32.0, height: 32.0), + transition: .easeInOut(duration: 0.2) + ) + context.add(keyboardButton + .position(CGPoint(x: context.availableSize.width - keyboardButton.size.width / 2.0, y: context.availableSize.height / 2.0)) + ) + } return context.availableSize } diff --git a/submodules/FeaturedStickersScreen/BUILD b/submodules/FeaturedStickersScreen/BUILD new file mode 100644 index 0000000000..6ccb138918 --- /dev/null +++ b/submodules/FeaturedStickersScreen/BUILD @@ -0,0 +1,39 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "FeaturedStickersScreen", + module_name = "FeaturedStickersScreen", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display:Display", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/AccountContext:AccountContext", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/TelegramUIPreferences:TelegramUIPreferences", + "//submodules/MergeLists:MergeLists", + "//submodules/StickerPackPreviewUI:StickerPackPreviewUI", + "//submodules/StickerPeekUI:StickerPeekUI", + "//submodules/OverlayStatusController:OverlayStatusController", + "//submodules/PresentationDataUtils:PresentationDataUtils", + "//submodules/SearchBarNode:SearchBarNode", + "//submodules/UndoUI:UndoUI", + "//submodules/ContextUI:ContextUI", + "//submodules/PremiumUI:PremiumUI", + "//submodules/ChatPresentationInterfaceState:ChatPresentationInterfaceState", + "//submodules/StickerResources:StickerResources", + "//submodules/AnimatedStickerNode:AnimatedStickerNode", + "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode", + "//submodules/TelegramUI/Components/ChatControllerInteraction:ChatControllerInteraction", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/FeaturedStickersScreen/Sources/ChatMediaInputPane.swift b/submodules/FeaturedStickersScreen/Sources/ChatMediaInputPane.swift new file mode 100644 index 0000000000..e94f4fe60f --- /dev/null +++ b/submodules/FeaturedStickersScreen/Sources/ChatMediaInputPane.swift @@ -0,0 +1,19 @@ +import Foundation +import AsyncDisplayKit +import Display +import ChatPresentationInterfaceState +import TelegramPresentationData + +open class ChatMediaInputPane: ASDisplayNode { + var inputNodeInteraction: ChatMediaInputNodeInteraction? + var collectionListPanelOffset: CGFloat = 0.0 + var isEmpty: Bool { + return false + } + + open func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, isExpanded: Bool, isVisible: Bool, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition) { + } + + open func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { + } +} diff --git a/submodules/TelegramUI/Sources/ChatMediaInputTrendingPane.swift b/submodules/FeaturedStickersScreen/Sources/ChatMediaInputTrendingPane.swift similarity index 88% rename from submodules/TelegramUI/Sources/ChatMediaInputTrendingPane.swift rename to submodules/FeaturedStickersScreen/Sources/ChatMediaInputTrendingPane.swift index f4369e9256..02e207b48a 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputTrendingPane.swift +++ b/submodules/FeaturedStickersScreen/Sources/ChatMediaInputTrendingPane.swift @@ -12,15 +12,16 @@ import AccountContext import StickerPackPreviewUI import PresentationDataUtils import UndoUI +import ChatControllerInteraction -final class TrendingPaneInteraction { - let installPack: (ItemCollectionInfo) -> Void - let openPack: (ItemCollectionInfo) -> Void - let getItemIsPreviewed: (StickerPackItem) -> Bool - let openSearch: () -> Void - let itemContext = StickerPaneSearchGlobalItemContext() +public final class TrendingPaneInteraction { + public let installPack: (ItemCollectionInfo) -> Void + public let openPack: (ItemCollectionInfo) -> Void + public let getItemIsPreviewed: (StickerPackItem) -> Bool + public let openSearch: () -> Void + public let itemContext = StickerPaneSearchGlobalItemContext() - init(installPack: @escaping (ItemCollectionInfo) -> Void, openPack: @escaping (ItemCollectionInfo) -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool, openSearch: @escaping () -> Void) { + public init(installPack: @escaping (ItemCollectionInfo) -> Void, openPack: @escaping (ItemCollectionInfo) -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool, openSearch: @escaping () -> Void) { self.installPack = installPack self.openPack = openPack self.getItemIsPreviewed = getItemIsPreviewed @@ -28,17 +29,17 @@ final class TrendingPaneInteraction { } } -final class TrendingPanePackEntry: Identifiable, Comparable { - let index: Int - let info: StickerPackCollectionInfo - let theme: PresentationTheme - let strings: PresentationStrings - let topItems: [StickerPackItem] - let installed: Bool - let unread: Bool - let topSeparator: Bool +public final class TrendingPanePackEntry: Identifiable, Comparable { + public let index: Int + public let info: StickerPackCollectionInfo + public let theme: PresentationTheme + public let strings: PresentationStrings + public let topItems: [StickerPackItem] + public let installed: Bool + public let unread: Bool + public let topSeparator: Bool - init(index: Int, info: StickerPackCollectionInfo, theme: PresentationTheme, strings: PresentationStrings, topItems: [StickerPackItem], installed: Bool, unread: Bool, topSeparator: Bool) { + public init(index: Int, info: StickerPackCollectionInfo, theme: PresentationTheme, strings: PresentationStrings, topItems: [StickerPackItem], installed: Bool, unread: Bool, topSeparator: Bool) { self.index = index self.info = info self.theme = theme @@ -49,11 +50,11 @@ final class TrendingPanePackEntry: Identifiable, Comparable { self.topSeparator = topSeparator } - var stableId: ItemCollectionId { + public var stableId: ItemCollectionId { return self.info.id } - static func ==(lhs: TrendingPanePackEntry, rhs: TrendingPanePackEntry) -> Bool { + public static func ==(lhs: TrendingPanePackEntry, rhs: TrendingPanePackEntry) -> Bool { if lhs.index != rhs.index { return false } @@ -81,11 +82,11 @@ final class TrendingPanePackEntry: Identifiable, Comparable { return true } - static func <(lhs: TrendingPanePackEntry, rhs: TrendingPanePackEntry) -> Bool { + public static func <(lhs: TrendingPanePackEntry, rhs: TrendingPanePackEntry) -> Bool { return lhs.index < rhs.index } - func item(account: Account, interaction: TrendingPaneInteraction, grid: Bool) -> GridItem { + public func item(account: Account, interaction: TrendingPaneInteraction, grid: Bool) -> GridItem { let info = self.info return StickerPaneSearchGlobalItem(account: account, theme: self.theme, strings: self.strings, listAppearance: false, info: self.info, topItems: self.topItems, topSeparator: self.topSeparator, regularInsets: false, installed: self.installed, unread: self.unread, open: { interaction.openPack(info) @@ -190,13 +191,13 @@ private func trendingPaneEntries(trendingEntries: [FeaturedStickerPackItem], ins return result } -final class ChatMediaInputTrendingPane: ChatMediaInputPane { +public final class ChatMediaInputTrendingPane: ChatMediaInputPane { private let context: AccountContext private let controllerInteraction: ChatControllerInteraction private let getItemIsPreviewed: (StickerPackItem) -> Bool private let isPane: Bool - let gridNode: GridNode + public let gridNode: GridNode private var enqueuedTransitions: [TrendingPaneTransition] = [] private var validLayout: (CGSize, CGFloat)? @@ -206,15 +207,15 @@ final class ChatMediaInputTrendingPane: ChatMediaInputPane { private let _ready = Promise() private var didSetReady = false - var ready: Signal { + public var ready: Signal { return self._ready.get() } - var scrollingInitiated: (() -> Void)? + public var scrollingInitiated: (() -> Void)? private let installDisposable = MetaDisposable() - init(context: AccountContext, controllerInteraction: ChatControllerInteraction, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool, isPane: Bool) { + public init(context: AccountContext, controllerInteraction: ChatControllerInteraction, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool, isPane: Bool) { self.context = context self.controllerInteraction = controllerInteraction self.getItemIsPreviewed = getItemIsPreviewed @@ -236,7 +237,7 @@ final class ChatMediaInputTrendingPane: ChatMediaInputPane { self.installDisposable.dispose() } - func activate() { + public func activate() { if self.isActivated { return } @@ -369,7 +370,7 @@ final class ChatMediaInputTrendingPane: ChatMediaInputPane { }) } - override func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, isExpanded: Bool, isVisible: Bool, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition) { + public override func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, isExpanded: Bool, isVisible: Bool, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition) { let hadValidLayout = self.validLayout != nil self.validLayout = (size, bottomInset) @@ -401,7 +402,7 @@ final class ChatMediaInputTrendingPane: ChatMediaInputPane { } } - override func willEnterHierarchy() { + public override func willEnterHierarchy() { super.willEnterHierarchy() self.activate() @@ -416,7 +417,7 @@ final class ChatMediaInputTrendingPane: ChatMediaInputPane { } } - func itemAt(point: CGPoint) -> (ASDisplayNode, StickerPackItem)? { + public func itemAt(point: CGPoint) -> (ASDisplayNode, StickerPackItem)? { let localPoint = self.view.convert(point, to: self.gridNode.view) var resultNode: StickerPaneSearchGlobalItemNode? self.gridNode.forEachItemNode { itemNode in @@ -430,7 +431,7 @@ final class ChatMediaInputTrendingPane: ChatMediaInputPane { return nil } - func updatePreviewing(animated: Bool) { + public func updatePreviewing(animated: Bool) { self.gridNode.forEachItemNode { itemNode in if let itemNode = itemNode as? StickerPaneSearchGlobalItemNode { itemNode.updatePreviewing(animated: animated) diff --git a/submodules/TelegramUI/Sources/FeaturedStickersScreen.swift b/submodules/FeaturedStickersScreen/Sources/FeaturedStickersScreen.swift similarity index 98% rename from submodules/TelegramUI/Sources/FeaturedStickersScreen.swift rename to submodules/FeaturedStickersScreen/Sources/FeaturedStickersScreen.swift index 1938a1364a..94985ccb08 100644 --- a/submodules/TelegramUI/Sources/FeaturedStickersScreen.swift +++ b/submodules/FeaturedStickersScreen/Sources/FeaturedStickersScreen.swift @@ -17,6 +17,7 @@ import SearchBarNode import UndoUI import ContextUI import PremiumUI +import ChatPresentationInterfaceState private final class FeaturedInteraction { let installPack: (ItemCollectionInfo, Bool) -> Void @@ -787,7 +788,7 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode { } } -final class FeaturedStickersScreen: ViewController { +public final class FeaturedStickersScreen: ViewController { private let context: AccountContext fileprivate let highlightedPackId: ItemCollectionId? private let sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? @@ -1492,3 +1493,17 @@ private final class FeaturedPaneSearchContentNode: ASDisplayNode { } } } + +public final class StickerPaneSearchInteraction { + public let open: (StickerPackCollectionInfo) -> Void + public let install: (StickerPackCollectionInfo, [ItemCollectionItem], Bool) -> Void + public let sendSticker: (FileMediaReference, UIView, CGRect) -> Void + public let getItemIsPreviewed: (StickerPackItem) -> Bool + + public init(open: @escaping (StickerPackCollectionInfo) -> Void, install: @escaping (StickerPackCollectionInfo, [ItemCollectionItem], Bool) -> Void, sendSticker: @escaping (FileMediaReference, UIView, CGRect) -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool) { + self.open = open + self.install = install + self.sendSticker = sendSticker + self.getItemIsPreviewed = getItemIsPreviewed + } +} diff --git a/submodules/TelegramUI/Sources/MediaInputPaneTrendingItem.swift b/submodules/FeaturedStickersScreen/Sources/MediaInputPaneTrendingItem.swift similarity index 100% rename from submodules/TelegramUI/Sources/MediaInputPaneTrendingItem.swift rename to submodules/FeaturedStickersScreen/Sources/MediaInputPaneTrendingItem.swift diff --git a/submodules/TelegramUI/Sources/PaneSearchBarPlaceholderItem.swift b/submodules/FeaturedStickersScreen/Sources/PaneSearchBarPlaceholderItem.swift similarity index 79% rename from submodules/TelegramUI/Sources/PaneSearchBarPlaceholderItem.swift rename to submodules/FeaturedStickersScreen/Sources/PaneSearchBarPlaceholderItem.swift index a3277a78c0..7e5c08cacf 100644 --- a/submodules/TelegramUI/Sources/PaneSearchBarPlaceholderItem.swift +++ b/submodules/FeaturedStickersScreen/Sources/PaneSearchBarPlaceholderItem.swift @@ -11,35 +11,35 @@ private func generateLoupeIcon(color: UIColor) -> UIImage? { return generateTintedImage(image: templateLoupeIcon, color: color) } -enum PaneSearchBarType { +public enum PaneSearchBarType { case stickers case gifs } -final class PaneSearchBarPlaceholderItem: GridItem { - let theme: PresentationTheme - let strings: PresentationStrings - let type: PaneSearchBarType - let activate: () -> Void +public final class PaneSearchBarPlaceholderItem: GridItem { + public let theme: PresentationTheme + public let strings: PresentationStrings + public let type: PaneSearchBarType + public let activate: () -> Void - let section: GridSection? = nil - let fillsRowWithHeight: (CGFloat, Bool)? = (56.0, true) + public let section: GridSection? = nil + public let fillsRowWithHeight: (CGFloat, Bool)? = (56.0, true) - init(theme: PresentationTheme, strings: PresentationStrings, type: PaneSearchBarType, activate: @escaping () -> Void) { + public init(theme: PresentationTheme, strings: PresentationStrings, type: PaneSearchBarType, activate: @escaping () -> Void) { self.theme = theme self.strings = strings self.type = type self.activate = activate } - func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode { + public func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode { let node = PaneSearchBarPlaceholderNode() node.activate = self.activate node.setup(theme: self.theme, strings: self.strings, type: self.type) return node } - func update(node: GridItemNode) { + public func update(node: GridItemNode) { guard let node = node as? PaneSearchBarPlaceholderNode else { assertionFailure() return @@ -49,15 +49,15 @@ final class PaneSearchBarPlaceholderItem: GridItem { } } -final class PaneSearchBarPlaceholderNode: GridItemNode { +public final class PaneSearchBarPlaceholderNode: GridItemNode { private var currentState: (PresentationTheme, PresentationStrings, PaneSearchBarType)? - var activate: (() -> Void)? + public var activate: (() -> Void)? - let backgroundNode: ASImageNode - let labelNode: ImmediateTextNode - let iconNode: ASImageNode + public let backgroundNode: ASImageNode + public let labelNode: ImmediateTextNode + public let iconNode: ASImageNode - override init() { + public override init() { self.backgroundNode = ASImageNode() self.backgroundNode.displaysAsynchronously = false self.backgroundNode.displayWithoutProcessing = true @@ -82,13 +82,13 @@ final class PaneSearchBarPlaceholderNode: GridItemNode { self.addSubnode(self.iconNode) } - override func didLoad() { + public override func didLoad() { super.didLoad() self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) } - func setup(theme: PresentationTheme, strings: PresentationStrings, type: PaneSearchBarType) { + public func setup(theme: PresentationTheme, strings: PresentationStrings, type: PaneSearchBarType) { if self.currentState?.0 !== theme || self.currentState?.1 !== strings || self.currentState?.2 != type { self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 36.0, color: theme.chat.inputMediaPanel.stickersSearchBackgroundColor) self.iconNode.image = generateLoupeIcon(color: theme.chat.inputMediaPanel.stickersSearchControlColor) @@ -105,7 +105,7 @@ final class PaneSearchBarPlaceholderNode: GridItemNode { } } - override func layout() { + public override func layout() { super.layout() let bounds = self.bounds diff --git a/submodules/TelegramUI/Sources/StickerPaneSearchGlobaltem.swift b/submodules/FeaturedStickersScreen/Sources/StickerPaneSearchGlobaltem.swift similarity index 90% rename from submodules/TelegramUI/Sources/StickerPaneSearchGlobaltem.swift rename to submodules/FeaturedStickersScreen/Sources/StickerPaneSearchGlobaltem.swift index ee041b7f04..2208362791 100644 --- a/submodules/TelegramUI/Sources/StickerPaneSearchGlobaltem.swift +++ b/submodules/FeaturedStickersScreen/Sources/StickerPaneSearchGlobaltem.swift @@ -69,30 +69,34 @@ private final class StickerPaneSearchGlobalSectionNode: ASDisplayNode { } } -final class StickerPaneSearchGlobalItemContext { - var canPlayMedia: Bool = false +public final class StickerPaneSearchGlobalItemContext { + public var canPlayMedia: Bool + + public init(canPlayMedia: Bool = false) { + self.canPlayMedia = canPlayMedia + } } -final class StickerPaneSearchGlobalItem: GridItem { - let account: Account - let theme: PresentationTheme - let strings: PresentationStrings - let listAppearance: Bool - let fillsRow: Bool - let info: StickerPackCollectionInfo - let topItems: [StickerPackItem] - let topSeparator: Bool - let regularInsets: Bool - let installed: Bool - let installing: Bool - let unread: Bool - let open: () -> Void - let install: () -> Void - let getItemIsPreviewed: (StickerPackItem) -> Bool - let itemContext: StickerPaneSearchGlobalItemContext +public final class StickerPaneSearchGlobalItem: GridItem { + public let account: Account + public let theme: PresentationTheme + public let strings: PresentationStrings + public let listAppearance: Bool + public let fillsRow: Bool + public let info: StickerPackCollectionInfo + public let topItems: [StickerPackItem] + public let topSeparator: Bool + public let regularInsets: Bool + public let installed: Bool + public let installing: Bool + public let unread: Bool + public let open: () -> Void + public let install: () -> Void + public let getItemIsPreviewed: (StickerPackItem) -> Bool + public let itemContext: StickerPaneSearchGlobalItemContext - let section: GridSection? - var fillsRowWithHeight: (CGFloat, Bool)? { + public let section: GridSection? + public var fillsRowWithHeight: (CGFloat, Bool)? { var additionalHeight: CGFloat = 0.0 if self.regularInsets { additionalHeight = 12.0 + 12.0 @@ -106,7 +110,7 @@ final class StickerPaneSearchGlobalItem: GridItem { return (128.0 + additionalHeight, self.fillsRow) } - init(account: Account, theme: PresentationTheme, strings: PresentationStrings, listAppearance: Bool, fillsRow: Bool = true, info: StickerPackCollectionInfo, topItems: [StickerPackItem], topSeparator: Bool, regularInsets: Bool, installed: Bool, installing: Bool = false, unread: Bool, open: @escaping () -> Void, install: @escaping () -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool, itemContext: StickerPaneSearchGlobalItemContext, sectionTitle: String? = nil) { + public init(account: Account, theme: PresentationTheme, strings: PresentationStrings, listAppearance: Bool, fillsRow: Bool = true, info: StickerPackCollectionInfo, topItems: [StickerPackItem], topSeparator: Bool, regularInsets: Bool, installed: Bool, installing: Bool = false, unread: Bool, open: @escaping () -> Void, install: @escaping () -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool, itemContext: StickerPaneSearchGlobalItemContext, sectionTitle: String? = nil) { self.account = account self.theme = theme self.strings = strings @@ -126,13 +130,13 @@ final class StickerPaneSearchGlobalItem: GridItem { self.section = StickerPaneSearchGlobalSection(title: sectionTitle, theme: theme) } - func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode { + public func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode { let node = StickerPaneSearchGlobalItemNode() node.setup(item: self) return node } - func update(node: GridItemNode) { + public func update(node: GridItemNode) { guard let node = node as? StickerPaneSearchGlobalItemNode else { assertionFailure() return @@ -145,7 +149,7 @@ private let titleFont = Font.bold(16.0) private let statusFont = Font.regular(15.0) private let buttonFont = Font.semibold(13.0) -class StickerPaneSearchGlobalItemNode: GridItemNode { +public class StickerPaneSearchGlobalItemNode: GridItemNode { private let titleNode: TextNode private let descriptionNode: TextNode private let unreadNode: ASImageNode @@ -159,7 +163,7 @@ class StickerPaneSearchGlobalItemNode: GridItemNode { private let topSeparatorNode: ASDisplayNode private var highlightNode: ASDisplayNode? - var item: StickerPaneSearchGlobalItem? + public var item: StickerPaneSearchGlobalItem? private var appliedItem: StickerPaneSearchGlobalItem? private let preloadDisposable = MetaDisposable() private let preloadedStickerPackThumbnailDisposable = MetaDisposable() @@ -175,7 +179,7 @@ class StickerPaneSearchGlobalItemNode: GridItemNode { } } - override var isVisibleInGrid: Bool { + public override var isVisibleInGrid: Bool { didSet { if oldValue != self.isVisibleInGrid { self.updatePlayback() @@ -200,7 +204,7 @@ class StickerPaneSearchGlobalItemNode: GridItemNode { } } - override init() { + public override init() { self.titleNode = TextNode() self.titleNode.isUserInteractionEnabled = false self.titleNode.contentMode = .left @@ -298,14 +302,14 @@ class StickerPaneSearchGlobalItemNode: GridItemNode { self.preloadedStickerPackThumbnailDisposable.dispose() } - override func didLoad() { + public override func didLoad() { super.didLoad() self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) } private var absoluteLocation: (CGRect, CGSize)? - override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + public override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { self.absoluteLocation = (rect, containerSize) for node in self.itemNodes { @@ -314,7 +318,7 @@ class StickerPaneSearchGlobalItemNode: GridItemNode { } } - func setup(item: StickerPaneSearchGlobalItem) { + public func setup(item: StickerPaneSearchGlobalItem) { if item.topItems.count < Int(item.info.count) && item.topItems.count < 5 && self.item?.info.id != item.info.id { self.preloadDisposable.set(preloadedFeaturedStickerSet(network: item.account.network, postbox: item.account.postbox, id: item.info.id).start()) } @@ -325,7 +329,7 @@ class StickerPaneSearchGlobalItemNode: GridItemNode { self.updatePreviewing(animated: false) } - func updateCanPlayMedia() { + public func updateCanPlayMedia() { guard let item = self.item else { return } @@ -333,7 +337,7 @@ class StickerPaneSearchGlobalItemNode: GridItemNode { self.canPlayMedia = item.itemContext.canPlayMedia } - func highlight() { + public func highlight() { guard self.highlightNode == nil else { return } @@ -354,7 +358,7 @@ class StickerPaneSearchGlobalItemNode: GridItemNode { } } - override func updateLayout(item: GridItem, size: CGSize, isVisible: Bool, synchronousLoads: Bool) { + public override func updateLayout(item: GridItem, size: CGSize, isVisible: Bool, synchronousLoads: Bool) { guard let item = self.item else { return } @@ -521,7 +525,7 @@ class StickerPaneSearchGlobalItemNode: GridItemNode { } } - func itemAt(point: CGPoint) -> (ASDisplayNode, StickerPackItem)? { + public func itemAt(point: CGPoint) -> (ASDisplayNode, StickerPackItem)? { guard let item = self.item else { return nil } @@ -535,7 +539,7 @@ class StickerPaneSearchGlobalItemNode: GridItemNode { return nil } - func updatePreviewing(animated: Bool) { + public func updatePreviewing(animated: Bool) { guard let item = self.item else { return } diff --git a/submodules/TelegramUI/Sources/StickerPaneSearchStickerItem.swift b/submodules/FeaturedStickersScreen/Sources/StickerPaneSearchStickerItem.swift similarity index 86% rename from submodules/TelegramUI/Sources/StickerPaneSearchStickerItem.swift rename to submodules/FeaturedStickersScreen/Sources/StickerPaneSearchStickerItem.swift index 158cb2b2a4..1895a289be 100644 --- a/submodules/TelegramUI/Sources/StickerPaneSearchStickerItem.swift +++ b/submodules/FeaturedStickersScreen/Sources/StickerPaneSearchStickerItem.swift @@ -10,6 +10,7 @@ import StickerResources import AccountContext import AnimatedStickerNode import TelegramAnimatedStickerNode +import ChatPresentationInterfaceState final class StickerPaneSearchStickerSection: GridSection { let code: String @@ -65,16 +66,16 @@ final class StickerPaneSearchStickerSectionNode: ASDisplayNode { } } -final class StickerPaneSearchStickerItem: GridItem { - let account: Account - let code: String? - let stickerItem: FoundStickerItem - let selected: (ASDisplayNode, CGRect) -> Void - let inputNodeInteraction: ChatMediaInputNodeInteraction +public final class StickerPaneSearchStickerItem: GridItem { + public let account: Account + public let code: String? + public let stickerItem: FoundStickerItem + public let selected: (ASDisplayNode, CGRect) -> Void + public let inputNodeInteraction: ChatMediaInputNodeInteraction - let section: GridSection? + public let section: GridSection? - init(account: Account, code: String?, stickerItem: FoundStickerItem, inputNodeInteraction: ChatMediaInputNodeInteraction, theme: PresentationTheme, selected: @escaping (ASDisplayNode, CGRect) -> Void) { + public init(account: Account, code: String?, stickerItem: FoundStickerItem, inputNodeInteraction: ChatMediaInputNodeInteraction, theme: PresentationTheme, selected: @escaping (ASDisplayNode, CGRect) -> Void) { self.account = account self.stickerItem = stickerItem self.inputNodeInteraction = inputNodeInteraction @@ -83,7 +84,7 @@ final class StickerPaneSearchStickerItem: GridItem { self.section = nil } - func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode { + public func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode { let node = StickerPaneSearchStickerItemNode() node.inputNodeInteraction = self.inputNodeInteraction node.setup(account: self.account, stickerItem: self.stickerItem, code: self.code) @@ -91,7 +92,7 @@ final class StickerPaneSearchStickerItem: GridItem { return node } - func update(node: GridItemNode) { + public func update(node: GridItemNode) { guard let node = node as? StickerPaneSearchStickerItemNode else { assertionFailure() return @@ -104,17 +105,17 @@ final class StickerPaneSearchStickerItem: GridItem { private let textFont = Font.regular(20.0) -final class StickerPaneSearchStickerItemNode: GridItemNode { +public final class StickerPaneSearchStickerItemNode: GridItemNode { private var currentState: (Account, FoundStickerItem, CGSize)? - let imageNode: TransformImageNode - private(set) var animationNode: AnimatedStickerNode? + public let imageNode: TransformImageNode + public private(set) var animationNode: AnimatedStickerNode? private let textNode: ASTextNode private let stickerFetchedDisposable = MetaDisposable() - var currentIsPreviewing = false + public var currentIsPreviewing = false - override var isVisibleInGrid: Bool { + public override var isVisibleInGrid: Bool { didSet { self.updateVisibility() } @@ -122,14 +123,14 @@ final class StickerPaneSearchStickerItemNode: GridItemNode { private var isPlaying = false - var inputNodeInteraction: ChatMediaInputNodeInteraction? - var selected: ((ASDisplayNode, CGRect) -> Void)? + public var inputNodeInteraction: ChatMediaInputNodeInteraction? + public var selected: ((ASDisplayNode, CGRect) -> Void)? - var stickerItem: FoundStickerItem? { + public var stickerItem: FoundStickerItem? { return self.currentState?.1 } - override init() { + public override init() { self.imageNode = TransformImageNode() self.textNode = ASTextNode() self.textNode.isUserInteractionEnabled = false @@ -145,7 +146,7 @@ final class StickerPaneSearchStickerItemNode: GridItemNode { self.stickerFetchedDisposable.dispose() } - override func didLoad() { + public override func didLoad() { super.didLoad() self.imageNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:)))) @@ -184,7 +185,7 @@ final class StickerPaneSearchStickerItemNode: GridItemNode { } } - override func layout() { + public override func layout() { super.layout() let bounds = self.bounds @@ -211,11 +212,11 @@ final class StickerPaneSearchStickerItemNode: GridItemNode { self.selected?(self, self.bounds) } - func transitionNode() -> ASDisplayNode? { + public func transitionNode() -> ASDisplayNode? { return self.imageNode } - func updateVisibility() { + public func updateVisibility() { let isPlaying = self.isVisibleInGrid if self.isPlaying != isPlaying { self.isPlaying = isPlaying @@ -223,7 +224,7 @@ final class StickerPaneSearchStickerItemNode: GridItemNode { } } - func updatePreviewing(animated: Bool) { + public func updatePreviewing(animated: Bool) { var isPreviewing = false if let (_, item, _) = self.currentState, let interaction = self.inputNodeInteraction { isPreviewing = interaction.previewedStickerPackItem == .found(item) diff --git a/submodules/GalleryData/Sources/GalleryData.swift b/submodules/GalleryData/Sources/GalleryData.swift index 7af4708784..05086770d2 100644 --- a/submodules/GalleryData/Sources/GalleryData.swift +++ b/submodules/GalleryData/Sources/GalleryData.swift @@ -116,6 +116,10 @@ public func chatMessageGalleryControllerData(context: AccountContext, chatLocati switch action.action { case let .photoUpdated(image), let .suggestedProfilePhoto(image): if let peer = messageMainPeer(EngineMessage(message)), let image = image { + var isSuggested = false + if case .suggestedProfilePhoto = action.action { + isSuggested = true + } let promise: Promise<[AvatarGalleryEntry]> = Promise([AvatarGalleryEntry.image(image.imageId, image.reference, image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), peer._asPeer(), message.timestamp, nil, message.id, image.immediateThumbnailData, "action", false)]) let sourceCorners: AvatarGalleryController.SourceCorners @@ -124,7 +128,7 @@ public func chatMessageGalleryControllerData(context: AccountContext, chatLocati } else { sourceCorners = .round } - let galleryController = AvatarGalleryController(context: context, peer: peer._asPeer(), sourceCorners: sourceCorners, remoteEntries: promise, skipInitial: true, replaceRootController: { controller, ready in + let galleryController = AvatarGalleryController(context: context, peer: peer._asPeer(), sourceCorners: sourceCorners, remoteEntries: promise, isSuggested: isSuggested, skipInitial: true, replaceRootController: { controller, ready in }) return .chatAvatars(galleryController, image) diff --git a/submodules/ItemListAvatarAndNameInfoItem/Sources/ItemListAvatarAndNameItem.swift b/submodules/ItemListAvatarAndNameInfoItem/Sources/ItemListAvatarAndNameItem.swift index 4116a457a3..18f03ec57f 100644 --- a/submodules/ItemListAvatarAndNameInfoItem/Sources/ItemListAvatarAndNameItem.swift +++ b/submodules/ItemListAvatarAndNameInfoItem/Sources/ItemListAvatarAndNameItem.swift @@ -60,7 +60,7 @@ public enum ItemListAvatarAndNameInfoItemName: Equatable { } } - public func composedDisplayTitle(strings: PresentationStrings) -> String { + public func composedDisplayTitle(context: AccountContext, strings: PresentationStrings) -> String { switch self { case let .personName(firstName, lastName, phone): if !firstName.isEmpty { @@ -72,7 +72,7 @@ public enum ItemListAvatarAndNameInfoItemName: Equatable { } else if !lastName.isEmpty { return lastName } else if !phone.isEmpty { - return formatPhoneNumber("+\(phone)") + return formatPhoneNumber(context: context, number: "+\(phone)") } else { return strings.User_DeletedAccount } @@ -395,7 +395,7 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo nameMaximumNumberOfLines = 2 } - let (nameNodeLayout, nameNodeApply) = layoutNameNode(TextNodeLayoutArguments(attributedString: NSAttributedString(string: displayTitle.composedDisplayTitle(strings: item.presentationData.strings), font: nameFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: nameMaximumNumberOfLines, truncationType: .end, constrainedSize: CGSize(width: baseWidth - 20 - 94.0 - (item.call != nil ? 36.0 : 0.0) - additionalTitleInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (nameNodeLayout, nameNodeApply) = layoutNameNode(TextNodeLayoutArguments(attributedString: NSAttributedString(string: displayTitle.composedDisplayTitle(context: item.accountContext, strings: item.presentationData.strings), font: nameFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: nameMaximumNumberOfLines, truncationType: .end, constrainedSize: CGSize(width: baseWidth - 20 - 94.0 - (item.call != nil ? 36.0 : 0.0) - additionalTitleInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) var statusText: String = "" let statusColor: UIColor @@ -404,7 +404,7 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo switch item.mode { case .settings: if let phone = peer.phone, !phone.isEmpty { - statusText += formatPhoneNumber(phone) + statusText += formatPhoneNumber(context: item.accountContext, number: phone) } if let username = peer.addressName, !username.isEmpty { if !statusText.isEmpty { diff --git a/submodules/ItemListPeerActionItem/Sources/ItemListPeerActionItem.swift b/submodules/ItemListPeerActionItem/Sources/ItemListPeerActionItem.swift index 1656908cb7..3bd18d4852 100644 --- a/submodules/ItemListPeerActionItem/Sources/ItemListPeerActionItem.swift +++ b/submodules/ItemListPeerActionItem/Sources/ItemListPeerActionItem.swift @@ -22,6 +22,7 @@ public enum ItemListPeerActionItemColor { public class ItemListPeerActionItem: ListViewItem, ItemListItem { let presentationData: ItemListPresentationData let icon: UIImage? + let iconSignal: Signal? let title: String public let alwaysPlain: Bool let hasSeparator: Bool @@ -31,9 +32,10 @@ public class ItemListPeerActionItem: ListViewItem, ItemListItem { public let sectionId: ItemListSectionId let action: (() -> Void)? - public init(presentationData: ItemListPresentationData, icon: UIImage?, title: String, alwaysPlain: Bool = false, hasSeparator: Bool = true, sectionId: ItemListSectionId, height: ItemListPeerActionItemHeight = .peerList, color: ItemListPeerActionItemColor = .accent, editing: Bool = false, action: (() -> Void)?) { + public init(presentationData: ItemListPresentationData, icon: UIImage?, iconSignal: Signal? = nil, title: String, alwaysPlain: Bool = false, hasSeparator: Bool = true, sectionId: ItemListSectionId, height: ItemListPeerActionItemHeight = .peerList, color: ItemListPeerActionItemColor = .accent, editing: Bool = false, action: (() -> Void)?) { self.presentationData = presentationData self.icon = icon + self.iconSignal = iconSignal self.title = title self.alwaysPlain = alwaysPlain self.hasSeparator = hasSeparator @@ -114,6 +116,8 @@ class ItemListPeerActionItemNode: ListViewItemNode { private var item: ItemListPeerActionItem? + private let iconDisposable = MetaDisposable() + init() { self.backgroundNode = ASDisplayNode() self.backgroundNode.isLayerBacked = true @@ -149,6 +153,10 @@ class ItemListPeerActionItemNode: ListViewItemNode { self.addSubnode(self.activateArea) } + deinit { + self.iconDisposable.dispose() + } + func asyncLayout() -> (_ item: ItemListPeerActionItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) { let makeTitleLayout = TextNode.asyncLayout(self.titleNode) @@ -171,7 +179,7 @@ class ItemListPeerActionItemNode: ListViewItemNode { iconOffset = 1.0 verticalInset = 11.0 verticalOffset = 0.0 - leftInset = (item.icon == nil ? 16.0 : 59.0) + params.leftInset + leftInset = (item.icon == nil && item.iconSignal == nil ? 16.0 : 59.0) + params.leftInset case .peerList: iconOffset = 3.0 verticalInset = 14.0 @@ -232,6 +240,15 @@ class ItemListPeerActionItemNode: ListViewItemNode { strongSelf.iconNode.image = item.icon if let image = item.icon { transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(origin: CGPoint(x: params.leftInset + editingOffset + floor((leftInset - params.leftInset - image.size.width) / 2.0) + iconOffset, y: floor((contentSize.height - image.size.height) / 2.0)), size: image.size)) + } else if let iconSignal = item.iconSignal { + let imageSize = CGSize(width: 28.0, height: 28.0) + strongSelf.iconDisposable.set((iconSignal + |> deliverOnMainQueue).start(next: { [weak self] image in + if let strongSelf = self, let image { + strongSelf.iconNode.image = image + } + })) + transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(origin: CGPoint(x: params.leftInset + editingOffset + floor((leftInset - params.leftInset - imageSize.width) / 2.0) + iconOffset, y: floor((contentSize.height - imageSize.height) / 2.0)), size: imageSize)) } if strongSelf.backgroundNode.supernode == nil { diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaEditingContext.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaEditingContext.h index bcf864bbd2..7871f47ecc 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaEditingContext.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaEditingContext.h @@ -94,7 +94,7 @@ - (UIImage *)paintingImageForItem:(NSObject *)item; - (UIImage *)stillPaintingImageForItem:(NSObject *)item; -- (bool)setPaintingData:(NSData *)data image:(UIImage *)image stillImage:(UIImage *)image forItem:(NSObject *)item dataUrl:(NSURL **)dataOutUrl imageUrl:(NSURL **)imageOutUrl forVideo:(bool)video; +- (bool)setPaintingData:(NSData *)data entitiesData:(NSData *)entitiesData image:(UIImage *)image stillImage:(UIImage *)stillImage forItem:(NSObject *)item dataUrl:(NSURL **)dataOutUrl entitiesDataUrl:(NSURL **)entitiesDataOutUrl imageUrl:(NSURL **)imageOutUrl forVideo:(bool)video; - (void)clearPaintingData; - (SSignal *)facesForItem:(NSObject *)item; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryVideoItemView.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryVideoItemView.h index fefbd2433b..e35ddc83f2 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryVideoItemView.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryVideoItemView.h @@ -3,8 +3,8 @@ #import #import -@class TGPhotoEntitiesContainerView; @protocol TGMediaEditableItem; +@protocol TGPhotoDrawingEntitiesView; @interface TGMediaPickerGalleryVideoItemView : TGModernGalleryItemView @@ -34,7 +34,7 @@ - (UIImage *)screenImage; - (UIImage *)transitionImage; - (CGRect)editorTransitionViewRect; -- (TGPhotoEntitiesContainerView *)entitiesView; +- (UIView *)entitiesView; - (id)editableMediaItem; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPaintingData.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPaintingData.h index 9b7cf065db..86c728194b 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPaintingData.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPaintingData.h @@ -8,21 +8,20 @@ @interface TGPaintingData : NSObject @property (nonatomic, readonly) NSString *imagePath; -@property (nonatomic, readonly) NSString *dataPath; -@property (nonatomic, readonly) NSArray *entities; -@property (nonatomic, readonly) TGPaintUndoManager *undoManager; -@property (nonatomic, readonly) NSArray *stickers; -@property (nonatomic, readonly) NSData *data; +@property (nonatomic, readonly) NSData *drawingData; +@property (nonatomic, readonly) NSData *entitiesData; + @property (nonatomic, readonly) UIImage *image; - @property (nonatomic, readonly) UIImage *stillImage; +@property (nonatomic, readonly) NSArray *stickers; + @property (nonatomic, readonly) bool hasAnimation; -+ (instancetype)dataWithPaintingData:(NSData *)data image:(UIImage *)image stillImage:(UIImage *)stillImage entities:(NSArray *)entities undoManager:(TGPaintUndoManager *)undoManager; ++ (instancetype)dataWithDrawingData:(NSData *)data entitiesData:(NSData *)entitiesData image:(UIImage *)image stillImage:(UIImage *)stillImage hasAnimation:(bool)hasAnimation; -+ (instancetype)dataWithPaintingImagePath:(NSString *)imagePath entities:(NSArray *)entities; ++ (instancetype)dataWithPaintingImagePath:(NSString *)imagePath entitiesData:(NSData *)entitiesData; + (instancetype)dataWithPaintingImagePath:(NSString *)imagePath; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoAvatarCropView.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoAvatarCropView.h index 871b8f0f4a..dd61efecaf 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoAvatarCropView.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoAvatarCropView.h @@ -1,7 +1,7 @@ #import @class PGPhotoEditorView; -@class TGPhotoEntitiesContainerView; +@protocol TGPhotoDrawingEntitiesView; @interface TGPhotoAvatarCropView : UIView @@ -23,7 +23,7 @@ @property (nonatomic, readonly) bool isTracking; @property (nonatomic, readonly) bool isAnimating; -- (instancetype)initWithOriginalSize:(CGSize)originalSize screenSize:(CGSize)screenSize fullPreviewView:(PGPhotoEditorView *)fullPreviewView fullPaintingView:(UIImageView *)fullPaintingView fullEntitiesView:(TGPhotoEntitiesContainerView *)fullEntitiesView square:(bool)square; +- (instancetype)initWithOriginalSize:(CGSize)originalSize screenSize:(CGSize)screenSize fullPreviewView:(PGPhotoEditorView *)fullPreviewView fullPaintingView:(UIImageView *)fullPaintingView fullEntitiesView:(UIView *)fullEntitiesView square:(bool)square; - (void)setSnapshotImage:(UIImage *)image; - (void)setSnapshotView:(UIView *)snapshotView; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorController.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorController.h index 2c5c7df125..12a8d4782d 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorController.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorController.h @@ -12,7 +12,7 @@ @class AVPlayer; @protocol TGPhotoPaintStickersContext; -@class TGPhotoEntitiesContainerView; +@protocol TGPhotoDrawingEntitiesView; typedef enum { TGPhotoEditorControllerGenericIntent = 0, @@ -60,9 +60,11 @@ typedef enum { @property (nonatomic, strong) PGCameraShotMetadata *metadata; @property (nonatomic, strong) NSArray *faces; +@property (nonatomic, strong) NSString *senderName; + @property (nonatomic, strong) AVPlayer *player; -@property (nonatomic, strong) TGPhotoEntitiesContainerView *entitiesView; +@property (nonatomic, strong) UIView *entitiesView; - (instancetype)initWithContext:(id)context item:(id)item intent:(TGPhotoEditorControllerIntent)intent adjustments:(id)adjustments caption:(NSAttributedString *)caption screenImage:(UIImage *)screenImage availableTabs:(TGPhotoEditorTab)availableTabs selectedTab:(TGPhotoEditorTab)selectedTab; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h index 7e8cdb437d..6f69b8ffd6 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h @@ -89,6 +89,8 @@ - (void)handlePinch:(UIPinchGestureRecognizer *)gestureRecognizer; - (void)handleRotate:(UIRotationGestureRecognizer *)gestureRecognizer; +- (void)setupWithEntitiesData:(NSData *)entitiesData; + @end @protocol TGPhotoDrawingInterfaceController @@ -134,6 +136,8 @@ - (UIView *)solidRoundedButton:(NSString *)title action:(void(^)(void))action; -- (id)drawingAdapter:(CGSize)size; +- (id)drawingAdapter:(CGSize)size originalSize:(CGSize)originalSize; + +- (UIView *)drawingEntitiesViewWithSize:(CGSize)size; @end diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoVideoEditor.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoVideoEditor.h index 7b60e1429c..737380548d 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoVideoEditor.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoVideoEditor.h @@ -2,7 +2,7 @@ @interface TGPhotoVideoEditor : NSObject -+ (void)presentWithContext:(id)context parentController:(TGViewController *)parentController image:(UIImage *)image video:(NSURL *)video stickersContext:(id)stickersContext transitionView:(UIView *)transitionView didFinishWithImage:(void (^)(UIImage *image))didFinishWithImage didFinishWithVideo:(void (^)(UIImage *image, NSURL *url, TGVideoEditAdjustments *adjustments))didFinishWithVideo dismissed:(void (^)(void))dismissed; ++ (void)presentWithContext:(id)context parentController:(TGViewController *)parentController image:(UIImage *)image video:(NSURL *)video stickersContext:(id)stickersContext transitionView:(UIView *)transitionView senderName:(NSString *)senderName didFinishWithImage:(void (^)(UIImage *image))didFinishWithImage didFinishWithVideo:(void (^)(UIImage *image, NSURL *url, TGVideoEditAdjustments *adjustments))didFinishWithVideo dismissed:(void (^)(void))dismissed; + (void)presentWithContext:(id)context controller:(TGViewController *)controller caption:(NSAttributedString *)caption withItem:(id)item paint:(bool)paint recipientName:(NSString *)recipientName stickersContext:(id)stickersContext snapshots:(NSArray *)snapshots immediate:(bool)immediate appeared:(void (^)(void))appeared completion:(void (^)(id, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed; diff --git a/submodules/LegacyComponents/Sources/TGCameraController.m b/submodules/LegacyComponents/Sources/TGCameraController.m index 488655ad5f..5eed923316 100644 --- a/submodules/LegacyComponents/Sources/TGCameraController.m +++ b/submodules/LegacyComponents/Sources/TGCameraController.m @@ -2969,14 +2969,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus if (adjustments.paintingData.stickers.count > 0) dict[@"stickers"] = adjustments.paintingData.stickers; - bool animated = false; - for (TGPhotoPaintEntity *entity in adjustments.paintingData.entities) { - if (entity.animated) { - animated = true; - break; - } - } - + bool animated = adjustments.paintingData.hasAnimation; if (animated) { dict[@"isAnimation"] = @true; if ([adjustments isKindOfClass:[PGPhotoEditorValues class]]) { diff --git a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m index d09dec21e7..df80734beb 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m @@ -929,10 +929,8 @@ grouping = false; } } - for (TGPhotoPaintEntity *entity in adjustments.paintingData.entities) { - if (entity.animated) { - grouping = true; - } + if (adjustments.paintingData.hasAnimation) { + grouping = false; } } } @@ -1159,14 +1157,7 @@ if (adjustments.paintingData.stickers.count > 0) dict[@"stickers"] = adjustments.paintingData.stickers; - bool animated = false; - for (TGPhotoPaintEntity *entity in adjustments.paintingData.entities) { - if (entity.animated) { - animated = true; - break; - } - } - + bool animated = adjustments.paintingData.hasAnimation; if (animated) { dict[@"isAnimation"] = @true; if ([adjustments isKindOfClass:[PGPhotoEditorValues class]]) { @@ -1448,10 +1439,8 @@ grouping = false; } } - for (TGPhotoPaintEntity *entity in adjustments.paintingData.entities) { - if (entity.animated) { - grouping = true; - } + if (adjustments.paintingData.hasAnimation) { + grouping = false; } } } diff --git a/submodules/LegacyComponents/Sources/TGMediaEditingContext.m b/submodules/LegacyComponents/Sources/TGMediaEditingContext.m index 40e90e8bf5..5578835d54 100644 --- a/submodules/LegacyComponents/Sources/TGMediaEditingContext.m +++ b/submodules/LegacyComponents/Sources/TGMediaEditingContext.m @@ -715,7 +715,7 @@ [_queue dispatch:block]; } -- (bool)setPaintingData:(NSData *)data image:(UIImage *)image stillImage:(UIImage *)stillImage forItem:(NSObject *)item dataUrl:(NSURL **)dataOutUrl imageUrl:(NSURL **)imageOutUrl forVideo:(bool)video +- (bool)setPaintingData:(NSData *)data entitiesData:(NSData *)entitiesData image:(UIImage *)image stillImage:(UIImage *)stillImage forItem:(NSObject *)item dataUrl:(NSURL **)dataOutUrl entitiesDataUrl:(NSURL **)entitiesDataOutUrl imageUrl:(NSURL **)imageOutUrl forVideo:(bool)video { NSString *itemId = [self _contextualIdForItemId:item.uniqueIdentifier]; @@ -725,6 +725,7 @@ NSURL *imagesDirectory = video ? _videoPaintingImagesUrl : _paintingImagesUrl; NSURL *imageUrl = [imagesDirectory URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.png", [TGStringUtils md5:itemId]]]; NSURL *dataUrl = [_paintingDatasUrl URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.dat", [TGStringUtils md5:itemId]]]; + NSURL *entitiesDataUrl = [_paintingDatasUrl URLByAppendingPathComponent:[NSString stringWithFormat:@"%@_entities.dat", [TGStringUtils md5:itemId]]]; [_paintingImageCache setImage:image forKey:itemId attributes:NULL]; @@ -733,12 +734,16 @@ bool imageSuccess = [imageData writeToURL:imageUrl options:NSDataWritingAtomic error:nil]; [[NSFileManager defaultManager] removeItemAtURL:dataUrl error:nil]; bool dataSuccess = [data writeToURL:dataUrl options:NSDataWritingAtomic error:nil]; + bool entitiesDataSuccess = [entitiesData writeToURL:entitiesDataUrl options:NSDataWritingAtomic error:nil]; if (imageSuccess && imageOutUrl != NULL) *imageOutUrl = imageUrl; if (dataSuccess && dataOutUrl != NULL) *dataOutUrl = dataUrl; + + if (entitiesDataSuccess && entitiesDataOutUrl != NULL) + *entitiesDataOutUrl = entitiesDataUrl; if (video) [_storeVideoPaintingImages addObject:imageUrl]; diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryModel.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryModel.m index a82be573e8..4168cd47c5 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryModel.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryModel.m @@ -21,8 +21,6 @@ #import -#import "TGPhotoEntitiesContainerView.h" - @interface TGMediaPickerGalleryModel () { TGMediaPickerGalleryInterfaceView *_interfaceView; @@ -363,7 +361,7 @@ UIView *referenceParentView = nil; UIImage *image = nil; - TGPhotoEntitiesContainerView *entitiesView = nil; + UIView *entitiesView = nil; id editableMediaItem = item.editableMediaItem; @@ -373,7 +371,7 @@ screenImage = [(UIImageView *)editorReferenceView image]; referenceView = editorReferenceView; - if ([editorReferenceView.subviews.firstObject.subviews.firstObject.subviews.firstObject isKindOfClass:[TGPhotoEntitiesContainerView class]]) { + if ([editorReferenceView.subviews.firstObject.subviews.firstObject.subviews.firstObject conformsToProtocol:@protocol(TGPhotoDrawingEntitiesView)]) { entitiesView = editorReferenceView.subviews.firstObject.subviews.firstObject.subviews.firstObject; } } diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryPhotoItemView.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryPhotoItemView.m index aa7bedcda2..0d17c5524e 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryPhotoItemView.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryPhotoItemView.m @@ -20,8 +20,7 @@ #import "TGMediaPickerGalleryPhotoItem.h" -#import "TGPhotoEntitiesContainerView.h" -#import "TGPhotoPaintController.h" +#import "TGPhotoDrawingController.h" #import @@ -42,7 +41,7 @@ UIView *_contentView; UIView *_contentWrapperView; - TGPhotoEntitiesContainerView *_entitiesContainerView; + UIView *_entitiesView; SMetaDisposable *_adjustmentsDisposable; SMetaDisposable *_attributesDisposable; @@ -90,11 +89,7 @@ _contentWrapperView = [[UIView alloc] init]; [_contentView addSubview:_contentWrapperView]; - - _entitiesContainerView = [[TGPhotoEntitiesContainerView alloc] init]; - _entitiesContainerView.userInteractionEnabled = false; - [_contentWrapperView addSubview:_entitiesContainerView]; - + _fileInfoLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 200, 20)]; _fileInfoLabel.backgroundColor = [UIColor clearColor]; _fileInfoLabel.font = TGSystemFontOfSize(13); @@ -143,7 +138,11 @@ [super setItem:item synchronously:synchronously]; - _entitiesContainerView.stickersContext = item.stickersContext; + if (_entitiesView == nil) { + _entitiesView = [item.stickersContext drawingEntitiesViewWithSize:item.asset.originalSize]; + _entitiesView.userInteractionEnabled = false; + [_contentWrapperView addSubview:_entitiesView]; + } _imageSize = item.asset.originalSize; [self reset]; @@ -223,7 +222,7 @@ return; [strongSelf layoutEntities]; - [strongSelf->_entitiesContainerView setupWithPaintingData:next.paintingData]; + [strongSelf->_entitiesView setupWithEntitiesData:next.paintingData.entitiesData]; }]]; } @@ -486,27 +485,27 @@ _contentView.transform = rotationTransform; _contentView.frame = previewFrame; - CGSize fittedContentSize = [TGPhotoPaintController fittedContentSize:cropRect orientation:orientation originalSize:originalSize]; - CGRect fittedCropRect = [TGPhotoPaintController fittedCropRect:cropRect originalSize:originalSize keepOriginalSize:false]; + CGSize fittedContentSize = [TGPhotoDrawingController fittedContentSize:cropRect orientation:orientation originalSize:originalSize]; + CGRect fittedCropRect = [TGPhotoDrawingController fittedCropRect:cropRect originalSize:originalSize keepOriginalSize:false]; _contentWrapperView.frame = CGRectMake(0.0f, 0.0f, fittedContentSize.width, fittedContentSize.height); CGFloat contentScale = _contentView.bounds.size.width / fittedCropRect.size.width; _contentWrapperView.transform = CGAffineTransformMakeScale(contentScale, contentScale); _contentWrapperView.frame = CGRectMake(0.0f, 0.0f, _contentView.bounds.size.width, _contentView.bounds.size.height); - CGRect rect = [TGPhotoPaintController fittedCropRect:cropRect originalSize:originalSize keepOriginalSize:true]; - _entitiesContainerView.frame = CGRectMake(0, 0, rect.size.width, rect.size.height); - _entitiesContainerView.transform = CGAffineTransformMakeRotation(rotation); + CGRect rect = [TGPhotoDrawingController fittedCropRect:cropRect originalSize:originalSize keepOriginalSize:true]; + _entitiesView.frame = CGRectMake(0, 0, rect.size.width, rect.size.height); + _entitiesView.transform = CGAffineTransformMakeRotation(rotation); - CGSize fittedOriginalSize = TGScaleToSize(originalSize, [TGPhotoPaintController maximumPaintingSize]); + CGSize fittedOriginalSize = TGScaleToSize(originalSize, [TGPhotoDrawingController maximumPaintingSize]); CGSize rotatedSize = TGRotatedContentSize(fittedOriginalSize, rotation); CGPoint centerPoint = CGPointMake(rotatedSize.width / 2.0f, rotatedSize.height / 2.0f); CGFloat scale = fittedOriginalSize.width / originalSize.width; - CGPoint offset = TGPaintSubtractPoints(centerPoint, [TGPhotoPaintController fittedCropRect:cropRect centerScale:scale]); + CGPoint offset = TGPaintSubtractPoints(centerPoint, [TGPhotoDrawingController fittedCropRect:cropRect centerScale:scale]); CGPoint boundsCenter = TGPaintCenterOfRect(_contentWrapperView.bounds); - _entitiesContainerView.center = TGPaintAddPoints(boundsCenter, offset); + _entitiesView.center = TGPaintAddPoints(boundsCenter, offset); } @end diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m index 9d4aa9a9d0..19a85c0db0 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m @@ -32,8 +32,7 @@ #import "TGMediaPickerScrubberHeaderView.h" #import "TGPhotoEditorPreviewView.h" -#import "TGPhotoEntitiesContainerView.h" -#import "TGPhotoPaintController.h" +#import "TGPhotoDrawingController.h" #import #import "TGModernGalleryVideoContentView.h" @@ -84,7 +83,7 @@ UIImageView *_paintingImageView; UIView *_contentView; UIView *_contentWrapperView; - TGPhotoEntitiesContainerView *_entitiesContainerView; + UIView *_entitiesView; NSTimer *_positionTimer; TGObserverProxy *_didPlayToEndObserver; @@ -174,12 +173,7 @@ _contentWrapperView = [[UIView alloc] init]; [_contentView addSubview:_contentWrapperView]; - - _entitiesContainerView = [[TGPhotoEntitiesContainerView alloc] init]; - _entitiesContainerView.hidden = true; - _entitiesContainerView.userInteractionEnabled = false; - [_contentWrapperView addSubview:_entitiesContainerView]; - + _curtainView = [[UIView alloc] init]; _curtainView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; _curtainView.backgroundColor = [UIColor blackColor]; @@ -425,8 +419,14 @@ _scrubberView.allowsTrimming = false; _videoDimensions = item.dimensions; - _entitiesContainerView.stickersContext = item.stickersContext; + if (_entitiesView == nil) { + _entitiesView = [item.stickersContext drawingEntitiesViewWithSize:item.dimensions]; + _entitiesView.hidden = true; + _entitiesView.userInteractionEnabled = false; + [_contentWrapperView addSubview:_entitiesView]; + } + __weak TGMediaPickerGalleryVideoItemView *weakSelf = self; [_videoDurationVar set:[[[item.durationSignal deliverOn:[SQueue mainQueue]] catch:^SSignal *(__unused id error) { @@ -504,8 +504,8 @@ if (baseAdjustments.sendAsGif || ([strongSelf itemIsLivePhoto])) [strongSelf setPlayButtonHidden:true animated:false]; - [strongSelf->_entitiesContainerView setupWithPaintingData:adjustments.paintingData]; - [strongSelf->_entitiesContainerView updateVisibility:strongSelf.isPlaying]; + [strongSelf->_entitiesView setupWithEntitiesData:adjustments.paintingData.entitiesData]; + [strongSelf->_entitiesView updateVisibility:strongSelf.isPlaying]; [strongSelf->_photoEditor importAdjustments:adjustments]; if (!strongSelf.isPlaying) { @@ -823,24 +823,24 @@ _contentView.transform = rotationTransform; _contentView.frame = _imageView.frame; - CGSize fittedContentSize = [TGPhotoPaintController fittedContentSize:cropRect orientation:orientation originalSize:originalSize]; + CGSize fittedContentSize = [TGPhotoDrawingController fittedContentSize:cropRect orientation:orientation originalSize:originalSize]; _contentWrapperView.frame = CGRectMake(0.0f, 0.0f, fittedContentSize.width, fittedContentSize.height); CGFloat contentScale = ratio; _contentWrapperView.transform = CGAffineTransformMakeScale(contentScale, contentScale); _contentWrapperView.frame = CGRectMake(0.0f, 0.0f, _contentView.bounds.size.width, _contentView.bounds.size.height); - CGRect rect = [TGPhotoPaintController fittedCropRect:cropRect originalSize:originalSize keepOriginalSize:true]; - _entitiesContainerView.frame = CGRectMake(0, 0, rect.size.width, rect.size.height); - _entitiesContainerView.transform = CGAffineTransformMakeRotation(0.0); + CGRect rect = [TGPhotoDrawingController fittedCropRect:cropRect originalSize:originalSize keepOriginalSize:true]; + _entitiesView.frame = CGRectMake(0, 0, rect.size.width, rect.size.height); + _entitiesView.transform = CGAffineTransformMakeRotation(0.0); - CGSize fittedOriginalSize = TGScaleToSize(originalSize, [TGPhotoPaintController maximumPaintingSize]); + CGSize fittedOriginalSize = TGScaleToSize(originalSize, [TGPhotoDrawingController maximumPaintingSize]); CGSize rotatedSize = TGRotatedContentSize(fittedOriginalSize, 0.0); __unused CGPoint centerPoint = CGPointMake(rotatedSize.width / 2.0f, rotatedSize.height / 2.0f); } -- (TGPhotoEntitiesContainerView *)entitiesView { - return _entitiesContainerView; +- (UIView *)entitiesView { + return _entitiesView; } - (void)singleTap @@ -1176,7 +1176,7 @@ strongSelf->_videoView.userInteractionEnabled = false; [strongSelf->_playerContainerView insertSubview:strongSelf->_videoView belowSubview:strongSelf->_paintingImageView]; - strongSelf->_entitiesContainerView.hidden = false; + strongSelf->_entitiesView.hidden = false; [strongSelf->_videoView setNeedsTransitionIn]; [strongSelf->_videoView performTransitionInIfNeeded]; @@ -1196,7 +1196,7 @@ [strongSelf->_player play]; } - [strongSelf->_entitiesContainerView updateVisibility:strongSelf.isPlaying]; + [strongSelf->_entitiesView updateVisibility:strongSelf.isPlaying]; strongSelf->_positionTimer = [TGTimerTarget scheduledMainThreadTimerWithTarget:self action:@selector(positionTimerEvent) interval:0.25 repeat:true]; [strongSelf positionTimerEvent]; @@ -1211,11 +1211,11 @@ { if (_player.rate > FLT_EPSILON) { [_scrubberView setIsPlaying:true]; - [_entitiesContainerView updateVisibility:true]; + [_entitiesView updateVisibility:true]; } else { [_scrubberView setIsPlaying:false]; - [_entitiesContainerView updateVisibility:false]; + [_entitiesView updateVisibility:false]; } } } @@ -1251,7 +1251,7 @@ [self positionTimerEvent]; } - [_entitiesContainerView updateVisibility:true]; + [_entitiesView updateVisibility:true]; } - (void)playIfAvailable @@ -1274,7 +1274,7 @@ [_positionTimer invalidate]; _positionTimer = nil; - [_entitiesContainerView updateVisibility:false]; + [_entitiesView updateVisibility:false]; } - (void)togglePlayback diff --git a/submodules/LegacyComponents/Sources/TGPaintingData.m b/submodules/LegacyComponents/Sources/TGPaintingData.m index def8a89827..b8f4b5dbe8 100644 --- a/submodules/LegacyComponents/Sources/TGPaintingData.m +++ b/submodules/LegacyComponents/Sources/TGPaintingData.m @@ -12,7 +12,6 @@ { UIImage *_image; UIImage *_stillImage; - NSData *_data; UIImage *(^_imageRetrievalBlock)(void); UIImage *(^_stillImageRetrievalBlock)(void); @@ -21,21 +20,21 @@ @implementation TGPaintingData -+ (instancetype)dataWithPaintingData:(NSData *)data image:(UIImage *)image stillImage:(UIImage *)stillImage entities:(NSArray *)entities undoManager:(TGPaintUndoManager *)undoManager ++ (instancetype)dataWithDrawingData:(NSData *)data entitiesData:(NSData *)entitiesData image:(UIImage *)image stillImage:(UIImage *)stillImage hasAnimation:(bool)hasAnimation { TGPaintingData *paintingData = [[TGPaintingData alloc] init]; - paintingData->_data = data; + paintingData->_drawingData = data; paintingData->_image = image; paintingData->_stillImage = stillImage; - paintingData->_entities = entities; - paintingData->_undoManager = undoManager; + paintingData->_entitiesData = entitiesData; + paintingData->_hasAnimation = hasAnimation; return paintingData; } -+ (instancetype)dataWithPaintingImagePath:(NSString *)imagePath entities:(NSArray *)entities { ++ (instancetype)dataWithPaintingImagePath:(NSString *)imagePath entitiesData:(NSData *)entitiesData { TGPaintingData *paintingData = [[TGPaintingData alloc] init]; paintingData->_imagePath = imagePath; - paintingData->_entities = entities; + paintingData->_entitiesData = entitiesData; return paintingData; } @@ -49,7 +48,7 @@ - (instancetype)dataForAnimation { TGPaintingData *paintingData = [[TGPaintingData alloc] init]; - paintingData->_entities = _entities; + paintingData->_entitiesData = _entitiesData; return paintingData; } @@ -58,17 +57,17 @@ [[TGPaintingData queue] dispatch:^ { NSURL *dataUrl = nil; + NSURL *entitiesDataUrl = nil; NSURL *imageUrl = nil; - NSData *compressedData = TGPaintGZipDeflate(data.data); - [context setPaintingData:compressedData image:data.image stillImage:data.stillImage forItem:item dataUrl:&dataUrl imageUrl:&imageUrl forVideo:video]; + NSData *compressedDrawingData = TGPaintGZipDeflate(data.drawingData); + NSData *compressedEntitiesData = TGPaintGZipDeflate(data.entitiesData); + [context setPaintingData:compressedDrawingData entitiesData:compressedEntitiesData image:data.image stillImage:data.stillImage forItem:item dataUrl:&dataUrl entitiesDataUrl:&entitiesDataUrl imageUrl:&imageUrl forVideo:video]; __weak TGMediaEditingContext *weakContext = context; [[SQueue mainQueue] dispatch:^ { - data->_dataPath = dataUrl.path; data->_imagePath = imageUrl.path; - data->_data = nil; data->_imageRetrievalBlock = ^UIImage * { @@ -100,20 +99,6 @@ }]; } -- (void)dealloc -{ - [self.undoManager reset]; -} - -- (NSData *)data -{ - if (_data != nil) - return _data; - else if (_dataPath != nil) - return TGPaintGZipInflate([[NSData alloc] initWithContentsOfFile:_dataPath]); - else - return nil; -} - (UIImage *)image { @@ -128,7 +113,7 @@ - (UIImage *)stillImage { if (_stillImage != nil) - return _stillImage; + return _stillImage; else if (_stillImageRetrievalBlock != nil) return _stillImageRetrievalBlock(); else @@ -137,23 +122,14 @@ - (NSArray *)stickers { - NSMutableSet *stickers = [[NSMutableSet alloc] init]; - for (TGPhotoPaintEntity *entity in self.entities) - { - if ([entity isKindOfClass:[TGPhotoPaintStickerEntity class]]) - [stickers addObject:((TGPhotoPaintStickerEntity *)entity).document]; - } - return [stickers allObjects]; -} - -- (bool)hasAnimation -{ - for (TGPhotoPaintEntity *entity in self.entities) - { - if ([entity isKindOfClass:[TGPhotoPaintStickerEntity class]] && ((TGPhotoPaintStickerEntity *)entity).animated) - return true; - } - return false; + return @[]; +// NSMutableSet *stickers = [[NSMutableSet alloc] init]; +// for (TGPhotoPaintEntity *entity in self.entities) +// { +// if ([entity isKindOfClass:[TGPhotoPaintStickerEntity class]]) +// [stickers addObject:((TGPhotoPaintStickerEntity *)entity).document]; +// } +// return [stickers allObjects]; } - (BOOL)isEqual:(id)object @@ -165,7 +141,7 @@ return false; TGPaintingData *data = (TGPaintingData *)object; - return [data.entities isEqual:self.entities] && ((data.data != nil && [data.data isEqualToData:self.data]) || (data.data == nil && self.data == nil)); + return [data.entitiesData isEqual:self.entitiesData] && ((data.drawingData != nil && [data.drawingData isEqualToData:self.drawingData]) || (data.drawingData == nil && self.drawingData == nil)); } + (SQueue *)queue diff --git a/submodules/LegacyComponents/Sources/TGPhotoAvatarCropView.m b/submodules/LegacyComponents/Sources/TGPhotoAvatarCropView.m index ddd3a11fa4..521eb6c6c5 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoAvatarCropView.m +++ b/submodules/LegacyComponents/Sources/TGPhotoAvatarCropView.m @@ -9,7 +9,6 @@ #import "TGPhotoEditorInterfaceAssets.h" #import "PGPhotoEditorView.h" -#import "TGPhotoEntitiesContainerView.h" const CGFloat TGPhotoAvatarCropViewOverscreenSize = 1000; const CGFloat TGPhotoAvatarCropViewCurtainSize = 300; @@ -46,13 +45,13 @@ const CGFloat TGPhotoAvatarCropViewCurtainMargin = 200; __weak PGPhotoEditorView *_fullPreviewView; __weak UIImageView *_fullPaintingView; - __weak TGPhotoEntitiesContainerView *_fullEntitiesView; + __weak UIView *_fullEntitiesView; } @end @implementation TGPhotoAvatarCropView -- (instancetype)initWithOriginalSize:(CGSize)originalSize screenSize:(CGSize)screenSize fullPreviewView:(PGPhotoEditorView *)fullPreviewView fullPaintingView:(UIImageView *)fullPaintingView fullEntitiesView:(TGPhotoEntitiesContainerView *)fullEntitiesView square:(bool)square +- (instancetype)initWithOriginalSize:(CGSize)originalSize screenSize:(CGSize)screenSize fullPreviewView:(PGPhotoEditorView *)fullPreviewView fullPaintingView:(UIImageView *)fullPaintingView fullEntitiesView:(UIView *)fullEntitiesView square:(bool)square { self = [super initWithFrame:CGRectZero]; if (self != nil) diff --git a/submodules/LegacyComponents/Sources/TGPhotoAvatarPreviewController.h b/submodules/LegacyComponents/Sources/TGPhotoAvatarPreviewController.h index 5a72f91f0e..aeb142f3a7 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoAvatarPreviewController.h +++ b/submodules/LegacyComponents/Sources/TGPhotoAvatarPreviewController.h @@ -3,7 +3,6 @@ @class PGPhotoEditor; @class PGPhotoTool; @class TGPhotoEditorPreviewView; -@class TGPhotoEntitiesContainerView; @class PGPhotoEditorView; @class TGMediaPickerGalleryVideoScrubber; @@ -23,12 +22,12 @@ @property (nonatomic, weak) UIView *dotMarkerView; @property (nonatomic, weak) PGPhotoEditorView *fullPreviewView; @property (nonatomic, weak) UIImageView *fullPaintingView; -@property (nonatomic, weak) TGPhotoEntitiesContainerView *fullEntitiesView; +@property (nonatomic, weak) UIView *fullEntitiesView; @property (nonatomic, weak) TGMediaPickerGalleryVideoScrubber *scrubberView; @property (nonatomic, strong) id stickersContext; -- (instancetype)initWithContext:(id)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView isForum:(bool)isForum isSuggestion:(bool)isSuggestion; +- (instancetype)initWithContext:(id)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView isForum:(bool)isForum isSuggestion:(bool)isSuggestion senderName:(NSString *)senderName; - (void)setImage:(UIImage *)image; - (void)setSnapshotImage:(UIImage *)snapshotImage; diff --git a/submodules/LegacyComponents/Sources/TGPhotoAvatarPreviewController.m b/submodules/LegacyComponents/Sources/TGPhotoAvatarPreviewController.m index 83e8c2cd7a..b604b83076 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoAvatarPreviewController.m +++ b/submodules/LegacyComponents/Sources/TGPhotoAvatarPreviewController.m @@ -16,11 +16,11 @@ #import "TGMediaPickerGalleryVideoScrubber.h" #import "TGModernGalleryVideoView.h" -#import "TGPhotoEntitiesContainerView.h" + #import "TGPhotoPaintStickersContext.h" -#import "TGPhotoPaintController.h" +#import "TGPhotoDrawingController.h" const CGFloat TGPhotoAvatarPreviewPanelSize = 96.0f; const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanelSize + 40.0f; @@ -48,6 +48,7 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel TGModernButton *_cancelButton; UILabel *_titleLabel; + UILabel *_subtitleLabel; UIView *_doneButton; bool _wasPlayingBeforeCropping; @@ -56,6 +57,7 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel bool _isForum; bool _isSuggestion; + NSString *_senderName; } @property (nonatomic, weak) PGPhotoEditor *photoEditor; @@ -65,7 +67,7 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel @implementation TGPhotoAvatarPreviewController -- (instancetype)initWithContext:(id)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView isForum:(bool)isForum isSuggestion:(bool)isSuggestion { +- (instancetype)initWithContext:(id)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView isForum:(bool)isForum isSuggestion:(bool)isSuggestion senderName:(NSString *)senderName { self = [super initWithContext:context]; if (self != nil) { @@ -73,6 +75,7 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel self.previewView = previewView; _isForum = isForum; _isSuggestion = isSuggestion; + _senderName = senderName; } return self; } @@ -181,7 +184,7 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel _coverLabel.backgroundColor = [UIColor clearColor]; _coverLabel.font = TGSystemFontOfSize(14.0f); _coverLabel.textColor = [UIColor whiteColor]; - _coverLabel.text = TGLocalized(@"PhotoEditor.SelectCoverFrame"); + _coverLabel.text = _isSuggestion ? TGLocalized(@"PhotoEditor.SelectCoverFrameSuggestion") : TGLocalized(@"PhotoEditor.SelectCoverFrame"); [_coverLabel sizeToFit]; [_portraitToolsWrapperView addSubview:_coverLabel]; @@ -193,10 +196,19 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel _titleLabel.backgroundColor = [UIColor clearColor]; _titleLabel.font = TGBoldSystemFontOfSize(17.0f); _titleLabel.textColor = [UIColor whiteColor]; - _titleLabel.text = self.item.isVideo ? TGLocalized(@"PhotoEditor.SetProfileVideo") : TGLocalized(@"PhotoEditor.SetProfilePhoto"); + _titleLabel.text = self.item.isVideo ? TGLocalized(@"Conversation.SuggestedVideoTitle") : TGLocalized(@"Conversation.SuggestedPhotoTitle"); [_titleLabel sizeToFit]; [_wrapperView addSubview:_titleLabel]; + _subtitleLabel = [[UILabel alloc] init]; + _subtitleLabel.backgroundColor = [UIColor clearColor]; + _subtitleLabel.font = TGSystemFontOfSize(14.0f); + _subtitleLabel.textColor = [UIColor whiteColor]; + _subtitleLabel.numberOfLines = 2; + _subtitleLabel.textAlignment = NSTextAlignmentCenter; + _subtitleLabel.text = [NSString stringWithFormat:self.item.isVideo ? TGLocalized(@"Conversation.SuggestedVideoText") : TGLocalized(@"Conversation.SuggestedPhotoText"), _senderName]; + [_wrapperView addSubview:_subtitleLabel]; + if (!self.item.isVideo) { _cancelButton = [[TGModernButton alloc] init]; [_cancelButton setTitle:TGLocalized(@"Common.Cancel") forState:UIControlStateNormal]; @@ -369,15 +381,17 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel [_cropView animateTransitionIn]; - _cancelButton.alpha = 0.0; - _titleLabel.alpha = 0.0; - _doneButton.alpha = 0.0; + _cancelButton.alpha = 0.0f; + _titleLabel.alpha = 0.0f; + _subtitleLabel.alpha = 0.0f; + _doneButton.alpha = 0.0f; [UIView animateWithDuration:0.3f animations:^ { - _cancelButton.alpha = 1.0; - _titleLabel.alpha = 1.0; - _doneButton.alpha = 1.0; + _cancelButton.alpha = 1.0f; + _titleLabel.alpha = 1.0f; + _subtitleLabel.alpha = 1.0f; + _doneButton.alpha = 1.0f; _portraitToolsWrapperView.alpha = 1.0f; _landscapeToolsWrapperView.alpha = 1.0f; @@ -468,7 +482,7 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel if (self.switchingToTab == TGPhotoEditorPaintTab) { - containerFrame = [TGPhotoPaintController photoContainerFrameForParentViewFrame:referenceBounds toolbarLandscapeSize:self.toolbarLandscapeSize orientation:orientation panelSize:TGPhotoPaintTopPanelSize + TGPhotoPaintBottomPanelSize hasOnScreenNavigation:self.hasOnScreenNavigation]; + containerFrame = [TGPhotoDrawingController photoContainerFrameForParentViewFrame:referenceBounds toolbarLandscapeSize:self.toolbarLandscapeSize orientation:orientation panelSize:TGPhotoPaintTopPanelSize + TGPhotoPaintBottomPanelSize hasOnScreenNavigation:self.hasOnScreenNavigation]; } CGSize fittedSize = TGScaleToSize(cropRectFrame.size, containerFrame.size); @@ -551,9 +565,10 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel _landscapeToolsWrapperView.alpha = 0.0f; _dotImageView.alpha = 0.0f; _dotMarkerView.alpha = 0.0f; - _cancelButton.alpha = 0.0; - _titleLabel.alpha = 0.0; - _doneButton.alpha = 0.0; + _cancelButton.alpha = 0.0f; + _titleLabel.alpha = 0.0f; + _subtitleLabel.alpha = 0.0f; + _doneButton.alpha = 0.0f; } completion:^(__unused BOOL finished) { if (!switching) { @@ -670,8 +685,9 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel _landscapeToolsWrapperView.alpha = 0.0f; _dotImageView.alpha = 0.0f; _titleLabel.alpha = 0.0f; + _subtitleLabel.alpha = 0.0f; _cancelButton.alpha = 0.0f; - _doneButton.alpha = 0.0; + _doneButton.alpha = 0.0f; } completion:nil]; } @@ -804,6 +820,8 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel CGSize buttonSize = CGSizeMake(MIN(referenceSize.width, referenceSize.height) - 16.0 * 2.0, 50.0f); [_doneButton updateWidth:buttonSize.width]; + CGSize subtitleSize = [_subtitleLabel sizeThatFits:CGSizeMake(referenceSize.width - 48.0, referenceSize.height)]; + switch (orientation) { case UIInterfaceOrientationLandscapeLeft: @@ -820,6 +838,7 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel _portraitToolsWrapperView.frame = CGRectMake((screenSide - referenceSize.width) / 2, screenSide - panelToolbarPortraitSize, referenceSize.width, panelToolbarPortraitSize); _titleLabel.frame = CGRectMake(screenEdges.left + floor((referenceSize.width - _titleLabel.frame.size.width) / 2.0), 0.0, _titleLabel.frame.size.width, _titleLabel.frame.size.height); + _subtitleLabel.frame = CGRectMake(screenEdges.left + floor((referenceSize.width - _subtitleLabel.frame.size.width) / 2.0), screenEdges.bottom + safeAreaInset.bottom, subtitleSize.width, subtitleSize.height); _cancelButton.frame = CGRectMake(-_cancelButton.frame.size.width, screenEdges.top + floor((44.0 - _cancelButton.frame.size.height) / 2.0), _cancelButton.frame.size.width, _cancelButton.frame.size.height); @@ -841,6 +860,7 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel _portraitToolsWrapperView.frame = CGRectMake((screenSide - referenceSize.width) / 2, screenSide - panelToolbarPortraitSize, referenceSize.width, panelToolbarPortraitSize); _titleLabel.frame = CGRectMake(screenEdges.left + floor((referenceSize.width - _titleLabel.frame.size.width) / 2.0), 0.0, _titleLabel.frame.size.width, _titleLabel.frame.size.height); + _subtitleLabel.frame = CGRectMake(screenEdges.left + floor((referenceSize.width - _subtitleLabel.frame.size.width) / 2.0), screenEdges.bottom + safeAreaInset.bottom, subtitleSize.width, subtitleSize.height); _cancelButton.frame = CGRectMake(-_cancelButton.frame.size.width, screenEdges.top + floor((44.0 - _cancelButton.frame.size.height) / 2.0), _cancelButton.frame.size.width, _cancelButton.frame.size.height); @@ -867,6 +887,7 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel _coverLabel.frame = CGRectMake(floor((_portraitToolsWrapperView.frame.size.width - _coverLabel.frame.size.width) / 2.0), CGRectGetMaxY(_scrubberView.frame) + 6.0, _coverLabel.frame.size.width, _coverLabel.frame.size.height); _titleLabel.frame = CGRectMake(screenEdges.left + floor((referenceSize.width - _titleLabel.frame.size.width) / 2.0), screenEdges.top + floor((44.0 - _titleLabel.frame.size.height) / 2.0), _titleLabel.frame.size.width, _titleLabel.frame.size.height); + _subtitleLabel.frame = CGRectMake(screenEdges.left + floor((referenceSize.width - _subtitleLabel.frame.size.width) / 2.0), screenEdges.bottom - 56.0 - buttonSize.height - subtitleSize.height - 8.0, subtitleSize.width, subtitleSize.height); _cancelButton.frame = CGRectMake(screenEdges.left + 16.0, screenEdges.top + floor((44.0 - _cancelButton.frame.size.height) / 2.0), _cancelButton.frame.size.width, _cancelButton.frame.size.height); diff --git a/submodules/LegacyComponents/Sources/TGPhotoDrawingController.h b/submodules/LegacyComponents/Sources/TGPhotoDrawingController.h index a0cd0459b3..eb000034f6 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoDrawingController.h +++ b/submodules/LegacyComponents/Sources/TGPhotoDrawingController.h @@ -12,8 +12,18 @@ @property (nonatomic, copy) void (^requestDismiss)(void); @property (nonatomic, copy) void (^requestApply)(void); -- (instancetype)initWithContext:(id)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView entitiesView:(TGPhotoEntitiesContainerView *)entitiesView stickersContext:(id)stickersContext; +- (instancetype)initWithContext:(id)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView entitiesView:(UIView *)entitiesView stickersContext:(id)stickersContext; - (TGPaintingData *)paintingData; ++ (CGRect)photoContainerFrameForParentViewFrame:(CGRect)parentViewFrame toolbarLandscapeSize:(CGFloat)toolbarLandscapeSize orientation:(UIInterfaceOrientation)orientation panelSize:(CGFloat)panelSize hasOnScreenNavigation:(bool)hasOnScreenNavigation; + ++ (CGSize)fittedContentSize:(CGRect)cropRect orientation:(UIImageOrientation)orientation originalSize:(CGSize)originalSize; ++ (CGRect)fittedCropRect:(CGRect)cropRect originalSize:(CGSize)originalSize keepOriginalSize:(bool)originalSize; ++ (CGPoint)fittedCropRect:(CGRect)cropRect centerScale:(CGFloat)scale; ++ (CGSize)maximumPaintingSize; + @end + +extern const CGFloat TGPhotoPaintTopPanelSize; +extern const CGFloat TGPhotoPaintBottomPanelSize; diff --git a/submodules/LegacyComponents/Sources/TGPhotoDrawingController.m b/submodules/LegacyComponents/Sources/TGPhotoDrawingController.m index 4c956f2500..9a8924f8de 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoDrawingController.m +++ b/submodules/LegacyComponents/Sources/TGPhotoDrawingController.m @@ -1,5 +1,4 @@ #import "TGPhotoDrawingController.h" -#import "TGPhotoPaintController.h" #import "LegacyComponentsInternal.h" @@ -21,17 +20,18 @@ #import "TGPaintingWrapperView.h" #import "TGPhotoEditorSparseView.h" -#import "TGPhotoEntitiesContainerView.h" #import "PGPhotoEditor.h" #import "TGPhotoEditorPreviewView.h" - - - #import "TGPaintCanvas.h" #import "TGPainting.h" +const CGFloat TGPhotoPaintTopPanelSize = 44.0f; +const CGFloat TGPhotoPaintBottomPanelSize = 79.0f; +const CGSize TGPhotoPaintingLightMaxSize = { 1280.0f, 1280.0f }; +const CGSize TGPhotoPaintingMaxSize = { 2560.0f, 2560.0f }; + @interface TGPhotoDrawingController () { id _context; @@ -76,7 +76,7 @@ @implementation TGPhotoDrawingController -- (instancetype)initWithContext:(id)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView entitiesView:(TGPhotoEntitiesContainerView *)entitiesView stickersContext:(id)stickersContext +- (instancetype)initWithContext:(id)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView entitiesView:(UIView *)entitiesView stickersContext:(id)stickersContext { self = [super initWithContext:context]; if (self != nil) @@ -84,8 +84,8 @@ _context = context; _stickersContext = stickersContext; - CGSize size = TGScaleToSize(photoEditor.originalSize, [TGPhotoPaintController maximumPaintingSize]); - _drawingAdapter = [_stickersContext drawingAdapter:size]; + CGSize size = TGScaleToSize(photoEditor.originalSize, [TGPhotoDrawingController maximumPaintingSize]); + _drawingAdapter = [_stickersContext drawingAdapter:size originalSize:photoEditor.originalSize]; _interfaceController = (UIViewController *)_drawingAdapter.interfaceController; __weak TGPhotoDrawingController *weakSelf = self; @@ -218,6 +218,16 @@ [self.view setNeedsLayout]; } +- (void)viewDidLoad +{ + [super viewDidLoad]; + + PGPhotoEditor *photoEditor = _photoEditor; + if (!_skipEntitiesSetup) { + [_entitiesView setupWithEntitiesData:photoEditor.paintingData.entitiesData]; + } +} + - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; @@ -292,11 +302,11 @@ - (CGSize)fittedContentSize { - return [TGPhotoPaintController fittedContentSize:_photoEditor.cropRect orientation:_photoEditor.cropOrientation originalSize:_photoEditor.originalSize]; + return [TGPhotoDrawingController fittedContentSize:_photoEditor.cropRect orientation:_photoEditor.cropOrientation originalSize:_photoEditor.originalSize]; } + (CGSize)fittedContentSize:(CGRect)cropRect orientation:(UIImageOrientation)orientation originalSize:(CGSize)originalSize { - CGSize fittedOriginalSize = TGScaleToSize(originalSize, [TGPhotoPaintController maximumPaintingSize]); + CGSize fittedOriginalSize = TGScaleToSize(originalSize, [TGPhotoDrawingController maximumPaintingSize]); CGFloat scale = fittedOriginalSize.width / originalSize.width; CGSize size = CGSizeMake(cropRect.size.width * scale, cropRect.size.height * scale); @@ -308,11 +318,11 @@ - (CGRect)fittedCropRect:(bool)originalSize { - return [TGPhotoPaintController fittedCropRect:_photoEditor.cropRect originalSize:_photoEditor.originalSize keepOriginalSize:originalSize]; + return [TGPhotoDrawingController fittedCropRect:_photoEditor.cropRect originalSize:_photoEditor.originalSize keepOriginalSize:originalSize]; } + (CGRect)fittedCropRect:(CGRect)cropRect originalSize:(CGSize)originalSize keepOriginalSize:(bool)keepOriginalSize { - CGSize fittedOriginalSize = TGScaleToSize(originalSize, [TGPhotoPaintController maximumPaintingSize]); + CGSize fittedOriginalSize = TGScaleToSize(originalSize, [TGPhotoDrawingController maximumPaintingSize]); CGFloat scale = fittedOriginalSize.width / originalSize.width; CGSize size = fittedOriginalSize; @@ -324,7 +334,7 @@ - (CGPoint)fittedCropCenterScale:(CGFloat)scale { - return [TGPhotoPaintController fittedCropRect:_photoEditor.cropRect centerScale:scale]; + return [TGPhotoDrawingController fittedCropRect:_photoEditor.cropRect centerScale:scale]; } + (CGPoint)fittedCropRect:(CGRect)cropRect centerScale:(CGFloat)scale @@ -488,7 +498,7 @@ - (CGRect)_targetFrameForTransitionInFromFrame:(CGRect)fromFrame { CGSize referenceSize = [self referenceViewSize]; - CGRect containerFrame = [TGPhotoPaintController photoContainerFrameForParentViewFrame:CGRectMake(0, 0, referenceSize.width, referenceSize.height) toolbarLandscapeSize:self.toolbarLandscapeSize orientation:self.effectiveOrientation panelSize:TGPhotoPaintTopPanelSize + TGPhotoPaintBottomPanelSize hasOnScreenNavigation:self.hasOnScreenNavigation]; + CGRect containerFrame = [TGPhotoDrawingController photoContainerFrameForParentViewFrame:CGRectMake(0, 0, referenceSize.width, referenceSize.height) toolbarLandscapeSize:self.toolbarLandscapeSize orientation:self.effectiveOrientation panelSize:TGPhotoPaintTopPanelSize + TGPhotoPaintBottomPanelSize hasOnScreenNavigation:self.hasOnScreenNavigation]; CGSize fittedSize = TGScaleToSize(fromFrame.size, containerFrame.size); CGRect toFrame = CGRectMake(containerFrame.origin.x + (containerFrame.size.width - fittedSize.width) / 2, containerFrame.origin.y + (containerFrame.size.height - fittedSize.height) / 2, fittedSize.width, fittedSize.height); @@ -518,7 +528,7 @@ _entitiesView.frame = CGRectMake(0, 0, rect.size.width, rect.size.height); _entitiesView.transform = CGAffineTransformMakeRotation(_photoEditor.cropRotation); - CGSize fittedOriginalSize = TGScaleToSize(_photoEditor.originalSize, [TGPhotoPaintController maximumPaintingSize]); + CGSize fittedOriginalSize = TGScaleToSize(_photoEditor.originalSize, [TGPhotoDrawingController maximumPaintingSize]); CGSize rotatedSize = TGRotatedContentSize(fittedOriginalSize, _photoEditor.cropRotation); CGPoint centerPoint = CGPointMake(rotatedSize.width / 2.0f, rotatedSize.height / 2.0f); @@ -554,7 +564,7 @@ - (CGRect)transitionOutSourceFrameForReferenceFrame:(CGRect)referenceFrame orientation:(UIInterfaceOrientation)orientation { - CGRect containerFrame = [TGPhotoPaintController photoContainerFrameForParentViewFrame:self.view.frame toolbarLandscapeSize:self.toolbarLandscapeSize orientation:orientation panelSize:TGPhotoPaintTopPanelSize + TGPhotoPaintBottomPanelSize hasOnScreenNavigation:self.hasOnScreenNavigation]; + CGRect containerFrame = [TGPhotoDrawingController photoContainerFrameForParentViewFrame:self.view.frame toolbarLandscapeSize:self.toolbarLandscapeSize orientation:orientation panelSize:TGPhotoPaintTopPanelSize + TGPhotoPaintBottomPanelSize hasOnScreenNavigation:self.hasOnScreenNavigation]; CGSize fittedSize = TGScaleToSize(referenceFrame.size, containerFrame.size); return CGRectMake(containerFrame.origin.x + (containerFrame.size.width - fittedSize.width) / 2, containerFrame.origin.y + (containerFrame.size.height - fittedSize.height) / 2, fittedSize.width, fittedSize.height); @@ -571,7 +581,7 @@ [previewView prepareForTransitionOut]; UIInterfaceOrientation orientation = self.effectiveOrientation; - CGRect containerFrame = [TGPhotoPaintController photoContainerFrameForParentViewFrame:self.view.frame toolbarLandscapeSize:self.toolbarLandscapeSize orientation:orientation panelSize:TGPhotoPaintTopPanelSize + TGPhotoPaintBottomPanelSize hasOnScreenNavigation:self.hasOnScreenNavigation]; + CGRect containerFrame = [TGPhotoDrawingController photoContainerFrameForParentViewFrame:self.view.frame toolbarLandscapeSize:self.toolbarLandscapeSize orientation:orientation panelSize:TGPhotoPaintTopPanelSize + TGPhotoPaintBottomPanelSize hasOnScreenNavigation:self.hasOnScreenNavigation]; CGRect referenceFrame = CGRectMake(0, 0, self.photoEditor.rotatedCropSize.width, self.photoEditor.rotatedCropSize.height); CGRect rect = CGRectOffset([self transitionOutSourceFrameForReferenceFrame:referenceFrame orientation:orientation], -containerFrame.origin.x, -containerFrame.origin.y); previewView.frame = rect; @@ -745,7 +755,7 @@ CGSize referenceSize = [self referenceViewSize]; CGFloat screenSide = MAX(referenceSize.width, referenceSize.height) + 2 * TGPhotoPaintBottomPanelSize; - CGRect containerFrame = [TGPhotoPaintController photoContainerFrameForParentViewFrame:CGRectMake(0, 0, referenceSize.width, referenceSize.height) toolbarLandscapeSize:self.toolbarLandscapeSize orientation:self.effectiveOrientation panelSize:TGPhotoPaintTopPanelSize + TGPhotoPaintBottomPanelSize hasOnScreenNavigation:self.hasOnScreenNavigation]; + CGRect containerFrame = [TGPhotoDrawingController photoContainerFrameForParentViewFrame:CGRectMake(0, 0, referenceSize.width, referenceSize.height) toolbarLandscapeSize:self.toolbarLandscapeSize orientation:self.effectiveOrientation panelSize:TGPhotoPaintTopPanelSize + TGPhotoPaintBottomPanelSize hasOnScreenNavigation:self.hasOnScreenNavigation]; CGFloat topInset = [self controllerStatusBarHeight] + 31.0; CGFloat visibleArea = self.view.frame.size.height - _keyboardHeight - topInset; @@ -800,7 +810,7 @@ screenEdges.bottom -= safeAreaInset.bottom; screenEdges.right -= safeAreaInset.right; - CGRect containerFrame = [TGPhotoPaintController photoContainerFrameForParentViewFrame:CGRectMake(0, 0, referenceSize.width, referenceSize.height) toolbarLandscapeSize:self.toolbarLandscapeSize orientation:orientation panelSize:TGPhotoPaintTopPanelSize + TGPhotoPaintBottomPanelSize hasOnScreenNavigation:self.hasOnScreenNavigation]; + CGRect containerFrame = [TGPhotoDrawingController photoContainerFrameForParentViewFrame:CGRectMake(0, 0, referenceSize.width, referenceSize.height) toolbarLandscapeSize:self.toolbarLandscapeSize orientation:orientation panelSize:TGPhotoPaintTopPanelSize + TGPhotoPaintBottomPanelSize hasOnScreenNavigation:self.hasOnScreenNavigation]; PGPhotoEditor *photoEditor = self.photoEditor; TGPhotoEditorPreviewView *previewView = self.previewView; @@ -880,4 +890,19 @@ return UIRectEdgeTop | UIRectEdgeBottom; } ++ (CGSize)maximumPaintingSize +{ + static dispatch_once_t onceToken; + static CGSize size; + dispatch_once(&onceToken, ^ + { + CGSize screenSize = TGScreenSize(); + if ((NSInteger)screenSize.height == 480) + size = TGPhotoPaintingLightMaxSize; + else + size = TGPhotoPaintingMaxSize; + }); + return size; +} + @end diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorController.m b/submodules/LegacyComponents/Sources/TGPhotoEditorController.m index 199e6f620d..b3f2e2afbd 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEditorController.m +++ b/submodules/LegacyComponents/Sources/TGPhotoEditorController.m @@ -29,7 +29,6 @@ #import "TGPhotoToolbarView.h" #import "TGPhotoEditorPreviewView.h" -#import "TGPhotoEntitiesContainerView.h" #import @@ -38,7 +37,6 @@ #import "TGPhotoCropController.h" #import "TGPhotoToolsController.h" -#import "TGPhotoPaintController.h" #import "TGPhotoDrawingController.h" #import "TGPhotoQualityController.h" #import "TGPhotoAvatarPreviewController.h" @@ -71,7 +69,7 @@ TGPhotoToolbarView *_landscapeToolbarView; TGPhotoEditorPreviewView *_previewView; PGPhotoEditorView *_fullPreviewView; - TGPhotoEntitiesContainerView *_fullEntitiesView; + UIView *_fullEntitiesView; UIImageView *_fullPaintingView; PGPhotoEditor *_photoEditor; @@ -288,10 +286,7 @@ case TGPhotoEditorPaintTab: case TGPhotoEditorEraserTab: - if ([strongSelf->_currentTabController isKindOfClass:[TGPhotoPaintController class]]) - [strongSelf->_currentTabController handleTabAction:tab]; - else - [strongSelf presentTab:TGPhotoEditorPaintTab]; + [strongSelf presentTab:TGPhotoEditorPaintTab]; break; case TGPhotoEditorStickerTab: @@ -353,9 +348,9 @@ _fullPaintingView = [[UIImageView alloc] init]; _fullPaintingView.frame = _fullPreviewView.frame; - _fullEntitiesView = [[TGPhotoEntitiesContainerView alloc] init]; + _fullEntitiesView = [_stickersContext drawingEntitiesViewWithSize:CGSizeMake(0, 0)]; _fullEntitiesView.userInteractionEnabled = false; - CGRect rect = [TGPhotoPaintController fittedCropRect:_photoEditor.cropRect originalSize:_photoEditor.originalSize keepOriginalSize:true]; + CGRect rect = [TGPhotoDrawingController fittedCropRect:_photoEditor.cropRect originalSize:_photoEditor.originalSize keepOriginalSize:true]; _fullEntitiesView.frame = CGRectMake(0, 0, rect.size.width, rect.size.height); } @@ -1341,7 +1336,7 @@ { bool skipInitialTransition = (![self presentedFromCamera] && self.navigationController != nil) || self.skipInitialTransition; - TGPhotoAvatarPreviewController *cropController = [[TGPhotoAvatarPreviewController alloc] initWithContext:_context photoEditor:_photoEditor previewView:_previewView isForum:[self presentedForForumAvatarCreation] isSuggestion:[self presentedForSuggestedAvatar]]; + TGPhotoAvatarPreviewController *cropController = [[TGPhotoAvatarPreviewController alloc] initWithContext:_context photoEditor:_photoEditor previewView:_previewView isForum:[self presentedForForumAvatarCreation] isSuggestion:[self presentedForSuggestedAvatar] senderName:self.senderName]; cropController.stickersContext = _stickersContext; cropController.scrubberView = _scrubberView; cropController.dotImageView = _dotImageView; diff --git a/submodules/LegacyComponents/Sources/TGPhotoEntitiesContainerView.m b/submodules/LegacyComponents/Sources/TGPhotoEntitiesContainerView.m index b0c3d3d654..07f3839f8e 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEntitiesContainerView.m +++ b/submodules/LegacyComponents/Sources/TGPhotoEntitiesContainerView.m @@ -173,10 +173,10 @@ - (void)setupWithPaintingData:(TGPaintingData *)paintingData { [self removeAll]; - for (TGPhotoPaintEntity *entity in paintingData.entities) { - UIView * entityView = [self createEntityViewWithEntity:entity]; - [self addSubview:entityView]; - } +// for (TGPhotoPaintEntity *entity in paintingData.entities) { +// UIView * entityView = [self createEntityViewWithEntity:entity]; +// [self addSubview:entityView]; +// } } - (TGPhotoPaintEntityView *)createEntityViewWithEntity:(TGPhotoPaintEntity *)entity { diff --git a/submodules/LegacyComponents/Sources/TGPhotoPaintController.h b/submodules/LegacyComponents/Sources/TGPhotoPaintController.h index 7cc96e30a0..341eae634a 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoPaintController.h +++ b/submodules/LegacyComponents/Sources/TGPhotoPaintController.h @@ -1,28 +1,28 @@ -#import "TGPhotoEditorTabController.h" - -#import - -@class PGPhotoEditor; -@class TGPhotoEditorPreviewView; - -@protocol TGPhotoPaintStickersContext; - -@interface TGPhotoPaintController : TGPhotoEditorTabController - -@property (nonatomic, strong) id stickersContext; - -- (instancetype)initWithContext:(id)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView entitiesView:(TGPhotoEntitiesContainerView *)entitiesView; - -- (TGPaintingData *)paintingData; - -+ (CGRect)photoContainerFrameForParentViewFrame:(CGRect)parentViewFrame toolbarLandscapeSize:(CGFloat)toolbarLandscapeSize orientation:(UIInterfaceOrientation)orientation panelSize:(CGFloat)panelSize hasOnScreenNavigation:(bool)hasOnScreenNavigation; - -+ (CGSize)fittedContentSize:(CGRect)cropRect orientation:(UIImageOrientation)orientation originalSize:(CGSize)originalSize; -+ (CGRect)fittedCropRect:(CGRect)cropRect originalSize:(CGSize)originalSize keepOriginalSize:(bool)originalSize; -+ (CGPoint)fittedCropRect:(CGRect)cropRect centerScale:(CGFloat)scale; -+ (CGSize)maximumPaintingSize; - -@end - -extern const CGFloat TGPhotoPaintTopPanelSize; -extern const CGFloat TGPhotoPaintBottomPanelSize; +//#import "TGPhotoEditorTabController.h" +// +//#import +// +//@class PGPhotoEditor; +//@class TGPhotoEditorPreviewView; +// +//@protocol TGPhotoPaintStickersContext; +// +//@interface TGPhotoPaintController : TGPhotoEditorTabController +// +//@property (nonatomic, strong) id stickersContext; +// +//- (instancetype)initWithContext:(id)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView entitiesView:(TGPhotoEntitiesContainerView *)entitiesView; +// +//- (TGPaintingData *)paintingData; +// +//+ (CGRect)photoContainerFrameForParentViewFrame:(CGRect)parentViewFrame toolbarLandscapeSize:(CGFloat)toolbarLandscapeSize orientation:(UIInterfaceOrientation)orientation panelSize:(CGFloat)panelSize hasOnScreenNavigation:(bool)hasOnScreenNavigation; +// +//+ (CGSize)fittedContentSize:(CGRect)cropRect orientation:(UIImageOrientation)orientation originalSize:(CGSize)originalSize; +//+ (CGRect)fittedCropRect:(CGRect)cropRect originalSize:(CGSize)originalSize keepOriginalSize:(bool)originalSize; +//+ (CGPoint)fittedCropRect:(CGRect)cropRect centerScale:(CGFloat)scale; +//+ (CGSize)maximumPaintingSize; +// +//@end +// +//extern const CGFloat TGPhotoPaintTopPanelSize; +//extern const CGFloat TGPhotoPaintBottomPanelSize; diff --git a/submodules/LegacyComponents/Sources/TGPhotoPaintController.m b/submodules/LegacyComponents/Sources/TGPhotoPaintController.m index 18333b75ee..862a34590b 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoPaintController.m +++ b/submodules/LegacyComponents/Sources/TGPhotoPaintController.m @@ -1,2634 +1,2631 @@ -#import "TGPhotoPaintController.h" - -#import "LegacyComponentsInternal.h" - -#import - -#import -#import -#import -#import "TGPhotoEditorInterfaceAssets.h" -#import - -#import -#import - -#import "TGMenuSheetController.h" - -#import -#import - -#import "TGPainting.h" -#import -#import "TGPaintRadialBrush.h" -#import "TGPaintEllipticalBrush.h" -#import "TGPaintNeonBrush.h" -#import "TGPaintArrowBrush.h" -#import "TGPaintCanvas.h" -#import "TGPaintingWrapperView.h" -#import "TGPaintState.h" -#import "TGPaintBrushPreview.h" -#import "TGPaintSwatch.h" -#import "TGPhotoPaintFont.h" -#import - -#import "PGPhotoEditor.h" -#import "TGPhotoEditorPreviewView.h" - -#import "TGPhotoPaintActionsView.h" -#import "TGPhotoPaintSettingsView.h" - -#import "TGPhotoPaintSettingsWrapperView.h" -#import "TGPhotoBrushSettingsView.h" -#import "TGPhotoTextSettingsView.h" - -#import "TGPhotoPaintSelectionContainerView.h" -#import "TGPhotoEntitiesContainerView.h" -#import "TGPhotoStickerEntityView.h" -#import "TGPhotoTextEntityView.h" -#import "TGPhotoPaintEyedropperView.h" - -#import "TGPaintFaceDetector.h" -#import "TGPhotoMaskPosition.h" - -const CGFloat TGPhotoPaintTopPanelSize = 44.0f; -const CGFloat TGPhotoPaintBottomPanelSize = 79.0f; -const CGSize TGPhotoPaintingLightMaxSize = { 1280.0f, 1280.0f }; -const CGSize TGPhotoPaintingMaxSize = { 2560.0f, 2560.0f }; - -const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; - -@interface TGPhotoPaintController () -{ - TGPaintUndoManager *_undoManager; - TGObserverProxy *_keyboardWillChangeFrameProxy; - CGFloat _keyboardHeight; - - TGModernGalleryZoomableScrollView *_scrollView; - UIView *_scrollContentView; - - UIButton *_containerView; - TGPhotoEditorSparseView *_wrapperView; - UIView *_portraitToolsWrapperView; - UIView *_landscapeToolsWrapperView; - - UIPinchGestureRecognizer *_pinchGestureRecognizer; - UIRotationGestureRecognizer *_rotationGestureRecognizer; - - NSArray *_brushes; - TGPainting *_painting; - TGPaintCanvas *_canvasView; - TGPaintBrushPreview *_brushPreview; - - CGSize _previousSize; - UIView *_contentView; - UIView *_contentWrapperView; - - UIView *_dimView; - TGModernButton *_doneButton; - - TGPhotoPaintActionsView *_landscapeActionsView; - TGPhotoPaintActionsView *_portraitActionsView; - - TGPhotoPaintSettingsView *_portraitSettingsView; - TGPhotoPaintSettingsView *_landscapeSettingsView; - - TGPhotoPaintSettingsWrapperView *_settingsViewWrapper; - UIView *_settingsView; - id _stickersScreen; - - double _stickerStartTime; - - bool _appeared; - bool _skipEntitiesSetup; - bool _entitiesReady; - - TGPhotoPaintFont *_selectedTextFont; - TGPhotoPaintTextEntityStyle _selectedTextStyle; - - TGPhotoEntitiesContainerView *_entitiesContainerView; - TGPhotoPaintEntityView *_currentEntityView; - - TGPhotoPaintSelectionContainerView *_selectionContainerView; - TGPhotoPaintEntitySelectionView *_entitySelectionView; - TGPhotoPaintEyedropperView *_eyedropperView; - - TGPhotoTextEntityView *_editedTextView; - CGPoint _editedTextCenter; - CGAffineTransform _editedTextTransform; - UIButton *_textEditingDismissButton; - - TGMenuContainerView *_menuContainerView; - - TGPaintingData *_resultData; - - TGPaintingWrapperView *_paintingWrapperView; - - bool _enableStickers; - - NSData *_eyedropperBackgroundData; - CGSize _eyedropperBackgroundSize; - NSInteger _eyedropperBackgroundBytesPerRow; - CGBitmapInfo _eyedropperBackgroundInfo; - - id _context; -} - -@property (nonatomic, strong) ASHandle *actionHandle; - -@property (nonatomic, weak) PGPhotoEditor *photoEditor; -@property (nonatomic, weak) TGPhotoEditorPreviewView *previewView; - -@end - -@implementation TGPhotoPaintController - -- (instancetype)initWithContext:(id)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView entitiesView:(TGPhotoEntitiesContainerView *)entitiesView -{ - self = [super initWithContext:context]; - if (self != nil) - { - _context = context; - _enableStickers = photoEditor.enableStickers; - - _stickerStartTime = NAN; - - _actionHandle = [[ASHandle alloc] initWithDelegate:self releaseOnMainThread:true]; - - self.photoEditor = photoEditor; - self.previewView = previewView; - _entitiesContainerView = entitiesView; - if (entitiesView != nil) { - _skipEntitiesSetup = true; - } - entitiesView.userInteractionEnabled = true; - - _brushes = @ - [ - [[TGPaintRadialBrush alloc] init], - [[TGPaintEllipticalBrush alloc] init], - [[TGPaintNeonBrush alloc] init], - [[TGPaintArrowBrush alloc] init], - ]; - _selectedTextFont = [[TGPhotoPaintFont availableFonts] firstObject]; - _selectedTextStyle = TGPhotoPaintTextEntityStyleFramed; - - if (_photoEditor.paintingData.undoManager != nil) - _undoManager = [_photoEditor.paintingData.undoManager copy]; - else - _undoManager = [[TGPaintUndoManager alloc] init]; - - CGSize size = TGScaleToSize(photoEditor.originalSize, [TGPhotoPaintController maximumPaintingSize]); - _painting = [[TGPainting alloc] initWithSize:size undoManager:_undoManager imageData:[_photoEditor.paintingData data]]; - _undoManager.painting = _painting; - - _keyboardWillChangeFrameProxy = [[TGObserverProxy alloc] initWithTarget:self targetSelector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification]; - } - return self; -} - -- (void)dealloc -{ - [_actionHandle reset]; -} - -- (void)loadView -{ - [super loadView]; - self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - - _scrollView = [[TGModernGalleryZoomableScrollView alloc] initWithFrame:self.view.bounds hasDoubleTap:false]; - if (@available(iOS 11.0, *)) { - _scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; - } - _scrollView.contentInset = UIEdgeInsetsZero; - _scrollView.delegate = self; - _scrollView.showsHorizontalScrollIndicator = false; - _scrollView.showsVerticalScrollIndicator = false; - [self.view addSubview:_scrollView]; - - _scrollContentView = [[UIView alloc] initWithFrame:self.view.bounds]; - [_scrollView addSubview:_scrollContentView]; - - _containerView = [[UIButton alloc] initWithFrame:self.view.bounds]; - _containerView.clipsToBounds = true; - [_containerView addTarget:self action:@selector(containerPressed) forControlEvents:UIControlEventTouchUpInside]; - [_scrollContentView addSubview:_containerView]; - - _pinchGestureRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinch:)]; - _pinchGestureRecognizer.delegate = self; - [_containerView addGestureRecognizer:_pinchGestureRecognizer]; - - _rotationGestureRecognizer = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(handleRotate:)]; - _rotationGestureRecognizer.delegate = self; - [_containerView addGestureRecognizer:_rotationGestureRecognizer]; - - TGPhotoEditorPreviewView *previewView = _previewView; - previewView.userInteractionEnabled = false; - previewView.hidden = true; - - __weak TGPhotoPaintController *weakSelf = self; - _paintingWrapperView = [[TGPaintingWrapperView alloc] init]; - _paintingWrapperView.clipsToBounds = true; - _paintingWrapperView.shouldReceiveTouch = ^bool - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf == nil) - return false; - - return (strongSelf->_editedTextView == nil); - }; - [_containerView addSubview:_paintingWrapperView]; - - _contentView = [[UIView alloc] init]; - _contentView.clipsToBounds = true; - _contentView.userInteractionEnabled = false; - [_containerView addSubview:_contentView]; - - _contentWrapperView = [[UIView alloc] init]; - _contentWrapperView.userInteractionEnabled = false; - [_contentView addSubview:_contentWrapperView]; - - if (_entitiesContainerView == nil) { - _entitiesContainerView = [[TGPhotoEntitiesContainerView alloc] init]; - _entitiesContainerView.clipsToBounds = true; - _entitiesContainerView.stickersContext = _stickersContext; - } - _entitiesContainerView.entitySelected = ^(TGPhotoPaintEntityView *sender) - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - [strongSelf selectEntityView:sender]; - }; - _entitiesContainerView.entityRemoved = ^(TGPhotoPaintEntityView *entity) - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - if (entity == strongSelf->_currentEntityView) - [strongSelf _clearCurrentSelection]; - - [strongSelf updateSettingsButton]; - }; - if (!_skipEntitiesSetup) { - [_contentWrapperView addSubview:_entitiesContainerView]; - } - _undoManager.entitiesContainer = _entitiesContainerView; - - _dimView = [[UIView alloc] init]; - _dimView.alpha = 0.0f; - _dimView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - _dimView.backgroundColor = UIColorRGBA(0x000000, 0.4f); - _dimView.userInteractionEnabled = false; - [_entitiesContainerView addSubview:_dimView]; - - _selectionContainerView = [[TGPhotoPaintSelectionContainerView alloc] init]; - _selectionContainerView.clipsToBounds = false; - [_containerView addSubview:_selectionContainerView]; - - _eyedropperView = [[TGPhotoPaintEyedropperView alloc] init]; - _eyedropperView.locationChanged = ^(CGPoint location, bool finished) { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf != nil) - { - UIColor *color = [strongSelf colorAtPoint:location]; - strongSelf->_eyedropperView.color = color; - - if (finished) { - TGPaintSwatch *swatch = [TGPaintSwatch swatchWithColor:color colorLocation:0.5 brushWeight:strongSelf->_portraitSettingsView.swatch.brushWeight]; - [strongSelf setCurrentSwatch:swatch sender:nil]; - - [strongSelf commitEyedropper:false]; - } - } - }; - _eyedropperView.hidden = true; - [_selectionContainerView addSubview:_eyedropperView]; - - _wrapperView = [[TGPhotoEditorSparseView alloc] initWithFrame:CGRectZero]; - [self.view addSubview:_wrapperView]; - - _portraitToolsWrapperView = [[UIView alloc] initWithFrame:CGRectZero]; - _portraitToolsWrapperView.alpha = 0.0f; - [_wrapperView addSubview:_portraitToolsWrapperView]; - - _landscapeToolsWrapperView = [[UIView alloc] initWithFrame:CGRectZero]; - _landscapeToolsWrapperView.alpha = 0.0f; - [_wrapperView addSubview:_landscapeToolsWrapperView]; - - void (^undoPressed)(void) = ^ - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf != nil) - [strongSelf->_undoManager undo]; - }; - - void (^clearPressed)(UIView *) = ^(UIView *sender) - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf != nil) - [strongSelf presentClearAllAlert:sender]; - }; - - _portraitActionsView = [[TGPhotoPaintActionsView alloc] init]; - _portraitActionsView.alpha = 0.0f; - _portraitActionsView.undoPressed = undoPressed; - _portraitActionsView.clearPressed = clearPressed; - [_wrapperView addSubview:_portraitActionsView]; - - _landscapeActionsView = [[TGPhotoPaintActionsView alloc] init]; - _landscapeActionsView.alpha = 0.0f; - _landscapeActionsView.undoPressed = undoPressed; - _landscapeActionsView.clearPressed = clearPressed; - [_wrapperView addSubview:_landscapeActionsView]; - - _doneButton = [[TGModernButton alloc] init]; - _doneButton.alpha = 0.0f; - _doneButton.userInteractionEnabled = false; - [_doneButton setTitle:TGLocalized(@"Common.Done") forState:UIControlStateNormal]; - _doneButton.titleLabel.font = TGSystemFontOfSize(17.0); - [_doneButton sizeToFit]; -// [_wrapperView addSubview:_doneButton]; - - void (^settingsPressed)(void) = ^ - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - [strongSelf commitEyedropper:true]; - - if ([strongSelf->_currentEntityView isKindOfClass:[TGPhotoTextEntityView class]]) - [strongSelf presentTextSettingsView]; - else if ([strongSelf->_currentEntityView isKindOfClass:[TGPhotoStickerEntityView class]]) - [strongSelf mirrorSelectedStickerEntity]; - else - [strongSelf presentBrushSettingsView]; - }; - - void (^eyedropperPressed)(void) = ^ - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - [self enableEyedropper]; - }; - - void (^beganColorPicking)(void) = ^ - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - [strongSelf commitEyedropper:true]; - - if (![strongSelf->_currentEntityView isKindOfClass:[TGPhotoTextEntityView class]]) - [strongSelf setDimHidden:false animated:true]; - }; - - void (^changedColor)(TGPhotoPaintSettingsView *, TGPaintSwatch *) = ^(TGPhotoPaintSettingsView *sender, TGPaintSwatch *swatch) - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - [strongSelf setCurrentSwatch:swatch sender:sender]; - }; - - void (^finishedColorPicking)(TGPhotoPaintSettingsView *, TGPaintSwatch *) = ^(TGPhotoPaintSettingsView *sender, TGPaintSwatch *swatch) - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - [strongSelf commitEyedropper:true]; - - [strongSelf setCurrentSwatch:swatch sender:sender]; - - if (![strongSelf->_currentEntityView isKindOfClass:[TGPhotoTextEntityView class]]) - [strongSelf setDimHidden:true animated:true]; - }; - - _portraitSettingsView = [[TGPhotoPaintSettingsView alloc] initWithContext:_context]; - _portraitSettingsView.eyedropperPressed = eyedropperPressed; - _portraitSettingsView.beganColorPicking = beganColorPicking; - _portraitSettingsView.changedColor = changedColor; - _portraitSettingsView.finishedColorPicking = finishedColorPicking; - _portraitSettingsView.settingsPressed = settingsPressed; - _portraitSettingsView.layer.rasterizationScale = TGScreenScaling(); - _portraitSettingsView.interfaceOrientation = UIInterfaceOrientationPortrait; - [_portraitToolsWrapperView addSubview:_portraitSettingsView]; - - _landscapeSettingsView = [[TGPhotoPaintSettingsView alloc] initWithContext:_context]; - _landscapeSettingsView.eyedropperPressed = eyedropperPressed; - _landscapeSettingsView.beganColorPicking = beganColorPicking; - _landscapeSettingsView.changedColor = changedColor; - _landscapeSettingsView.finishedColorPicking = finishedColorPicking; - _landscapeSettingsView.settingsPressed = settingsPressed; - _landscapeSettingsView.layer.rasterizationScale = TGScreenScaling(); - _landscapeSettingsView.interfaceOrientation = UIInterfaceOrientationLandscapeLeft; - [_landscapeToolsWrapperView addSubview:_landscapeSettingsView]; - - [self setCurrentSwatch:_portraitSettingsView.swatch sender:nil]; - - if (![self _updateControllerInset:false]) - [self controllerInsetUpdated:UIEdgeInsetsZero]; -} - -- (void)setStickersContext:(id)stickersContext { - _stickersContext = stickersContext; - _entitiesContainerView.stickersContext = stickersContext; -} - -- (void)setupCanvas -{ - if (_canvasView == nil) { - __weak TGPhotoPaintController *weakSelf = self; - _canvasView = [[TGPaintCanvas alloc] initWithFrame:CGRectZero]; - _canvasView.pointInsideContainer = ^bool(CGPoint point) - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf == nil) - return false; - - return [strongSelf->_containerView pointInside:[strongSelf->_canvasView convertPoint:point toView:strongSelf->_containerView] withEvent:nil]; - }; - _canvasView.shouldDraw = ^bool - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf == nil) - return false; - - return ![strongSelf->_entitiesContainerView isTrackingAnyEntityView]; - }; - _canvasView.shouldDrawOnSingleTap = ^bool - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf == nil) - return false; - - bool rotating = (strongSelf->_rotationGestureRecognizer.state == UIGestureRecognizerStateBegan || strongSelf->_rotationGestureRecognizer.state == UIGestureRecognizerStateChanged); - bool pinching = (strongSelf->_pinchGestureRecognizer.state == UIGestureRecognizerStateBegan || strongSelf->_pinchGestureRecognizer.state == UIGestureRecognizerStateChanged); - - if (strongSelf->_currentEntityView != nil && !rotating && !pinching) - { - [strongSelf selectEntityView:nil]; - return false; - } - - return true; - }; - _canvasView.strokeBegan = ^ - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf != nil) - [strongSelf selectEntityView:nil]; - }; - _canvasView.strokeCommited = ^ - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf != nil) - [strongSelf updateActionsView]; - }; - _canvasView.hitTest = ^UIView *(CGPoint point, UIEvent *event) - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf == nil) - return nil; - - return [strongSelf->_entitiesContainerView hitTest:[strongSelf->_canvasView convertPoint:point toView:strongSelf->_entitiesContainerView] withEvent:event]; - }; - _canvasView.cropRect = _photoEditor.cropRect; - _canvasView.cropOrientation = _photoEditor.cropOrientation; - _canvasView.originalSize = _photoEditor.originalSize; - [_canvasView setPainting:_painting]; - [_canvasView setBrush:_brushes.firstObject]; - [self setCurrentSwatch:_portraitSettingsView.swatch sender:nil]; - [_paintingWrapperView addSubview:_canvasView]; - } - - _canvasView.hidden = false; - [self.view setNeedsLayout]; -} - -- (void)viewDidLoad -{ - [super viewDidLoad]; - - PGPhotoEditor *photoEditor = _photoEditor; - if (!_skipEntitiesSetup) { - [_entitiesContainerView setupWithPaintingData:photoEditor.paintingData]; - } - for (TGPhotoPaintEntityView *view in _entitiesContainerView.subviews) - { - if (![view isKindOfClass:[TGPhotoPaintEntityView class]]) - continue; - - [self _commonEntityViewSetup:view]; - } - - __weak TGPhotoPaintController *weakSelf = self; - _undoManager.historyChanged = ^ - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf != nil) - [strongSelf updateActionsView]; - }; - - [self updateActionsView]; -} - -- (void)viewDidAppear:(BOOL)animated -{ - [super viewDidAppear:animated]; - - [self transitionIn]; -} - -#pragma mark - Tab Bar - -- (TGPhotoEditorTab)availableTabs -{ - TGPhotoEditorTab result = TGPhotoEditorPaintTab | TGPhotoEditorEraserTab | TGPhotoEditorTextTab; - if (_enableStickers && _stickersContext != nil) { - result |= TGPhotoEditorStickerTab; - } - return result; -} - -- (void)handleTabAction:(TGPhotoEditorTab)tab -{ - [self commitEyedropper:true]; - - switch (tab) - { - case TGPhotoEditorStickerTab: - { - [self presentStickersView]; - } - break; - - case TGPhotoEditorTextTab: - { - [self createNewTextLabel]; - } - break; - - case TGPhotoEditorPaintTab: - { - [self selectEntityView:nil]; - - if (_canvasView.state.eraser) - [self toggleEraserMode]; - } - break; - - case TGPhotoEditorEraserTab: - { - [self selectEntityView:nil]; - [self toggleEraserMode]; - } - break; - - default: - break; - } -} - -- (TGPhotoEditorTab)activeTab -{ - TGPhotoEditorTab tabs = TGPhotoEditorNoneTab; - - if (_currentEntityView != nil) - return tabs; - - if (_canvasView.state.eraser) - tabs |= TGPhotoEditorEraserTab; - else - tabs |= TGPhotoEditorPaintTab; - - return tabs; -} - -#pragma mark - Undo & Redo - -- (void)updateActionsView -{ - if (_portraitActionsView == nil || _landscapeActionsView == nil) - return; - - NSArray *views = @[ _portraitActionsView, _landscapeActionsView ]; - for (TGPhotoPaintActionsView *view in views) - { - [view setUndoEnabled:_undoManager.canUndo]; - [view setClearEnabled:_undoManager.canUndo]; - } -} - -- (void)presentClearAllAlert:(UIView *)sender -{ - TGMenuSheetController *controller = [[TGMenuSheetController alloc] initWithContext:_context dark:false]; - controller.dismissesByOutsideTap = true; - controller.narrowInLandscape = true; - controller.permittedArrowDirections = UIPopoverArrowDirectionUp; - __weak TGMenuSheetController *weakController = controller; - - __weak TGPhotoPaintController *weakSelf = self; - NSArray *items = @ - [ - [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Paint.ClearConfirm") type:TGMenuSheetButtonTypeDestructive fontSize:20.0 action:^ - { - __strong TGMenuSheetController *strongController = weakController; - if (strongController == nil) - return; - - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - [strongSelf->_painting clear]; - [strongSelf->_undoManager reset]; - - [strongSelf->_entitiesContainerView removeAll]; - [strongSelf _clearCurrentSelection]; - - [strongSelf updateSettingsButton]; - - [strongController dismissAnimated:true manual:false completion:nil]; - }], - [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Common.Cancel") type:TGMenuSheetButtonTypeCancel fontSize:20.0 action:^ - { - __strong TGMenuSheetController *strongController = weakController; - if (strongController != nil) - [strongController dismissAnimated:true]; - }] - ]; - - [controller setItemViews:items]; - controller.sourceRect = ^ - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf == nil) - return CGRectZero; - return [sender convertRect:sender.bounds toView:strongSelf.view]; - }; - [controller presentInViewController:self.parentViewController sourceView:self.view animated:true]; -} - -- (void)_clearCurrentSelection -{ - _scrollView.pinchGestureRecognizer.enabled = true; - _currentEntityView = nil; - if (_entitySelectionView != nil) - { - [_entitySelectionView removeFromSuperview]; - _entitySelectionView = nil; - } -} - -#pragma mark - Data Handling - -- (UIImage *)eyedropperImage -{ - UIImage *backgroundImage = [self.photoEditor currentResultImage]; - - CGSize fittedSize = TGFitSize(_painting.size, TGPhotoEditorResultImageMaxSize); - UIImage *paintingImage = _painting.isEmpty ? nil : [_painting imageWithSize:fittedSize andData:NULL]; - NSMutableArray *entities = [[NSMutableArray alloc] init]; - - UIImage *entitiesImage = nil; - if (paintingImage == nil && _entitiesContainerView.entitiesCount < 1) - { - return backgroundImage; - } - else if (_entitiesContainerView.entitiesCount > 0) - { - for (TGPhotoPaintEntityView *view in _entitiesContainerView.subviews) - { - if (![view isKindOfClass:[TGPhotoPaintEntityView class]]) - continue; - - TGPhotoPaintEntity *entity = [view entity]; - if (entity != nil) { - [entities addObject:entity]; - } - } - entitiesImage = [_entitiesContainerView imageInRect:_entitiesContainerView.bounds background:nil still:true]; - } - - if (entitiesImage == nil && paintingImage == nil) { - return backgroundImage; - } else { - UIGraphicsBeginImageContextWithOptions(fittedSize, false, 1.0); - - [backgroundImage drawInRect:CGRectMake(0.0, 0.0, fittedSize.width, fittedSize.height)]; - [paintingImage drawInRect:CGRectMake(0.0, 0.0, fittedSize.width, fittedSize.height)]; - [entitiesImage drawInRect:CGRectMake(0.0, 0.0, fittedSize.width, fittedSize.height)]; - - UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - return result; - } -} - -- (TGPaintingData *)_prepareResultData -{ - if (_resultData != nil) - return _resultData; - - NSData *data = nil; - CGSize fittedSize = TGFitSize(_painting.size, TGPhotoEditorResultImageMaxSize); - UIImage *image = _painting.isEmpty ? nil : [_painting imageWithSize:fittedSize andData:&data]; - NSMutableArray *entities = [[NSMutableArray alloc] init]; - - bool hasAnimatedEntities = false; - UIImage *stillImage = nil; - if (image == nil && _entitiesContainerView.entitiesCount < 1) - { - _resultData = nil; - return _resultData; - } - else if (_entitiesContainerView.entitiesCount > 0) - { - for (TGPhotoPaintEntityView *view in _entitiesContainerView.subviews) - { - if (![view isKindOfClass:[TGPhotoPaintEntityView class]]) - continue; - - TGPhotoPaintEntity *entity = [view entity]; - if (entity != nil) { - if (entity.animated) { - hasAnimatedEntities = true; - } - [entities addObject:entity]; - } - } - - if (hasAnimatedEntities) { - for (TGPhotoPaintEntity *entity in entities) { - if ([entity isKindOfClass:[TGPhotoPaintTextEntity class]]) { - TGPhotoPaintTextEntity *textEntity = (TGPhotoPaintTextEntity *)entity; - for (TGPhotoPaintEntityView *view in _entitiesContainerView.subviews) - { - if (![view isKindOfClass:[TGPhotoPaintEntityView class]]) - continue; - - if (view.entityUUID == textEntity.uuid) { - textEntity.renderImage = [(TGPhotoTextEntityView *)view image]; - break; - } - } - } - } - } - - if (!hasAnimatedEntities) { - image = [_entitiesContainerView imageInRect:_entitiesContainerView.bounds background:image still:false]; - } else { - stillImage = [_entitiesContainerView imageInRect:_entitiesContainerView.bounds background:image still:true]; - } - } - - _resultData = [TGPaintingData dataWithPaintingData:data image:image stillImage:stillImage entities:entities undoManager:_undoManager]; - return _resultData; -} - -- (UIImage *)image -{ - TGPaintingData *paintingData = [self _prepareResultData]; - return paintingData.image; -} - -- (TGPaintingData *)paintingData -{ - return [self _prepareResultData]; -} - -- (void)enableEyedropper { - if (!_eyedropperView.isHidden) - return; - - [self selectEntityView:nil]; - - self.controlVideoPlayback(false); - [_entitiesContainerView updateVisibility:false]; - - UIImage *image = [self eyedropperImage]; - CGImageRef cgImage = image.CGImage; - CFDataRef pixelData = CGDataProviderCopyData(CGImageGetDataProvider(cgImage)); - - _eyedropperBackgroundData = (__bridge NSData *)pixelData; - _eyedropperBackgroundSize = image.size; - _eyedropperBackgroundBytesPerRow = CGImageGetBytesPerRow(cgImage); - _eyedropperBackgroundInfo = CGImageGetBitmapInfo(cgImage); - - [_eyedropperView update]; - [_eyedropperView present]; -} - -- (void)commitEyedropper:(bool)immediate { - self.controlVideoPlayback(true); - [_entitiesContainerView updateVisibility:true]; - - _eyedropperBackgroundData = nil; - _eyedropperBackgroundSize = CGSizeZero; - _eyedropperBackgroundBytesPerRow = 0; - _eyedropperBackgroundInfo = 0; - - double timeout = immediate ? 0.0 : 0.2; - TGDispatchAfter(timeout, dispatch_get_main_queue(), ^{ - [_eyedropperView dismiss]; - }); -} - -- (UIColor *)colorFromData:(NSData *)data width:(NSInteger)width height:(NSInteger)height x:(NSInteger)x y:(NSInteger)y bpr:(NSInteger)bpr { - uint8_t *pixel = (uint8_t *)data.bytes + bpr * y + x * 4; - if (_eyedropperBackgroundInfo & kCGBitmapByteOrder32Little) { - return [UIColor colorWithRed:pixel[2] / 255.0 green:pixel[1] / 255.0 blue:pixel[0] / 255.0 alpha:1.0]; - } else { - return [UIColor colorWithRed:pixel[0] / 255.0 green:pixel[1] / 255.0 blue:pixel[2] / 255.0 alpha:1.0]; - } -} - -- (UIColor *)colorAtPoint:(CGPoint)point -{ - CGPoint convertedPoint = CGPointMake(point.x / _eyedropperView.bounds.size.width * _eyedropperBackgroundSize.width, point.y / _eyedropperView.bounds.size.height * _eyedropperBackgroundSize.height); - UIColor *backgroundColor = [self colorFromData:_eyedropperBackgroundData width:_eyedropperBackgroundSize.width height:_eyedropperBackgroundSize.height x:convertedPoint.x y:convertedPoint.y bpr:_eyedropperBackgroundBytesPerRow]; - return backgroundColor; -} - - -#pragma mark - Entities - -- (void)selectEntityView:(TGPhotoPaintEntityView *)view -{ - if (_editedTextView != nil) - return; - - if (_currentEntityView != nil) - { - if (_currentEntityView == view) - { - [self showMenuForEntityView]; - return; - } - - [self _clearCurrentSelection]; - } - - _currentEntityView = view; - [self updateSettingsButton]; - - _scrollView.pinchGestureRecognizer.enabled = _currentEntityView == nil; - - if (view != nil) - { - [_currentEntityView.superview bringSubviewToFront:_currentEntityView]; - } - else - { - [self hideMenu]; - return; - } - - if ([view isKindOfClass:[TGPhotoTextEntityView class]]) - { - TGPaintSwatch *textSwatch = ((TGPhotoPaintTextEntity *)view.entity).swatch; - [self setCurrentSwatch:[TGPaintSwatch swatchWithColor:textSwatch.color colorLocation:textSwatch.colorLocation brushWeight:_portraitSettingsView.swatch.brushWeight] sender:nil]; - } - - _entitySelectionView = [view createSelectionView]; - view.selectionView = _entitySelectionView; - [_selectionContainerView addSubview:_entitySelectionView]; - - __weak TGPhotoPaintController *weakSelf = self; - _entitySelectionView.entityResized = ^(CGFloat scale) - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - [strongSelf->_entitySelectionView.entityView scale:scale absolute:true]; - }; - _entitySelectionView.entityRotated = ^(CGFloat angle) - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - [strongSelf->_entitySelectionView.entityView rotate:angle absolute:true]; - }; - - [_entitySelectionView update]; -} - -- (void)deleteEntityView:(TGPhotoPaintEntityView *)view -{ - [_undoManager unregisterUndoWithUUID:view.entityUUID]; - - [view removeFromSuperview]; - - [self _clearCurrentSelection]; - - [self updateActionsView]; - [self updateSettingsButton]; -} - -- (void)duplicateEntityView:(TGPhotoPaintEntityView *)view -{ - TGPhotoPaintEntity *entity = [view.entity duplicate]; - entity.position = [self startPositionRelativeToEntity:entity]; - - TGPhotoPaintEntityView *entityView = nil; - if ([entity isKindOfClass:[TGPhotoPaintStickerEntity class]]) - { - TGPhotoStickerEntityView *stickerView = (TGPhotoStickerEntityView *)[_entitiesContainerView createEntityViewWithEntity:entity]; - [self _commonEntityViewSetup:stickerView]; - entityView = stickerView; - } - else - { - TGPhotoTextEntityView *textView = (TGPhotoTextEntityView *)[_entitiesContainerView createEntityViewWithEntity:entity]; - [self _commonEntityViewSetup:textView]; - entityView = textView; - } - - [self selectEntityView:entityView]; - [self _registerEntityRemovalUndo:entity]; - [self updateActionsView]; -} - -- (void)editEntityView:(TGPhotoPaintEntityView *)view -{ - if ([view isKindOfClass:[TGPhotoTextEntityView class]]) - [(TGPhotoTextEntityView *)view beginEditing]; -} - -#pragma mark Menu - -- (void)showMenuForEntityView -{ - if (_menuContainerView != nil) - { - TGMenuContainerView *container = _menuContainerView; - bool isShowingMenu = container.isShowingMenu; - _menuContainerView = nil; - - [container removeFromSuperview]; - - if (!isShowingMenu && container.menuView.userInfo[@"entity"] == _currentEntityView) - { - if ([_currentEntityView isKindOfClass:[TGPhotoTextEntityView class]]) - [self editEntityView:_currentEntityView]; - - return; - } - } - - UIView *parentView = self.view; - _menuContainerView = [[TGMenuContainerView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, parentView.frame.size.width, parentView.frame.size.height)]; - [parentView addSubview:_menuContainerView]; - - NSArray *actions = nil; - - if ([_currentEntityView isKindOfClass:[TGPhotoStickerEntityView class]]) - { - actions = @ - [ - @{ @"title": TGLocalized(@"Paint.Delete"), @"action": @"delete" }, - @{ @"title": TGLocalized(@"Paint.Duplicate"), @"action": @"duplicate" }, - ]; - } - else - { - actions = @ - [ - @{ @"title": TGLocalized(@"Paint.Delete"), @"action": @"delete" }, - @{ @"title": TGLocalized(@"Paint.Edit"), @"action": @"edit" }, - @{ @"title": TGLocalized(@"Paint.Duplicate"), @"action": @"duplicate" }, - ]; - } - - [_menuContainerView.menuView setUserInfo:@{ @"entity": _currentEntityView }]; - [_menuContainerView.menuView setButtonsAndActions:actions watcherHandle:_actionHandle]; - [_menuContainerView.menuView sizeToFit]; - - CGRect sourceRect = CGRectOffset([_currentEntityView convertRect:_currentEntityView.bounds toView:_menuContainerView], 0, -15.0f); - [_menuContainerView showMenuFromRect:sourceRect animated:false]; -} - -- (void)hideMenu -{ - [_menuContainerView hideMenu]; -} - -- (void)actionStageActionRequested:(NSString *)action options:(id)options -{ - if ([action isEqualToString:@"menuAction"]) - { - NSString *menuAction = options[@"action"]; - TGPhotoPaintEntityView *entity = options[@"userInfo"][@"entity"]; - - if ([menuAction isEqualToString:@"delete"]) - { - [self deleteEntityView:entity]; - } - else if ([menuAction isEqualToString:@"duplicate"]) - { - [self duplicateEntityView:entity]; - } - else if ([menuAction isEqualToString:@"edit"]) - { - [self editEntityView:entity]; - } - } - else if ([action isEqualToString:@"menuWillHide"]) - { - } -} - -#pragma mark View - -- (CGPoint)centerPointFittedCropRect -{ - return [_previewView convertPoint:TGPaintCenterOfRect(_previewView.bounds) toView:_entitiesContainerView]; -} - -- (CGFloat)startRotation -{ - return TGCounterRotationForOrientation(_photoEditor.cropOrientation) - _photoEditor.cropRotation; -} - -- (CGPoint)startPositionRelativeToEntity:(TGPhotoPaintEntity *)entity -{ - const CGPoint offset = CGPointMake(200.0f, 200.0f); - - if (entity != nil) - { - return TGPaintAddPoints(entity.position, offset); - } - else - { - const CGFloat minimalDistance = 100.0f; - CGPoint position = [self centerPointFittedCropRect]; - - while (true) - { - bool occupied = false; - for (TGPhotoPaintEntityView *view in _entitiesContainerView.subviews) - { - if (![view isKindOfClass:[TGPhotoPaintEntityView class]]) - continue; - - CGPoint location = view.center; - CGFloat distance = sqrt(pow(location.x - position.x, 2) + pow(location.y - position.y, 2)); - if (distance < minimalDistance) - occupied = true; - } - - if (!occupied) - break; - else - position = TGPaintAddPoints(position, offset); - } - - return position; - } -} - -- (void)_commonEntityViewSetup:(TGPhotoPaintEntityView *)entityView -{ - [self hideMenu]; - - __weak TGPhotoPaintController *weakSelf = self; - entityView.shouldTouchEntity = ^bool (__unused TGPhotoPaintEntityView *sender) - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf == nil) - return false; - - return ![strongSelf->_canvasView isTracking] && ![strongSelf->_entitiesContainerView isTrackingAnyEntityView]; - }; - entityView.entityBeganDragging = ^(TGPhotoPaintEntityView *sender) - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf != nil && sender != strongSelf->_entitySelectionView.entityView) - [strongSelf selectEntityView:sender]; - }; - entityView.entityChanged = ^(TGPhotoPaintEntityView *sender) - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - if (sender == strongSelf->_entitySelectionView.entityView) - [strongSelf->_entitySelectionView update]; - - [strongSelf updateActionsView]; - }; - - if ([entityView isKindOfClass:[TGPhotoTextEntityView class]]) { - TGPhotoTextEntityView *textView = (TGPhotoTextEntityView *)entityView; - - __weak TGPhotoPaintController *weakSelf = self; - textView.beganEditing = ^(TGPhotoTextEntityView *sender) - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - [strongSelf bringTextEntityViewFront:sender]; - }; - - textView.finishedEditing = ^(__unused TGPhotoTextEntityView *sender) - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - [strongSelf sendTextEntityViewBack]; - }; - } -} - -- (void)_registerEntityRemovalUndo:(TGPhotoPaintEntity *)entity -{ - [_undoManager registerUndoWithUUID:entity.uuid block:^(__unused TGPainting *painting, TGPhotoEntitiesContainerView *entitiesContainer, NSInteger uuid) - { - [entitiesContainer removeViewWithUUID:uuid]; - }]; -} - -#pragma mark Stickers - -- (void)presentStickersView -{ - if (_stickersScreen != nil) { - [_stickersScreen restore]; - return; - } - - __weak TGPhotoPaintController *weakSelf = self; - _stickersScreen = _stickersContext.presentStickersController(^(id document, bool animated, UIView *view, CGRect rect) { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf != nil) { - [strongSelf createNewStickerWithDocument:document animated:animated transitionPoint:CGPointZero snapshotView:nil]; - } - }); - _stickersScreen.screenDidAppear = ^{ - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf != nil) { - strongSelf.controlVideoPlayback(false); - [strongSelf->_entitiesContainerView updateVisibility:false]; - } - }; - _stickersScreen.screenWillDisappear = ^{ - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf != nil) { - strongSelf.controlVideoPlayback(true); - [strongSelf->_entitiesContainerView updateVisibility:true]; - } - }; -} - -- (void)createNewStickerWithDocument:(id)document animated:(bool)animated transitionPoint:(CGPoint)transitionPoint snapshotView:(UIView *)snapshotView -{ - TGPhotoPaintStickerEntity *entity = [[TGPhotoPaintStickerEntity alloc] initWithDocument:document baseSize:[self _stickerBaseSizeForCurrentPainting] animated:animated]; - [self _setStickerEntityPosition:entity]; - - - TGPhotoStickerEntityView *stickerView = (TGPhotoStickerEntityView *)[_entitiesContainerView createEntityViewWithEntity:entity]; - - bool hasStickers = false; - TGPhotoStickerEntityView *existingStickerView; - for (TGPhotoPaintEntityView *view in _entitiesContainerView.subviews) { - if ([view isKindOfClass:[TGPhotoStickerEntityView class]]) { - hasStickers = true; - - if (((TGPhotoStickerEntityView *)view).documentId == stickerView.documentId) { - existingStickerView = (TGPhotoStickerEntityView *)view; - } - break; - } - } - - [_entitiesContainerView addSubview:stickerView]; - [self _commonEntityViewSetup:stickerView]; - - __weak TGPhotoPaintController *weakSelf = self; - stickerView.started = ^(double duration) { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf != nil) { - TGPhotoEditorController *editorController = (TGPhotoEditorController *)strongSelf.parentViewController; - if (![editorController isKindOfClass:[TGPhotoEditorController class]]) - return; - - if (!hasStickers) { - [editorController setMinimalVideoDuration:duration]; - } - } - }; - - NSTimeInterval currentTime = NAN; - NSTimeInterval stickerStartTime = _stickerStartTime; - TGPhotoEditorController *editorController = (TGPhotoEditorController *)self.parentViewController; - if ([editorController isKindOfClass:[TGPhotoEditorController class]]) { - currentTime = editorController.currentTime; - } - - if (!isnan(currentTime)) { - [stickerView seekTo:currentTime]; - [stickerView play]; - } else { - NSTimeInterval currentTime = CACurrentMediaTime(); - if (!isnan(stickerStartTime)) { - if (existingStickerView != nil) { - [stickerView copyStickerView:existingStickerView]; - } else { - NSTimeInterval position = currentTime - stickerStartTime; - [stickerView seekTo:position]; - [stickerView play]; - } - } else { - _stickerStartTime = currentTime; - [stickerView play]; - } - } - - [self selectEntityView:stickerView]; - _entitySelectionView.alpha = 0.0f; - - [_entitySelectionView fadeIn]; - - [self _registerEntityRemovalUndo:entity]; - [self updateActionsView]; -} - -- (void)mirrorSelectedStickerEntity -{ - if ([_currentEntityView isKindOfClass:[TGPhotoStickerEntityView class]]) - [((TGPhotoStickerEntityView *)_currentEntityView) mirror]; -} - -#pragma mark Text - -- (void)createNewTextLabel -{ - TGPaintSwatch *currentSwatch = _portraitSettingsView.swatch; - TGPaintSwatch *whiteSwatch = [TGPaintSwatch swatchWithColor:UIColorRGB(0xffffff) colorLocation:1.0f brushWeight:currentSwatch.brushWeight]; - TGPaintSwatch *blackSwatch = [TGPaintSwatch swatchWithColor:UIColorRGB(0x000000) colorLocation:0.85f brushWeight:currentSwatch.brushWeight]; - [self setCurrentSwatch:_selectedTextStyle == TGPhotoPaintTextEntityStyleOutlined ? blackSwatch : whiteSwatch sender:nil]; - - CGFloat maxWidth = [self fittedContentSize].width - 26.0f; - TGPhotoPaintTextEntity *entity = [[TGPhotoPaintTextEntity alloc] initWithText:@"" font:_selectedTextFont swatch:_portraitSettingsView.swatch baseFontSize:[self _textBaseFontSizeForCurrentPainting] maxWidth:maxWidth style:_selectedTextStyle]; - entity.position = [self startPositionRelativeToEntity:nil]; - entity.angle = [self startRotation]; - - TGPhotoTextEntityView *textView = (TGPhotoTextEntityView *)[_entitiesContainerView createEntityViewWithEntity:entity]; - [_entitiesContainerView addSubview:textView]; - [self _commonEntityViewSetup:textView]; - - [self selectEntityView:textView]; - - [self _registerEntityRemovalUndo:entity]; - [self updateActionsView]; - - [textView beginEditing]; -} - -- (void)bringTextEntityViewFront:(TGPhotoTextEntityView *)entityView -{ - _editedTextView = entityView; - entityView.inhibitGestures = true; - - [_dimView.superview insertSubview:_dimView belowSubview:entityView]; - - _textEditingDismissButton = [[UIButton alloc] initWithFrame:_dimView.bounds]; - _dimView.userInteractionEnabled = true; - [_textEditingDismissButton addTarget:self action:@selector(_dismissButtonTapped) forControlEvents:UIControlEventTouchUpInside]; - [_dimView addSubview:_textEditingDismissButton]; - - _editedTextCenter = entityView.center; - _editedTextTransform = entityView.transform; - - _entitySelectionView.alpha = 0.0f; - - void (^changeBlock)(void) = ^ - { - entityView.center = [self centerPointFittedCropRect]; - entityView.transform = CGAffineTransformMakeRotation([self startRotation]); - - _dimView.alpha = 1.0f; - }; - - _contentView.userInteractionEnabled = true; - _contentWrapperView.userInteractionEnabled = true; - - if (iosMajorVersion() >= 7) - { - [UIView animateWithDuration:0.4 delay:0.0 usingSpringWithDamping:0.8f initialSpringVelocity:0.0f options:kNilOptions animations:changeBlock completion:nil]; - } - else - { - [UIView animateWithDuration:0.35 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:changeBlock completion:nil]; - } - - [self setInterfaceHidden:true animated:true]; -} - -- (void)_dismissButtonTapped -{ - TGPhotoTextEntityView *entityView = _editedTextView; - [entityView endEditing]; -} - -- (void)sendTextEntityViewBack -{ - _contentView.userInteractionEnabled = false; - _contentWrapperView.userInteractionEnabled = false; - - _dimView.userInteractionEnabled = false; - [_textEditingDismissButton removeFromSuperview]; - _textEditingDismissButton = nil; - - TGPhotoTextEntityView *entityView = _editedTextView; - _editedTextView = nil; - - void (^changeBlock)(void) = ^ - { - entityView.center = _editedTextCenter; - entityView.transform = _editedTextTransform; - _dimView.alpha = 0.0f; - }; - - void (^completionBlock)(BOOL) = ^(__unused BOOL finished) - { - [_dimView.superview bringSubviewToFront:_dimView]; - entityView.inhibitGestures = false; - - if (entityView.isEmpty) - { - [self deleteEntityView:entityView]; - } - else - { - [_entitySelectionView update]; - [_entitySelectionView fadeIn]; - } - }; - - if (iosMajorVersion() >= 7) - { - [UIView animateWithDuration:0.4 delay:0.0 usingSpringWithDamping:0.8f initialSpringVelocity:0.0f options:kNilOptions animations:changeBlock completion:completionBlock]; - } - else - { - [UIView animateWithDuration:0.35 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:changeBlock completion:completionBlock]; - } - - [self setInterfaceHidden:false animated:true]; - - TGMenuContainerView *container = _menuContainerView; - _menuContainerView = nil; - [container removeFromSuperview]; -} - -- (void)containerPressed -{ - if (_currentEntityView == nil) - return; - - if ([_currentEntityView isKindOfClass:[TGPhotoTextEntityView class]]) - { - TGPhotoTextEntityView *textEntityView = (TGPhotoTextEntityView *)_currentEntityView; - if ([textEntityView isEditing]) - { - [textEntityView endEditing]; - return; - } - } - [self selectEntityView:nil]; -} - -#pragma mark - Relative Size Calculation - -- (CGSize)_stickerBaseSizeForCurrentPainting -{ - CGSize fittedSize = [self fittedContentSize]; - CGFloat maxSide = MAX(fittedSize.width, fittedSize.height); - CGFloat side = ceil(maxSide * 0.3125f); - return CGSizeMake(side, side); -} - -- (CGFloat)_textBaseFontSizeForCurrentPainting -{ - CGSize fittedSize = [self fittedContentSize]; - CGFloat maxSide = MAX(fittedSize.width, fittedSize.height); - return ceil(maxSide * 0.08f); -} - -- (CGFloat)_brushBaseWeightForCurrentPainting -{ - return 15.0f / TGPhotoPaintingMaxSize.width * _painting.size.width; -} - -- (CGFloat)_brushWeightRangeForCurrentPainting -{ - return 125.0f / TGPhotoPaintingMaxSize.width * _painting.size.width; -} - -- (CGFloat)_brushWeightForSize:(CGFloat)size -{ - CGFloat scale = MAX(0.001, _scrollView.zoomScale); - return ([self _brushBaseWeightForCurrentPainting] + [self _brushWeightRangeForCurrentPainting] * size) / scale; -} - -+ (CGSize)maximumPaintingSize -{ - static dispatch_once_t onceToken; - static CGSize size; - dispatch_once(&onceToken, ^ - { - CGSize screenSize = TGScreenSize(); - if ((NSInteger)screenSize.height == 480) - size = TGPhotoPaintingLightMaxSize; - else - size = TGPhotoPaintingMaxSize; - }); - return size; -} - -#pragma mark - Settings - -- (void)setCurrentSwatch:(TGPaintSwatch *)swatch sender:(id)sender -{ - [_canvasView setBrushColor:swatch.color]; - [_canvasView setBrushWeight:[self _brushWeightForSize:swatch.brushWeight]]; - if ([_currentEntityView isKindOfClass:[TGPhotoTextEntityView class]]) - [(TGPhotoTextEntityView *)_currentEntityView setSwatch:swatch]; - - if (sender != _landscapeSettingsView) - [_landscapeSettingsView setSwatch:swatch]; - - if (sender != _portraitSettingsView) - [_portraitSettingsView setSwatch:swatch]; -} - -- (void)updateSettingsButton -{ - if ([_currentEntityView isKindOfClass:[TGPhotoTextEntityView class]]) { - TGPhotoPaintSettingsViewIcon icon; - switch (((TGPhotoTextEntityView *)_currentEntityView).entity.style) { - case TGPhotoPaintTextEntityStyleRegular: - icon = TGPhotoPaintSettingsViewIconTextRegular; - break; - case TGPhotoPaintTextEntityStyleOutlined: - icon = TGPhotoPaintSettingsViewIconTextOutlined; - break; - case TGPhotoPaintTextEntityStyleFramed: - icon = TGPhotoPaintSettingsViewIconTextFramed; - break; - } - [self setSettingsButtonIcon:icon]; - } - else if ([_currentEntityView isKindOfClass:[TGPhotoStickerEntityView class]]) { - [self setSettingsButtonIcon:TGPhotoPaintSettingsViewIconMirror]; - } - else { - TGPhotoPaintSettingsViewIcon icon = TGPhotoPaintSettingsViewIconBrushPen; - if ([_canvasView.state.brush isKindOfClass:[TGPaintEllipticalBrush class]]) { - icon = TGPhotoPaintSettingsViewIconBrushMarker; - } else if ([_canvasView.state.brush isKindOfClass:[TGPaintNeonBrush class]]) { - icon = TGPhotoPaintSettingsViewIconBrushNeon; - } else if ([_canvasView.state.brush isKindOfClass:[TGPaintArrowBrush class]]) { - icon = TGPhotoPaintSettingsViewIconBrushArrow; - } - [self setSettingsButtonIcon:icon]; - } - [self _updateTabs]; -} - -- (void)setSettingsButtonIcon:(TGPhotoPaintSettingsViewIcon)icon -{ - [_portraitSettingsView setIcon:icon animated:true]; - [_landscapeSettingsView setIcon:icon animated:true]; -} - -- (void)settingsWrapperPressed -{ - [_settingsView dismissWithCompletion:^ - { - [_settingsView removeFromSuperview]; - _settingsView = nil; - - [_settingsViewWrapper removeFromSuperview]; - }]; -} - -- (UIView *)settingsViewWrapper -{ - if (_settingsViewWrapper == nil) - { - _settingsViewWrapper = [[TGPhotoPaintSettingsWrapperView alloc] initWithFrame:self.parentViewController.view.bounds]; - _settingsViewWrapper.exclusiveTouch = true; - - __weak TGPhotoPaintController *weakSelf = self; - _settingsViewWrapper.pressed = ^(__unused CGPoint location) - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf != nil) - [strongSelf settingsWrapperPressed]; - }; - _settingsViewWrapper.suppressTouchAtPoint = ^bool(CGPoint location) - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf == nil) - return false; - - UIView *view = [strongSelf.view hitTest:[strongSelf.view convertPoint:location fromView:nil] withEvent:nil]; - if ([view isKindOfClass:[TGModernButton class]]) - return true; - - if ([view isKindOfClass:[TGPaintCanvas class]]) - return true; - - if (view == strongSelf->_portraitToolsWrapperView || view == strongSelf->_landscapeToolsWrapperView) - return true; - - return false; - }; - } - - [self.parentViewController.view addSubview:_settingsViewWrapper]; - - return _settingsViewWrapper; -} - -- (TGPaintBrushPreview *)brushPreview -{ - if ([_brushes.firstObject previewImage] != nil) - return nil; - - if (_brushPreview == nil) - _brushPreview = [[TGPaintBrushPreview alloc] init]; - - return _brushPreview; -} - -- (void)presentBrushSettingsView -{ - TGPhotoBrushSettingsView *view = [[TGPhotoBrushSettingsView alloc] initWithBrushes:_brushes preview:[self brushPreview]]; - [view setBrush:_painting.brush]; - - __weak TGPhotoPaintController *weakSelf = self; - view.brushChanged = ^(TGPaintBrush *brush) - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - if (strongSelf->_canvasView.state.eraser && (brush.lightSaber || brush.arrow)) - brush = strongSelf->_brushes.firstObject; - - [strongSelf->_canvasView setBrush:brush]; - - [strongSelf settingsWrapperPressed]; - [strongSelf updateSettingsButton]; - }; - _settingsView = view; - [view sizeToFit]; - - UIView *wrapper = [self settingsViewWrapper]; - wrapper.userInteractionEnabled = true; - [wrapper addSubview:view]; - - [self viewWillLayoutSubviews]; - - [view present]; -} - -- (void)presentTextSettingsView -{ - TGPhotoTextSettingsView *view = [[TGPhotoTextSettingsView alloc] initWithFonts:[TGPhotoPaintFont availableFonts] selectedFont:_selectedTextFont selectedStyle:_selectedTextStyle]; - - __weak TGPhotoPaintController *weakSelf = self; - view.fontChanged = ^(TGPhotoPaintFont *font) - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - strongSelf->_selectedTextFont = font; - - TGPhotoTextEntityView *textView = (TGPhotoTextEntityView *)strongSelf->_currentEntityView; - [textView setFont:font]; - - [strongSelf settingsWrapperPressed]; - [strongSelf updateSettingsButton]; - }; - view.styleChanged = ^(TGPhotoPaintTextEntityStyle style) - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - strongSelf->_selectedTextStyle = style; - - if (style == TGPhotoPaintTextEntityStyleOutlined && [strongSelf->_portraitSettingsView.swatch.color isEqual:UIColorRGB(0xffffff)]) - { - TGPaintSwatch *currentSwatch = strongSelf->_portraitSettingsView.swatch; - TGPaintSwatch *blackSwatch = [TGPaintSwatch swatchWithColor:UIColorRGB(0x000000) colorLocation:0.85f brushWeight:currentSwatch.brushWeight]; - [strongSelf setCurrentSwatch:blackSwatch sender:nil]; - } - else if (style != TGPhotoPaintTextEntityStyleOutlined && [strongSelf->_portraitSettingsView.swatch.color isEqual:UIColorRGB(0x000000)]) - { - TGPaintSwatch *currentSwatch = strongSelf->_portraitSettingsView.swatch; - TGPaintSwatch *whiteSwatch = [TGPaintSwatch swatchWithColor:UIColorRGB(0xffffff) colorLocation:1.0f brushWeight:currentSwatch.brushWeight]; - [strongSelf setCurrentSwatch:whiteSwatch sender:nil]; - } - - TGPhotoTextEntityView *textView = (TGPhotoTextEntityView *)strongSelf->_currentEntityView; - [textView setStyle:style]; - - [strongSelf settingsWrapperPressed]; - [strongSelf updateSettingsButton]; - }; - - _settingsView = view; - [view sizeToFit]; - - UIView *wrapper = [self settingsViewWrapper]; - wrapper.userInteractionEnabled = true; - [wrapper addSubview:view]; - - [self viewWillLayoutSubviews]; - - [view present]; -} - -- (void)toggleEraserMode -{ - _canvasView.state.eraser = !_canvasView.state.isEraser; - - if (_canvasView.state.eraser) - { - if (_canvasView.state.brush.lightSaber || _canvasView.state.brush.arrow) - [_canvasView setBrush:_brushes.firstObject]; - } - - [_portraitSettingsView setHighlighted:_canvasView.state.isEraser]; - [_landscapeSettingsView setHighlighted:_canvasView.state.isEraser]; - - [self updateSettingsButton]; - [self _updateTabs]; -} - -#pragma mark - Scroll View - -- (CGSize)fittedContentSize -{ - return [TGPhotoPaintController fittedContentSize:_photoEditor.cropRect orientation:_photoEditor.cropOrientation originalSize:_photoEditor.originalSize]; -} - -+ (CGSize)fittedContentSize:(CGRect)cropRect orientation:(UIImageOrientation)orientation originalSize:(CGSize)originalSize { - CGSize fittedOriginalSize = TGScaleToSize(originalSize, [TGPhotoPaintController maximumPaintingSize]); - CGFloat scale = fittedOriginalSize.width / originalSize.width; - - CGSize size = CGSizeMake(cropRect.size.width * scale, cropRect.size.height * scale); - if (orientation == UIImageOrientationLeft || orientation == UIImageOrientationRight) - size = CGSizeMake(size.height, size.width); - - return CGSizeMake(floor(size.width), floor(size.height)); -} - -- (CGRect)fittedCropRect:(bool)originalSize -{ - return [TGPhotoPaintController fittedCropRect:_photoEditor.cropRect originalSize:_photoEditor.originalSize keepOriginalSize:originalSize]; -} - -+ (CGRect)fittedCropRect:(CGRect)cropRect originalSize:(CGSize)originalSize keepOriginalSize:(bool)keepOriginalSize { - CGSize fittedOriginalSize = TGScaleToSize(originalSize, [TGPhotoPaintController maximumPaintingSize]); - CGFloat scale = fittedOriginalSize.width / originalSize.width; - - CGSize size = fittedOriginalSize; - if (!keepOriginalSize) - size = CGSizeMake(cropRect.size.width * scale, cropRect.size.height * scale); - - return CGRectMake(-cropRect.origin.x * scale, -cropRect.origin.y * scale, size.width, size.height); -} - -- (CGPoint)fittedCropCenterScale:(CGFloat)scale -{ - return [TGPhotoPaintController fittedCropRect:_photoEditor.cropRect centerScale:scale]; -} - -+ (CGPoint)fittedCropRect:(CGRect)cropRect centerScale:(CGFloat)scale -{ - CGSize size = CGSizeMake(cropRect.size.width * scale, cropRect.size.height * scale); - CGRect rect = CGRectMake(cropRect.origin.x * scale, cropRect.origin.y * scale, size.width, size.height); - - return CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect)); -} - -- (void)resetScrollView -{ - CGSize fittedContentSize = [self fittedContentSize]; - CGRect fittedCropRect = [self fittedCropRect:false]; - _contentWrapperView.frame = CGRectMake(0.0f, 0.0f, fittedContentSize.width, fittedContentSize.height); - - CGFloat scale = _contentView.bounds.size.width / fittedCropRect.size.width; - _contentWrapperView.transform = CGAffineTransformMakeScale(scale, scale); - _contentWrapperView.frame = CGRectMake(0.0f, 0.0f, _contentView.bounds.size.width, _contentView.bounds.size.height); - - CGSize contentSize = [self contentSize]; - _scrollView.minimumZoomScale = 1.0f; - _scrollView.maximumZoomScale = 1.0f; - _scrollView.normalZoomScale = 1.0f; - _scrollView.zoomScale = 1.0f; - _scrollView.contentSize = contentSize; - [self contentView].frame = CGRectMake(0.0f, 0.0f, contentSize.width, contentSize.height); - - [self adjustZoom]; - _scrollView.zoomScale = _scrollView.normalZoomScale; -} - -- (void)scrollViewWillBeginZooming:(UIScrollView *)__unused scrollView withView:(UIView *)__unused view -{ -} - -- (void)scrollViewDidZoom:(UIScrollView *)__unused scrollView -{ - [self adjustZoom]; -} - -- (void)scrollViewDidEndZooming:(UIScrollView *)__unused scrollView withView:(UIView *)__unused view atScale:(CGFloat)__unused scale -{ - [self adjustZoom]; - - TGPaintSwatch *currentSwatch = _portraitSettingsView.swatch; - [_canvasView setBrushWeight:[self _brushWeightForSize:currentSwatch.brushWeight]]; - - if (_scrollView.zoomScale < _scrollView.normalZoomScale - FLT_EPSILON) - { - [TGHacks setAnimationDurationFactor:0.5f]; - [_scrollView setZoomScale:_scrollView.normalZoomScale animated:true]; - [TGHacks setAnimationDurationFactor:1.0f]; - } -} - -- (UIView *)contentView -{ - return _scrollContentView; -} - -- (CGSize)contentSize -{ - return _scrollView.frame.size; -} - -- (UIView *)viewForZoomingInScrollView:(UIScrollView *)__unused scrollView -{ - return [self contentView]; -} - -- (void)adjustZoom -{ - CGSize contentSize = [self contentSize]; - CGSize boundsSize = _scrollView.frame.size; - if (contentSize.width < FLT_EPSILON || contentSize.height < FLT_EPSILON || boundsSize.width < FLT_EPSILON || boundsSize.height < FLT_EPSILON) - return; - - CGFloat scaleWidth = boundsSize.width / contentSize.width; - CGFloat scaleHeight = boundsSize.height / contentSize.height; - CGFloat minScale = MIN(scaleWidth, scaleHeight); - CGFloat maxScale = MAX(scaleWidth, scaleHeight); - maxScale = MAX(maxScale, minScale * 3.0f); - - if (ABS(maxScale - minScale) < 0.01f) - maxScale = minScale; - - _scrollView.contentInset = UIEdgeInsetsZero; - - if (_scrollView.minimumZoomScale != 0.05f) - _scrollView.minimumZoomScale = 0.05f; - if (_scrollView.normalZoomScale != minScale) - _scrollView.normalZoomScale = minScale; - if (_scrollView.maximumZoomScale != maxScale) - _scrollView.maximumZoomScale = maxScale; - - CGRect contentFrame = [self contentView].frame; - - if (boundsSize.width > contentFrame.size.width) - contentFrame.origin.x = (boundsSize.width - contentFrame.size.width) / 2.0f; - else - contentFrame.origin.x = 0; - - if (boundsSize.height > contentFrame.size.height) - contentFrame.origin.y = (boundsSize.height - contentFrame.size.height) / 2.0f; - else - contentFrame.origin.y = 0; - - [self contentView].frame = contentFrame; - - _scrollView.scrollEnabled = ABS(_scrollView.zoomScale - _scrollView.normalZoomScale) > FLT_EPSILON; -} - -#pragma mark - Gestures - -- (void)handlePinch:(UIPinchGestureRecognizer *)gestureRecognizer -{ - [_entitiesContainerView handlePinch:gestureRecognizer]; -} - -- (void)handleRotate:(UIRotationGestureRecognizer *)gestureRecognizer -{ - [_entitiesContainerView handleRotate:gestureRecognizer]; -} - -- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)__unused gestureRecognizer -{ - if (gestureRecognizer == _pinchGestureRecognizer && _currentEntityView == nil) { - return false; - } - return !_canvasView.isTracking; -} - -- (BOOL)gestureRecognizer:(UIGestureRecognizer *)__unused gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)__unused otherGestureRecognizer -{ - return true; -} - -#pragma mark - Transitions - -- (void)transitionIn -{ - _portraitSettingsView.layer.shouldRasterize = true; - _landscapeSettingsView.layer.shouldRasterize = true; - - [UIView animateWithDuration:0.3f animations:^ - { - _portraitToolsWrapperView.alpha = 1.0f; - _landscapeToolsWrapperView.alpha = 1.0f; - - _portraitActionsView.alpha = 1.0f; - _landscapeActionsView.alpha = 1.0f; - } completion:^(__unused BOOL finished) - { - _portraitSettingsView.layer.shouldRasterize = false; - _landscapeSettingsView.layer.shouldRasterize = false; - }]; - - if (self.presentedForAvatarCreation) { - _canvasView.hidden = true; - } -} - -+ (CGRect)photoContainerFrameForParentViewFrame:(CGRect)parentViewFrame toolbarLandscapeSize:(CGFloat)toolbarLandscapeSize orientation:(UIInterfaceOrientation)orientation panelSize:(CGFloat)panelSize hasOnScreenNavigation:(bool)hasOnScreenNavigation -{ - CGRect frame = [TGPhotoEditorTabController photoContainerFrameForParentViewFrame:parentViewFrame toolbarLandscapeSize:toolbarLandscapeSize orientation:orientation panelSize:panelSize hasOnScreenNavigation:hasOnScreenNavigation]; - - switch (orientation) - { - case UIInterfaceOrientationLandscapeLeft: - frame.origin.x -= TGPhotoPaintTopPanelSize; - break; - - case UIInterfaceOrientationLandscapeRight: - frame.origin.x += TGPhotoPaintTopPanelSize; - break; - - default: - frame.origin.y += TGPhotoPaintTopPanelSize; - break; - } - - return frame; -} - -- (CGRect)_targetFrameForTransitionInFromFrame:(CGRect)fromFrame -{ - CGSize referenceSize = [self referenceViewSize]; - CGRect containerFrame = [TGPhotoPaintController photoContainerFrameForParentViewFrame:CGRectMake(0, 0, referenceSize.width, referenceSize.height) toolbarLandscapeSize:self.toolbarLandscapeSize orientation:self.effectiveOrientation panelSize:TGPhotoPaintTopPanelSize + TGPhotoPaintBottomPanelSize hasOnScreenNavigation:self.hasOnScreenNavigation]; - - CGSize fittedSize = TGScaleToSize(fromFrame.size, containerFrame.size); - CGRect toFrame = CGRectMake(containerFrame.origin.x + (containerFrame.size.width - fittedSize.width) / 2, containerFrame.origin.y + (containerFrame.size.height - fittedSize.height) / 2, fittedSize.width, fittedSize.height); - - return toFrame; -} - -- (void)_finishedTransitionInWithView:(UIView *)transitionView -{ - _appeared = true; - - if ([transitionView isKindOfClass:[TGPhotoEditorPreviewView class]]) { - - } else { - [transitionView removeFromSuperview]; - } - - [self setupCanvas]; - _entitiesContainerView.hidden = false; - - TGPhotoEditorPreviewView *previewView = _previewView; - [previewView setPaintingHidden:true]; - previewView.hidden = false; - [_containerView insertSubview:previewView belowSubview:_paintingWrapperView]; - [self updateContentViewLayout]; - [previewView performTransitionInIfNeeded]; - - CGRect rect = [self fittedCropRect:true]; - _entitiesContainerView.frame = CGRectMake(0, 0, rect.size.width, rect.size.height); - _entitiesContainerView.transform = CGAffineTransformMakeRotation(_photoEditor.cropRotation); - - CGSize fittedOriginalSize = TGScaleToSize(_photoEditor.originalSize, [TGPhotoPaintController maximumPaintingSize]); - CGSize rotatedSize = TGRotatedContentSize(fittedOriginalSize, _photoEditor.cropRotation); - CGPoint centerPoint = CGPointMake(rotatedSize.width / 2.0f, rotatedSize.height / 2.0f); - - CGFloat scale = fittedOriginalSize.width / _photoEditor.originalSize.width; - CGPoint offset = TGPaintSubtractPoints(centerPoint, [self fittedCropCenterScale:scale]); - - CGPoint boundsCenter = TGPaintCenterOfRect(_contentWrapperView.bounds); - _entitiesContainerView.center = TGPaintAddPoints(boundsCenter, offset); - - if (!_skipEntitiesSetup || _entitiesReady) { - [_contentWrapperView addSubview:_entitiesContainerView]; - } - _entitiesReady = true; - [self resetScrollView]; -} - -- (void)prepareForCustomTransitionOut -{ - _previewView.hidden = true; - _canvasView.hidden = true; - _contentView.hidden = true; - [UIView animateWithDuration:0.3f animations:^ - { - _portraitToolsWrapperView.alpha = 0.0f; - _landscapeToolsWrapperView.alpha = 0.0f; - } completion:nil]; -} - -- (void)transitionOutSwitching:(bool)__unused switching completion:(void (^)(void))completion -{ - [_stickersScreen invalidate]; - - TGPhotoEditorPreviewView *previewView = self.previewView; - previewView.interactionEnded = nil; - - _portraitSettingsView.layer.shouldRasterize = true; - _landscapeSettingsView.layer.shouldRasterize = true; - - [UIView animateWithDuration:0.3f animations:^ - { - _portraitToolsWrapperView.alpha = 0.0f; - _landscapeToolsWrapperView.alpha = 0.0f; - - _portraitActionsView.alpha = 0.0f; - _landscapeActionsView.alpha = 0.0f; - } completion:^(__unused BOOL finished) - { - if (completion != nil) - completion(); - }]; -} - -- (CGRect)transitionOutSourceFrameForReferenceFrame:(CGRect)referenceFrame orientation:(UIInterfaceOrientation)orientation -{ - CGRect containerFrame = [TGPhotoPaintController photoContainerFrameForParentViewFrame:self.view.frame toolbarLandscapeSize:self.toolbarLandscapeSize orientation:orientation panelSize:TGPhotoPaintTopPanelSize + TGPhotoPaintBottomPanelSize hasOnScreenNavigation:self.hasOnScreenNavigation]; - - CGSize fittedSize = TGScaleToSize(referenceFrame.size, containerFrame.size); - return CGRectMake(containerFrame.origin.x + (containerFrame.size.width - fittedSize.width) / 2, containerFrame.origin.y + (containerFrame.size.height - fittedSize.height) / 2, fittedSize.width, fittedSize.height); -} - -- (void)_animatePreviewViewTransitionOutToFrame:(CGRect)targetFrame saving:(bool)saving parentView:(UIView *)parentView completion:(void (^)(void))completion -{ - _dismissing = true; - - [_entitySelectionView removeFromSuperview]; - _entitySelectionView = nil; - - TGPhotoEditorPreviewView *previewView = self.previewView; - [previewView prepareForTransitionOut]; - - UIInterfaceOrientation orientation = self.effectiveOrientation; - CGRect containerFrame = [TGPhotoPaintController photoContainerFrameForParentViewFrame:self.view.frame toolbarLandscapeSize:self.toolbarLandscapeSize orientation:orientation panelSize:TGPhotoPaintTopPanelSize + TGPhotoPaintBottomPanelSize hasOnScreenNavigation:self.hasOnScreenNavigation]; - CGRect referenceFrame = CGRectMake(0, 0, self.photoEditor.rotatedCropSize.width, self.photoEditor.rotatedCropSize.height); - CGRect rect = CGRectOffset([self transitionOutSourceFrameForReferenceFrame:referenceFrame orientation:orientation], -containerFrame.origin.x, -containerFrame.origin.y); - previewView.frame = rect; - - UIView *snapshotView = nil; - POPSpringAnimation *snapshotAnimation = nil; - NSMutableArray *animations = [[NSMutableArray alloc] init]; - - if (saving && CGRectIsNull(targetFrame) && parentView != nil) - { - snapshotView = [previewView snapshotViewAfterScreenUpdates:false]; - snapshotView.frame = [_containerView convertRect:previewView.frame toView:parentView]; - - UIView *canvasSnapshotView = [_paintingWrapperView resizableSnapshotViewFromRect:[_paintingWrapperView convertRect:previewView.bounds fromView:previewView] afterScreenUpdates:false withCapInsets:UIEdgeInsetsZero]; - canvasSnapshotView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - canvasSnapshotView.transform = _contentView.transform; - canvasSnapshotView.frame = snapshotView.bounds; - [snapshotView addSubview:canvasSnapshotView]; - - UIView *entitiesSnapshotView = [_contentWrapperView resizableSnapshotViewFromRect:[_contentWrapperView convertRect:previewView.bounds fromView:previewView] afterScreenUpdates:false withCapInsets:UIEdgeInsetsZero]; - entitiesSnapshotView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - entitiesSnapshotView.transform = _contentView.transform; - entitiesSnapshotView.frame = snapshotView.bounds; - [snapshotView addSubview:entitiesSnapshotView]; - - CGSize fittedSize = TGScaleToSize(previewView.frame.size, self.view.frame.size); - targetFrame = CGRectMake((self.view.frame.size.width - fittedSize.width) / 2, (self.view.frame.size.height - fittedSize.height) / 2, fittedSize.width, fittedSize.height); - - [parentView addSubview:snapshotView]; - - snapshotAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewFrame]; - snapshotAnimation.fromValue = [NSValue valueWithCGRect:snapshotView.frame]; - snapshotAnimation.toValue = [NSValue valueWithCGRect:targetFrame]; - [animations addObject:snapshotAnimation]; - } - - targetFrame = CGRectOffset(targetFrame, -containerFrame.origin.x, -containerFrame.origin.y); - CGPoint targetCenter = TGPaintCenterOfRect(targetFrame); - - POPSpringAnimation *previewAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewFrame]; - previewAnimation.fromValue = [NSValue valueWithCGRect:previewView.frame]; - previewAnimation.toValue = [NSValue valueWithCGRect:targetFrame]; - [animations addObject:previewAnimation]; - - POPSpringAnimation *previewAlphaAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewAlpha]; - previewAlphaAnimation.fromValue = @(previewView.alpha); - previewAlphaAnimation.toValue = @(0.0f); - [animations addObject:previewAnimation]; - - POPSpringAnimation *entitiesAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewCenter]; - entitiesAnimation.fromValue = [NSValue valueWithCGPoint:_contentView.center]; - entitiesAnimation.toValue = [NSValue valueWithCGPoint:targetCenter]; - [animations addObject:entitiesAnimation]; - - CGFloat targetEntitiesScale = targetFrame.size.width / _contentView.frame.size.width; - POPSpringAnimation *entitiesScaleAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewScaleXY]; - entitiesScaleAnimation.fromValue = [NSValue valueWithCGSize:CGSizeMake(1.0f, 1.0f)]; - entitiesScaleAnimation.toValue = [NSValue valueWithCGSize:CGSizeMake(targetEntitiesScale, targetEntitiesScale)]; - [animations addObject:entitiesScaleAnimation]; - - POPSpringAnimation *entitiesAlphaAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewAlpha]; - entitiesAlphaAnimation.fromValue = @(_canvasView.alpha); - entitiesAlphaAnimation.toValue = @(0.0f); - [animations addObject:entitiesAlphaAnimation]; - - POPSpringAnimation *paintingAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewCenter]; - paintingAnimation.fromValue = [NSValue valueWithCGPoint:_paintingWrapperView.center]; - paintingAnimation.toValue = [NSValue valueWithCGPoint:targetCenter]; - [animations addObject:paintingAnimation]; - - CGFloat targetPaintingScale = targetFrame.size.width / _paintingWrapperView.frame.size.width; - POPSpringAnimation *paintingScaleAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewScaleXY]; - paintingScaleAnimation.fromValue = [NSValue valueWithCGSize:CGSizeMake(1.0f, 1.0f)]; - paintingScaleAnimation.toValue = [NSValue valueWithCGSize:CGSizeMake(targetPaintingScale, targetPaintingScale)]; - [animations addObject:paintingScaleAnimation]; - - POPSpringAnimation *paintingAlphaAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewAlpha]; - paintingAlphaAnimation.fromValue = @(_paintingWrapperView.alpha); - paintingAlphaAnimation.toValue = @(0.0f); - [animations addObject:paintingAlphaAnimation]; - - [TGPhotoEditorAnimation performBlock:^(__unused bool allFinished) - { - [snapshotView removeFromSuperview]; - - if (completion != nil) - completion(); - } whenCompletedAllAnimations:animations]; - - if (snapshotAnimation != nil) - [snapshotView pop_addAnimation:snapshotAnimation forKey:@"frame"]; - [previewView pop_addAnimation:previewAnimation forKey:@"frame"]; - [previewView pop_addAnimation:previewAlphaAnimation forKey:@"alpha"]; - - [_contentView pop_addAnimation:entitiesAnimation forKey:@"frame"]; - [_contentView pop_addAnimation:entitiesScaleAnimation forKey:@"scale"]; - [_contentView pop_addAnimation:entitiesAlphaAnimation forKey:@"alpha"]; - - [_paintingWrapperView pop_addAnimation:paintingAnimation forKey:@"frame"]; - [_paintingWrapperView pop_addAnimation:paintingScaleAnimation forKey:@"scale"]; - [_paintingWrapperView pop_addAnimation:paintingAlphaAnimation forKey:@"alpha"]; - - if (saving) - { - _contentView.hidden = true; - _paintingWrapperView.hidden = true; - previewView.hidden = true; - } -} - -- (CGRect)transitionOutReferenceFrame -{ - TGPhotoEditorPreviewView *previewView = _previewView; - return [previewView convertRect:previewView.bounds toView:self.view]; -} - -- (UIView *)transitionOutReferenceView -{ - return _previewView; -} - -- (UIView *)snapshotView -{ - TGPhotoEditorPreviewView *previewView = self.previewView; - return [previewView originalSnapshotView]; -} - -- (void)setInterfaceHidden:(bool)hidden animated:(bool)animated -{ - CGFloat targetAlpha = hidden ? 0.0f : 1.0; - void (^changeBlock)(void) = ^ - { - _portraitActionsView.alpha = targetAlpha; - _landscapeActionsView.alpha = targetAlpha; - _portraitSettingsView.alpha = targetAlpha; - _landscapeSettingsView.alpha = targetAlpha; - }; - - if (animated) - [UIView animateWithDuration:0.25 animations:changeBlock]; - else - changeBlock(); - - TGPhotoEditorController *editorController = (TGPhotoEditorController *)self.parentViewController; - if (![editorController isKindOfClass:[TGPhotoEditorController class]]) - return; - - [editorController setToolbarHidden:hidden animated:animated]; -} - -- (void)setDimHidden:(bool)hidden animated:(bool)animated -{ - if (!hidden) - { - [_entitySelectionView fadeOut]; - - if ([_currentEntityView isKindOfClass:[TGPhotoTextEntityView class]]) - [_dimView.superview insertSubview:_dimView belowSubview:_currentEntityView]; - else - [_dimView.superview bringSubviewToFront:_dimView]; - - [_doneButton.superview bringSubviewToFront:_doneButton]; - } - else - { - [_entitySelectionView fadeIn]; - - [_dimView.superview bringSubviewToFront:_dimView]; - - [_doneButton.superview bringSubviewToFront:_doneButton]; - } - - void (^changeBlock)(void) = ^ - { - _dimView.alpha = hidden ? 0.0f : 1.0f; - _doneButton.alpha = hidden ? 0.0f : 1.0f; - }; - - if (animated) - [UIView animateWithDuration:0.25 animations:changeBlock]; - else - changeBlock(); -} - -- (id)currentResultRepresentation -{ - return TGPaintCombineCroppedImages(self.photoEditor.currentResultImage, [self image], true, _photoEditor.originalSize, _photoEditor.cropRect, _photoEditor.cropOrientation, _photoEditor.cropRotation, false); -} - -#pragma mark - Layout - -- (void)viewWillLayoutSubviews -{ - [super viewWillLayoutSubviews]; - - [self updateLayout:[[LegacyComponentsGlobals provider] applicationStatusBarOrientation]]; - [_entitySelectionView update]; -} - -- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration -{ - [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration]; - - if (_menuContainerView != nil) - { - [_menuContainerView removeFromSuperview]; - _menuContainerView = nil; - } - - [self updateLayout:toInterfaceOrientation]; -} - -- (void)updateContentViewLayout -{ - CGAffineTransform rotationTransform = CGAffineTransformMakeRotation(TGRotationForOrientation(_photoEditor.cropOrientation)); - _contentView.transform = rotationTransform; - _contentView.frame = self.previewView.frame; - [self resetScrollView]; -} - -- (void)updateLayout:(UIInterfaceOrientation)orientation -{ - if ([self inFormSheet] || [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) - { - _landscapeToolsWrapperView.hidden = true; - orientation = UIInterfaceOrientationPortrait; - } - - CGSize referenceSize = [self referenceViewSize]; - CGFloat screenSide = MAX(referenceSize.width, referenceSize.height) + 2 * TGPhotoPaintBottomPanelSize; - - bool sizeUpdated = false; - if (!CGSizeEqualToSize(referenceSize, _previousSize)) { - sizeUpdated = true; - _previousSize = referenceSize; - } - - CGFloat panelToolbarPortraitSize = TGPhotoPaintBottomPanelSize + TGPhotoEditorToolbarSize; - CGFloat panelToolbarLandscapeSize = TGPhotoPaintBottomPanelSize + self.toolbarLandscapeSize; - - UIEdgeInsets safeAreaInset = [TGViewController safeAreaInsetForOrientation:orientation hasOnScreenNavigation:self.hasOnScreenNavigation]; - UIEdgeInsets screenEdges = UIEdgeInsetsMake((screenSide - referenceSize.height) / 2, (screenSide - referenceSize.width) / 2, (screenSide + referenceSize.height) / 2, (screenSide + referenceSize.width) / 2); - screenEdges.top += safeAreaInset.top; - screenEdges.left += safeAreaInset.left; - screenEdges.bottom -= safeAreaInset.bottom; - screenEdges.right -= safeAreaInset.right; - - CGRect containerFrame = [TGPhotoPaintController photoContainerFrameForParentViewFrame:CGRectMake(0, 0, referenceSize.width, referenceSize.height) toolbarLandscapeSize:self.toolbarLandscapeSize orientation:orientation panelSize:TGPhotoPaintTopPanelSize + TGPhotoPaintBottomPanelSize hasOnScreenNavigation:self.hasOnScreenNavigation]; - - _settingsViewWrapper.frame = self.parentViewController.view.bounds; - - _doneButton.frame = CGRectMake(screenEdges.right - _doneButton.frame.size.width - 8.0, screenEdges.top + 2.0, _doneButton.frame.size.width, _doneButton.frame.size.height); - - if (_settingsView != nil) - [_settingsView setInterfaceOrientation:orientation]; - - switch (orientation) - { - case UIInterfaceOrientationLandscapeLeft: - { - _landscapeSettingsView.interfaceOrientation = orientation; - - [UIView performWithoutAnimation:^ - { - _landscapeToolsWrapperView.frame = CGRectMake(0, screenEdges.top, panelToolbarLandscapeSize, _landscapeToolsWrapperView.frame.size.height); - _landscapeSettingsView.frame = CGRectMake(panelToolbarLandscapeSize - TGPhotoPaintBottomPanelSize, 0, TGPhotoPaintBottomPanelSize, _landscapeSettingsView.frame.size.height); - }]; - - _landscapeToolsWrapperView.frame = CGRectMake(screenEdges.left, screenEdges.top, panelToolbarLandscapeSize, referenceSize.height); - _landscapeSettingsView.frame = CGRectMake(_landscapeSettingsView.frame.origin.x, _landscapeSettingsView.frame.origin.y, _landscapeSettingsView.frame.size.width, _landscapeToolsWrapperView.frame.size.height); - - _portraitToolsWrapperView.frame = CGRectMake(screenEdges.left, screenSide - panelToolbarPortraitSize, referenceSize.width, panelToolbarPortraitSize); - _portraitSettingsView.frame = CGRectMake(0, 0, _portraitToolsWrapperView.frame.size.width, TGPhotoPaintBottomPanelSize); - - _landscapeActionsView.frame = CGRectMake(screenEdges.right - TGPhotoPaintTopPanelSize, screenEdges.top, TGPhotoPaintTopPanelSize, referenceSize.height); - - _settingsView.frame = CGRectMake(self.toolbarLandscapeSize + 50.0f + safeAreaInset.left, 0.0f, _settingsView.frame.size.width, _settingsView.frame.size.height); - } - break; - - case UIInterfaceOrientationLandscapeRight: - { - _landscapeSettingsView.interfaceOrientation = orientation; - - [UIView performWithoutAnimation:^ - { - _landscapeToolsWrapperView.frame = CGRectMake(screenSide - panelToolbarLandscapeSize, screenEdges.top, panelToolbarLandscapeSize, _landscapeToolsWrapperView.frame.size.height); - _landscapeSettingsView.frame = CGRectMake(0, 0, TGPhotoPaintBottomPanelSize, _landscapeSettingsView.frame.size.height); - }]; - - _landscapeToolsWrapperView.frame = CGRectMake(screenEdges.right - panelToolbarLandscapeSize, screenEdges.top, panelToolbarLandscapeSize, referenceSize.height); - _landscapeSettingsView.frame = CGRectMake(_landscapeSettingsView.frame.origin.x, _landscapeSettingsView.frame.origin.y, _landscapeSettingsView.frame.size.width, _landscapeToolsWrapperView.frame.size.height); - - _portraitToolsWrapperView.frame = CGRectMake(screenEdges.top, screenSide - panelToolbarPortraitSize, referenceSize.width, panelToolbarPortraitSize); - _portraitSettingsView.frame = CGRectMake(0, 0, _portraitToolsWrapperView.frame.size.width, TGPhotoPaintBottomPanelSize); - - _landscapeActionsView.frame = CGRectMake(screenEdges.left, screenEdges.top, TGPhotoPaintTopPanelSize, referenceSize.height); - - _settingsView.frame = CGRectMake(_settingsViewWrapper.frame.size.width - _settingsView.frame.size.width - self.toolbarLandscapeSize - 50.0f - safeAreaInset.right, 0.0f, _settingsView.frame.size.width, _settingsView.frame.size.height); - } - break; - - default: - { - CGFloat x = _landscapeToolsWrapperView.frame.origin.x; - if (x < screenSide / 2) - x = 0; - else - x = screenSide - TGPhotoEditorPanelSize; - _landscapeToolsWrapperView.frame = CGRectMake(x, screenEdges.top, panelToolbarLandscapeSize, referenceSize.height); - - _portraitToolsWrapperView.frame = CGRectMake(screenEdges.left, screenEdges.bottom - panelToolbarPortraitSize, referenceSize.width, panelToolbarPortraitSize); - _portraitSettingsView.frame = CGRectMake(0, 0, referenceSize.width, TGPhotoPaintBottomPanelSize); - - _portraitActionsView.frame = CGRectMake(screenEdges.left, screenEdges.top, referenceSize.width, TGPhotoPaintTopPanelSize); - - if ([_context currentSizeClass] == UIUserInterfaceSizeClassRegular) - { - _settingsView.frame = CGRectMake(_settingsViewWrapper.frame.size.width / 2.0f - 10.0f, _settingsViewWrapper.frame.size.height - _settingsView.frame.size.height - TGPhotoEditorToolbarSize - 50.0f, _settingsView.frame.size.width, _settingsView.frame.size.height); - } - else - { - _settingsView.frame = CGRectMake(_settingsViewWrapper.frame.size.width - _settingsView.frame.size.width, _settingsViewWrapper.frame.size.height - _settingsView.frame.size.height - TGPhotoEditorToolbarSize - 50.0f - safeAreaInset.bottom, _settingsView.frame.size.width, _settingsView.frame.size.height); - } - } - break; - } - - PGPhotoEditor *photoEditor = self.photoEditor; - TGPhotoEditorPreviewView *previewView = self.previewView; - - CGSize fittedSize = TGScaleToSize(photoEditor.rotatedCropSize, containerFrame.size); - CGRect previewFrame = CGRectMake((containerFrame.size.width - fittedSize.width) / 2, (containerFrame.size.height - fittedSize.height) / 2, fittedSize.width, fittedSize.height); - - CGFloat visibleArea = self.view.frame.size.height - _keyboardHeight; - CGFloat yCenter = visibleArea / 2.0f; - CGFloat offset = yCenter - _previewView.center.y - containerFrame.origin.y; - CGFloat offsetHeight = _keyboardHeight > FLT_EPSILON ? offset : 0.0f; - - _wrapperView.frame = CGRectMake((referenceSize.width - screenSide) / 2, (referenceSize.height - screenSide) / 2 + offsetHeight, screenSide, screenSide); - - if (_dismissing || (previewView.superview != _containerView && previewView.superview != self.view)) - return; - - if (previewView.superview == self.view) - { - previewFrame = CGRectMake(containerFrame.origin.x + (containerFrame.size.width - fittedSize.width) / 2, containerFrame.origin.y + (containerFrame.size.height - fittedSize.height) / 2, fittedSize.width, fittedSize.height); - } - - UIImageOrientation cropOrientation = _photoEditor.cropOrientation; - CGRect cropRect = _photoEditor.cropRect; - CGSize originalSize = _photoEditor.originalSize; - CGFloat rotation = _photoEditor.cropRotation; - - CGAffineTransform rotationTransform = CGAffineTransformMakeRotation(TGRotationForOrientation(cropOrientation)); - _contentView.transform = rotationTransform; - _contentView.frame = previewFrame; - - _scrollView.frame = self.view.bounds; - - if (sizeUpdated) { - [self resetScrollView]; - } - [self adjustZoom]; - - _paintingWrapperView.transform = CGAffineTransformMakeRotation(TGRotationForOrientation(cropOrientation)); - _paintingWrapperView.frame = previewFrame; - - CGFloat originalWidth = TGOrientationIsSideward(cropOrientation, NULL) ? previewFrame.size.height : previewFrame.size.width; - CGFloat ratio = originalWidth / cropRect.size.width; - CGRect originalFrame = CGRectMake(-cropRect.origin.x * ratio, -cropRect.origin.y * ratio, originalSize.width * ratio, originalSize.height * ratio); - - previewView.frame = previewFrame; - - if ([self presentedForAvatarCreation]) { - CGAffineTransform transform = CGAffineTransformMakeRotation(TGRotationForOrientation(photoEditor.cropOrientation)); - if (photoEditor.cropMirrored) - transform = CGAffineTransformScale(transform, -1.0f, 1.0f); - previewView.transform = transform; - } - - CGSize fittedOriginalSize = CGSizeMake(originalSize.width * ratio, originalSize.height * ratio); - CGSize rotatedSize = TGRotatedContentSize(fittedOriginalSize, rotation); - CGPoint centerPoint = CGPointMake(rotatedSize.width / 2.0f, rotatedSize.height / 2.0f); - - CGFloat scale = fittedOriginalSize.width / _photoEditor.originalSize.width; - CGPoint centerOffset = TGPaintSubtractPoints(centerPoint, [self fittedCropCenterScale:scale]); - - _canvasView.transform = CGAffineTransformIdentity; - _canvasView.frame = originalFrame; - _canvasView.transform = CGAffineTransformMakeRotation(rotation); - _canvasView.center = TGPaintAddPoints(TGPaintCenterOfRect(_paintingWrapperView.bounds), centerOffset); - - _selectionContainerView.transform = CGAffineTransformRotate(rotationTransform, rotation); - _selectionContainerView.frame = previewFrame; - _eyedropperView.frame = _selectionContainerView.bounds; - - _containerView.frame = CGRectMake(containerFrame.origin.x, containerFrame.origin.y + offsetHeight, containerFrame.size.width, containerFrame.size.height); -} - -#pragma mark - Keyboard Avoidance - -- (void)keyboardWillChangeFrame:(NSNotification *)notification -{ - UIView *parentView = self.view; - - NSTimeInterval duration = notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] == nil ? 0.3 : [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; - int curve = [notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] intValue]; - CGRect screenKeyboardFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; - CGRect keyboardFrame = [parentView convertRect:screenKeyboardFrame fromView:nil]; - - CGFloat keyboardHeight = (keyboardFrame.size.height <= FLT_EPSILON || keyboardFrame.size.width <= FLT_EPSILON) ? 0.0f : (parentView.frame.size.height - keyboardFrame.origin.y); - keyboardHeight = MAX(keyboardHeight, 0.0f); - - _keyboardHeight = keyboardHeight; - - [self keyboardHeightChangedTo:keyboardHeight duration:duration curve:curve]; -} - -- (void)keyboardHeightChangedTo:(CGFloat)height duration:(NSTimeInterval)duration curve:(NSInteger)curve -{ - CGSize referenceSize = [self referenceViewSize]; - CGFloat screenSide = MAX(referenceSize.width, referenceSize.height) + 2 * TGPhotoPaintBottomPanelSize; - - CGRect containerFrame = [TGPhotoPaintController photoContainerFrameForParentViewFrame:CGRectMake(0, 0, referenceSize.width, referenceSize.height) toolbarLandscapeSize:self.toolbarLandscapeSize orientation:self.effectiveOrientation panelSize:TGPhotoPaintTopPanelSize + TGPhotoPaintBottomPanelSize hasOnScreenNavigation:self.hasOnScreenNavigation]; - - CGFloat visibleArea = self.view.frame.size.height - height; - CGFloat yCenter = visibleArea / 2.0f; - CGFloat offset = yCenter - _previewView.center.y - containerFrame.origin.y; - CGFloat offsetHeight = height > FLT_EPSILON ? offset : 0.0f; - - [UIView animateWithDuration:duration delay:0.0 options:curve animations:^ - { - _wrapperView.frame = CGRectMake((referenceSize.width - screenSide) / 2, (referenceSize.height - screenSide) / 2 + offsetHeight, _wrapperView.frame.size.width, _wrapperView.frame.size.height); - _containerView.frame = CGRectMake(containerFrame.origin.x, containerFrame.origin.y + offsetHeight, containerFrame.size.width, containerFrame.size.height); - } completion:nil]; -} - -- (void)_setStickerEntityPosition:(TGPhotoPaintStickerEntity *)entity -{ - TGStickerMaskDescription *mask = [_stickersContext maskDescriptionForDocument:entity.document]; - int64_t documentId = [_stickersContext documentIdForDocument:entity.document]; - TGPhotoMaskPosition *position = [self _positionForMaskDescription:mask documentId:documentId]; - if (position != nil) - { - entity.position = position.center; - entity.angle = position.angle; - entity.scale = position.scale; - } - else - { - entity.position = [self startPositionRelativeToEntity:nil]; - entity.angle = [self startRotation]; - } -} - -- (TGPhotoMaskPosition *)_positionForMaskDescription:(TGStickerMaskDescription *)mask documentId:(int64_t)documentId -{ - if (mask == nil) - return nil; - - TGPhotoMaskAnchor anchor = [TGPhotoMaskPosition anchorOfMask:mask]; - if (anchor == TGPhotoMaskAnchorNone) - return nil; - - TGPaintFace *face = [self _randomFaceWithVacantAnchor:anchor documentId:documentId]; - if (face == nil) - return nil; - - CGPoint referencePoint = CGPointZero; - CGFloat referenceWidth = 0.0f; - CGFloat angle = 0.0f; - CGSize baseSize = [self _stickerBaseSizeForCurrentPainting]; - CGRect faceBounds = [TGPaintFaceUtils transposeRect:face.bounds paintingSize:_painting.size originalSize:_photoEditor.originalSize]; - - switch (anchor) - { - case TGPhotoMaskAnchorForehead: - { - referencePoint = [TGPaintFaceUtils transposePoint:[face foreheadPoint] paintingSize:_painting.size originalSize:_photoEditor.originalSize]; - referenceWidth = faceBounds.size.width; - angle = face.angle; - } - break; - - case TGPhotoMaskAnchorEyes: - { - CGPoint point = [face eyesCenterPointAndDistance:&referenceWidth]; - referenceWidth = [TGPaintFaceUtils transposeWidth:referenceWidth paintingSize:_painting.size originalSize:_photoEditor.originalSize]; - referencePoint = [TGPaintFaceUtils transposePoint:point paintingSize:_painting.size originalSize:_photoEditor.originalSize]; - angle = [face eyesAngle]; - } - break; - - case TGPhotoMaskAnchorMouth: - { - referencePoint = [TGPaintFaceUtils transposePoint:[face mouthPoint] paintingSize:_painting.size originalSize:_photoEditor.originalSize]; - referenceWidth = faceBounds.size.width; - angle = face.angle; - } - break; - - case TGPhotoMaskAnchorChin: - { - referencePoint = [TGPaintFaceUtils transposePoint:[face chinPoint] paintingSize:_painting.size originalSize:_photoEditor.originalSize]; - referenceWidth = faceBounds.size.width; - angle = face.angle; - } - break; - - default: - break; - } - - CGFloat scale = referenceWidth / baseSize.width * mask.zoom; - - CGPoint xComp = CGPointMake(sin(M_PI_2 - angle) * referenceWidth * mask.point.x, - cos(M_PI_2 - angle) * referenceWidth * mask.point.x); - CGPoint yComp = CGPointMake(cos(M_PI_2 + angle) * referenceWidth * mask.point.y, - sin(M_PI_2 + angle) * referenceWidth * mask.point.y); - - CGPoint position = CGPointMake(referencePoint.x + xComp.x + yComp.x, referencePoint.y + xComp.y + yComp.y); - - return [TGPhotoMaskPosition maskPositionWithCenter:position scale:scale angle:angle]; -} - -- (TGPaintFace *)_randomFaceWithVacantAnchor:(TGPhotoMaskAnchor)anchor documentId:(int64_t)documentId -{ - NSInteger randomIndex = (NSInteger)arc4random_uniform((uint32_t)self.faces.count); - NSInteger count = self.faces.count; - NSInteger remaining = self.faces.count; - - for (NSInteger i = randomIndex; remaining > 0; (i = (i + 1) % count), remaining--) - { - TGPaintFace *face = self.faces[i]; - if (![self _isFaceAnchorOccupied:face anchor:anchor documentId:documentId]) - return face; - } - - return nil; -} - -- (bool)_isFaceAnchorOccupied:(TGPaintFace *)face anchor:(TGPhotoMaskAnchor)anchor documentId:(int64_t)documentId -{ - CGPoint anchorPoint = CGPointZero; - switch (anchor) - { - case TGPhotoMaskAnchorForehead: - { - anchorPoint = [TGPaintFaceUtils transposePoint:[face foreheadPoint] paintingSize:_painting.size originalSize:_photoEditor.originalSize]; - } - break; - - case TGPhotoMaskAnchorEyes: - { - anchorPoint = [TGPaintFaceUtils transposePoint:[face eyesCenterPointAndDistance:NULL] paintingSize:_painting.size originalSize:_photoEditor.originalSize]; - } - break; - - case TGPhotoMaskAnchorMouth: - { - anchorPoint = [TGPaintFaceUtils transposePoint:[face mouthPoint] paintingSize:_painting.size originalSize:_photoEditor.originalSize]; - } - break; - - case TGPhotoMaskAnchorChin: - { - anchorPoint = [TGPaintFaceUtils transposePoint:[face chinPoint] paintingSize:_painting.size originalSize:_photoEditor.originalSize]; - } - break; - - default: - { - - } - break; - } - - CGRect faceBounds = [TGPaintFaceUtils transposeRect:face.bounds paintingSize:_painting.size originalSize:_photoEditor.originalSize]; - CGFloat minDistance = faceBounds.size.width * 1.1; - - for (TGPhotoStickerEntityView *view in _entitiesContainerView.subviews) - { - if (![view isKindOfClass:[TGPhotoStickerEntityView class]]) - continue; - - TGPhotoPaintStickerEntity *entity = view.entity; - TGStickerMaskDescription *mask = [_stickersContext maskDescriptionForDocument:view.entity.document]; - int64_t maskDocumentId = [_stickersContext documentIdForDocument:entity.document]; - - if ([TGPhotoMaskPosition anchorOfMask:mask] != anchor) - continue; - - if ((documentId == maskDocumentId || self.faces.count > 1) && TGPaintDistance(entity.position, anchorPoint) < minDistance) - return true; - } - - return false; -} - -- (NSArray *)faces -{ - TGPhotoEditorController *editorController = (TGPhotoEditorController *)self.parentViewController; - if ([editorController isKindOfClass:[TGPhotoEditorController class]]) - return editorController.faces; - else - return @[]; -} - -- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures -{ - return UIRectEdgeTop | UIRectEdgeBottom; -} - -@end +//#import "TGPhotoPaintController.h" +// +//#import "LegacyComponentsInternal.h" +// +//#import +// +//#import +//#import +//#import +//#import "TGPhotoEditorInterfaceAssets.h" +//#import +// +//#import +//#import +// +//#import "TGMenuSheetController.h" +// +//#import +//#import +// +//#import "TGPainting.h" +//#import +//#import "TGPaintRadialBrush.h" +//#import "TGPaintEllipticalBrush.h" +//#import "TGPaintNeonBrush.h" +//#import "TGPaintArrowBrush.h" +//#import "TGPaintCanvas.h" +//#import "TGPaintingWrapperView.h" +//#import "TGPaintState.h" +//#import "TGPaintBrushPreview.h" +//#import "TGPaintSwatch.h" +//#import "TGPhotoPaintFont.h" +//#import +// +//#import "PGPhotoEditor.h" +//#import "TGPhotoEditorPreviewView.h" +// +//#import "TGPhotoPaintActionsView.h" +//#import "TGPhotoPaintSettingsView.h" +// +//#import "TGPhotoPaintSettingsWrapperView.h" +//#import "TGPhotoBrushSettingsView.h" +//#import "TGPhotoTextSettingsView.h" +// +//#import "TGPhotoPaintSelectionContainerView.h" +//#import "TGPhotoEntitiesContainerView.h" +//#import "TGPhotoStickerEntityView.h" +//#import "TGPhotoTextEntityView.h" +//#import "TGPhotoPaintEyedropperView.h" +// +//#import "TGPaintFaceDetector.h" +//#import "TGPhotoMaskPosition.h" +// +//const CGFloat TGPhotoPaintTopPanelSize = 44.0f; +//const CGFloat TGPhotoPaintBottomPanelSize = 79.0f; +//const CGSize TGPhotoPaintingLightMaxSize = { 1280.0f, 1280.0f }; +//const CGSize TGPhotoPaintingMaxSize = { 2560.0f, 2560.0f }; +// +//const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; +// +//@interface TGPhotoPaintController () +//{ +// TGPaintUndoManager *_undoManager; +// TGObserverProxy *_keyboardWillChangeFrameProxy; +// CGFloat _keyboardHeight; +// +// TGModernGalleryZoomableScrollView *_scrollView; +// UIView *_scrollContentView; +// +// UIButton *_containerView; +// TGPhotoEditorSparseView *_wrapperView; +// UIView *_portraitToolsWrapperView; +// UIView *_landscapeToolsWrapperView; +// +// UIPinchGestureRecognizer *_pinchGestureRecognizer; +// UIRotationGestureRecognizer *_rotationGestureRecognizer; +// +// NSArray *_brushes; +// TGPainting *_painting; +// TGPaintCanvas *_canvasView; +// TGPaintBrushPreview *_brushPreview; +// +// CGSize _previousSize; +// UIView *_contentView; +// UIView *_contentWrapperView; +// +// UIView *_dimView; +// TGModernButton *_doneButton; +// +// TGPhotoPaintActionsView *_landscapeActionsView; +// TGPhotoPaintActionsView *_portraitActionsView; +// +// TGPhotoPaintSettingsView *_portraitSettingsView; +// TGPhotoPaintSettingsView *_landscapeSettingsView; +// +// TGPhotoPaintSettingsWrapperView *_settingsViewWrapper; +// UIView *_settingsView; +// id _stickersScreen; +// +// double _stickerStartTime; +// +// bool _appeared; +// bool _skipEntitiesSetup; +// bool _entitiesReady; +// +// TGPhotoPaintFont *_selectedTextFont; +// TGPhotoPaintTextEntityStyle _selectedTextStyle; +// +// TGPhotoEntitiesContainerView *_entitiesContainerView; +// TGPhotoPaintEntityView *_currentEntityView; +// +// TGPhotoPaintSelectionContainerView *_selectionContainerView; +// TGPhotoPaintEntitySelectionView *_entitySelectionView; +// TGPhotoPaintEyedropperView *_eyedropperView; +// +// TGPhotoTextEntityView *_editedTextView; +// CGPoint _editedTextCenter; +// CGAffineTransform _editedTextTransform; +// UIButton *_textEditingDismissButton; +// +// TGMenuContainerView *_menuContainerView; +// +// TGPaintingData *_resultData; +// +// TGPaintingWrapperView *_paintingWrapperView; +// +// bool _enableStickers; +// +// NSData *_eyedropperBackgroundData; +// CGSize _eyedropperBackgroundSize; +// NSInteger _eyedropperBackgroundBytesPerRow; +// CGBitmapInfo _eyedropperBackgroundInfo; +// +// id _context; +//} +// +//@property (nonatomic, strong) ASHandle *actionHandle; +// +//@property (nonatomic, weak) PGPhotoEditor *photoEditor; +//@property (nonatomic, weak) TGPhotoEditorPreviewView *previewView; +// +//@end +// +//@implementation TGPhotoPaintController +// +//- (instancetype)initWithContext:(id)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView entitiesView:(TGPhotoEntitiesContainerView *)entitiesView +//{ +// self = [super initWithContext:context]; +// if (self != nil) +// { +// _context = context; +// _enableStickers = photoEditor.enableStickers; +// +// _stickerStartTime = NAN; +// +// _actionHandle = [[ASHandle alloc] initWithDelegate:self releaseOnMainThread:true]; +// +// self.photoEditor = photoEditor; +// self.previewView = previewView; +// _entitiesContainerView = entitiesView; +// if (entitiesView != nil) { +// _skipEntitiesSetup = true; +// } +// entitiesView.userInteractionEnabled = true; +// +// _brushes = @ +// [ +// [[TGPaintRadialBrush alloc] init], +// [[TGPaintEllipticalBrush alloc] init], +// [[TGPaintNeonBrush alloc] init], +// [[TGPaintArrowBrush alloc] init], +// ]; +// _selectedTextFont = [[TGPhotoPaintFont availableFonts] firstObject]; +// _selectedTextStyle = TGPhotoPaintTextEntityStyleFramed; +// +// _undoManager = [[TGPaintUndoManager alloc] init]; +// +// CGSize size = TGScaleToSize(photoEditor.originalSize, [TGPhotoPaintController maximumPaintingSize]); +//// _painting = [[TGPainting alloc] initWithSize:size undoManager:_undoManager imageData:[_photoEditor.paintingData data]]; +// _undoManager.painting = _painting; +// +// _keyboardWillChangeFrameProxy = [[TGObserverProxy alloc] initWithTarget:self targetSelector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification]; +// } +// return self; +//} +// +//- (void)dealloc +//{ +// [_actionHandle reset]; +//} +// +//- (void)loadView +//{ +// [super loadView]; +// self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; +// +// _scrollView = [[TGModernGalleryZoomableScrollView alloc] initWithFrame:self.view.bounds hasDoubleTap:false]; +// if (@available(iOS 11.0, *)) { +// _scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; +// } +// _scrollView.contentInset = UIEdgeInsetsZero; +// _scrollView.delegate = self; +// _scrollView.showsHorizontalScrollIndicator = false; +// _scrollView.showsVerticalScrollIndicator = false; +// [self.view addSubview:_scrollView]; +// +// _scrollContentView = [[UIView alloc] initWithFrame:self.view.bounds]; +// [_scrollView addSubview:_scrollContentView]; +// +// _containerView = [[UIButton alloc] initWithFrame:self.view.bounds]; +// _containerView.clipsToBounds = true; +// [_containerView addTarget:self action:@selector(containerPressed) forControlEvents:UIControlEventTouchUpInside]; +// [_scrollContentView addSubview:_containerView]; +// +// _pinchGestureRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinch:)]; +// _pinchGestureRecognizer.delegate = self; +// [_containerView addGestureRecognizer:_pinchGestureRecognizer]; +// +// _rotationGestureRecognizer = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(handleRotate:)]; +// _rotationGestureRecognizer.delegate = self; +// [_containerView addGestureRecognizer:_rotationGestureRecognizer]; +// +// TGPhotoEditorPreviewView *previewView = _previewView; +// previewView.userInteractionEnabled = false; +// previewView.hidden = true; +// +// __weak TGPhotoPaintController *weakSelf = self; +// _paintingWrapperView = [[TGPaintingWrapperView alloc] init]; +// _paintingWrapperView.clipsToBounds = true; +// _paintingWrapperView.shouldReceiveTouch = ^bool +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf == nil) +// return false; +// +// return (strongSelf->_editedTextView == nil); +// }; +// [_containerView addSubview:_paintingWrapperView]; +// +// _contentView = [[UIView alloc] init]; +// _contentView.clipsToBounds = true; +// _contentView.userInteractionEnabled = false; +// [_containerView addSubview:_contentView]; +// +// _contentWrapperView = [[UIView alloc] init]; +// _contentWrapperView.userInteractionEnabled = false; +// [_contentView addSubview:_contentWrapperView]; +// +// if (_entitiesContainerView == nil) { +// _entitiesContainerView = [[TGPhotoEntitiesContainerView alloc] init]; +// _entitiesContainerView.clipsToBounds = true; +// _entitiesContainerView.stickersContext = _stickersContext; +// } +// _entitiesContainerView.entitySelected = ^(TGPhotoPaintEntityView *sender) +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf == nil) +// return; +// +// [strongSelf selectEntityView:sender]; +// }; +// _entitiesContainerView.entityRemoved = ^(TGPhotoPaintEntityView *entity) +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf == nil) +// return; +// +// if (entity == strongSelf->_currentEntityView) +// [strongSelf _clearCurrentSelection]; +// +// [strongSelf updateSettingsButton]; +// }; +// if (!_skipEntitiesSetup) { +// [_contentWrapperView addSubview:_entitiesContainerView]; +// } +// _undoManager.entitiesContainer = _entitiesContainerView; +// +// _dimView = [[UIView alloc] init]; +// _dimView.alpha = 0.0f; +// _dimView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; +// _dimView.backgroundColor = UIColorRGBA(0x000000, 0.4f); +// _dimView.userInteractionEnabled = false; +// [_entitiesContainerView addSubview:_dimView]; +// +// _selectionContainerView = [[TGPhotoPaintSelectionContainerView alloc] init]; +// _selectionContainerView.clipsToBounds = false; +// [_containerView addSubview:_selectionContainerView]; +// +// _eyedropperView = [[TGPhotoPaintEyedropperView alloc] init]; +// _eyedropperView.locationChanged = ^(CGPoint location, bool finished) { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf != nil) +// { +// UIColor *color = [strongSelf colorAtPoint:location]; +// strongSelf->_eyedropperView.color = color; +// +// if (finished) { +// TGPaintSwatch *swatch = [TGPaintSwatch swatchWithColor:color colorLocation:0.5 brushWeight:strongSelf->_portraitSettingsView.swatch.brushWeight]; +// [strongSelf setCurrentSwatch:swatch sender:nil]; +// +// [strongSelf commitEyedropper:false]; +// } +// } +// }; +// _eyedropperView.hidden = true; +// [_selectionContainerView addSubview:_eyedropperView]; +// +// _wrapperView = [[TGPhotoEditorSparseView alloc] initWithFrame:CGRectZero]; +// [self.view addSubview:_wrapperView]; +// +// _portraitToolsWrapperView = [[UIView alloc] initWithFrame:CGRectZero]; +// _portraitToolsWrapperView.alpha = 0.0f; +// [_wrapperView addSubview:_portraitToolsWrapperView]; +// +// _landscapeToolsWrapperView = [[UIView alloc] initWithFrame:CGRectZero]; +// _landscapeToolsWrapperView.alpha = 0.0f; +// [_wrapperView addSubview:_landscapeToolsWrapperView]; +// +// void (^undoPressed)(void) = ^ +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf != nil) +// [strongSelf->_undoManager undo]; +// }; +// +// void (^clearPressed)(UIView *) = ^(UIView *sender) +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf != nil) +// [strongSelf presentClearAllAlert:sender]; +// }; +// +// _portraitActionsView = [[TGPhotoPaintActionsView alloc] init]; +// _portraitActionsView.alpha = 0.0f; +// _portraitActionsView.undoPressed = undoPressed; +// _portraitActionsView.clearPressed = clearPressed; +// [_wrapperView addSubview:_portraitActionsView]; +// +// _landscapeActionsView = [[TGPhotoPaintActionsView alloc] init]; +// _landscapeActionsView.alpha = 0.0f; +// _landscapeActionsView.undoPressed = undoPressed; +// _landscapeActionsView.clearPressed = clearPressed; +// [_wrapperView addSubview:_landscapeActionsView]; +// +// _doneButton = [[TGModernButton alloc] init]; +// _doneButton.alpha = 0.0f; +// _doneButton.userInteractionEnabled = false; +// [_doneButton setTitle:TGLocalized(@"Common.Done") forState:UIControlStateNormal]; +// _doneButton.titleLabel.font = TGSystemFontOfSize(17.0); +// [_doneButton sizeToFit]; +//// [_wrapperView addSubview:_doneButton]; +// +// void (^settingsPressed)(void) = ^ +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf == nil) +// return; +// +// [strongSelf commitEyedropper:true]; +// +// if ([strongSelf->_currentEntityView isKindOfClass:[TGPhotoTextEntityView class]]) +// [strongSelf presentTextSettingsView]; +// else if ([strongSelf->_currentEntityView isKindOfClass:[TGPhotoStickerEntityView class]]) +// [strongSelf mirrorSelectedStickerEntity]; +// else +// [strongSelf presentBrushSettingsView]; +// }; +// +// void (^eyedropperPressed)(void) = ^ +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf == nil) +// return; +// +// [self enableEyedropper]; +// }; +// +// void (^beganColorPicking)(void) = ^ +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf == nil) +// return; +// +// [strongSelf commitEyedropper:true]; +// +// if (![strongSelf->_currentEntityView isKindOfClass:[TGPhotoTextEntityView class]]) +// [strongSelf setDimHidden:false animated:true]; +// }; +// +// void (^changedColor)(TGPhotoPaintSettingsView *, TGPaintSwatch *) = ^(TGPhotoPaintSettingsView *sender, TGPaintSwatch *swatch) +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf == nil) +// return; +// +// [strongSelf setCurrentSwatch:swatch sender:sender]; +// }; +// +// void (^finishedColorPicking)(TGPhotoPaintSettingsView *, TGPaintSwatch *) = ^(TGPhotoPaintSettingsView *sender, TGPaintSwatch *swatch) +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf == nil) +// return; +// +// [strongSelf commitEyedropper:true]; +// +// [strongSelf setCurrentSwatch:swatch sender:sender]; +// +// if (![strongSelf->_currentEntityView isKindOfClass:[TGPhotoTextEntityView class]]) +// [strongSelf setDimHidden:true animated:true]; +// }; +// +// _portraitSettingsView = [[TGPhotoPaintSettingsView alloc] initWithContext:_context]; +// _portraitSettingsView.eyedropperPressed = eyedropperPressed; +// _portraitSettingsView.beganColorPicking = beganColorPicking; +// _portraitSettingsView.changedColor = changedColor; +// _portraitSettingsView.finishedColorPicking = finishedColorPicking; +// _portraitSettingsView.settingsPressed = settingsPressed; +// _portraitSettingsView.layer.rasterizationScale = TGScreenScaling(); +// _portraitSettingsView.interfaceOrientation = UIInterfaceOrientationPortrait; +// [_portraitToolsWrapperView addSubview:_portraitSettingsView]; +// +// _landscapeSettingsView = [[TGPhotoPaintSettingsView alloc] initWithContext:_context]; +// _landscapeSettingsView.eyedropperPressed = eyedropperPressed; +// _landscapeSettingsView.beganColorPicking = beganColorPicking; +// _landscapeSettingsView.changedColor = changedColor; +// _landscapeSettingsView.finishedColorPicking = finishedColorPicking; +// _landscapeSettingsView.settingsPressed = settingsPressed; +// _landscapeSettingsView.layer.rasterizationScale = TGScreenScaling(); +// _landscapeSettingsView.interfaceOrientation = UIInterfaceOrientationLandscapeLeft; +// [_landscapeToolsWrapperView addSubview:_landscapeSettingsView]; +// +// [self setCurrentSwatch:_portraitSettingsView.swatch sender:nil]; +// +// if (![self _updateControllerInset:false]) +// [self controllerInsetUpdated:UIEdgeInsetsZero]; +//} +// +//- (void)setStickersContext:(id)stickersContext { +// _stickersContext = stickersContext; +// _entitiesContainerView.stickersContext = stickersContext; +//} +// +//- (void)setupCanvas +//{ +// if (_canvasView == nil) { +// __weak TGPhotoPaintController *weakSelf = self; +// _canvasView = [[TGPaintCanvas alloc] initWithFrame:CGRectZero]; +// _canvasView.pointInsideContainer = ^bool(CGPoint point) +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf == nil) +// return false; +// +// return [strongSelf->_containerView pointInside:[strongSelf->_canvasView convertPoint:point toView:strongSelf->_containerView] withEvent:nil]; +// }; +// _canvasView.shouldDraw = ^bool +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf == nil) +// return false; +// +// return ![strongSelf->_entitiesContainerView isTrackingAnyEntityView]; +// }; +// _canvasView.shouldDrawOnSingleTap = ^bool +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf == nil) +// return false; +// +// bool rotating = (strongSelf->_rotationGestureRecognizer.state == UIGestureRecognizerStateBegan || strongSelf->_rotationGestureRecognizer.state == UIGestureRecognizerStateChanged); +// bool pinching = (strongSelf->_pinchGestureRecognizer.state == UIGestureRecognizerStateBegan || strongSelf->_pinchGestureRecognizer.state == UIGestureRecognizerStateChanged); +// +// if (strongSelf->_currentEntityView != nil && !rotating && !pinching) +// { +// [strongSelf selectEntityView:nil]; +// return false; +// } +// +// return true; +// }; +// _canvasView.strokeBegan = ^ +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf != nil) +// [strongSelf selectEntityView:nil]; +// }; +// _canvasView.strokeCommited = ^ +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf != nil) +// [strongSelf updateActionsView]; +// }; +// _canvasView.hitTest = ^UIView *(CGPoint point, UIEvent *event) +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf == nil) +// return nil; +// +// return [strongSelf->_entitiesContainerView hitTest:[strongSelf->_canvasView convertPoint:point toView:strongSelf->_entitiesContainerView] withEvent:event]; +// }; +// _canvasView.cropRect = _photoEditor.cropRect; +// _canvasView.cropOrientation = _photoEditor.cropOrientation; +// _canvasView.originalSize = _photoEditor.originalSize; +// [_canvasView setPainting:_painting]; +// [_canvasView setBrush:_brushes.firstObject]; +// [self setCurrentSwatch:_portraitSettingsView.swatch sender:nil]; +// [_paintingWrapperView addSubview:_canvasView]; +// } +// +// _canvasView.hidden = false; +// [self.view setNeedsLayout]; +//} +// +//- (void)viewDidLoad +//{ +// [super viewDidLoad]; +// +// PGPhotoEditor *photoEditor = _photoEditor; +// if (!_skipEntitiesSetup) { +// [_entitiesContainerView setupWithPaintingData:photoEditor.paintingData]; +// } +// for (TGPhotoPaintEntityView *view in _entitiesContainerView.subviews) +// { +// if (![view isKindOfClass:[TGPhotoPaintEntityView class]]) +// continue; +// +// [self _commonEntityViewSetup:view]; +// } +// +// __weak TGPhotoPaintController *weakSelf = self; +// _undoManager.historyChanged = ^ +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf != nil) +// [strongSelf updateActionsView]; +// }; +// +// [self updateActionsView]; +//} +// +//- (void)viewDidAppear:(BOOL)animated +//{ +// [super viewDidAppear:animated]; +// +// [self transitionIn]; +//} +// +//#pragma mark - Tab Bar +// +//- (TGPhotoEditorTab)availableTabs +//{ +// TGPhotoEditorTab result = TGPhotoEditorPaintTab | TGPhotoEditorEraserTab | TGPhotoEditorTextTab; +// if (_enableStickers && _stickersContext != nil) { +// result |= TGPhotoEditorStickerTab; +// } +// return result; +//} +// +//- (void)handleTabAction:(TGPhotoEditorTab)tab +//{ +// [self commitEyedropper:true]; +// +// switch (tab) +// { +// case TGPhotoEditorStickerTab: +// { +// [self presentStickersView]; +// } +// break; +// +// case TGPhotoEditorTextTab: +// { +// [self createNewTextLabel]; +// } +// break; +// +// case TGPhotoEditorPaintTab: +// { +// [self selectEntityView:nil]; +// +// if (_canvasView.state.eraser) +// [self toggleEraserMode]; +// } +// break; +// +// case TGPhotoEditorEraserTab: +// { +// [self selectEntityView:nil]; +// [self toggleEraserMode]; +// } +// break; +// +// default: +// break; +// } +//} +// +//- (TGPhotoEditorTab)activeTab +//{ +// TGPhotoEditorTab tabs = TGPhotoEditorNoneTab; +// +// if (_currentEntityView != nil) +// return tabs; +// +// if (_canvasView.state.eraser) +// tabs |= TGPhotoEditorEraserTab; +// else +// tabs |= TGPhotoEditorPaintTab; +// +// return tabs; +//} +// +//#pragma mark - Undo & Redo +// +//- (void)updateActionsView +//{ +// if (_portraitActionsView == nil || _landscapeActionsView == nil) +// return; +// +// NSArray *views = @[ _portraitActionsView, _landscapeActionsView ]; +// for (TGPhotoPaintActionsView *view in views) +// { +// [view setUndoEnabled:_undoManager.canUndo]; +// [view setClearEnabled:_undoManager.canUndo]; +// } +//} +// +//- (void)presentClearAllAlert:(UIView *)sender +//{ +// TGMenuSheetController *controller = [[TGMenuSheetController alloc] initWithContext:_context dark:false]; +// controller.dismissesByOutsideTap = true; +// controller.narrowInLandscape = true; +// controller.permittedArrowDirections = UIPopoverArrowDirectionUp; +// __weak TGMenuSheetController *weakController = controller; +// +// __weak TGPhotoPaintController *weakSelf = self; +// NSArray *items = @ +// [ +// [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Paint.ClearConfirm") type:TGMenuSheetButtonTypeDestructive fontSize:20.0 action:^ +// { +// __strong TGMenuSheetController *strongController = weakController; +// if (strongController == nil) +// return; +// +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf == nil) +// return; +// +// [strongSelf->_painting clear]; +// [strongSelf->_undoManager reset]; +// +// [strongSelf->_entitiesContainerView removeAll]; +// [strongSelf _clearCurrentSelection]; +// +// [strongSelf updateSettingsButton]; +// +// [strongController dismissAnimated:true manual:false completion:nil]; +// }], +// [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Common.Cancel") type:TGMenuSheetButtonTypeCancel fontSize:20.0 action:^ +// { +// __strong TGMenuSheetController *strongController = weakController; +// if (strongController != nil) +// [strongController dismissAnimated:true]; +// }] +// ]; +// +// [controller setItemViews:items]; +// controller.sourceRect = ^ +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf == nil) +// return CGRectZero; +// return [sender convertRect:sender.bounds toView:strongSelf.view]; +// }; +// [controller presentInViewController:self.parentViewController sourceView:self.view animated:true]; +//} +// +//- (void)_clearCurrentSelection +//{ +// _scrollView.pinchGestureRecognizer.enabled = true; +// _currentEntityView = nil; +// if (_entitySelectionView != nil) +// { +// [_entitySelectionView removeFromSuperview]; +// _entitySelectionView = nil; +// } +//} +// +//#pragma mark - Data Handling +// +//- (UIImage *)eyedropperImage +//{ +// UIImage *backgroundImage = [self.photoEditor currentResultImage]; +// +// CGSize fittedSize = TGFitSize(_painting.size, TGPhotoEditorResultImageMaxSize); +// UIImage *paintingImage = _painting.isEmpty ? nil : [_painting imageWithSize:fittedSize andData:NULL]; +// NSMutableArray *entities = [[NSMutableArray alloc] init]; +// +// UIImage *entitiesImage = nil; +// if (paintingImage == nil && _entitiesContainerView.entitiesCount < 1) +// { +// return backgroundImage; +// } +// else if (_entitiesContainerView.entitiesCount > 0) +// { +// for (TGPhotoPaintEntityView *view in _entitiesContainerView.subviews) +// { +// if (![view isKindOfClass:[TGPhotoPaintEntityView class]]) +// continue; +// +// TGPhotoPaintEntity *entity = [view entity]; +// if (entity != nil) { +// [entities addObject:entity]; +// } +// } +// entitiesImage = [_entitiesContainerView imageInRect:_entitiesContainerView.bounds background:nil still:true]; +// } +// +// if (entitiesImage == nil && paintingImage == nil) { +// return backgroundImage; +// } else { +// UIGraphicsBeginImageContextWithOptions(fittedSize, false, 1.0); +// +// [backgroundImage drawInRect:CGRectMake(0.0, 0.0, fittedSize.width, fittedSize.height)]; +// [paintingImage drawInRect:CGRectMake(0.0, 0.0, fittedSize.width, fittedSize.height)]; +// [entitiesImage drawInRect:CGRectMake(0.0, 0.0, fittedSize.width, fittedSize.height)]; +// +// UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); +// UIGraphicsEndImageContext(); +// return result; +// } +//} +// +//- (TGPaintingData *)_prepareResultData +//{ +// if (_resultData != nil) +// return _resultData; +// +// NSData *data = nil; +// CGSize fittedSize = TGFitSize(_painting.size, TGPhotoEditorResultImageMaxSize); +// UIImage *image = _painting.isEmpty ? nil : [_painting imageWithSize:fittedSize andData:&data]; +// NSMutableArray *entities = [[NSMutableArray alloc] init]; +// +// bool hasAnimatedEntities = false; +// UIImage *stillImage = nil; +// if (image == nil && _entitiesContainerView.entitiesCount < 1) +// { +// _resultData = nil; +// return _resultData; +// } +// else if (_entitiesContainerView.entitiesCount > 0) +// { +// for (TGPhotoPaintEntityView *view in _entitiesContainerView.subviews) +// { +// if (![view isKindOfClass:[TGPhotoPaintEntityView class]]) +// continue; +// +// TGPhotoPaintEntity *entity = [view entity]; +// if (entity != nil) { +// if (entity.animated) { +// hasAnimatedEntities = true; +// } +// [entities addObject:entity]; +// } +// } +// +// if (hasAnimatedEntities) { +// for (TGPhotoPaintEntity *entity in entities) { +// if ([entity isKindOfClass:[TGPhotoPaintTextEntity class]]) { +// TGPhotoPaintTextEntity *textEntity = (TGPhotoPaintTextEntity *)entity; +// for (TGPhotoPaintEntityView *view in _entitiesContainerView.subviews) +// { +// if (![view isKindOfClass:[TGPhotoPaintEntityView class]]) +// continue; +// +// if (view.entityUUID == textEntity.uuid) { +// textEntity.renderImage = [(TGPhotoTextEntityView *)view image]; +// break; +// } +// } +// } +// } +// } +// +// if (!hasAnimatedEntities) { +// image = [_entitiesContainerView imageInRect:_entitiesContainerView.bounds background:image still:false]; +// } else { +// stillImage = [_entitiesContainerView imageInRect:_entitiesContainerView.bounds background:image still:true]; +// } +// } +// +//// _resultData = [TGPaintingData dataWithPaintingData:data image:image stillImage:stillImage entities:entities undoManager:_undoManager]; +// return _resultData; +//} +// +//- (UIImage *)image +//{ +// TGPaintingData *paintingData = [self _prepareResultData]; +// return paintingData.image; +//} +// +//- (TGPaintingData *)paintingData +//{ +// return [self _prepareResultData]; +//} +// +//- (void)enableEyedropper { +// if (!_eyedropperView.isHidden) +// return; +// +// [self selectEntityView:nil]; +// +// self.controlVideoPlayback(false); +// [_entitiesContainerView updateVisibility:false]; +// +// UIImage *image = [self eyedropperImage]; +// CGImageRef cgImage = image.CGImage; +// CFDataRef pixelData = CGDataProviderCopyData(CGImageGetDataProvider(cgImage)); +// +// _eyedropperBackgroundData = (__bridge NSData *)pixelData; +// _eyedropperBackgroundSize = image.size; +// _eyedropperBackgroundBytesPerRow = CGImageGetBytesPerRow(cgImage); +// _eyedropperBackgroundInfo = CGImageGetBitmapInfo(cgImage); +// +// [_eyedropperView update]; +// [_eyedropperView present]; +//} +// +//- (void)commitEyedropper:(bool)immediate { +// self.controlVideoPlayback(true); +// [_entitiesContainerView updateVisibility:true]; +// +// _eyedropperBackgroundData = nil; +// _eyedropperBackgroundSize = CGSizeZero; +// _eyedropperBackgroundBytesPerRow = 0; +// _eyedropperBackgroundInfo = 0; +// +// double timeout = immediate ? 0.0 : 0.2; +// TGDispatchAfter(timeout, dispatch_get_main_queue(), ^{ +// [_eyedropperView dismiss]; +// }); +//} +// +//- (UIColor *)colorFromData:(NSData *)data width:(NSInteger)width height:(NSInteger)height x:(NSInteger)x y:(NSInteger)y bpr:(NSInteger)bpr { +// uint8_t *pixel = (uint8_t *)data.bytes + bpr * y + x * 4; +// if (_eyedropperBackgroundInfo & kCGBitmapByteOrder32Little) { +// return [UIColor colorWithRed:pixel[2] / 255.0 green:pixel[1] / 255.0 blue:pixel[0] / 255.0 alpha:1.0]; +// } else { +// return [UIColor colorWithRed:pixel[0] / 255.0 green:pixel[1] / 255.0 blue:pixel[2] / 255.0 alpha:1.0]; +// } +//} +// +//- (UIColor *)colorAtPoint:(CGPoint)point +//{ +// CGPoint convertedPoint = CGPointMake(point.x / _eyedropperView.bounds.size.width * _eyedropperBackgroundSize.width, point.y / _eyedropperView.bounds.size.height * _eyedropperBackgroundSize.height); +// UIColor *backgroundColor = [self colorFromData:_eyedropperBackgroundData width:_eyedropperBackgroundSize.width height:_eyedropperBackgroundSize.height x:convertedPoint.x y:convertedPoint.y bpr:_eyedropperBackgroundBytesPerRow]; +// return backgroundColor; +//} +// +// +//#pragma mark - Entities +// +//- (void)selectEntityView:(TGPhotoPaintEntityView *)view +//{ +// if (_editedTextView != nil) +// return; +// +// if (_currentEntityView != nil) +// { +// if (_currentEntityView == view) +// { +// [self showMenuForEntityView]; +// return; +// } +// +// [self _clearCurrentSelection]; +// } +// +// _currentEntityView = view; +// [self updateSettingsButton]; +// +// _scrollView.pinchGestureRecognizer.enabled = _currentEntityView == nil; +// +// if (view != nil) +// { +// [_currentEntityView.superview bringSubviewToFront:_currentEntityView]; +// } +// else +// { +// [self hideMenu]; +// return; +// } +// +// if ([view isKindOfClass:[TGPhotoTextEntityView class]]) +// { +// TGPaintSwatch *textSwatch = ((TGPhotoPaintTextEntity *)view.entity).swatch; +// [self setCurrentSwatch:[TGPaintSwatch swatchWithColor:textSwatch.color colorLocation:textSwatch.colorLocation brushWeight:_portraitSettingsView.swatch.brushWeight] sender:nil]; +// } +// +// _entitySelectionView = [view createSelectionView]; +// view.selectionView = _entitySelectionView; +// [_selectionContainerView addSubview:_entitySelectionView]; +// +// __weak TGPhotoPaintController *weakSelf = self; +// _entitySelectionView.entityResized = ^(CGFloat scale) +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf == nil) +// return; +// +// [strongSelf->_entitySelectionView.entityView scale:scale absolute:true]; +// }; +// _entitySelectionView.entityRotated = ^(CGFloat angle) +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf == nil) +// return; +// +// [strongSelf->_entitySelectionView.entityView rotate:angle absolute:true]; +// }; +// +// [_entitySelectionView update]; +//} +// +//- (void)deleteEntityView:(TGPhotoPaintEntityView *)view +//{ +// [_undoManager unregisterUndoWithUUID:view.entityUUID]; +// +// [view removeFromSuperview]; +// +// [self _clearCurrentSelection]; +// +// [self updateActionsView]; +// [self updateSettingsButton]; +//} +// +//- (void)duplicateEntityView:(TGPhotoPaintEntityView *)view +//{ +// TGPhotoPaintEntity *entity = [view.entity duplicate]; +// entity.position = [self startPositionRelativeToEntity:entity]; +// +// TGPhotoPaintEntityView *entityView = nil; +// if ([entity isKindOfClass:[TGPhotoPaintStickerEntity class]]) +// { +// TGPhotoStickerEntityView *stickerView = (TGPhotoStickerEntityView *)[_entitiesContainerView createEntityViewWithEntity:entity]; +// [self _commonEntityViewSetup:stickerView]; +// entityView = stickerView; +// } +// else +// { +// TGPhotoTextEntityView *textView = (TGPhotoTextEntityView *)[_entitiesContainerView createEntityViewWithEntity:entity]; +// [self _commonEntityViewSetup:textView]; +// entityView = textView; +// } +// +// [self selectEntityView:entityView]; +// [self _registerEntityRemovalUndo:entity]; +// [self updateActionsView]; +//} +// +//- (void)editEntityView:(TGPhotoPaintEntityView *)view +//{ +// if ([view isKindOfClass:[TGPhotoTextEntityView class]]) +// [(TGPhotoTextEntityView *)view beginEditing]; +//} +// +//#pragma mark Menu +// +//- (void)showMenuForEntityView +//{ +// if (_menuContainerView != nil) +// { +// TGMenuContainerView *container = _menuContainerView; +// bool isShowingMenu = container.isShowingMenu; +// _menuContainerView = nil; +// +// [container removeFromSuperview]; +// +// if (!isShowingMenu && container.menuView.userInfo[@"entity"] == _currentEntityView) +// { +// if ([_currentEntityView isKindOfClass:[TGPhotoTextEntityView class]]) +// [self editEntityView:_currentEntityView]; +// +// return; +// } +// } +// +// UIView *parentView = self.view; +// _menuContainerView = [[TGMenuContainerView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, parentView.frame.size.width, parentView.frame.size.height)]; +// [parentView addSubview:_menuContainerView]; +// +// NSArray *actions = nil; +// +// if ([_currentEntityView isKindOfClass:[TGPhotoStickerEntityView class]]) +// { +// actions = @ +// [ +// @{ @"title": TGLocalized(@"Paint.Delete"), @"action": @"delete" }, +// @{ @"title": TGLocalized(@"Paint.Duplicate"), @"action": @"duplicate" }, +// ]; +// } +// else +// { +// actions = @ +// [ +// @{ @"title": TGLocalized(@"Paint.Delete"), @"action": @"delete" }, +// @{ @"title": TGLocalized(@"Paint.Edit"), @"action": @"edit" }, +// @{ @"title": TGLocalized(@"Paint.Duplicate"), @"action": @"duplicate" }, +// ]; +// } +// +// [_menuContainerView.menuView setUserInfo:@{ @"entity": _currentEntityView }]; +// [_menuContainerView.menuView setButtonsAndActions:actions watcherHandle:_actionHandle]; +// [_menuContainerView.menuView sizeToFit]; +// +// CGRect sourceRect = CGRectOffset([_currentEntityView convertRect:_currentEntityView.bounds toView:_menuContainerView], 0, -15.0f); +// [_menuContainerView showMenuFromRect:sourceRect animated:false]; +//} +// +//- (void)hideMenu +//{ +// [_menuContainerView hideMenu]; +//} +// +//- (void)actionStageActionRequested:(NSString *)action options:(id)options +//{ +// if ([action isEqualToString:@"menuAction"]) +// { +// NSString *menuAction = options[@"action"]; +// TGPhotoPaintEntityView *entity = options[@"userInfo"][@"entity"]; +// +// if ([menuAction isEqualToString:@"delete"]) +// { +// [self deleteEntityView:entity]; +// } +// else if ([menuAction isEqualToString:@"duplicate"]) +// { +// [self duplicateEntityView:entity]; +// } +// else if ([menuAction isEqualToString:@"edit"]) +// { +// [self editEntityView:entity]; +// } +// } +// else if ([action isEqualToString:@"menuWillHide"]) +// { +// } +//} +// +//#pragma mark View +// +//- (CGPoint)centerPointFittedCropRect +//{ +// return [_previewView convertPoint:TGPaintCenterOfRect(_previewView.bounds) toView:_entitiesContainerView]; +//} +// +//- (CGFloat)startRotation +//{ +// return TGCounterRotationForOrientation(_photoEditor.cropOrientation) - _photoEditor.cropRotation; +//} +// +//- (CGPoint)startPositionRelativeToEntity:(TGPhotoPaintEntity *)entity +//{ +// const CGPoint offset = CGPointMake(200.0f, 200.0f); +// +// if (entity != nil) +// { +// return TGPaintAddPoints(entity.position, offset); +// } +// else +// { +// const CGFloat minimalDistance = 100.0f; +// CGPoint position = [self centerPointFittedCropRect]; +// +// while (true) +// { +// bool occupied = false; +// for (TGPhotoPaintEntityView *view in _entitiesContainerView.subviews) +// { +// if (![view isKindOfClass:[TGPhotoPaintEntityView class]]) +// continue; +// +// CGPoint location = view.center; +// CGFloat distance = sqrt(pow(location.x - position.x, 2) + pow(location.y - position.y, 2)); +// if (distance < minimalDistance) +// occupied = true; +// } +// +// if (!occupied) +// break; +// else +// position = TGPaintAddPoints(position, offset); +// } +// +// return position; +// } +//} +// +//- (void)_commonEntityViewSetup:(TGPhotoPaintEntityView *)entityView +//{ +// [self hideMenu]; +// +// __weak TGPhotoPaintController *weakSelf = self; +// entityView.shouldTouchEntity = ^bool (__unused TGPhotoPaintEntityView *sender) +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf == nil) +// return false; +// +// return ![strongSelf->_canvasView isTracking] && ![strongSelf->_entitiesContainerView isTrackingAnyEntityView]; +// }; +// entityView.entityBeganDragging = ^(TGPhotoPaintEntityView *sender) +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf != nil && sender != strongSelf->_entitySelectionView.entityView) +// [strongSelf selectEntityView:sender]; +// }; +// entityView.entityChanged = ^(TGPhotoPaintEntityView *sender) +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf == nil) +// return; +// +// if (sender == strongSelf->_entitySelectionView.entityView) +// [strongSelf->_entitySelectionView update]; +// +// [strongSelf updateActionsView]; +// }; +// +// if ([entityView isKindOfClass:[TGPhotoTextEntityView class]]) { +// TGPhotoTextEntityView *textView = (TGPhotoTextEntityView *)entityView; +// +// __weak TGPhotoPaintController *weakSelf = self; +// textView.beganEditing = ^(TGPhotoTextEntityView *sender) +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf == nil) +// return; +// +// [strongSelf bringTextEntityViewFront:sender]; +// }; +// +// textView.finishedEditing = ^(__unused TGPhotoTextEntityView *sender) +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf == nil) +// return; +// +// [strongSelf sendTextEntityViewBack]; +// }; +// } +//} +// +//- (void)_registerEntityRemovalUndo:(TGPhotoPaintEntity *)entity +//{ +// [_undoManager registerUndoWithUUID:entity.uuid block:^(__unused TGPainting *painting, TGPhotoEntitiesContainerView *entitiesContainer, NSInteger uuid) +// { +// [entitiesContainer removeViewWithUUID:uuid]; +// }]; +//} +// +//#pragma mark Stickers +// +//- (void)presentStickersView +//{ +// if (_stickersScreen != nil) { +// [_stickersScreen restore]; +// return; +// } +// +// __weak TGPhotoPaintController *weakSelf = self; +// _stickersScreen = _stickersContext.presentStickersController(^(id document, bool animated, UIView *view, CGRect rect) { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf != nil) { +// [strongSelf createNewStickerWithDocument:document animated:animated transitionPoint:CGPointZero snapshotView:nil]; +// } +// }); +// _stickersScreen.screenDidAppear = ^{ +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf != nil) { +// strongSelf.controlVideoPlayback(false); +// [strongSelf->_entitiesContainerView updateVisibility:false]; +// } +// }; +// _stickersScreen.screenWillDisappear = ^{ +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf != nil) { +// strongSelf.controlVideoPlayback(true); +// [strongSelf->_entitiesContainerView updateVisibility:true]; +// } +// }; +//} +// +//- (void)createNewStickerWithDocument:(id)document animated:(bool)animated transitionPoint:(CGPoint)transitionPoint snapshotView:(UIView *)snapshotView +//{ +// TGPhotoPaintStickerEntity *entity = [[TGPhotoPaintStickerEntity alloc] initWithDocument:document baseSize:[self _stickerBaseSizeForCurrentPainting] animated:animated]; +// [self _setStickerEntityPosition:entity]; +// +// +// TGPhotoStickerEntityView *stickerView = (TGPhotoStickerEntityView *)[_entitiesContainerView createEntityViewWithEntity:entity]; +// +// bool hasStickers = false; +// TGPhotoStickerEntityView *existingStickerView; +// for (TGPhotoPaintEntityView *view in _entitiesContainerView.subviews) { +// if ([view isKindOfClass:[TGPhotoStickerEntityView class]]) { +// hasStickers = true; +// +// if (((TGPhotoStickerEntityView *)view).documentId == stickerView.documentId) { +// existingStickerView = (TGPhotoStickerEntityView *)view; +// } +// break; +// } +// } +// +// [_entitiesContainerView addSubview:stickerView]; +// [self _commonEntityViewSetup:stickerView]; +// +// __weak TGPhotoPaintController *weakSelf = self; +// stickerView.started = ^(double duration) { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf != nil) { +// TGPhotoEditorController *editorController = (TGPhotoEditorController *)strongSelf.parentViewController; +// if (![editorController isKindOfClass:[TGPhotoEditorController class]]) +// return; +// +// if (!hasStickers) { +// [editorController setMinimalVideoDuration:duration]; +// } +// } +// }; +// +// NSTimeInterval currentTime = NAN; +// NSTimeInterval stickerStartTime = _stickerStartTime; +// TGPhotoEditorController *editorController = (TGPhotoEditorController *)self.parentViewController; +// if ([editorController isKindOfClass:[TGPhotoEditorController class]]) { +// currentTime = editorController.currentTime; +// } +// +// if (!isnan(currentTime)) { +// [stickerView seekTo:currentTime]; +// [stickerView play]; +// } else { +// NSTimeInterval currentTime = CACurrentMediaTime(); +// if (!isnan(stickerStartTime)) { +// if (existingStickerView != nil) { +// [stickerView copyStickerView:existingStickerView]; +// } else { +// NSTimeInterval position = currentTime - stickerStartTime; +// [stickerView seekTo:position]; +// [stickerView play]; +// } +// } else { +// _stickerStartTime = currentTime; +// [stickerView play]; +// } +// } +// +// [self selectEntityView:stickerView]; +// _entitySelectionView.alpha = 0.0f; +// +// [_entitySelectionView fadeIn]; +// +// [self _registerEntityRemovalUndo:entity]; +// [self updateActionsView]; +//} +// +//- (void)mirrorSelectedStickerEntity +//{ +// if ([_currentEntityView isKindOfClass:[TGPhotoStickerEntityView class]]) +// [((TGPhotoStickerEntityView *)_currentEntityView) mirror]; +//} +// +//#pragma mark Text +// +//- (void)createNewTextLabel +//{ +// TGPaintSwatch *currentSwatch = _portraitSettingsView.swatch; +// TGPaintSwatch *whiteSwatch = [TGPaintSwatch swatchWithColor:UIColorRGB(0xffffff) colorLocation:1.0f brushWeight:currentSwatch.brushWeight]; +// TGPaintSwatch *blackSwatch = [TGPaintSwatch swatchWithColor:UIColorRGB(0x000000) colorLocation:0.85f brushWeight:currentSwatch.brushWeight]; +// [self setCurrentSwatch:_selectedTextStyle == TGPhotoPaintTextEntityStyleOutlined ? blackSwatch : whiteSwatch sender:nil]; +// +// CGFloat maxWidth = [self fittedContentSize].width - 26.0f; +// TGPhotoPaintTextEntity *entity = [[TGPhotoPaintTextEntity alloc] initWithText:@"" font:_selectedTextFont swatch:_portraitSettingsView.swatch baseFontSize:[self _textBaseFontSizeForCurrentPainting] maxWidth:maxWidth style:_selectedTextStyle]; +// entity.position = [self startPositionRelativeToEntity:nil]; +// entity.angle = [self startRotation]; +// +// TGPhotoTextEntityView *textView = (TGPhotoTextEntityView *)[_entitiesContainerView createEntityViewWithEntity:entity]; +// [_entitiesContainerView addSubview:textView]; +// [self _commonEntityViewSetup:textView]; +// +// [self selectEntityView:textView]; +// +// [self _registerEntityRemovalUndo:entity]; +// [self updateActionsView]; +// +// [textView beginEditing]; +//} +// +//- (void)bringTextEntityViewFront:(TGPhotoTextEntityView *)entityView +//{ +// _editedTextView = entityView; +// entityView.inhibitGestures = true; +// +// [_dimView.superview insertSubview:_dimView belowSubview:entityView]; +// +// _textEditingDismissButton = [[UIButton alloc] initWithFrame:_dimView.bounds]; +// _dimView.userInteractionEnabled = true; +// [_textEditingDismissButton addTarget:self action:@selector(_dismissButtonTapped) forControlEvents:UIControlEventTouchUpInside]; +// [_dimView addSubview:_textEditingDismissButton]; +// +// _editedTextCenter = entityView.center; +// _editedTextTransform = entityView.transform; +// +// _entitySelectionView.alpha = 0.0f; +// +// void (^changeBlock)(void) = ^ +// { +// entityView.center = [self centerPointFittedCropRect]; +// entityView.transform = CGAffineTransformMakeRotation([self startRotation]); +// +// _dimView.alpha = 1.0f; +// }; +// +// _contentView.userInteractionEnabled = true; +// _contentWrapperView.userInteractionEnabled = true; +// +// if (iosMajorVersion() >= 7) +// { +// [UIView animateWithDuration:0.4 delay:0.0 usingSpringWithDamping:0.8f initialSpringVelocity:0.0f options:kNilOptions animations:changeBlock completion:nil]; +// } +// else +// { +// [UIView animateWithDuration:0.35 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:changeBlock completion:nil]; +// } +// +// [self setInterfaceHidden:true animated:true]; +//} +// +//- (void)_dismissButtonTapped +//{ +// TGPhotoTextEntityView *entityView = _editedTextView; +// [entityView endEditing]; +//} +// +//- (void)sendTextEntityViewBack +//{ +// _contentView.userInteractionEnabled = false; +// _contentWrapperView.userInteractionEnabled = false; +// +// _dimView.userInteractionEnabled = false; +// [_textEditingDismissButton removeFromSuperview]; +// _textEditingDismissButton = nil; +// +// TGPhotoTextEntityView *entityView = _editedTextView; +// _editedTextView = nil; +// +// void (^changeBlock)(void) = ^ +// { +// entityView.center = _editedTextCenter; +// entityView.transform = _editedTextTransform; +// _dimView.alpha = 0.0f; +// }; +// +// void (^completionBlock)(BOOL) = ^(__unused BOOL finished) +// { +// [_dimView.superview bringSubviewToFront:_dimView]; +// entityView.inhibitGestures = false; +// +// if (entityView.isEmpty) +// { +// [self deleteEntityView:entityView]; +// } +// else +// { +// [_entitySelectionView update]; +// [_entitySelectionView fadeIn]; +// } +// }; +// +// if (iosMajorVersion() >= 7) +// { +// [UIView animateWithDuration:0.4 delay:0.0 usingSpringWithDamping:0.8f initialSpringVelocity:0.0f options:kNilOptions animations:changeBlock completion:completionBlock]; +// } +// else +// { +// [UIView animateWithDuration:0.35 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:changeBlock completion:completionBlock]; +// } +// +// [self setInterfaceHidden:false animated:true]; +// +// TGMenuContainerView *container = _menuContainerView; +// _menuContainerView = nil; +// [container removeFromSuperview]; +//} +// +//- (void)containerPressed +//{ +// if (_currentEntityView == nil) +// return; +// +// if ([_currentEntityView isKindOfClass:[TGPhotoTextEntityView class]]) +// { +// TGPhotoTextEntityView *textEntityView = (TGPhotoTextEntityView *)_currentEntityView; +// if ([textEntityView isEditing]) +// { +// [textEntityView endEditing]; +// return; +// } +// } +// [self selectEntityView:nil]; +//} +// +//#pragma mark - Relative Size Calculation +// +//- (CGSize)_stickerBaseSizeForCurrentPainting +//{ +// CGSize fittedSize = [self fittedContentSize]; +// CGFloat maxSide = MAX(fittedSize.width, fittedSize.height); +// CGFloat side = ceil(maxSide * 0.3125f); +// return CGSizeMake(side, side); +//} +// +//- (CGFloat)_textBaseFontSizeForCurrentPainting +//{ +// CGSize fittedSize = [self fittedContentSize]; +// CGFloat maxSide = MAX(fittedSize.width, fittedSize.height); +// return ceil(maxSide * 0.08f); +//} +// +//- (CGFloat)_brushBaseWeightForCurrentPainting +//{ +// return 15.0f / TGPhotoPaintingMaxSize.width * _painting.size.width; +//} +// +//- (CGFloat)_brushWeightRangeForCurrentPainting +//{ +// return 125.0f / TGPhotoPaintingMaxSize.width * _painting.size.width; +//} +// +//- (CGFloat)_brushWeightForSize:(CGFloat)size +//{ +// CGFloat scale = MAX(0.001, _scrollView.zoomScale); +// return ([self _brushBaseWeightForCurrentPainting] + [self _brushWeightRangeForCurrentPainting] * size) / scale; +//} +// +//+ (CGSize)maximumPaintingSize +//{ +// static dispatch_once_t onceToken; +// static CGSize size; +// dispatch_once(&onceToken, ^ +// { +// CGSize screenSize = TGScreenSize(); +// if ((NSInteger)screenSize.height == 480) +// size = TGPhotoPaintingLightMaxSize; +// else +// size = TGPhotoPaintingMaxSize; +// }); +// return size; +//} +// +//#pragma mark - Settings +// +//- (void)setCurrentSwatch:(TGPaintSwatch *)swatch sender:(id)sender +//{ +// [_canvasView setBrushColor:swatch.color]; +// [_canvasView setBrushWeight:[self _brushWeightForSize:swatch.brushWeight]]; +// if ([_currentEntityView isKindOfClass:[TGPhotoTextEntityView class]]) +// [(TGPhotoTextEntityView *)_currentEntityView setSwatch:swatch]; +// +// if (sender != _landscapeSettingsView) +// [_landscapeSettingsView setSwatch:swatch]; +// +// if (sender != _portraitSettingsView) +// [_portraitSettingsView setSwatch:swatch]; +//} +// +//- (void)updateSettingsButton +//{ +// if ([_currentEntityView isKindOfClass:[TGPhotoTextEntityView class]]) { +// TGPhotoPaintSettingsViewIcon icon; +// switch (((TGPhotoTextEntityView *)_currentEntityView).entity.style) { +// case TGPhotoPaintTextEntityStyleRegular: +// icon = TGPhotoPaintSettingsViewIconTextRegular; +// break; +// case TGPhotoPaintTextEntityStyleOutlined: +// icon = TGPhotoPaintSettingsViewIconTextOutlined; +// break; +// case TGPhotoPaintTextEntityStyleFramed: +// icon = TGPhotoPaintSettingsViewIconTextFramed; +// break; +// } +// [self setSettingsButtonIcon:icon]; +// } +// else if ([_currentEntityView isKindOfClass:[TGPhotoStickerEntityView class]]) { +// [self setSettingsButtonIcon:TGPhotoPaintSettingsViewIconMirror]; +// } +// else { +// TGPhotoPaintSettingsViewIcon icon = TGPhotoPaintSettingsViewIconBrushPen; +// if ([_canvasView.state.brush isKindOfClass:[TGPaintEllipticalBrush class]]) { +// icon = TGPhotoPaintSettingsViewIconBrushMarker; +// } else if ([_canvasView.state.brush isKindOfClass:[TGPaintNeonBrush class]]) { +// icon = TGPhotoPaintSettingsViewIconBrushNeon; +// } else if ([_canvasView.state.brush isKindOfClass:[TGPaintArrowBrush class]]) { +// icon = TGPhotoPaintSettingsViewIconBrushArrow; +// } +// [self setSettingsButtonIcon:icon]; +// } +// [self _updateTabs]; +//} +// +//- (void)setSettingsButtonIcon:(TGPhotoPaintSettingsViewIcon)icon +//{ +// [_portraitSettingsView setIcon:icon animated:true]; +// [_landscapeSettingsView setIcon:icon animated:true]; +//} +// +//- (void)settingsWrapperPressed +//{ +// [_settingsView dismissWithCompletion:^ +// { +// [_settingsView removeFromSuperview]; +// _settingsView = nil; +// +// [_settingsViewWrapper removeFromSuperview]; +// }]; +//} +// +//- (UIView *)settingsViewWrapper +//{ +// if (_settingsViewWrapper == nil) +// { +// _settingsViewWrapper = [[TGPhotoPaintSettingsWrapperView alloc] initWithFrame:self.parentViewController.view.bounds]; +// _settingsViewWrapper.exclusiveTouch = true; +// +// __weak TGPhotoPaintController *weakSelf = self; +// _settingsViewWrapper.pressed = ^(__unused CGPoint location) +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf != nil) +// [strongSelf settingsWrapperPressed]; +// }; +// _settingsViewWrapper.suppressTouchAtPoint = ^bool(CGPoint location) +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf == nil) +// return false; +// +// UIView *view = [strongSelf.view hitTest:[strongSelf.view convertPoint:location fromView:nil] withEvent:nil]; +// if ([view isKindOfClass:[TGModernButton class]]) +// return true; +// +// if ([view isKindOfClass:[TGPaintCanvas class]]) +// return true; +// +// if (view == strongSelf->_portraitToolsWrapperView || view == strongSelf->_landscapeToolsWrapperView) +// return true; +// +// return false; +// }; +// } +// +// [self.parentViewController.view addSubview:_settingsViewWrapper]; +// +// return _settingsViewWrapper; +//} +// +//- (TGPaintBrushPreview *)brushPreview +//{ +// if ([_brushes.firstObject previewImage] != nil) +// return nil; +// +// if (_brushPreview == nil) +// _brushPreview = [[TGPaintBrushPreview alloc] init]; +// +// return _brushPreview; +//} +// +//- (void)presentBrushSettingsView +//{ +// TGPhotoBrushSettingsView *view = [[TGPhotoBrushSettingsView alloc] initWithBrushes:_brushes preview:[self brushPreview]]; +// [view setBrush:_painting.brush]; +// +// __weak TGPhotoPaintController *weakSelf = self; +// view.brushChanged = ^(TGPaintBrush *brush) +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf == nil) +// return; +// +// if (strongSelf->_canvasView.state.eraser && (brush.lightSaber || brush.arrow)) +// brush = strongSelf->_brushes.firstObject; +// +// [strongSelf->_canvasView setBrush:brush]; +// +// [strongSelf settingsWrapperPressed]; +// [strongSelf updateSettingsButton]; +// }; +// _settingsView = view; +// [view sizeToFit]; +// +// UIView *wrapper = [self settingsViewWrapper]; +// wrapper.userInteractionEnabled = true; +// [wrapper addSubview:view]; +// +// [self viewWillLayoutSubviews]; +// +// [view present]; +//} +// +//- (void)presentTextSettingsView +//{ +// TGPhotoTextSettingsView *view = [[TGPhotoTextSettingsView alloc] initWithFonts:[TGPhotoPaintFont availableFonts] selectedFont:_selectedTextFont selectedStyle:_selectedTextStyle]; +// +// __weak TGPhotoPaintController *weakSelf = self; +// view.fontChanged = ^(TGPhotoPaintFont *font) +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf == nil) +// return; +// +// strongSelf->_selectedTextFont = font; +// +// TGPhotoTextEntityView *textView = (TGPhotoTextEntityView *)strongSelf->_currentEntityView; +// [textView setFont:font]; +// +// [strongSelf settingsWrapperPressed]; +// [strongSelf updateSettingsButton]; +// }; +// view.styleChanged = ^(TGPhotoPaintTextEntityStyle style) +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf == nil) +// return; +// +// strongSelf->_selectedTextStyle = style; +// +// if (style == TGPhotoPaintTextEntityStyleOutlined && [strongSelf->_portraitSettingsView.swatch.color isEqual:UIColorRGB(0xffffff)]) +// { +// TGPaintSwatch *currentSwatch = strongSelf->_portraitSettingsView.swatch; +// TGPaintSwatch *blackSwatch = [TGPaintSwatch swatchWithColor:UIColorRGB(0x000000) colorLocation:0.85f brushWeight:currentSwatch.brushWeight]; +// [strongSelf setCurrentSwatch:blackSwatch sender:nil]; +// } +// else if (style != TGPhotoPaintTextEntityStyleOutlined && [strongSelf->_portraitSettingsView.swatch.color isEqual:UIColorRGB(0x000000)]) +// { +// TGPaintSwatch *currentSwatch = strongSelf->_portraitSettingsView.swatch; +// TGPaintSwatch *whiteSwatch = [TGPaintSwatch swatchWithColor:UIColorRGB(0xffffff) colorLocation:1.0f brushWeight:currentSwatch.brushWeight]; +// [strongSelf setCurrentSwatch:whiteSwatch sender:nil]; +// } +// +// TGPhotoTextEntityView *textView = (TGPhotoTextEntityView *)strongSelf->_currentEntityView; +// [textView setStyle:style]; +// +// [strongSelf settingsWrapperPressed]; +// [strongSelf updateSettingsButton]; +// }; +// +// _settingsView = view; +// [view sizeToFit]; +// +// UIView *wrapper = [self settingsViewWrapper]; +// wrapper.userInteractionEnabled = true; +// [wrapper addSubview:view]; +// +// [self viewWillLayoutSubviews]; +// +// [view present]; +//} +// +//- (void)toggleEraserMode +//{ +// _canvasView.state.eraser = !_canvasView.state.isEraser; +// +// if (_canvasView.state.eraser) +// { +// if (_canvasView.state.brush.lightSaber || _canvasView.state.brush.arrow) +// [_canvasView setBrush:_brushes.firstObject]; +// } +// +// [_portraitSettingsView setHighlighted:_canvasView.state.isEraser]; +// [_landscapeSettingsView setHighlighted:_canvasView.state.isEraser]; +// +// [self updateSettingsButton]; +// [self _updateTabs]; +//} +// +//#pragma mark - Scroll View +// +//- (CGSize)fittedContentSize +//{ +// return [TGPhotoPaintController fittedContentSize:_photoEditor.cropRect orientation:_photoEditor.cropOrientation originalSize:_photoEditor.originalSize]; +//} +// +//+ (CGSize)fittedContentSize:(CGRect)cropRect orientation:(UIImageOrientation)orientation originalSize:(CGSize)originalSize { +// CGSize fittedOriginalSize = TGScaleToSize(originalSize, [TGPhotoPaintController maximumPaintingSize]); +// CGFloat scale = fittedOriginalSize.width / originalSize.width; +// +// CGSize size = CGSizeMake(cropRect.size.width * scale, cropRect.size.height * scale); +// if (orientation == UIImageOrientationLeft || orientation == UIImageOrientationRight) +// size = CGSizeMake(size.height, size.width); +// +// return CGSizeMake(floor(size.width), floor(size.height)); +//} +// +//- (CGRect)fittedCropRect:(bool)originalSize +//{ +// return [TGPhotoPaintController fittedCropRect:_photoEditor.cropRect originalSize:_photoEditor.originalSize keepOriginalSize:originalSize]; +//} +// +//+ (CGRect)fittedCropRect:(CGRect)cropRect originalSize:(CGSize)originalSize keepOriginalSize:(bool)keepOriginalSize { +// CGSize fittedOriginalSize = TGScaleToSize(originalSize, [TGPhotoPaintController maximumPaintingSize]); +// CGFloat scale = fittedOriginalSize.width / originalSize.width; +// +// CGSize size = fittedOriginalSize; +// if (!keepOriginalSize) +// size = CGSizeMake(cropRect.size.width * scale, cropRect.size.height * scale); +// +// return CGRectMake(-cropRect.origin.x * scale, -cropRect.origin.y * scale, size.width, size.height); +//} +// +//- (CGPoint)fittedCropCenterScale:(CGFloat)scale +//{ +// return [TGPhotoPaintController fittedCropRect:_photoEditor.cropRect centerScale:scale]; +//} +// +//+ (CGPoint)fittedCropRect:(CGRect)cropRect centerScale:(CGFloat)scale +//{ +// CGSize size = CGSizeMake(cropRect.size.width * scale, cropRect.size.height * scale); +// CGRect rect = CGRectMake(cropRect.origin.x * scale, cropRect.origin.y * scale, size.width, size.height); +// +// return CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect)); +//} +// +//- (void)resetScrollView +//{ +// CGSize fittedContentSize = [self fittedContentSize]; +// CGRect fittedCropRect = [self fittedCropRect:false]; +// _contentWrapperView.frame = CGRectMake(0.0f, 0.0f, fittedContentSize.width, fittedContentSize.height); +// +// CGFloat scale = _contentView.bounds.size.width / fittedCropRect.size.width; +// _contentWrapperView.transform = CGAffineTransformMakeScale(scale, scale); +// _contentWrapperView.frame = CGRectMake(0.0f, 0.0f, _contentView.bounds.size.width, _contentView.bounds.size.height); +// +// CGSize contentSize = [self contentSize]; +// _scrollView.minimumZoomScale = 1.0f; +// _scrollView.maximumZoomScale = 1.0f; +// _scrollView.normalZoomScale = 1.0f; +// _scrollView.zoomScale = 1.0f; +// _scrollView.contentSize = contentSize; +// [self contentView].frame = CGRectMake(0.0f, 0.0f, contentSize.width, contentSize.height); +// +// [self adjustZoom]; +// _scrollView.zoomScale = _scrollView.normalZoomScale; +//} +// +//- (void)scrollViewWillBeginZooming:(UIScrollView *)__unused scrollView withView:(UIView *)__unused view +//{ +//} +// +//- (void)scrollViewDidZoom:(UIScrollView *)__unused scrollView +//{ +// [self adjustZoom]; +//} +// +//- (void)scrollViewDidEndZooming:(UIScrollView *)__unused scrollView withView:(UIView *)__unused view atScale:(CGFloat)__unused scale +//{ +// [self adjustZoom]; +// +// TGPaintSwatch *currentSwatch = _portraitSettingsView.swatch; +// [_canvasView setBrushWeight:[self _brushWeightForSize:currentSwatch.brushWeight]]; +// +// if (_scrollView.zoomScale < _scrollView.normalZoomScale - FLT_EPSILON) +// { +// [TGHacks setAnimationDurationFactor:0.5f]; +// [_scrollView setZoomScale:_scrollView.normalZoomScale animated:true]; +// [TGHacks setAnimationDurationFactor:1.0f]; +// } +//} +// +//- (UIView *)contentView +//{ +// return _scrollContentView; +//} +// +//- (CGSize)contentSize +//{ +// return _scrollView.frame.size; +//} +// +//- (UIView *)viewForZoomingInScrollView:(UIScrollView *)__unused scrollView +//{ +// return [self contentView]; +//} +// +//- (void)adjustZoom +//{ +// CGSize contentSize = [self contentSize]; +// CGSize boundsSize = _scrollView.frame.size; +// if (contentSize.width < FLT_EPSILON || contentSize.height < FLT_EPSILON || boundsSize.width < FLT_EPSILON || boundsSize.height < FLT_EPSILON) +// return; +// +// CGFloat scaleWidth = boundsSize.width / contentSize.width; +// CGFloat scaleHeight = boundsSize.height / contentSize.height; +// CGFloat minScale = MIN(scaleWidth, scaleHeight); +// CGFloat maxScale = MAX(scaleWidth, scaleHeight); +// maxScale = MAX(maxScale, minScale * 3.0f); +// +// if (ABS(maxScale - minScale) < 0.01f) +// maxScale = minScale; +// +// _scrollView.contentInset = UIEdgeInsetsZero; +// +// if (_scrollView.minimumZoomScale != 0.05f) +// _scrollView.minimumZoomScale = 0.05f; +// if (_scrollView.normalZoomScale != minScale) +// _scrollView.normalZoomScale = minScale; +// if (_scrollView.maximumZoomScale != maxScale) +// _scrollView.maximumZoomScale = maxScale; +// +// CGRect contentFrame = [self contentView].frame; +// +// if (boundsSize.width > contentFrame.size.width) +// contentFrame.origin.x = (boundsSize.width - contentFrame.size.width) / 2.0f; +// else +// contentFrame.origin.x = 0; +// +// if (boundsSize.height > contentFrame.size.height) +// contentFrame.origin.y = (boundsSize.height - contentFrame.size.height) / 2.0f; +// else +// contentFrame.origin.y = 0; +// +// [self contentView].frame = contentFrame; +// +// _scrollView.scrollEnabled = ABS(_scrollView.zoomScale - _scrollView.normalZoomScale) > FLT_EPSILON; +//} +// +//#pragma mark - Gestures +// +//- (void)handlePinch:(UIPinchGestureRecognizer *)gestureRecognizer +//{ +// [_entitiesContainerView handlePinch:gestureRecognizer]; +//} +// +//- (void)handleRotate:(UIRotationGestureRecognizer *)gestureRecognizer +//{ +// [_entitiesContainerView handleRotate:gestureRecognizer]; +//} +// +//- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)__unused gestureRecognizer +//{ +// if (gestureRecognizer == _pinchGestureRecognizer && _currentEntityView == nil) { +// return false; +// } +// return !_canvasView.isTracking; +//} +// +//- (BOOL)gestureRecognizer:(UIGestureRecognizer *)__unused gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)__unused otherGestureRecognizer +//{ +// return true; +//} +// +//#pragma mark - Transitions +// +//- (void)transitionIn +//{ +// _portraitSettingsView.layer.shouldRasterize = true; +// _landscapeSettingsView.layer.shouldRasterize = true; +// +// [UIView animateWithDuration:0.3f animations:^ +// { +// _portraitToolsWrapperView.alpha = 1.0f; +// _landscapeToolsWrapperView.alpha = 1.0f; +// +// _portraitActionsView.alpha = 1.0f; +// _landscapeActionsView.alpha = 1.0f; +// } completion:^(__unused BOOL finished) +// { +// _portraitSettingsView.layer.shouldRasterize = false; +// _landscapeSettingsView.layer.shouldRasterize = false; +// }]; +// +// if (self.presentedForAvatarCreation) { +// _canvasView.hidden = true; +// } +//} +// +//+ (CGRect)photoContainerFrameForParentViewFrame:(CGRect)parentViewFrame toolbarLandscapeSize:(CGFloat)toolbarLandscapeSize orientation:(UIInterfaceOrientation)orientation panelSize:(CGFloat)panelSize hasOnScreenNavigation:(bool)hasOnScreenNavigation +//{ +// CGRect frame = [TGPhotoEditorTabController photoContainerFrameForParentViewFrame:parentViewFrame toolbarLandscapeSize:toolbarLandscapeSize orientation:orientation panelSize:panelSize hasOnScreenNavigation:hasOnScreenNavigation]; +// +// switch (orientation) +// { +// case UIInterfaceOrientationLandscapeLeft: +// frame.origin.x -= TGPhotoPaintTopPanelSize; +// break; +// +// case UIInterfaceOrientationLandscapeRight: +// frame.origin.x += TGPhotoPaintTopPanelSize; +// break; +// +// default: +// frame.origin.y += TGPhotoPaintTopPanelSize; +// break; +// } +// +// return frame; +//} +// +//- (CGRect)_targetFrameForTransitionInFromFrame:(CGRect)fromFrame +//{ +// CGSize referenceSize = [self referenceViewSize]; +// CGRect containerFrame = [TGPhotoPaintController photoContainerFrameForParentViewFrame:CGRectMake(0, 0, referenceSize.width, referenceSize.height) toolbarLandscapeSize:self.toolbarLandscapeSize orientation:self.effectiveOrientation panelSize:TGPhotoPaintTopPanelSize + TGPhotoPaintBottomPanelSize hasOnScreenNavigation:self.hasOnScreenNavigation]; +// +// CGSize fittedSize = TGScaleToSize(fromFrame.size, containerFrame.size); +// CGRect toFrame = CGRectMake(containerFrame.origin.x + (containerFrame.size.width - fittedSize.width) / 2, containerFrame.origin.y + (containerFrame.size.height - fittedSize.height) / 2, fittedSize.width, fittedSize.height); +// +// return toFrame; +//} +// +//- (void)_finishedTransitionInWithView:(UIView *)transitionView +//{ +// _appeared = true; +// +// if ([transitionView isKindOfClass:[TGPhotoEditorPreviewView class]]) { +// +// } else { +// [transitionView removeFromSuperview]; +// } +// +// [self setupCanvas]; +// _entitiesContainerView.hidden = false; +// +// TGPhotoEditorPreviewView *previewView = _previewView; +// [previewView setPaintingHidden:true]; +// previewView.hidden = false; +// [_containerView insertSubview:previewView belowSubview:_paintingWrapperView]; +// [self updateContentViewLayout]; +// [previewView performTransitionInIfNeeded]; +// +// CGRect rect = [self fittedCropRect:true]; +// _entitiesContainerView.frame = CGRectMake(0, 0, rect.size.width, rect.size.height); +// _entitiesContainerView.transform = CGAffineTransformMakeRotation(_photoEditor.cropRotation); +// +// CGSize fittedOriginalSize = TGScaleToSize(_photoEditor.originalSize, [TGPhotoPaintController maximumPaintingSize]); +// CGSize rotatedSize = TGRotatedContentSize(fittedOriginalSize, _photoEditor.cropRotation); +// CGPoint centerPoint = CGPointMake(rotatedSize.width / 2.0f, rotatedSize.height / 2.0f); +// +// CGFloat scale = fittedOriginalSize.width / _photoEditor.originalSize.width; +// CGPoint offset = TGPaintSubtractPoints(centerPoint, [self fittedCropCenterScale:scale]); +// +// CGPoint boundsCenter = TGPaintCenterOfRect(_contentWrapperView.bounds); +// _entitiesContainerView.center = TGPaintAddPoints(boundsCenter, offset); +// +// if (!_skipEntitiesSetup || _entitiesReady) { +// [_contentWrapperView addSubview:_entitiesContainerView]; +// } +// _entitiesReady = true; +// [self resetScrollView]; +//} +// +//- (void)prepareForCustomTransitionOut +//{ +// _previewView.hidden = true; +// _canvasView.hidden = true; +// _contentView.hidden = true; +// [UIView animateWithDuration:0.3f animations:^ +// { +// _portraitToolsWrapperView.alpha = 0.0f; +// _landscapeToolsWrapperView.alpha = 0.0f; +// } completion:nil]; +//} +// +//- (void)transitionOutSwitching:(bool)__unused switching completion:(void (^)(void))completion +//{ +// [_stickersScreen invalidate]; +// +// TGPhotoEditorPreviewView *previewView = self.previewView; +// previewView.interactionEnded = nil; +// +// _portraitSettingsView.layer.shouldRasterize = true; +// _landscapeSettingsView.layer.shouldRasterize = true; +// +// [UIView animateWithDuration:0.3f animations:^ +// { +// _portraitToolsWrapperView.alpha = 0.0f; +// _landscapeToolsWrapperView.alpha = 0.0f; +// +// _portraitActionsView.alpha = 0.0f; +// _landscapeActionsView.alpha = 0.0f; +// } completion:^(__unused BOOL finished) +// { +// if (completion != nil) +// completion(); +// }]; +//} +// +//- (CGRect)transitionOutSourceFrameForReferenceFrame:(CGRect)referenceFrame orientation:(UIInterfaceOrientation)orientation +//{ +// CGRect containerFrame = [TGPhotoPaintController photoContainerFrameForParentViewFrame:self.view.frame toolbarLandscapeSize:self.toolbarLandscapeSize orientation:orientation panelSize:TGPhotoPaintTopPanelSize + TGPhotoPaintBottomPanelSize hasOnScreenNavigation:self.hasOnScreenNavigation]; +// +// CGSize fittedSize = TGScaleToSize(referenceFrame.size, containerFrame.size); +// return CGRectMake(containerFrame.origin.x + (containerFrame.size.width - fittedSize.width) / 2, containerFrame.origin.y + (containerFrame.size.height - fittedSize.height) / 2, fittedSize.width, fittedSize.height); +//} +// +//- (void)_animatePreviewViewTransitionOutToFrame:(CGRect)targetFrame saving:(bool)saving parentView:(UIView *)parentView completion:(void (^)(void))completion +//{ +// _dismissing = true; +// +// [_entitySelectionView removeFromSuperview]; +// _entitySelectionView = nil; +// +// TGPhotoEditorPreviewView *previewView = self.previewView; +// [previewView prepareForTransitionOut]; +// +// UIInterfaceOrientation orientation = self.effectiveOrientation; +// CGRect containerFrame = [TGPhotoPaintController photoContainerFrameForParentViewFrame:self.view.frame toolbarLandscapeSize:self.toolbarLandscapeSize orientation:orientation panelSize:TGPhotoPaintTopPanelSize + TGPhotoPaintBottomPanelSize hasOnScreenNavigation:self.hasOnScreenNavigation]; +// CGRect referenceFrame = CGRectMake(0, 0, self.photoEditor.rotatedCropSize.width, self.photoEditor.rotatedCropSize.height); +// CGRect rect = CGRectOffset([self transitionOutSourceFrameForReferenceFrame:referenceFrame orientation:orientation], -containerFrame.origin.x, -containerFrame.origin.y); +// previewView.frame = rect; +// +// UIView *snapshotView = nil; +// POPSpringAnimation *snapshotAnimation = nil; +// NSMutableArray *animations = [[NSMutableArray alloc] init]; +// +// if (saving && CGRectIsNull(targetFrame) && parentView != nil) +// { +// snapshotView = [previewView snapshotViewAfterScreenUpdates:false]; +// snapshotView.frame = [_containerView convertRect:previewView.frame toView:parentView]; +// +// UIView *canvasSnapshotView = [_paintingWrapperView resizableSnapshotViewFromRect:[_paintingWrapperView convertRect:previewView.bounds fromView:previewView] afterScreenUpdates:false withCapInsets:UIEdgeInsetsZero]; +// canvasSnapshotView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; +// canvasSnapshotView.transform = _contentView.transform; +// canvasSnapshotView.frame = snapshotView.bounds; +// [snapshotView addSubview:canvasSnapshotView]; +// +// UIView *entitiesSnapshotView = [_contentWrapperView resizableSnapshotViewFromRect:[_contentWrapperView convertRect:previewView.bounds fromView:previewView] afterScreenUpdates:false withCapInsets:UIEdgeInsetsZero]; +// entitiesSnapshotView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; +// entitiesSnapshotView.transform = _contentView.transform; +// entitiesSnapshotView.frame = snapshotView.bounds; +// [snapshotView addSubview:entitiesSnapshotView]; +// +// CGSize fittedSize = TGScaleToSize(previewView.frame.size, self.view.frame.size); +// targetFrame = CGRectMake((self.view.frame.size.width - fittedSize.width) / 2, (self.view.frame.size.height - fittedSize.height) / 2, fittedSize.width, fittedSize.height); +// +// [parentView addSubview:snapshotView]; +// +// snapshotAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewFrame]; +// snapshotAnimation.fromValue = [NSValue valueWithCGRect:snapshotView.frame]; +// snapshotAnimation.toValue = [NSValue valueWithCGRect:targetFrame]; +// [animations addObject:snapshotAnimation]; +// } +// +// targetFrame = CGRectOffset(targetFrame, -containerFrame.origin.x, -containerFrame.origin.y); +// CGPoint targetCenter = TGPaintCenterOfRect(targetFrame); +// +// POPSpringAnimation *previewAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewFrame]; +// previewAnimation.fromValue = [NSValue valueWithCGRect:previewView.frame]; +// previewAnimation.toValue = [NSValue valueWithCGRect:targetFrame]; +// [animations addObject:previewAnimation]; +// +// POPSpringAnimation *previewAlphaAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewAlpha]; +// previewAlphaAnimation.fromValue = @(previewView.alpha); +// previewAlphaAnimation.toValue = @(0.0f); +// [animations addObject:previewAnimation]; +// +// POPSpringAnimation *entitiesAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewCenter]; +// entitiesAnimation.fromValue = [NSValue valueWithCGPoint:_contentView.center]; +// entitiesAnimation.toValue = [NSValue valueWithCGPoint:targetCenter]; +// [animations addObject:entitiesAnimation]; +// +// CGFloat targetEntitiesScale = targetFrame.size.width / _contentView.frame.size.width; +// POPSpringAnimation *entitiesScaleAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewScaleXY]; +// entitiesScaleAnimation.fromValue = [NSValue valueWithCGSize:CGSizeMake(1.0f, 1.0f)]; +// entitiesScaleAnimation.toValue = [NSValue valueWithCGSize:CGSizeMake(targetEntitiesScale, targetEntitiesScale)]; +// [animations addObject:entitiesScaleAnimation]; +// +// POPSpringAnimation *entitiesAlphaAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewAlpha]; +// entitiesAlphaAnimation.fromValue = @(_canvasView.alpha); +// entitiesAlphaAnimation.toValue = @(0.0f); +// [animations addObject:entitiesAlphaAnimation]; +// +// POPSpringAnimation *paintingAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewCenter]; +// paintingAnimation.fromValue = [NSValue valueWithCGPoint:_paintingWrapperView.center]; +// paintingAnimation.toValue = [NSValue valueWithCGPoint:targetCenter]; +// [animations addObject:paintingAnimation]; +// +// CGFloat targetPaintingScale = targetFrame.size.width / _paintingWrapperView.frame.size.width; +// POPSpringAnimation *paintingScaleAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewScaleXY]; +// paintingScaleAnimation.fromValue = [NSValue valueWithCGSize:CGSizeMake(1.0f, 1.0f)]; +// paintingScaleAnimation.toValue = [NSValue valueWithCGSize:CGSizeMake(targetPaintingScale, targetPaintingScale)]; +// [animations addObject:paintingScaleAnimation]; +// +// POPSpringAnimation *paintingAlphaAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewAlpha]; +// paintingAlphaAnimation.fromValue = @(_paintingWrapperView.alpha); +// paintingAlphaAnimation.toValue = @(0.0f); +// [animations addObject:paintingAlphaAnimation]; +// +// [TGPhotoEditorAnimation performBlock:^(__unused bool allFinished) +// { +// [snapshotView removeFromSuperview]; +// +// if (completion != nil) +// completion(); +// } whenCompletedAllAnimations:animations]; +// +// if (snapshotAnimation != nil) +// [snapshotView pop_addAnimation:snapshotAnimation forKey:@"frame"]; +// [previewView pop_addAnimation:previewAnimation forKey:@"frame"]; +// [previewView pop_addAnimation:previewAlphaAnimation forKey:@"alpha"]; +// +// [_contentView pop_addAnimation:entitiesAnimation forKey:@"frame"]; +// [_contentView pop_addAnimation:entitiesScaleAnimation forKey:@"scale"]; +// [_contentView pop_addAnimation:entitiesAlphaAnimation forKey:@"alpha"]; +// +// [_paintingWrapperView pop_addAnimation:paintingAnimation forKey:@"frame"]; +// [_paintingWrapperView pop_addAnimation:paintingScaleAnimation forKey:@"scale"]; +// [_paintingWrapperView pop_addAnimation:paintingAlphaAnimation forKey:@"alpha"]; +// +// if (saving) +// { +// _contentView.hidden = true; +// _paintingWrapperView.hidden = true; +// previewView.hidden = true; +// } +//} +// +//- (CGRect)transitionOutReferenceFrame +//{ +// TGPhotoEditorPreviewView *previewView = _previewView; +// return [previewView convertRect:previewView.bounds toView:self.view]; +//} +// +//- (UIView *)transitionOutReferenceView +//{ +// return _previewView; +//} +// +//- (UIView *)snapshotView +//{ +// TGPhotoEditorPreviewView *previewView = self.previewView; +// return [previewView originalSnapshotView]; +//} +// +//- (void)setInterfaceHidden:(bool)hidden animated:(bool)animated +//{ +// CGFloat targetAlpha = hidden ? 0.0f : 1.0; +// void (^changeBlock)(void) = ^ +// { +// _portraitActionsView.alpha = targetAlpha; +// _landscapeActionsView.alpha = targetAlpha; +// _portraitSettingsView.alpha = targetAlpha; +// _landscapeSettingsView.alpha = targetAlpha; +// }; +// +// if (animated) +// [UIView animateWithDuration:0.25 animations:changeBlock]; +// else +// changeBlock(); +// +// TGPhotoEditorController *editorController = (TGPhotoEditorController *)self.parentViewController; +// if (![editorController isKindOfClass:[TGPhotoEditorController class]]) +// return; +// +// [editorController setToolbarHidden:hidden animated:animated]; +//} +// +//- (void)setDimHidden:(bool)hidden animated:(bool)animated +//{ +// if (!hidden) +// { +// [_entitySelectionView fadeOut]; +// +// if ([_currentEntityView isKindOfClass:[TGPhotoTextEntityView class]]) +// [_dimView.superview insertSubview:_dimView belowSubview:_currentEntityView]; +// else +// [_dimView.superview bringSubviewToFront:_dimView]; +// +// [_doneButton.superview bringSubviewToFront:_doneButton]; +// } +// else +// { +// [_entitySelectionView fadeIn]; +// +// [_dimView.superview bringSubviewToFront:_dimView]; +// +// [_doneButton.superview bringSubviewToFront:_doneButton]; +// } +// +// void (^changeBlock)(void) = ^ +// { +// _dimView.alpha = hidden ? 0.0f : 1.0f; +// _doneButton.alpha = hidden ? 0.0f : 1.0f; +// }; +// +// if (animated) +// [UIView animateWithDuration:0.25 animations:changeBlock]; +// else +// changeBlock(); +//} +// +//- (id)currentResultRepresentation +//{ +// return TGPaintCombineCroppedImages(self.photoEditor.currentResultImage, [self image], true, _photoEditor.originalSize, _photoEditor.cropRect, _photoEditor.cropOrientation, _photoEditor.cropRotation, false); +//} +// +//#pragma mark - Layout +// +//- (void)viewWillLayoutSubviews +//{ +// [super viewWillLayoutSubviews]; +// +// [self updateLayout:[[LegacyComponentsGlobals provider] applicationStatusBarOrientation]]; +// [_entitySelectionView update]; +//} +// +//- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration +//{ +// [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration]; +// +// if (_menuContainerView != nil) +// { +// [_menuContainerView removeFromSuperview]; +// _menuContainerView = nil; +// } +// +// [self updateLayout:toInterfaceOrientation]; +//} +// +//- (void)updateContentViewLayout +//{ +// CGAffineTransform rotationTransform = CGAffineTransformMakeRotation(TGRotationForOrientation(_photoEditor.cropOrientation)); +// _contentView.transform = rotationTransform; +// _contentView.frame = self.previewView.frame; +// [self resetScrollView]; +//} +// +//- (void)updateLayout:(UIInterfaceOrientation)orientation +//{ +// if ([self inFormSheet] || [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) +// { +// _landscapeToolsWrapperView.hidden = true; +// orientation = UIInterfaceOrientationPortrait; +// } +// +// CGSize referenceSize = [self referenceViewSize]; +// CGFloat screenSide = MAX(referenceSize.width, referenceSize.height) + 2 * TGPhotoPaintBottomPanelSize; +// +// bool sizeUpdated = false; +// if (!CGSizeEqualToSize(referenceSize, _previousSize)) { +// sizeUpdated = true; +// _previousSize = referenceSize; +// } +// +// CGFloat panelToolbarPortraitSize = TGPhotoPaintBottomPanelSize + TGPhotoEditorToolbarSize; +// CGFloat panelToolbarLandscapeSize = TGPhotoPaintBottomPanelSize + self.toolbarLandscapeSize; +// +// UIEdgeInsets safeAreaInset = [TGViewController safeAreaInsetForOrientation:orientation hasOnScreenNavigation:self.hasOnScreenNavigation]; +// UIEdgeInsets screenEdges = UIEdgeInsetsMake((screenSide - referenceSize.height) / 2, (screenSide - referenceSize.width) / 2, (screenSide + referenceSize.height) / 2, (screenSide + referenceSize.width) / 2); +// screenEdges.top += safeAreaInset.top; +// screenEdges.left += safeAreaInset.left; +// screenEdges.bottom -= safeAreaInset.bottom; +// screenEdges.right -= safeAreaInset.right; +// +// CGRect containerFrame = [TGPhotoPaintController photoContainerFrameForParentViewFrame:CGRectMake(0, 0, referenceSize.width, referenceSize.height) toolbarLandscapeSize:self.toolbarLandscapeSize orientation:orientation panelSize:TGPhotoPaintTopPanelSize + TGPhotoPaintBottomPanelSize hasOnScreenNavigation:self.hasOnScreenNavigation]; +// +// _settingsViewWrapper.frame = self.parentViewController.view.bounds; +// +// _doneButton.frame = CGRectMake(screenEdges.right - _doneButton.frame.size.width - 8.0, screenEdges.top + 2.0, _doneButton.frame.size.width, _doneButton.frame.size.height); +// +// if (_settingsView != nil) +// [_settingsView setInterfaceOrientation:orientation]; +// +// switch (orientation) +// { +// case UIInterfaceOrientationLandscapeLeft: +// { +// _landscapeSettingsView.interfaceOrientation = orientation; +// +// [UIView performWithoutAnimation:^ +// { +// _landscapeToolsWrapperView.frame = CGRectMake(0, screenEdges.top, panelToolbarLandscapeSize, _landscapeToolsWrapperView.frame.size.height); +// _landscapeSettingsView.frame = CGRectMake(panelToolbarLandscapeSize - TGPhotoPaintBottomPanelSize, 0, TGPhotoPaintBottomPanelSize, _landscapeSettingsView.frame.size.height); +// }]; +// +// _landscapeToolsWrapperView.frame = CGRectMake(screenEdges.left, screenEdges.top, panelToolbarLandscapeSize, referenceSize.height); +// _landscapeSettingsView.frame = CGRectMake(_landscapeSettingsView.frame.origin.x, _landscapeSettingsView.frame.origin.y, _landscapeSettingsView.frame.size.width, _landscapeToolsWrapperView.frame.size.height); +// +// _portraitToolsWrapperView.frame = CGRectMake(screenEdges.left, screenSide - panelToolbarPortraitSize, referenceSize.width, panelToolbarPortraitSize); +// _portraitSettingsView.frame = CGRectMake(0, 0, _portraitToolsWrapperView.frame.size.width, TGPhotoPaintBottomPanelSize); +// +// _landscapeActionsView.frame = CGRectMake(screenEdges.right - TGPhotoPaintTopPanelSize, screenEdges.top, TGPhotoPaintTopPanelSize, referenceSize.height); +// +// _settingsView.frame = CGRectMake(self.toolbarLandscapeSize + 50.0f + safeAreaInset.left, 0.0f, _settingsView.frame.size.width, _settingsView.frame.size.height); +// } +// break; +// +// case UIInterfaceOrientationLandscapeRight: +// { +// _landscapeSettingsView.interfaceOrientation = orientation; +// +// [UIView performWithoutAnimation:^ +// { +// _landscapeToolsWrapperView.frame = CGRectMake(screenSide - panelToolbarLandscapeSize, screenEdges.top, panelToolbarLandscapeSize, _landscapeToolsWrapperView.frame.size.height); +// _landscapeSettingsView.frame = CGRectMake(0, 0, TGPhotoPaintBottomPanelSize, _landscapeSettingsView.frame.size.height); +// }]; +// +// _landscapeToolsWrapperView.frame = CGRectMake(screenEdges.right - panelToolbarLandscapeSize, screenEdges.top, panelToolbarLandscapeSize, referenceSize.height); +// _landscapeSettingsView.frame = CGRectMake(_landscapeSettingsView.frame.origin.x, _landscapeSettingsView.frame.origin.y, _landscapeSettingsView.frame.size.width, _landscapeToolsWrapperView.frame.size.height); +// +// _portraitToolsWrapperView.frame = CGRectMake(screenEdges.top, screenSide - panelToolbarPortraitSize, referenceSize.width, panelToolbarPortraitSize); +// _portraitSettingsView.frame = CGRectMake(0, 0, _portraitToolsWrapperView.frame.size.width, TGPhotoPaintBottomPanelSize); +// +// _landscapeActionsView.frame = CGRectMake(screenEdges.left, screenEdges.top, TGPhotoPaintTopPanelSize, referenceSize.height); +// +// _settingsView.frame = CGRectMake(_settingsViewWrapper.frame.size.width - _settingsView.frame.size.width - self.toolbarLandscapeSize - 50.0f - safeAreaInset.right, 0.0f, _settingsView.frame.size.width, _settingsView.frame.size.height); +// } +// break; +// +// default: +// { +// CGFloat x = _landscapeToolsWrapperView.frame.origin.x; +// if (x < screenSide / 2) +// x = 0; +// else +// x = screenSide - TGPhotoEditorPanelSize; +// _landscapeToolsWrapperView.frame = CGRectMake(x, screenEdges.top, panelToolbarLandscapeSize, referenceSize.height); +// +// _portraitToolsWrapperView.frame = CGRectMake(screenEdges.left, screenEdges.bottom - panelToolbarPortraitSize, referenceSize.width, panelToolbarPortraitSize); +// _portraitSettingsView.frame = CGRectMake(0, 0, referenceSize.width, TGPhotoPaintBottomPanelSize); +// +// _portraitActionsView.frame = CGRectMake(screenEdges.left, screenEdges.top, referenceSize.width, TGPhotoPaintTopPanelSize); +// +// if ([_context currentSizeClass] == UIUserInterfaceSizeClassRegular) +// { +// _settingsView.frame = CGRectMake(_settingsViewWrapper.frame.size.width / 2.0f - 10.0f, _settingsViewWrapper.frame.size.height - _settingsView.frame.size.height - TGPhotoEditorToolbarSize - 50.0f, _settingsView.frame.size.width, _settingsView.frame.size.height); +// } +// else +// { +// _settingsView.frame = CGRectMake(_settingsViewWrapper.frame.size.width - _settingsView.frame.size.width, _settingsViewWrapper.frame.size.height - _settingsView.frame.size.height - TGPhotoEditorToolbarSize - 50.0f - safeAreaInset.bottom, _settingsView.frame.size.width, _settingsView.frame.size.height); +// } +// } +// break; +// } +// +// PGPhotoEditor *photoEditor = self.photoEditor; +// TGPhotoEditorPreviewView *previewView = self.previewView; +// +// CGSize fittedSize = TGScaleToSize(photoEditor.rotatedCropSize, containerFrame.size); +// CGRect previewFrame = CGRectMake((containerFrame.size.width - fittedSize.width) / 2, (containerFrame.size.height - fittedSize.height) / 2, fittedSize.width, fittedSize.height); +// +// CGFloat visibleArea = self.view.frame.size.height - _keyboardHeight; +// CGFloat yCenter = visibleArea / 2.0f; +// CGFloat offset = yCenter - _previewView.center.y - containerFrame.origin.y; +// CGFloat offsetHeight = _keyboardHeight > FLT_EPSILON ? offset : 0.0f; +// +// _wrapperView.frame = CGRectMake((referenceSize.width - screenSide) / 2, (referenceSize.height - screenSide) / 2 + offsetHeight, screenSide, screenSide); +// +// if (_dismissing || (previewView.superview != _containerView && previewView.superview != self.view)) +// return; +// +// if (previewView.superview == self.view) +// { +// previewFrame = CGRectMake(containerFrame.origin.x + (containerFrame.size.width - fittedSize.width) / 2, containerFrame.origin.y + (containerFrame.size.height - fittedSize.height) / 2, fittedSize.width, fittedSize.height); +// } +// +// UIImageOrientation cropOrientation = _photoEditor.cropOrientation; +// CGRect cropRect = _photoEditor.cropRect; +// CGSize originalSize = _photoEditor.originalSize; +// CGFloat rotation = _photoEditor.cropRotation; +// +// CGAffineTransform rotationTransform = CGAffineTransformMakeRotation(TGRotationForOrientation(cropOrientation)); +// _contentView.transform = rotationTransform; +// _contentView.frame = previewFrame; +// +// _scrollView.frame = self.view.bounds; +// +// if (sizeUpdated) { +// [self resetScrollView]; +// } +// [self adjustZoom]; +// +// _paintingWrapperView.transform = CGAffineTransformMakeRotation(TGRotationForOrientation(cropOrientation)); +// _paintingWrapperView.frame = previewFrame; +// +// CGFloat originalWidth = TGOrientationIsSideward(cropOrientation, NULL) ? previewFrame.size.height : previewFrame.size.width; +// CGFloat ratio = originalWidth / cropRect.size.width; +// CGRect originalFrame = CGRectMake(-cropRect.origin.x * ratio, -cropRect.origin.y * ratio, originalSize.width * ratio, originalSize.height * ratio); +// +// previewView.frame = previewFrame; +// +// if ([self presentedForAvatarCreation]) { +// CGAffineTransform transform = CGAffineTransformMakeRotation(TGRotationForOrientation(photoEditor.cropOrientation)); +// if (photoEditor.cropMirrored) +// transform = CGAffineTransformScale(transform, -1.0f, 1.0f); +// previewView.transform = transform; +// } +// +// CGSize fittedOriginalSize = CGSizeMake(originalSize.width * ratio, originalSize.height * ratio); +// CGSize rotatedSize = TGRotatedContentSize(fittedOriginalSize, rotation); +// CGPoint centerPoint = CGPointMake(rotatedSize.width / 2.0f, rotatedSize.height / 2.0f); +// +// CGFloat scale = fittedOriginalSize.width / _photoEditor.originalSize.width; +// CGPoint centerOffset = TGPaintSubtractPoints(centerPoint, [self fittedCropCenterScale:scale]); +// +// _canvasView.transform = CGAffineTransformIdentity; +// _canvasView.frame = originalFrame; +// _canvasView.transform = CGAffineTransformMakeRotation(rotation); +// _canvasView.center = TGPaintAddPoints(TGPaintCenterOfRect(_paintingWrapperView.bounds), centerOffset); +// +// _selectionContainerView.transform = CGAffineTransformRotate(rotationTransform, rotation); +// _selectionContainerView.frame = previewFrame; +// _eyedropperView.frame = _selectionContainerView.bounds; +// +// _containerView.frame = CGRectMake(containerFrame.origin.x, containerFrame.origin.y + offsetHeight, containerFrame.size.width, containerFrame.size.height); +//} +// +//#pragma mark - Keyboard Avoidance +// +//- (void)keyboardWillChangeFrame:(NSNotification *)notification +//{ +// UIView *parentView = self.view; +// +// NSTimeInterval duration = notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] == nil ? 0.3 : [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; +// int curve = [notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] intValue]; +// CGRect screenKeyboardFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; +// CGRect keyboardFrame = [parentView convertRect:screenKeyboardFrame fromView:nil]; +// +// CGFloat keyboardHeight = (keyboardFrame.size.height <= FLT_EPSILON || keyboardFrame.size.width <= FLT_EPSILON) ? 0.0f : (parentView.frame.size.height - keyboardFrame.origin.y); +// keyboardHeight = MAX(keyboardHeight, 0.0f); +// +// _keyboardHeight = keyboardHeight; +// +// [self keyboardHeightChangedTo:keyboardHeight duration:duration curve:curve]; +//} +// +//- (void)keyboardHeightChangedTo:(CGFloat)height duration:(NSTimeInterval)duration curve:(NSInteger)curve +//{ +// CGSize referenceSize = [self referenceViewSize]; +// CGFloat screenSide = MAX(referenceSize.width, referenceSize.height) + 2 * TGPhotoPaintBottomPanelSize; +// +// CGRect containerFrame = [TGPhotoPaintController photoContainerFrameForParentViewFrame:CGRectMake(0, 0, referenceSize.width, referenceSize.height) toolbarLandscapeSize:self.toolbarLandscapeSize orientation:self.effectiveOrientation panelSize:TGPhotoPaintTopPanelSize + TGPhotoPaintBottomPanelSize hasOnScreenNavigation:self.hasOnScreenNavigation]; +// +// CGFloat visibleArea = self.view.frame.size.height - height; +// CGFloat yCenter = visibleArea / 2.0f; +// CGFloat offset = yCenter - _previewView.center.y - containerFrame.origin.y; +// CGFloat offsetHeight = height > FLT_EPSILON ? offset : 0.0f; +// +// [UIView animateWithDuration:duration delay:0.0 options:curve animations:^ +// { +// _wrapperView.frame = CGRectMake((referenceSize.width - screenSide) / 2, (referenceSize.height - screenSide) / 2 + offsetHeight, _wrapperView.frame.size.width, _wrapperView.frame.size.height); +// _containerView.frame = CGRectMake(containerFrame.origin.x, containerFrame.origin.y + offsetHeight, containerFrame.size.width, containerFrame.size.height); +// } completion:nil]; +//} +// +//- (void)_setStickerEntityPosition:(TGPhotoPaintStickerEntity *)entity +//{ +// TGStickerMaskDescription *mask = [_stickersContext maskDescriptionForDocument:entity.document]; +// int64_t documentId = [_stickersContext documentIdForDocument:entity.document]; +// TGPhotoMaskPosition *position = [self _positionForMaskDescription:mask documentId:documentId]; +// if (position != nil) +// { +// entity.position = position.center; +// entity.angle = position.angle; +// entity.scale = position.scale; +// } +// else +// { +// entity.position = [self startPositionRelativeToEntity:nil]; +// entity.angle = [self startRotation]; +// } +//} +// +//- (TGPhotoMaskPosition *)_positionForMaskDescription:(TGStickerMaskDescription *)mask documentId:(int64_t)documentId +//{ +// if (mask == nil) +// return nil; +// +// TGPhotoMaskAnchor anchor = [TGPhotoMaskPosition anchorOfMask:mask]; +// if (anchor == TGPhotoMaskAnchorNone) +// return nil; +// +// TGPaintFace *face = [self _randomFaceWithVacantAnchor:anchor documentId:documentId]; +// if (face == nil) +// return nil; +// +// CGPoint referencePoint = CGPointZero; +// CGFloat referenceWidth = 0.0f; +// CGFloat angle = 0.0f; +// CGSize baseSize = [self _stickerBaseSizeForCurrentPainting]; +// CGRect faceBounds = [TGPaintFaceUtils transposeRect:face.bounds paintingSize:_painting.size originalSize:_photoEditor.originalSize]; +// +// switch (anchor) +// { +// case TGPhotoMaskAnchorForehead: +// { +// referencePoint = [TGPaintFaceUtils transposePoint:[face foreheadPoint] paintingSize:_painting.size originalSize:_photoEditor.originalSize]; +// referenceWidth = faceBounds.size.width; +// angle = face.angle; +// } +// break; +// +// case TGPhotoMaskAnchorEyes: +// { +// CGPoint point = [face eyesCenterPointAndDistance:&referenceWidth]; +// referenceWidth = [TGPaintFaceUtils transposeWidth:referenceWidth paintingSize:_painting.size originalSize:_photoEditor.originalSize]; +// referencePoint = [TGPaintFaceUtils transposePoint:point paintingSize:_painting.size originalSize:_photoEditor.originalSize]; +// angle = [face eyesAngle]; +// } +// break; +// +// case TGPhotoMaskAnchorMouth: +// { +// referencePoint = [TGPaintFaceUtils transposePoint:[face mouthPoint] paintingSize:_painting.size originalSize:_photoEditor.originalSize]; +// referenceWidth = faceBounds.size.width; +// angle = face.angle; +// } +// break; +// +// case TGPhotoMaskAnchorChin: +// { +// referencePoint = [TGPaintFaceUtils transposePoint:[face chinPoint] paintingSize:_painting.size originalSize:_photoEditor.originalSize]; +// referenceWidth = faceBounds.size.width; +// angle = face.angle; +// } +// break; +// +// default: +// break; +// } +// +// CGFloat scale = referenceWidth / baseSize.width * mask.zoom; +// +// CGPoint xComp = CGPointMake(sin(M_PI_2 - angle) * referenceWidth * mask.point.x, +// cos(M_PI_2 - angle) * referenceWidth * mask.point.x); +// CGPoint yComp = CGPointMake(cos(M_PI_2 + angle) * referenceWidth * mask.point.y, +// sin(M_PI_2 + angle) * referenceWidth * mask.point.y); +// +// CGPoint position = CGPointMake(referencePoint.x + xComp.x + yComp.x, referencePoint.y + xComp.y + yComp.y); +// +// return [TGPhotoMaskPosition maskPositionWithCenter:position scale:scale angle:angle]; +//} +// +//- (TGPaintFace *)_randomFaceWithVacantAnchor:(TGPhotoMaskAnchor)anchor documentId:(int64_t)documentId +//{ +// NSInteger randomIndex = (NSInteger)arc4random_uniform((uint32_t)self.faces.count); +// NSInteger count = self.faces.count; +// NSInteger remaining = self.faces.count; +// +// for (NSInteger i = randomIndex; remaining > 0; (i = (i + 1) % count), remaining--) +// { +// TGPaintFace *face = self.faces[i]; +// if (![self _isFaceAnchorOccupied:face anchor:anchor documentId:documentId]) +// return face; +// } +// +// return nil; +//} +// +//- (bool)_isFaceAnchorOccupied:(TGPaintFace *)face anchor:(TGPhotoMaskAnchor)anchor documentId:(int64_t)documentId +//{ +// CGPoint anchorPoint = CGPointZero; +// switch (anchor) +// { +// case TGPhotoMaskAnchorForehead: +// { +// anchorPoint = [TGPaintFaceUtils transposePoint:[face foreheadPoint] paintingSize:_painting.size originalSize:_photoEditor.originalSize]; +// } +// break; +// +// case TGPhotoMaskAnchorEyes: +// { +// anchorPoint = [TGPaintFaceUtils transposePoint:[face eyesCenterPointAndDistance:NULL] paintingSize:_painting.size originalSize:_photoEditor.originalSize]; +// } +// break; +// +// case TGPhotoMaskAnchorMouth: +// { +// anchorPoint = [TGPaintFaceUtils transposePoint:[face mouthPoint] paintingSize:_painting.size originalSize:_photoEditor.originalSize]; +// } +// break; +// +// case TGPhotoMaskAnchorChin: +// { +// anchorPoint = [TGPaintFaceUtils transposePoint:[face chinPoint] paintingSize:_painting.size originalSize:_photoEditor.originalSize]; +// } +// break; +// +// default: +// { +// +// } +// break; +// } +// +// CGRect faceBounds = [TGPaintFaceUtils transposeRect:face.bounds paintingSize:_painting.size originalSize:_photoEditor.originalSize]; +// CGFloat minDistance = faceBounds.size.width * 1.1; +// +// for (TGPhotoStickerEntityView *view in _entitiesContainerView.subviews) +// { +// if (![view isKindOfClass:[TGPhotoStickerEntityView class]]) +// continue; +// +// TGPhotoPaintStickerEntity *entity = view.entity; +// TGStickerMaskDescription *mask = [_stickersContext maskDescriptionForDocument:view.entity.document]; +// int64_t maskDocumentId = [_stickersContext documentIdForDocument:entity.document]; +// +// if ([TGPhotoMaskPosition anchorOfMask:mask] != anchor) +// continue; +// +// if ((documentId == maskDocumentId || self.faces.count > 1) && TGPaintDistance(entity.position, anchorPoint) < minDistance) +// return true; +// } +// +// return false; +//} +// +//- (NSArray *)faces +//{ +// TGPhotoEditorController *editorController = (TGPhotoEditorController *)self.parentViewController; +// if ([editorController isKindOfClass:[TGPhotoEditorController class]]) +// return editorController.faces; +// else +// return @[]; +//} +// +//- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures +//{ +// return UIRectEdgeTop | UIRectEdgeBottom; +//} +// +//@end diff --git a/submodules/LegacyComponents/Sources/TGPhotoToolsController.h b/submodules/LegacyComponents/Sources/TGPhotoToolsController.h index 00134a7c5e..824ff1cb6f 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoToolsController.h +++ b/submodules/LegacyComponents/Sources/TGPhotoToolsController.h @@ -3,10 +3,10 @@ @class PGPhotoEditor; @class PGPhotoTool; @class TGPhotoEditorPreviewView; -@class TGPhotoEntitiesContainerView; +@protocol TGPhotoDrawingEntitiesView; @interface TGPhotoToolsController : TGPhotoEditorTabController -- (instancetype)initWithContext:(id)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView entitiesView:(TGPhotoEntitiesContainerView *)entitiesView; +- (instancetype)initWithContext:(id)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView entitiesView:(UIView *)entitiesView; @end diff --git a/submodules/LegacyComponents/Sources/TGPhotoToolsController.m b/submodules/LegacyComponents/Sources/TGPhotoToolsController.m index 6a635517e7..f66777e0ae 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoToolsController.m +++ b/submodules/LegacyComponents/Sources/TGPhotoToolsController.m @@ -22,9 +22,8 @@ #import "TGPhotoEditorPreviewView.h" #import "TGPhotoEditorHUDView.h" #import "TGPhotoEditorSparseView.h" -#import "TGPhotoEntitiesContainerView.h" -#import "TGPhotoPaintController.h" +#import "TGPhotoDrawingController.h" const CGFloat TGPhotoEditorToolsPanelSize = 180.0f; const CGFloat TGPhotoEditorToolsLandscapePanelSize = TGPhotoEditorToolsPanelSize + 40.0f; @@ -48,7 +47,7 @@ const CGFloat TGPhotoEditorToolsLandscapePanelSize = TGPhotoEditorToolsPanelSize TGPhotoEditorCollectionView *_portraitCollectionView; TGPhotoEditorCollectionView *_landscapeCollectionView; TGPhotoEditorHUDView *_hudView; - TGPhotoEntitiesContainerView *_entitiesView; + UIView *_entitiesView; void (^_changeBlock)(PGPhotoTool *, id, bool); void (^_interactionBegan)(void); @@ -71,7 +70,7 @@ const CGFloat TGPhotoEditorToolsLandscapePanelSize = TGPhotoEditorToolsPanelSize @implementation TGPhotoToolsController -- (instancetype)initWithContext:(id)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView entitiesView:(TGPhotoEntitiesContainerView *)entitiesView +- (instancetype)initWithContext:(id)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView entitiesView:(UIView *)entitiesView { self = [super initWithContext:context]; if (self != nil) diff --git a/submodules/LegacyComponents/Sources/TGPhotoVideoEditor.m b/submodules/LegacyComponents/Sources/TGPhotoVideoEditor.m index febe7151b2..bbc8f2084f 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoVideoEditor.m +++ b/submodules/LegacyComponents/Sources/TGPhotoVideoEditor.m @@ -12,7 +12,7 @@ @implementation TGPhotoVideoEditor -+ (void)presentWithContext:(id)context parentController:(TGViewController *)parentController image:(UIImage *)image video:(NSURL *)video stickersContext:(id)stickersContext transitionView:(UIView *)transitionView didFinishWithImage:(void (^)(UIImage *image))didFinishWithImage didFinishWithVideo:(void (^)(UIImage *image, NSURL *url, TGVideoEditAdjustments *adjustments))didFinishWithVideo dismissed:(void (^)(void))dismissed ++ (void)presentWithContext:(id)context parentController:(TGViewController *)parentController image:(UIImage *)image video:(NSURL *)video stickersContext:(id)stickersContext transitionView:(UIView *)transitionView senderName:(NSString *)senderName didFinishWithImage:(void (^)(UIImage *image))didFinishWithImage didFinishWithVideo:(void (^)(UIImage *image, NSURL *url, TGVideoEditAdjustments *adjustments))didFinishWithVideo dismissed:(void (^)(void))dismissed { id windowManager = [context makeOverlayWindowManager]; @@ -35,6 +35,7 @@ void (^present)(UIImage *) = ^(UIImage *screenImage) { TGPhotoEditorController *controller = [[TGPhotoEditorController alloc] initWithContext:[windowManager context] item:editableItem intent:TGPhotoEditorControllerAvatarIntent | TGPhotoEditorControllerSuggestedAvatarIntent adjustments:nil caption:nil screenImage:screenImage availableTabs:[TGPhotoEditorController defaultTabsForAvatarIntent] selectedTab:TGPhotoEditorCropTab]; + controller.senderName = senderName; controller.stickersContext = stickersContext; TGMediaAvatarEditorTransition *transition; diff --git a/submodules/LegacyComponents/Sources/TGVideoEditAdjustments.m b/submodules/LegacyComponents/Sources/TGVideoEditAdjustments.m index f6b96bb53d..fd39b394cc 100644 --- a/submodules/LegacyComponents/Sources/TGVideoEditAdjustments.m +++ b/submodules/LegacyComponents/Sources/TGVideoEditAdjustments.m @@ -76,33 +76,8 @@ const NSTimeInterval TGVideoEditMaximumGifDuration = 30.5; } if (dictionary[@"originalSize"]) adjustments->_originalSize = [dictionary[@"originalSize"] CGSizeValue]; - if (dictionary[@"entities"]) { - NSMutableArray *entities = [[NSMutableArray alloc] init]; - - for (NSDictionary *dict in dictionary[@"entities"]) { - if ([dict[@"type"] isEqualToString:@"sticker"]) { - TGPhotoPaintStickerEntity *entity = [[TGPhotoPaintStickerEntity alloc] initWithDocument:dict[@"data"] baseSize:[dict[@"baseSize"] CGSizeValue] animated:[dict[@"animated"] boolValue]]; - entity.uuid = [dict[@"uuid"] integerValue]; - entity.position = [dict[@"position"] CGPointValue]; - entity.scale = [dict[@"scale"] floatValue]; - entity.angle = [dict[@"angle"] floatValue]; - entity.mirrored = [dict[@"mirrored"] boolValue]; - [entities addObject:entity]; - } else if ([dict[@"type"] isEqualToString:@"text"]) { - UIImage *renderImage = [[UIImage alloc] initWithData:dict[@"data"]]; - if (renderImage != nil) { - TGPhotoPaintTextEntity *entity = [[TGPhotoPaintTextEntity alloc] initWithText:nil font:nil swatch:nil baseFontSize:0.0 maxWidth:0.0 style:TGPhotoPaintTextEntityStyleRegular]; - entity.uuid = [dict[@"uuid"] integerValue]; - entity.position = [dict[@"position"] CGPointValue]; - entity.scale = [dict[@"scale"] floatValue]; - entity.angle = [dict[@"angle"] floatValue]; - entity.renderImage = renderImage; - [entities addObject:entity]; - } - } - } - - adjustments->_paintingData = [TGPaintingData dataWithPaintingImagePath:dictionary[@"paintingImagePath"] entities:entities]; + if (dictionary[@"entitiesData"]) { + adjustments->_paintingData = [TGPaintingData dataWithPaintingImagePath:dictionary[@"paintingImagePath"] entitiesData:dictionary[@"entitiesData"]]; } else if (dictionary[@"paintingImagePath"]) { adjustments->_paintingData = [TGPaintingData dataWithPaintingImagePath:dictionary[@"paintingImagePath"]]; } @@ -240,42 +215,8 @@ const NSTimeInterval TGVideoEditMaximumGifDuration = 30.5; if (self.paintingData.imagePath != nil) { dict[@"paintingImagePath"] = self.paintingData.imagePath; } - - NSMutableArray *entities = [[NSMutableArray alloc] init]; - - if (self.paintingData.entities != nil) { - for (TGPhotoPaintEntity *entity in self.paintingData.entities) { - if ([entity isKindOfClass:[TGPhotoPaintStickerEntity class]]) { - TGPhotoPaintStickerEntity *stickerEntity = (TGPhotoPaintStickerEntity *)entity; - NSMutableDictionary *sticker = [[NSMutableDictionary alloc] init]; - sticker[@"type"] = @"sticker"; - sticker[@"baseSize"] = [NSValue valueWithCGSize:stickerEntity.baseSize]; - sticker[@"uuid"] = @(stickerEntity.uuid); - sticker[@"data"] = stickerEntity.document; - sticker[@"position"] = [NSValue valueWithCGPoint:stickerEntity.position]; - sticker[@"scale"] = @(stickerEntity.scale); - sticker[@"angle"] = @(stickerEntity.angle); - sticker[@"mirrored"] = @(stickerEntity.mirrored); - sticker[@"animated"] = @(stickerEntity.animated); - [entities addObject:sticker]; - } else if ([entity isKindOfClass:[TGPhotoPaintTextEntity class]]) { - TGPhotoPaintTextEntity *textEntity = (TGPhotoPaintTextEntity *)entity; - NSMutableDictionary *text = [[NSMutableDictionary alloc] init]; - if (textEntity.renderImage != nil) { - text[@"type"] = @"text"; - text[@"uuid"] = @(textEntity.uuid); - text[@"data"] = UIImagePNGRepresentation(textEntity.renderImage); - text[@"position"] = [NSValue valueWithCGPoint:textEntity.position]; - text[@"scale"] = @(textEntity.scale); - text[@"angle"] = @(textEntity.angle); - [entities addObject:text]; - } - } - } - } - - if (entities.count > 0) { - dict[@"entities"] = entities; + if (self.paintingData.entitiesData != nil) { + dict[@"entitiesData"] = self.paintingData.entitiesData; } } diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyAvatarPicker.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyAvatarPicker.swift index 8e97fffc79..9c8150a9a4 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyAvatarPicker.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyAvatarPicker.swift @@ -53,7 +53,7 @@ public func presentLegacyAvatarPicker(holder: Atomic, signup: Bool, t } -public func legacyAvatarEditor(context: AccountContext, media: AnyMediaReference, transitionView: UIView?, present: @escaping (ViewController, Any?) -> Void, imageCompletion: @escaping (UIImage) -> Void, videoCompletion: @escaping (UIImage, URL, TGVideoEditAdjustments) -> Void) { +public func legacyAvatarEditor(context: AccountContext, media: AnyMediaReference, transitionView: UIView?, senderName: String? = nil, present: @escaping (ViewController, Any?) -> Void, imageCompletion: @escaping (UIImage) -> Void, videoCompletion: @escaping (UIImage, URL, TGVideoEditAdjustments) -> Void) { let _ = (fetchMediaData(context: context, postbox: context.account.postbox, mediaReference: media) |> deliverOnMainQueue).start(next: { (value, isImage) in guard case let .data(data) = value, data.complete else { @@ -93,7 +93,7 @@ public func legacyAvatarEditor(context: AccountContext, media: AnyMediaReference present(legacyController, nil) - TGPhotoVideoEditor.present(with: legacyController.context, parentController: emptyController, image: image, video: url, stickersContext: paintStickersContext, transitionView: transitionView, didFinishWithImage: { image in + TGPhotoVideoEditor.present(with: legacyController.context, parentController: emptyController, image: image, video: url, stickersContext: paintStickersContext, transitionView: transitionView, senderName: senderName, didFinishWithImage: { image in if let image = image { imageCompletion(image) } diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift index 24e00747b3..8d67d52708 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift @@ -290,18 +290,18 @@ public final class LegacyPaintEntityRenderer: NSObject, TGPhotoPaintEntityRender self.originalSize = adjustments.originalSize self.cropRect = adjustments.cropRect.isEmpty ? nil : adjustments.cropRect - var entities: [LegacyPaintEntity] = [] - if let paintingData = adjustments.paintingData, let paintingEntities = paintingData.entities { - for paintingEntity in paintingEntities { - if let sticker = paintingEntity as? TGPhotoPaintStickerEntity { - if let account = account, let entity = LegacyPaintStickerEntity(account: account, entity: sticker) { - entities.append(entity) - } - } else if let text = paintingEntity as? TGPhotoPaintTextEntity { - entities.append(LegacyPaintTextEntity(entity: text)) - } - } - } + let entities: [LegacyPaintEntity] = [] +// if let paintingData = adjustments.paintingData, let paintingEntities = paintingData.entities { +// for paintingEntity in paintingEntities { +// if let sticker = paintingEntity as? TGPhotoPaintStickerEntity { +// if let account = account, let entity = LegacyPaintStickerEntity(account: account, entity: sticker) { +// entities.append(entity) +// } +// } else if let text = paintingEntity as? TGPhotoPaintTextEntity { +// entities.append(LegacyPaintTextEntity(entity: text)) +// } +// } +// } self.entities = entities super.init() @@ -482,8 +482,8 @@ public final class LegacyPaintStickersContext: NSObject, TGPhotoPaintStickersCon let contentWrapperView: UIView! let interfaceController: TGPhotoDrawingInterfaceController! - init(context: AccountContext, size: CGSize) { - let interfaceController = DrawingScreen(context: context, size: size) + init(context: AccountContext, size: CGSize, originalSize: CGSize) { + let interfaceController = DrawingScreen(context: context, size: size, originalSize: originalSize) self.interfaceController = interfaceController self.drawingView = interfaceController.drawingView self.drawingEntitiesView = interfaceController.entitiesView @@ -494,8 +494,8 @@ public final class LegacyPaintStickersContext: NSObject, TGPhotoPaintStickersCon } } - public func drawingAdapter(_ size: CGSize) -> TGPhotoDrawingAdapter! { - return LegacyDrawingAdapter(context: self.context, size: size) + public func drawingAdapter(_ size: CGSize, originalSize: CGSize) -> TGPhotoDrawingAdapter! { + return LegacyDrawingAdapter(context: self.context, size: size, originalSize: originalSize) } public func solidRoundedButton(_ title: String!, action: (() -> Void)!) -> (UIView & TGPhotoSolidRoundedButtonView)! { @@ -504,6 +504,11 @@ public final class LegacyPaintStickersContext: NSObject, TGPhotoPaintStickersCon button.pressed = action return button } + + public func drawingEntitiesView(with size: CGSize) -> (UIView & TGPhotoDrawingEntitiesView)! { + let view = DrawingEntitiesView(context: self.context, size: size) + return view + } } extension SolidRoundedButtonView: TGPhotoSolidRoundedButtonView { diff --git a/submodules/LocalizedPeerData/Sources/PeerTitle.swift b/submodules/LocalizedPeerData/Sources/PeerTitle.swift index 341388e51a..db16fdd65e 100644 --- a/submodules/LocalizedPeerData/Sources/PeerTitle.swift +++ b/submodules/LocalizedPeerData/Sources/PeerTitle.swift @@ -13,7 +13,7 @@ public extension EnginePeer { } else if let lastName = user.lastName, !lastName.isEmpty { return lastName } else if let _ = user.phone { - return ""// formatPhoneNumber("+\(phone)") + return "" //formatPhoneNumber("+\(phone)") } else { return "" } @@ -46,7 +46,7 @@ public extension EnginePeer { } else if let lastName = user.lastName, !lastName.isEmpty { return lastName } else if let _ = user.phone { - return ""//formatPhoneNumber("+\(phone)") + return "" //formatPhoneNumber("+\(phone)") } else { return strings.User_DeletedAccount } diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index 8ec3804ee2..35948a3a70 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -1355,13 +1355,13 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } if let undoOverlayController = strongSelf.undoOverlayController { - undoOverlayController.content = .image(image: image ?? UIImage(), title: nil, text: text, undo: true) + undoOverlayController.content = .image(image: image ?? UIImage(), title: nil, text: text, round: false, undo: true) } else { var elevatedLayout = true if let layout = strongSelf.validLayout, case .regular = layout.metrics.widthClass { elevatedLayout = false } - let undoOverlayController = UndoOverlayController(presentationData: presentationData, content: .image(image: image ?? UIImage(), title: nil, text: text, undo: true), elevatedLayout: elevatedLayout, action: { [weak self] action in + let undoOverlayController = UndoOverlayController(presentationData: presentationData, content: .image(image: image ?? UIImage(), title: nil, text: text, round: false, undo: true), elevatedLayout: elevatedLayout, action: { [weak self] action in guard let strongSelf = self else { return true } diff --git a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift index 3816e4b866..617ac521da 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift @@ -364,6 +364,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr private let context: AccountContext private let peer: Peer private let sourceCorners: SourceCorners + private let isSuggested: Bool private var presentationData: PresentationData @@ -403,10 +404,11 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr private let editDisposable = MetaDisposable () - public init(context: AccountContext, peer: Peer, sourceCorners: SourceCorners = .round, remoteEntries: Promise<[AvatarGalleryEntry]>? = nil, skipInitial: Bool = false, centralEntryIndex: Int? = nil, replaceRootController: @escaping (ViewController, Promise?) -> Void, synchronousLoad: Bool = false) { + public init(context: AccountContext, peer: Peer, sourceCorners: SourceCorners = .round, remoteEntries: Promise<[AvatarGalleryEntry]>? = nil, isSuggested: Bool = false, skipInitial: Bool = false, centralEntryIndex: Int? = nil, replaceRootController: @escaping (ViewController, Promise?) -> Void, synchronousLoad: Bool = false) { self.context = context self.peer = peer self.sourceCorners = sourceCorners + self.isSuggested = isSuggested self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.replaceRootController = replaceRootController @@ -460,6 +462,11 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr if strongSelf.centralEntryIndex == nil { strongSelf.centralEntryIndex = 0 } + + if strongSelf.isSuggested, let firstEntry = entries.first { + strongSelf.navigationItem.title = !firstEntry.videoRepresentations.isEmpty ? strongSelf.presentationData.strings.Conversation_SuggestedVideoTitle : strongSelf.presentationData.strings.Conversation_SuggestedPhotoTitle + } + if strongSelf.isViewLoaded { strongSelf.galleryNode.pager.replaceItems(strongSelf.entries.map({ entry in PeerAvatarImageGalleryItem(context: context, peer: peer, presentationData: presentationData, entry: entry, sourceCorners: sourceCorners, delete: strongSelf.canDelete ? { self?.deleteEntry(entry) diff --git a/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift b/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift index 4c54231775..d7df6cc2a6 100644 --- a/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift +++ b/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift @@ -532,7 +532,7 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { var highlightedSide: Bool? public let stripContainerNode: ASDisplayNode public let highlightContainerNode: ASDisplayNode - private let setByYouNode: ImmediateTextNode + public let setByYouNode: ImmediateTextNode private let setByYouImageNode: ImageNode private var setByYouTapRecognizer: UITapGestureRecognizer? @@ -883,7 +883,7 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { self.currentIndexUpdated?() } if let size = self.validLayout { - self.updateItems(size: size, transition: .immediate, stripTransition: .animated(duration: 0.3, curve: .spring)) + self.updateItems(size: size, transition: .immediate, stripTransition: .animated(duration: 0.3, curve: .spring), synchronous: true) } } diff --git a/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift b/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift index 4982bdab7e..04597be426 100644 --- a/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift +++ b/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift @@ -152,13 +152,8 @@ public final class QrCodeScanScreen: ViewController { } @objc private func cancelPressed() { - guard let layout = self.validLayout else { - return - } self.completion(nil) - self.controllerNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: layout.size.height), duration: 0.2, removeOnCompletion: false, additive: true, completion: { _ in - self.dismiss() - }) + self.dismissAnimated() } @objc private func myCodePressed() { @@ -210,12 +205,20 @@ public final class QrCodeScanScreen: ViewController { } } + public func dismissAnimated() { + guard let layout = self.validLayout else { + return + } + self.controllerNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: layout.size.height), duration: 0.2, removeOnCompletion: false, additive: true, completion: { _ in + self.dismiss() + }) + } + private func completeWithCode(_ code: String) { guard case .custom = self.subject else { return } self.completion(code) - self.dismiss() } override public func loadDisplayNode() { diff --git a/submodules/SettingsUI/Sources/ChangePhoneNumberController.swift b/submodules/SettingsUI/Sources/ChangePhoneNumberController.swift index 83c822d9da..741dc7edc0 100644 --- a/submodules/SettingsUI/Sources/ChangePhoneNumberController.swift +++ b/submodules/SettingsUI/Sources/ChangePhoneNumberController.swift @@ -152,185 +152,3 @@ public func ChangePhoneNumberController(context: AccountContext) -> ViewControll return controller } - -//public final class ChangePhoneNumberController: ViewController, MFMailComposeViewControllerDelegate { -// private var controllerNode: ChangePhoneNumberControllerNode { -// return self.displayNode as! ChangePhoneNumberControllerNode -// } -// -// private let context: AccountContext -// -// private var currentData: (Int32, String?, String)? -// private let requestDisposable = MetaDisposable() -// -// var inProgress: Bool = false { -// didSet { -// if self.inProgress { -// let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: self.presentationData.theme.rootController.navigationBar.accentTextColor)) -// self.navigationItem.rightBarButtonItem = item -// } else { -// self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Next, style: .done, target: self, action: #selector(self.nextPressed)) -// } -// self.controllerNode.inProgress = self.inProgress -// } -// } -// var loginWithNumber: ((String) -> Void)? -// -// private let hapticFeedback = HapticFeedback() -// -// private var presentationData: PresentationData -// -// public init(context: AccountContext) { -// self.context = context -// self.presentationData = context.sharedContext.currentPresentationData.with { $0 } -// -// super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) -// -// self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) -// self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style -// -// self.title = self.presentationData.strings.ChangePhoneNumberNumber_Title -// self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Next, style: .done, target: self, action: #selector(self.nextPressed)) -// self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) -// } -// -// required init(coder aDecoder: NSCoder) { -// fatalError("init(coder:) has not been implemented") -// } -// -// deinit { -// self.requestDisposable.dispose() -// } -// -// func updateData(countryCode: Int32, countryName: String, number: String) { -// if self.currentData == nil || self.currentData! != (countryCode, countryName, number) { -// self.currentData = (countryCode, countryName, number) -// if self.isNodeLoaded { -// self.controllerNode.codeAndNumber = (countryCode, countryName, number) -// } -// } -// } -// -// override public func loadDisplayNode() { -// self.displayNode = ChangePhoneNumberControllerNode(presentationData: self.presentationData) -// self.displayNodeDidLoad() -// self.controllerNode.selectCountryCode = { [weak self] in -// if let strongSelf = self { -// let controller = AuthorizationSequenceCountrySelectionController(strings: strongSelf.presentationData.strings, theme: strongSelf.presentationData.theme) -// controller.completeWithCountryCode = { code, name in -// if let strongSelf = self { -// strongSelf.updateData(countryCode: Int32(code), countryName: name, number: strongSelf.controllerNode.codeAndNumber.2) -// strongSelf.controllerNode.activateInput() -// } -// } -// strongSelf.controllerNode.view.endEditing(true) -// strongSelf.push(controller) -// } -// } -// -// loadServerCountryCodes(accountManager: self.context.sharedContext.accountManager, engine: self.context.engine, completion: { [weak self] in -// if let strongSelf = self { -// strongSelf.controllerNode.updateCountryCode() -// } -// }) -// -// self.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate) -// } -// -// override public func viewWillAppear(_ animated: Bool) { -// super.viewWillAppear(animated) -// -// self.controllerNode.activateInput() -// } -// -// override public func viewDidAppear(_ animated: Bool) { -// super.viewDidAppear(animated) -// -// self.controllerNode.activateInput() -// } -// -// override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { -// super.containerLayoutUpdated(layout, transition: transition) -// -// self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: 0.0, transition: transition) -// } -// -// @objc func nextPressed() { -// let (code, _, number) = self.controllerNode.codeAndNumber -// var phoneNumber = number -// if let code = code { -// phoneNumber = "\(code)\(phoneNumber)" -// } -// if !number.isEmpty { -// self.inProgress = true -// self.requestDisposable.set((self.context.engine.accountData.requestChangeAccountPhoneNumberVerification(phoneNumber: self.controllerNode.currentNumber) |> deliverOnMainQueue).start(next: { [weak self] next in -// if let strongSelf = self { -// strongSelf.inProgress = false -// (strongSelf.navigationController as? NavigationController)?.pushViewController(changePhoneNumberCodeController(context: strongSelf.context, phoneNumber: strongSelf.controllerNode.currentNumber, codeData: next)) -// } -// }, error: { [weak self] error in -// if let strongSelf = self { -// strongSelf.inProgress = false -// -// let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } -// -// let text: String -// var actions: [TextAlertAction] = [] -// switch error { -// case .limitExceeded: -// text = presentationData.strings.Login_CodeFloodError -// actions.append(TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})) -// case .invalidPhoneNumber: -// text = presentationData.strings.Login_InvalidPhoneError -// actions.append(TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})) -// case .phoneNumberOccupied: -// text = presentationData.strings.ChangePhone_ErrorOccupied(formatPhoneNumber(context: strongSelf.context, number: phoneNumber)).string -// actions.append(TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})) -// case .phoneBanned: -// text = presentationData.strings.Login_PhoneBannedError -// actions.append(TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {})) -// actions.append(TextAlertAction(type: .genericAction, title: presentationData.strings.Login_PhoneNumberHelp, action: { [weak self] in -// guard let strongSelf = self else { -// return -// } -// let formattedNumber = formatPhoneNumber(context: strongSelf.context, number: number) -// let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown" -// let systemVersion = UIDevice.current.systemVersion -// let locale = Locale.current.identifier -// let carrier = CTCarrier() -// let mnc = carrier.mobileNetworkCode ?? "none" -// -// strongSelf.presentEmailComposeController(address: "login@stel.com", subject: presentationData.strings.Login_PhoneBannedEmailSubject(formattedNumber).string, body: presentationData.strings.Login_PhoneBannedEmailBody(formattedNumber, appVersion, systemVersion, locale, mnc).string) -// })) -// case .generic: -// text = presentationData.strings.Login_UnknownError -// actions.append(TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})) -// } -// -// strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: actions), in: .window(.root)) -// } -// })) -// } else { -// self.hapticFeedback.error() -// self.controllerNode.animateError() -// } -// } -// -// private func presentEmailComposeController(address: String, subject: String, body: String) { -// if MFMailComposeViewController.canSendMail() { -// let composeController = MFMailComposeViewController() -// composeController.setToRecipients([address]) -// composeController.setSubject(subject) -// composeController.setMessageBody(body, isHTML: false) -// composeController.mailComposeDelegate = self -// -// self.view.window?.rootViewController?.present(composeController, animated: true, completion: nil) -// } else { -// self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.Login_EmailNotConfiguredError, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) -// } -// } -// -// public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { -// controller.dismiss(animated: true, completion: nil) -// } -//} diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroController.swift b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroController.swift index 4fafef3f8e..73a50a6592 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroController.swift @@ -44,14 +44,14 @@ public enum PrivacyIntroControllerMode { } } - func title(strings: PresentationStrings) -> String { + func title(context: AccountContext, strings: PresentationStrings) -> String { switch self { case .passcode: return strings.PasscodeSettings_Title case .twoStepVerification: return strings.TwoStepAuth_AdditionalPassword case let .changePhoneNumber(phoneNumber): - return formatPhoneNumber(phoneNumber) + return formatPhoneNumber(context: context, number: phoneNumber) } } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroControllerNode.swift b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroControllerNode.swift index 327ee55a34..66a1e65b1a 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroControllerNode.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroControllerNode.swift @@ -118,7 +118,7 @@ final class PrivacyIntroControllerNode: ViewControllerTracingNode { if self.animationNode.isHidden { self.iconNode.image = self.mode.icon(theme: presentationData.theme) } - self.titleNode.attributedText = NSAttributedString(string: self.mode.title(strings: presentationData.strings), font: titleFont, textColor: presentationData.theme.list.sectionHeaderTextColor, paragraphAlignment: .center) + self.titleNode.attributedText = NSAttributedString(string: self.mode.title(context: self.context, strings: presentationData.strings), font: titleFont, textColor: presentationData.theme.list.sectionHeaderTextColor, paragraphAlignment: .center) self.textNode.attributedText = NSAttributedString(string: self.mode.text(strings: presentationData.strings), font: textFont, textColor: presentationData.theme.list.freeTextColor, paragraphAlignment: .center) self.noticeNode.attributedText = NSAttributedString(string: self.mode.notice(strings: presentationData.strings), font: textFont, textColor: presentationData.theme.list.freeTextColor, paragraphAlignment: .center) self.buttonTextNode.attributedText = NSAttributedString(string: self.mode.buttonTitle(strings: presentationData.strings), font: buttonFont, textColor: presentationData.theme.list.itemAccentColor, paragraphAlignment: .center) diff --git a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift index 6e4f784f84..1b77771f37 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift @@ -11,6 +11,7 @@ import PresentationDataUtils import AccountContext import UndoUI import ItemListPeerActionItem +import AvatarNode enum SelectivePrivacySettingsKind { case presence @@ -414,8 +415,8 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.addPhotoIcon(theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: { arguments.setPublicPhoto?() }) - case let .removePublicPhoto(theme, text, _, _): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.deleteIconImage(theme), title: text, sectionId: self.section, height: .generic, color: .destructive, editing: false, action: { + case let .removePublicPhoto(_, text, peer, image): + return ItemListPeerActionItem(presentationData: presentationData, icon: nil, iconSignal: peerAvatarCompleteImage(account: arguments.context.account, peer: peer, forceProvidedRepresentation: true, representation: image?.representationForDisplayAtSize(PixelDimensions(width: 28, height: 28)), size: CGSize(width: 28.0, height: 28.0)), title: text, sectionId: self.section, height: .generic, color: .destructive, editing: false, action: { arguments.removePublicPhoto?() }) case let .publicPhotoInfo(_, text): @@ -676,10 +677,10 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present entries.append(.phoneDiscoveryInfo(presentationData.theme, state.phoneDiscoveryEnabled != false ? presentationData.strings.PrivacyPhoneNumberSettings_CustomPublicLink("+\(phoneNumber)").string : presentationData.strings.PrivacyPhoneNumberSettings_CustomDisabledHelp, phoneLink)) } - if case .profilePhoto = kind, let peer = peer { + if case .profilePhoto = kind, let peer = peer, state.setting != .everybody { if let publicPhoto = publicPhoto { entries.append(.setPublicPhoto(presentationData.theme, presentationData.strings.Privacy_ProfilePhoto_UpdatePublicPhoto)) - entries.append(.removePublicPhoto(presentationData.theme, presentationData.strings.Privacy_ProfilePhoto_RemovePublicPhoto, peer, publicPhoto)) + entries.append(.removePublicPhoto(presentationData.theme, !publicPhoto.videoRepresentations.isEmpty ? presentationData.strings.Privacy_ProfilePhoto_RemovePublicVideo : presentationData.strings.Privacy_ProfilePhoto_RemovePublicPhoto, peer, publicPhoto)) } else { entries.append(.setPublicPhoto(presentationData.theme, presentationData.strings.Privacy_ProfilePhoto_SetPublicPhoto)) } diff --git a/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift b/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift index 77eceee8e1..fd4e23be83 100644 --- a/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift +++ b/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift @@ -699,13 +699,6 @@ private func installedStickerPacksControllerEntries(presentationData: Presentati return entries } -public enum InstalledStickerPacksControllerMode { - case general - case modal - case masks - case emoji -} - public func installedStickerPacksController(context: AccountContext, mode: InstalledStickerPacksControllerMode, archivedPacks: [ArchivedStickerPackItem]? = nil, updatedPacks: @escaping ([ArchivedStickerPackItem]?) -> Void = { _ in }, focusOnItemTag: InstalledStickerPacksEntryTag? = nil) -> ViewController { let initialState = InstalledStickerPacksControllerState().withUpdatedEditing(mode == .modal).withUpdatedSelectedPackIds(mode == .modal ? Set() : nil) let statePromise = ValuePromise(initialState, ignoreRepeated: true) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridSearchContentNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeGridSearchContentNode.swift index e10768ddbf..9a674220e0 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeGridSearchContentNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeGridSearchContentNode.swift @@ -539,7 +539,7 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode { return (.complete() |> delay(0.1, queue: Queue.concurrentDefaultQueue())) |> then( - requestContextResults(context: context, botId: user.id, query: wallpaperQuery, peerId: context.account.peerId, limit: 16) + requestContextResults(engine: context.engine, botId: user.id, query: wallpaperQuery, peerId: context.account.peerId, limit: 16) |> map { results -> ChatContextResultCollection? in return results?.results } diff --git a/submodules/TelegramCore/Sources/ApiUtils/ChatContextResult.swift b/submodules/TelegramCore/Sources/ApiUtils/ChatContextResult.swift index 11f06f6a33..1d933fcf28 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/ChatContextResult.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/ChatContextResult.swift @@ -535,3 +535,48 @@ extension ChatContextResultCollection { } } } + +public func requestContextResults(engine: TelegramEngine, botId: EnginePeer.Id, query: String, peerId: EnginePeer.Id, offset: String = "", existingResults: ChatContextResultCollection? = nil, incompleteResults: Bool = false, staleCachedResults: Bool = false, limit: Int = 60) -> Signal { + return engine.messages.requestChatContextResults(botId: botId, peerId: peerId, query: query, offset: offset, incompleteResults: incompleteResults, staleCachedResults: staleCachedResults) + |> `catch` { error -> Signal in + return .single(nil) + } + |> mapToSignal { resultsStruct -> Signal in + let results = resultsStruct?.results + + var collection = existingResults + var updated: Bool = false + if let existingResults = existingResults, let results = results { + var newResults: [ChatContextResult] = [] + var existingIds = Set() + for result in existingResults.results { + newResults.append(result) + existingIds.insert(result.id) + } + for result in results.results { + if !existingIds.contains(result.id) { + newResults.append(result) + existingIds.insert(result.id) + updated = true + } + } + collection = ChatContextResultCollection(botId: existingResults.botId, peerId: existingResults.peerId, query: existingResults.query, geoPoint: existingResults.geoPoint, queryId: results.queryId, nextOffset: results.nextOffset, presentation: existingResults.presentation, switchPeer: existingResults.switchPeer, results: newResults, cacheTimeout: existingResults.cacheTimeout) + } else { + collection = results + updated = true + } + if let collection = collection, collection.results.count < limit, let nextOffset = collection.nextOffset, updated { + let nextResults = requestContextResults(engine: engine, botId: botId, query: query, peerId: peerId, offset: nextOffset, existingResults: collection, limit: limit) + if collection.results.count > 10 { + return .single(RequestChatContextResultsResult(results: collection, isStale: resultsStruct?.isStale ?? false)) + |> then(nextResults) + } else { + return nextResults + } + } else if let collection = collection { + return .single(RequestChatContextResultsResult(results: collection, isStale: resultsStruct?.isStale ?? false)) + } else { + return .single(nil) + } + } +} diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index 541a597875..5e24fa5ba7 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -337,6 +337,7 @@ swift_library( "//submodules/TelegramUI/Components/EmojiSuggestionsComponent:EmojiSuggestionsComponent", "//submodules/TelegramUI/Components/EmojiStatusSelectionComponent:EmojiStatusSelectionComponent", "//submodules/TelegramUI/Components/EmojiStatusComponent:EmojiStatusComponent", + "//submodules/TelegramUI/Components/ChatControllerInteraction:ChatControllerInteraction", "//submodules/Components/ComponentDisplayAdapters:ComponentDisplayAdapters", "//submodules/Media/ConvertOpusToAAC:ConvertOpusToAAC", "//submodules/Media/LocalAudioTranscription:LocalAudioTranscription", @@ -348,8 +349,11 @@ swift_library( "//submodules/InviteLinksUI:InviteLinksUI", "//submodules/TelegramUI/Components/NotificationPeerExceptionController", "//submodules/TelegramUI/Components/ChatListHeaderComponent", + "//submodules/TelegramUI/Components/ChatInputNode", + "//submodules/TelegramUI/Components/ChatEntityKeyboardInputNode", "//submodules/MediaPasteboardUI:MediaPasteboardUI", "//submodules/DrawingUI:DrawingUI", + "//submodules/FeaturedStickersScreen:FeaturedStickersScreen", ] + select({ "@build_bazel_rules_apple//apple:ios_armv7": [], "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, diff --git a/submodules/TelegramUI/Components/ChatControllerInteraction/BUILD b/submodules/TelegramUI/Components/ChatControllerInteraction/BUILD new file mode 100644 index 0000000000..684e3ca458 --- /dev/null +++ b/submodules/TelegramUI/Components/ChatControllerInteraction/BUILD @@ -0,0 +1,33 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatControllerInteraction", + module_name = "ChatControllerInteraction", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/ChatPresentationInterfaceState:ChatPresentationInterfaceState", + "//submodules/TelegramUIPreferences:TelegramUIPreferences", + "//submodules/TextSelectionNode:TextSelectionNode", + "//submodules/ContextUI:ContextUI", + "//submodules/ChatInterfaceState:ChatInterfaceState", + "//submodules/UndoUI:UndoUI", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/TextFormat:TextFormat", + "//submodules/WallpaperBackgroundNode:WallpaperBackgroundNode", + "//submodules/TelegramUI/Components/AnimationCache:AnimationCache", + "//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift similarity index 58% rename from submodules/TelegramUI/Sources/ChatControllerInteraction.swift rename to submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift index b15069eb58..77e164e118 100644 --- a/submodules/TelegramUI/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift @@ -15,33 +15,28 @@ import UndoUI import TelegramPresentationData import ChatPresentationInterfaceState import TextFormat +import WallpaperBackgroundNode +import AnimationCache +import MultiAnimationRenderer -struct ChatInterfaceHighlightedState: Equatable { - let messageStableId: UInt32 +public struct ChatInterfaceHighlightedState: Equatable { + public let messageStableId: UInt32 - static func ==(lhs: ChatInterfaceHighlightedState, rhs: ChatInterfaceHighlightedState) -> Bool { + public init(messageStableId: UInt32) { + self.messageStableId = messageStableId + } + + public static func ==(lhs: ChatInterfaceHighlightedState, rhs: ChatInterfaceHighlightedState) -> Bool { return lhs.messageStableId == rhs.messageStableId } } -struct ChatInterfaceStickerSettings: Equatable { - let loopAnimatedStickers: Bool +public struct ChatInterfacePollActionState: Equatable { + public var pollMessageIdsInProgress: [MessageId: [Data]] = [:] - public init(loopAnimatedStickers: Bool) { - self.loopAnimatedStickers = loopAnimatedStickers + public init(pollMessageIdsInProgress: [MessageId: [Data]] = [:]) { + self.pollMessageIdsInProgress = pollMessageIdsInProgress } - - public init(stickerSettings: StickerSettings) { - self.loopAnimatedStickers = stickerSettings.loopAnimatedStickers - } - - static func ==(lhs: ChatInterfaceStickerSettings, rhs: ChatInterfaceStickerSettings) -> Bool { - return lhs.loopAnimatedStickers == rhs.loopAnimatedStickers - } -} - -struct ChatInterfacePollActionState: Equatable { - var pollMessageIdsInProgress: [MessageId: [Data]] = [:] } public enum ChatControllerInteractionSwipeAction { @@ -54,128 +49,150 @@ public enum ChatControllerInteractionReaction { case reaction(MessageReaction.Reaction) } -struct UnreadMessageRangeKey: Hashable { - var peerId: PeerId - var namespace: MessageId.Namespace +public struct UnreadMessageRangeKey: Hashable { + public var peerId: PeerId + public var namespace: MessageId.Namespace + + public init(peerId: PeerId, namespace: MessageId.Namespace) { + self.peerId = peerId + self.namespace = namespace + } +} + +public class ChatPresentationContext { + public weak var backgroundNode: WallpaperBackgroundNode? + public let animationCache: AnimationCache + public let animationRenderer: MultiAnimationRenderer + + public init(context: AccountContext, backgroundNode: WallpaperBackgroundNode?) { + self.backgroundNode = backgroundNode + + self.animationCache = context.animationCache + self.animationRenderer = context.animationRenderer + } +} + +public protocol ChatMessageTransitionProtocol: ASDisplayNode { + } public final class ChatControllerInteraction { - enum OpenPeerSource { + public enum OpenPeerSource { case `default` case reaction case groupParticipant } - let openMessage: (Message, ChatControllerInteractionOpenMessageMode) -> Bool - let openPeer: (EnginePeer, ChatControllerInteractionNavigateToPeer, MessageReference?, OpenPeerSource) -> Void - let openPeerMention: (String) -> Void - let openMessageContextMenu: (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?, CGPoint?) -> Void - let updateMessageReaction: (Message, ChatControllerInteractionReaction) -> Void - let openMessageReactionContextMenu: (Message, ContextExtractedContentContainingView, ContextGesture?, MessageReaction.Reaction) -> Void - let activateMessagePinch: (PinchSourceContainerNode) -> Void - let openMessageContextActions: (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void - let navigateToMessage: (MessageId, MessageId) -> Void - let navigateToMessageStandalone: (MessageId) -> Void - let navigateToThreadMessage: (PeerId, Int64, MessageId?) -> Void - let tapMessage: ((Message) -> Void)? - let clickThroughMessage: () -> Void - let toggleMessagesSelection: ([MessageId], Bool) -> Void - let sendCurrentMessage: (Bool) -> Void - let sendMessage: (String) -> Void - let sendSticker: (FileMediaReference, Bool, Bool, String?, Bool, UIView, CGRect, CALayer?, [ItemCollectionId]) -> Bool - let sendEmoji: (String, ChatTextInputTextCustomEmojiAttribute) -> Void - let sendGif: (FileMediaReference, UIView, CGRect, Bool, Bool) -> Bool - let sendBotContextResultAsGif: (ChatContextResultCollection, ChatContextResult, UIView, CGRect, Bool) -> Bool - let requestMessageActionCallback: (MessageId, MemoryBuffer?, Bool, Bool) -> Void - let requestMessageActionUrlAuth: (String, MessageActionUrlSubject) -> Void - let activateSwitchInline: (PeerId?, String) -> Void - let openUrl: (String, Bool, Bool?, Message?) -> Void - let shareCurrentLocation: () -> Void - let shareAccountContact: () -> Void - let sendBotCommand: (MessageId?, String) -> Void - let openInstantPage: (Message, ChatMessageItemAssociatedData?) -> Void - let openWallpaper: (Message) -> Void - let openTheme: (Message) -> Void - let openHashtag: (String?, String) -> Void - let updateInputState: ((ChatTextInputState) -> ChatTextInputState) -> Void - let updateInputMode: ((ChatInputMode) -> ChatInputMode) -> Void - let openMessageShareMenu: (MessageId) -> Void - let presentController: (ViewController, Any?) -> Void - let presentControllerInCurrent: (ViewController, Any?) -> Void - let navigationController: () -> NavigationController? - let chatControllerNode: () -> ASDisplayNode? - let presentGlobalOverlayController: (ViewController, Any?) -> Void - let callPeer: (PeerId, Bool) -> Void - let longTap: (ChatControllerInteractionLongTapAction, Message?) -> Void - let openCheckoutOrReceipt: (MessageId) -> Void - let openSearch: () -> Void - let setupReply: (MessageId) -> Void - let canSetupReply: (Message) -> ChatControllerInteractionSwipeAction - let navigateToFirstDateMessage: (Int32, Bool) -> Void - let requestRedeliveryOfFailedMessages: (MessageId) -> Void - let addContact: (String) -> Void - let rateCall: (Message, CallId, Bool) -> Void - let requestSelectMessagePollOptions: (MessageId, [Data]) -> Void - let requestOpenMessagePollResults: (MessageId, MediaId) -> Void - let openAppStorePage: () -> Void - let displayMessageTooltip: (MessageId, String, ASDisplayNode?, CGRect?) -> Void - let seekToTimecode: (Message, Double, Bool) -> Void - let scheduleCurrentMessage: () -> Void - let sendScheduledMessagesNow: ([MessageId]) -> Void - let editScheduledMessagesTime: ([MessageId]) -> Void - let performTextSelectionAction: (Bool, NSAttributedString, TextSelectionAction) -> Void - let displayImportedMessageTooltip: (ASDisplayNode) -> Void - let displaySwipeToReplyHint: () -> Void - let dismissReplyMarkupMessage: (Message) -> Void - let openMessagePollResults: (MessageId, Data) -> Void - let openPollCreation: (Bool?) -> Void - let displayPollSolution: (TelegramMediaPollResults.Solution, ASDisplayNode) -> Void - let displayPsa: (String, ASDisplayNode) -> Void - let displayDiceTooltip: (TelegramMediaDice) -> Void - let animateDiceSuccess: (Bool, Bool) -> Void - let displayPremiumStickerTooltip: (TelegramMediaFile, Message) -> Void - let displayEmojiPackTooltip: (TelegramMediaFile, Message) -> Void - let openPeerContextMenu: (Peer, MessageId?, ASDisplayNode, CGRect, ContextGesture?) -> Void - let openMessageReplies: (MessageId, Bool, Bool) -> Void - let openReplyThreadOriginalMessage: (Message) -> Void - let openMessageStats: (MessageId) -> Void - let editMessageMedia: (MessageId, Bool) -> Void - let copyText: (String) -> Void - let displayUndo: (UndoOverlayContent) -> Void - let isAnimatingMessage: (UInt32) -> Bool - let getMessageTransitionNode: () -> ChatMessageTransitionNode? - let updateChoosingSticker: (Bool) -> Void - let commitEmojiInteraction: (MessageId, String, EmojiInteraction, TelegramMediaFile) -> Void - let openLargeEmojiInfo: (String, String?, TelegramMediaFile) -> Void - let openJoinLink: (String) -> Void - let openWebView: (String, String, Bool, Bool) -> Void - let activateAdAction: (EngineMessage.Id) -> Void + public let openMessage: (Message, ChatControllerInteractionOpenMessageMode) -> Bool + public let openPeer: (EnginePeer, ChatControllerInteractionNavigateToPeer, MessageReference?, OpenPeerSource) -> Void + public let openPeerMention: (String) -> Void + public let openMessageContextMenu: (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?, CGPoint?) -> Void + public let updateMessageReaction: (Message, ChatControllerInteractionReaction) -> Void + public let openMessageReactionContextMenu: (Message, ContextExtractedContentContainingView, ContextGesture?, MessageReaction.Reaction) -> Void + public let activateMessagePinch: (PinchSourceContainerNode) -> Void + public let openMessageContextActions: (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void + public let navigateToMessage: (MessageId, MessageId) -> Void + public let navigateToMessageStandalone: (MessageId) -> Void + public let navigateToThreadMessage: (PeerId, Int64, MessageId?) -> Void + public let tapMessage: ((Message) -> Void)? + public let clickThroughMessage: () -> Void + public let toggleMessagesSelection: ([MessageId], Bool) -> Void + public let sendCurrentMessage: (Bool) -> Void + public let sendMessage: (String) -> Void + public let sendSticker: (FileMediaReference, Bool, Bool, String?, Bool, UIView, CGRect, CALayer?, [ItemCollectionId]) -> Bool + public let sendEmoji: (String, ChatTextInputTextCustomEmojiAttribute) -> Void + public let sendGif: (FileMediaReference, UIView, CGRect, Bool, Bool) -> Bool + public let sendBotContextResultAsGif: (ChatContextResultCollection, ChatContextResult, UIView, CGRect, Bool) -> Bool + public let requestMessageActionCallback: (MessageId, MemoryBuffer?, Bool, Bool) -> Void + public let requestMessageActionUrlAuth: (String, MessageActionUrlSubject) -> Void + public let activateSwitchInline: (PeerId?, String) -> Void + public let openUrl: (String, Bool, Bool?, Message?) -> Void + public let shareCurrentLocation: () -> Void + public let shareAccountContact: () -> Void + public let sendBotCommand: (MessageId?, String) -> Void + public let openInstantPage: (Message, ChatMessageItemAssociatedData?) -> Void + public let openWallpaper: (Message) -> Void + public let openTheme: (Message) -> Void + public let openHashtag: (String?, String) -> Void + public let updateInputState: ((ChatTextInputState) -> ChatTextInputState) -> Void + public let updateInputMode: ((ChatInputMode) -> ChatInputMode) -> Void + public let openMessageShareMenu: (MessageId) -> Void + public let presentController: (ViewController, Any?) -> Void + public let presentControllerInCurrent: (ViewController, Any?) -> Void + public let navigationController: () -> NavigationController? + public let chatControllerNode: () -> ASDisplayNode? + public let presentGlobalOverlayController: (ViewController, Any?) -> Void + public let callPeer: (PeerId, Bool) -> Void + public let longTap: (ChatControllerInteractionLongTapAction, Message?) -> Void + public let openCheckoutOrReceipt: (MessageId) -> Void + public let openSearch: () -> Void + public let setupReply: (MessageId) -> Void + public let canSetupReply: (Message) -> ChatControllerInteractionSwipeAction + public let navigateToFirstDateMessage: (Int32, Bool) -> Void + public let requestRedeliveryOfFailedMessages: (MessageId) -> Void + public let addContact: (String) -> Void + public let rateCall: (Message, CallId, Bool) -> Void + public let requestSelectMessagePollOptions: (MessageId, [Data]) -> Void + public let requestOpenMessagePollResults: (MessageId, MediaId) -> Void + public let openAppStorePage: () -> Void + public let displayMessageTooltip: (MessageId, String, ASDisplayNode?, CGRect?) -> Void + public let seekToTimecode: (Message, Double, Bool) -> Void + public let scheduleCurrentMessage: () -> Void + public let sendScheduledMessagesNow: ([MessageId]) -> Void + public let editScheduledMessagesTime: ([MessageId]) -> Void + public let performTextSelectionAction: (Bool, NSAttributedString, TextSelectionAction) -> Void + public let displayImportedMessageTooltip: (ASDisplayNode) -> Void + public let displaySwipeToReplyHint: () -> Void + public let dismissReplyMarkupMessage: (Message) -> Void + public let openMessagePollResults: (MessageId, Data) -> Void + public let openPollCreation: (Bool?) -> Void + public let displayPollSolution: (TelegramMediaPollResults.Solution, ASDisplayNode) -> Void + public let displayPsa: (String, ASDisplayNode) -> Void + public let displayDiceTooltip: (TelegramMediaDice) -> Void + public let animateDiceSuccess: (Bool, Bool) -> Void + public let displayPremiumStickerTooltip: (TelegramMediaFile, Message) -> Void + public let displayEmojiPackTooltip: (TelegramMediaFile, Message) -> Void + public let openPeerContextMenu: (Peer, MessageId?, ASDisplayNode, CGRect, ContextGesture?) -> Void + public let openMessageReplies: (MessageId, Bool, Bool) -> Void + public let openReplyThreadOriginalMessage: (Message) -> Void + public let openMessageStats: (MessageId) -> Void + public let editMessageMedia: (MessageId, Bool) -> Void + public let copyText: (String) -> Void + public let displayUndo: (UndoOverlayContent) -> Void + public let isAnimatingMessage: (UInt32) -> Bool + public let getMessageTransitionNode: () -> ChatMessageTransitionProtocol? + public let updateChoosingSticker: (Bool) -> Void + public let commitEmojiInteraction: (MessageId, String, EmojiInteraction, TelegramMediaFile) -> Void + public let openLargeEmojiInfo: (String, String?, TelegramMediaFile) -> Void + public let openJoinLink: (String) -> Void + public let openWebView: (String, String, Bool, Bool) -> Void + public let activateAdAction: (EngineMessage.Id) -> Void - let requestMessageUpdate: (MessageId, Bool) -> Void - let cancelInteractiveKeyboardGestures: () -> Void - let dismissTextInput: () -> Void - let scrollToMessageId: (MessageIndex) -> Void + public let requestMessageUpdate: (MessageId, Bool) -> Void + public let cancelInteractiveKeyboardGestures: () -> Void + public let dismissTextInput: () -> Void + public let scrollToMessageId: (MessageIndex) -> Void - var canPlayMedia: Bool = false - var hiddenMedia: [MessageId: [Media]] = [:] - var expandedTranslationMessageStableIds: Set = Set() - var selectionState: ChatInterfaceSelectionState? - var highlightedState: ChatInterfaceHighlightedState? - var contextHighlightedState: ChatInterfaceHighlightedState? - var automaticMediaDownloadSettings: MediaAutoDownloadSettings - var pollActionState: ChatInterfacePollActionState - var currentPollMessageWithTooltip: MessageId? - var currentPsaMessageWithTooltip: MessageId? - var stickerSettings: ChatInterfaceStickerSettings - var searchTextHighightState: (String, [MessageIndex])? - var unreadMessageRange: [UnreadMessageRangeKey: Range] = [:] - var seenOneTimeAnimatedMedia = Set() - var currentMessageWithLoadingReplyThread: MessageId? - var updatedPresentationData: (initial: PresentationData, signal: Signal)? - let presentationContext: ChatPresentationContext - var playNextOutgoingGift: Bool = false + public var canPlayMedia: Bool = false + public var hiddenMedia: [MessageId: [Media]] = [:] + public var expandedTranslationMessageStableIds: Set = Set() + public var selectionState: ChatInterfaceSelectionState? + public var highlightedState: ChatInterfaceHighlightedState? + public var contextHighlightedState: ChatInterfaceHighlightedState? + public var automaticMediaDownloadSettings: MediaAutoDownloadSettings + public var pollActionState: ChatInterfacePollActionState + public var currentPollMessageWithTooltip: MessageId? + public var currentPsaMessageWithTooltip: MessageId? + public var stickerSettings: ChatInterfaceStickerSettings + public var searchTextHighightState: (String, [MessageIndex])? + public var unreadMessageRange: [UnreadMessageRangeKey: Range] = [:] + public var seenOneTimeAnimatedMedia = Set() + public var currentMessageWithLoadingReplyThread: MessageId? + public var updatedPresentationData: (initial: PresentationData, signal: Signal)? + public let presentationContext: ChatPresentationContext + public var playNextOutgoingGift: Bool = false - init( + public init( openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (EnginePeer, ChatControllerInteractionNavigateToPeer, MessageReference?, OpenPeerSource) -> Void, openPeerMention: @escaping (String) -> Void, @@ -253,7 +270,7 @@ public final class ChatControllerInteraction { copyText: @escaping (String) -> Void, displayUndo: @escaping (UndoOverlayContent) -> Void, isAnimatingMessage: @escaping (UInt32) -> Bool, - getMessageTransitionNode: @escaping () -> ChatMessageTransitionNode?, + getMessageTransitionNode: @escaping () -> ChatMessageTransitionProtocol?, updateChoosingSticker: @escaping (Bool) -> Void, commitEmojiInteraction: @escaping (MessageId, String, EmojiInteraction, TelegramMediaFile) -> Void, openLargeEmojiInfo: @escaping (String, String?, TelegramMediaFile) -> Void, diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/BUILD b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/BUILD new file mode 100644 index 0000000000..5ec6a15c17 --- /dev/null +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/BUILD @@ -0,0 +1,45 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatEntityKeyboardInputNode", + module_name = "ChatEntityKeyboardInputNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/AccountContext:AccountContext", + "//submodules/ChatPresentationInterfaceState:ChatPresentationInterfaceState", + "//submodules/ComponentFlow:ComponentFlow", + "//submodules/Components/ComponentDisplayAdapters:ComponentDisplayAdapters", + "//submodules/TelegramUI/Components/EntityKeyboard:EntityKeyboard", + "//submodules/TelegramUI/Components/AnimationCache:AnimationCache", + "//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer", + "//submodules/TextFormat:TextFormat", + "//submodules/AppBundle:AppBundle", + "//submodules/TelegramUI/Components/ChatInputNode:ChatInputNode", + "//submodules/Components/PagerComponent:PagerComponent", + "//submodules/PremiumUI:PremiumUI", + "//submodules/UndoUI:UndoUI", + "//submodules/ContextUI:ContextUI", + "//submodules/GalleryUI:GalleryUI", + "//submodules/AttachmentTextInputPanelNode:AttachmentTextInputPanelNode", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/TelegramNotices:TelegramNotices", + "//submodules/StickerPeekUI:StickerPeekUI", + "//submodules/TelegramUIPreferences:TelegramUIPreferences", + "//submodules/TelegramUI/Components/MultiplexedVideoNode:MultiplexedVideoNode", + "//submodules/TelegramUI/Components/ChatControllerInteraction:ChatControllerInteraction", + "//submodules/FeaturedStickersScreen:FeaturedStickersScreen", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift similarity index 91% rename from submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift rename to submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift index 06f6adb241..331ee3687a 100644 --- a/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift @@ -12,7 +12,6 @@ import MultiAnimationRenderer import Postbox import TelegramCore import ComponentDisplayAdapters -import SettingsUI import TextFormat import PagerComponent import AppBundle @@ -25,17 +24,37 @@ import AttachmentTextInputPanelNode import TelegramPresentationData import TelegramNotices import StickerPeekUI +import ChatInputNode +import TelegramUIPreferences +import MultiplexedVideoNode +import ChatControllerInteraction +import FeaturedStickersScreen -final class EntityKeyboardGifContent: Equatable { - let hasRecentGifs: Bool - let component: GifPagerContentComponent +public struct ChatMediaInputPaneScrollState { + let absoluteOffset: CGFloat? + let relativeChange: CGFloat +} + +public final class ChatMediaInputGifPaneTrendingState { + public let files: [MultiplexedVideoNodeFile] + public let nextOffset: String? - init(hasRecentGifs: Bool, component: GifPagerContentComponent) { + public init(files: [MultiplexedVideoNodeFile], nextOffset: String?) { + self.files = files + self.nextOffset = nextOffset + } +} + +public final class EntityKeyboardGifContent: Equatable { + public let hasRecentGifs: Bool + public let component: GifPagerContentComponent + + public init(hasRecentGifs: Bool, component: GifPagerContentComponent) { self.hasRecentGifs = hasRecentGifs self.component = component } - static func ==(lhs: EntityKeyboardGifContent, rhs: EntityKeyboardGifContent) -> Bool { + public static func ==(lhs: EntityKeyboardGifContent, rhs: EntityKeyboardGifContent) -> Bool { if lhs.hasRecentGifs != rhs.hasRecentGifs { return false } @@ -46,14 +65,14 @@ final class EntityKeyboardGifContent: Equatable { } } -final class ChatEntityKeyboardInputNode: ChatInputNode { - struct InputData: Equatable { - var emoji: EmojiPagerContentComponent - var stickers: EmojiPagerContentComponent? - var gifs: EntityKeyboardGifContent? - var availableGifSearchEmojies: [EntityKeyboardComponent.GifSearchEmoji] +public final class ChatEntityKeyboardInputNode: ChatInputNode { + public struct InputData: Equatable { + public var emoji: EmojiPagerContentComponent + public var stickers: EmojiPagerContentComponent? + public var gifs: EntityKeyboardGifContent? + public var availableGifSearchEmojies: [EntityKeyboardComponent.GifSearchEmoji] - init( + public init( emoji: EmojiPagerContentComponent, stickers: EmojiPagerContentComponent?, gifs: EntityKeyboardGifContent?, @@ -66,7 +85,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { } } - static func hasPremium(context: AccountContext, chatPeerId: EnginePeer.Id?, premiumIfSavedMessages: Bool) -> Signal { + public static func hasPremium(context: AccountContext, chatPeerId: EnginePeer.Id?, premiumIfSavedMessages: Bool) -> Signal { let hasPremium: Signal if premiumIfSavedMessages, let chatPeerId = chatPeerId, chatPeerId == context.account.peerId { hasPremium = .single(true) @@ -83,7 +102,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { return hasPremium } - static func inputData(context: AccountContext, interfaceInteraction: ChatPanelInterfaceInteraction, controllerInteraction: ChatControllerInteraction?, chatPeerId: PeerId?, areCustomEmojiEnabled: Bool) -> Signal { + public static func inputData(context: AccountContext, interfaceInteraction: ChatPanelInterfaceInteraction, controllerInteraction: ChatControllerInteraction?, chatPeerId: PeerId?, areCustomEmojiEnabled: Bool) -> Signal { let animationCache = context.animationCache let animationRenderer = context.animationRenderer @@ -94,7 +113,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { let strings = context.sharedContext.currentPresentationData.with({ $0 }).strings - let stickerItems = EmojiPagerContentComponent.stickerInputData(context: context, animationCache: animationCache, animationRenderer: animationRenderer, stickerNamespaces: stickerNamespaces, stickerOrderedItemListCollectionIds: stickerOrderedItemListCollectionIds, chatPeerId: chatPeerId) + let stickerItems = EmojiPagerContentComponent.stickerInputData(context: context, animationCache: animationCache, animationRenderer: animationRenderer, stickerNamespaces: stickerNamespaces, stickerOrderedItemListCollectionIds: stickerOrderedItemListCollectionIds, chatPeerId: chatPeerId, hasSearch: true, hasTrending: true) let reactions: Signal<[String], NoError> = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.App()) |> map { appConfiguration -> [String] in @@ -153,7 +172,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { isLoading: false, loadMoreToken: nil, displaySearchWithPlaceholder: nil, - searchInitiallyHidden: false + searchInitiallyHidden: true ) )) @@ -238,11 +257,11 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { } var externalTopPanelContainerImpl: PagerExternalTopPanelContainer? - override var externalTopPanelContainer: UIView? { + public override var externalTopPanelContainer: UIView? { return self.externalTopPanelContainerImpl } - var switchToTextInput: (() -> Void)? + public var switchToTextInput: (() -> Void)? private var currentState: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, interfaceState: ChatPresentationInterfaceState, deviceMetrics: DeviceMetrics, isVisible: Bool, isExpanded: Bool)? @@ -257,7 +276,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { } } - var canSwitchToTextInputAutomatically: Bool { + public var canSwitchToTextInputAutomatically: Bool { if let pagerView = self.entityKeyboardView.componentView as? EntityKeyboardComponent.View, let centralId = pagerView.centralId { if centralId == AnyHashable("emoji") { return false @@ -321,7 +340,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { isLoading: false, loadMoreToken: nil, displaySearchWithPlaceholder: presentationData.strings.GifSearch_SearchGifPlaceholder, - searchInitiallyHidden: false + searchInitiallyHidden: true ) ) } @@ -352,7 +371,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { isLoading: isLoading, loadMoreToken: nil, displaySearchWithPlaceholder: nil, - searchInitiallyHidden: false + searchInitiallyHidden: true ) ) } @@ -385,7 +404,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { isLoading: isLoading, loadMoreToken: loadMoreToken, displaySearchWithPlaceholder: nil, - searchInitiallyHidden: false + searchInitiallyHidden: true ) ) } @@ -466,7 +485,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { isLoading: isLoading, loadMoreToken: loadMoreToken, displaySearchWithPlaceholder: nil, - searchInitiallyHidden: false + searchInitiallyHidden: true ) ) } @@ -492,8 +511,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { private weak var currentUndoOverlayController: UndoOverlayController? - - init(context: AccountContext, currentInputData: InputData, updatedInputData: Signal, defaultToEmojiTab: Bool, controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?, chatPeerId: PeerId?) { + public init(context: AccountContext, currentInputData: InputData, updatedInputData: Signal, defaultToEmojiTab: Bool, controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?, chatPeerId: PeerId?) { self.context = context self.currentInputData = currentInputData self.defaultToEmojiTab = defaultToEmojiTab @@ -517,7 +535,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { self.emojiInputInteraction = EmojiPagerContentComponent.InputInteraction( performItemAction: { [weak self, weak interfaceInteraction, weak controllerInteraction] groupId, item, _, _, _, _ in let _ = (ChatEntityKeyboardInputNode.hasPremium(context: context, chatPeerId: chatPeerId, premiumIfSavedMessages: true) |> take(1) |> deliverOnMainQueue).start(next: { hasPremium in - guard let strongSelf = self, let controllerInteraction = controllerInteraction, let interfaceInteraction = interfaceInteraction else { + guard let strongSelf = self, let controllerInteraction = controllerInteraction, let interfaceInteraction = interfaceInteraction else { return } @@ -935,7 +953,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { guard let controllerInteraction = controllerInteraction else { return } - let controller = installedStickerPacksController(context: context, mode: .modal) + let controller = context.sharedContext.makeInstalledStickerPacksController(context: context, mode: .modal) controller.navigationPresentation = .modal controllerInteraction.navigationController()?.pushViewController(controller) }, @@ -1230,7 +1248,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { } } - func markInputCollapsed() { + public func markInputCollapsed() { self.isMarkInputCollapsed = true } @@ -1242,14 +1260,14 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .immediate, interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible, isExpanded: isExpanded) } - func simulateUpdateLayout(isVisible: Bool) { + public func simulateUpdateLayout(isVisible: Bool) { guard let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, _, isExpanded) = self.currentState else { return } let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .immediate, interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible, isExpanded: isExpanded) } - override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, deviceMetrics: DeviceMetrics, isVisible: Bool, isExpanded: Bool) -> (CGFloat, CGFloat) { + public override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, deviceMetrics: DeviceMetrics, isVisible: Bool, isExpanded: Bool) -> (CGFloat, CGFloat) { self.currentState = (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible, isExpanded) let innerTransition: Transition @@ -1318,11 +1336,14 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { topPanelInsets: UIEdgeInsets(), emojiContent: self.currentInputData.emoji, stickerContent: stickerContent, + maskContent: nil, gifContent: gifContent?.component, hasRecentGifs: gifContent?.hasRecentGifs ?? false, availableGifSearchEmojies: self.currentInputData.availableGifSearchEmojies, defaultToEmojiTab: self.defaultToEmojiTab, externalTopPanelContainer: self.externalTopPanelContainerImpl, + externalBottomPanelContainer: nil, + displayTopPanelBackground: false, topPanelExtensionUpdated: { [weak self] topPanelExtension, transition in guard let strongSelf = self else { return @@ -1502,6 +1523,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { namespace = Namespaces.ItemCollection.CloudStickerPacks case .emoji: namespace = Namespaces.ItemCollection.CloudEmojiPacks + case .masks: + namespace = Namespaces.ItemCollection.CloudMaskPacks } self.stableReorderableGroupOrder.removeValue(forKey: category) @@ -1685,7 +1708,7 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent } } -final class EntityInputView: UIView, AttachmentTextInputPanelInputView, UIInputViewAudioFeedback { +public final class EntityInputView: UIInputView, AttachmentTextInputPanelInputView, UIInputViewAudioFeedback { private let context: AccountContext public var insertText: ((NSAttributedString) -> Void)? @@ -1698,7 +1721,7 @@ final class EntityInputView: UIView, AttachmentTextInputPanelInputView, UIInputV private let animationCache: AnimationCache private let animationRenderer: MultiAnimationRenderer - init( + public init( context: AccountContext, isDark: Bool, areCustomEmojiEnabled: Bool @@ -1713,8 +1736,8 @@ final class EntityInputView: UIView, AttachmentTextInputPanelInputView, UIInputV self.presentationData = self.presentationData.withUpdated(theme: defaultDarkPresentationTheme) } - //super.init(frame: CGRect(origin: CGPoint(), size: CGSize(width: 1.0, height: 1.0)), inputViewStyle: .default) - super.init(frame: CGRect(origin: CGPoint(), size: CGSize(width: 1.0, height: 1.0))) + super.init(frame: CGRect(origin: CGPoint(), size: CGSize(width: 1.0, height: 1.0)), inputViewStyle: .default) +// super.init(frame: CGRect(origin: CGPoint(), size: CGSize(width: 1.0, height: 1.0))) self.autoresizingMask = [.flexibleWidth, .flexibleHeight] self.clipsToBounds = true @@ -1818,7 +1841,7 @@ final class EntityInputView: UIView, AttachmentTextInputPanelInputView, UIInputV navigationController: { return nil }, - requestUpdate: { _ in + requestUpdate: { _ in }, updateSearchQuery: { _, _ in }, @@ -1874,7 +1897,7 @@ final class EntityInputView: UIView, AttachmentTextInputPanelInputView, UIInputV fatalError("init(coder:) has not been implemented") } - override func layoutSubviews() { + public override func layoutSubviews() { super.layoutSubviews() guard let inputNode = self.inputNode else { @@ -2136,3 +2159,112 @@ private final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { } } } + +public class PaneGifSearchForQueryResult { + public let files: [MultiplexedVideoNodeFile] + public let nextOffset: String? + public let isComplete: Bool + public let isStale: Bool + + public init(files: [MultiplexedVideoNodeFile], nextOffset: String?, isComplete: Bool, isStale: Bool) { + self.files = files + self.nextOffset = nextOffset + self.isComplete = isComplete + self.isStale = isStale + } +} + +public func paneGifSearchForQuery(context: AccountContext, query: String, offset: String?, incompleteResults: Bool = false, staleCachedResults: Bool = false, delayRequest: Bool = true, updateActivity: ((Bool) -> Void)?) -> Signal { + let contextBot = context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.SearchBots()) + |> mapToSignal { searchBots -> Signal in + let botName = searchBots.gifBotUsername ?? "gif" + return context.engine.peers.resolvePeerByName(name: botName) + } + |> mapToSignal { peer -> Signal<(ChatPresentationInputQueryResult?, Bool, Bool), NoError> in + if case let .user(user) = peer, let botInfo = user.botInfo, let _ = botInfo.inlinePlaceholder { + let results = requestContextResults(engine: context.engine, botId: user.id, query: query, peerId: context.account.peerId, offset: offset ?? "", incompleteResults: incompleteResults, staleCachedResults: staleCachedResults, limit: 1) + |> map { results -> (ChatPresentationInputQueryResult?, Bool, Bool) in + return (.contextRequestResult(.user(user), results?.results), results != nil, results?.isStale ?? false) + } + + let maybeDelayedContextResults: Signal<(ChatPresentationInputQueryResult?, Bool, Bool), NoError> + if delayRequest { + maybeDelayedContextResults = results |> delay(0.4, queue: Queue.concurrentDefaultQueue()) + } else { + maybeDelayedContextResults = results + } + + return maybeDelayedContextResults + } else { + return .single((nil, true, false)) + } + } + return contextBot + |> mapToSignal { result -> Signal in + if let r = result.0, case let .contextRequestResult(_, maybeCollection) = r, let collection = maybeCollection { + let results = collection.results + var references: [MultiplexedVideoNodeFile] = [] + for result in results { + switch result { + case let .externalReference(externalReference): + var imageResource: TelegramMediaResource? + var thumbnailResource: TelegramMediaResource? + var thumbnailIsVideo: Bool = false + var uniqueId: Int64? + if let content = externalReference.content { + imageResource = content.resource + if let resource = content.resource as? WebFileReferenceMediaResource { + uniqueId = Int64(HashFunctions.murMurHash32(resource.url)) + } + } + if let thumbnail = externalReference.thumbnail { + thumbnailResource = thumbnail.resource + if thumbnail.mimeType.hasPrefix("video/") { + thumbnailIsVideo = true + } + } + + if externalReference.type == "gif", let resource = imageResource, let content = externalReference.content, let dimensions = content.dimensions { + var previews: [TelegramMediaImageRepresentation] = [] + var videoThumbnails: [TelegramMediaFile.VideoThumbnail] = [] + if let thumbnailResource = thumbnailResource { + if thumbnailIsVideo { + videoThumbnails.append(TelegramMediaFile.VideoThumbnail( + dimensions: dimensions, + resource: thumbnailResource + )) + } else { + previews.append(TelegramMediaImageRepresentation( + dimensions: dimensions, + resource: thumbnailResource, + progressiveSizes: [], + immediateThumbnailData: nil, + hasVideo: false, + isPersonal: false + )) + } + } + let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: uniqueId ?? 0), partialReference: nil, resource: resource, previewRepresentations: previews, videoThumbnails: videoThumbnails, immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [])]) + references.append(MultiplexedVideoNodeFile(file: FileMediaReference.standalone(media: file), contextResult: (collection, result))) + } + case let .internalReference(internalReference): + if let file = internalReference.file { + references.append(MultiplexedVideoNodeFile(file: FileMediaReference.standalone(media: file), contextResult: (collection, result))) + } + } + } + return .single(PaneGifSearchForQueryResult(files: references, nextOffset: collection.nextOffset, isComplete: result.1, isStale: result.2)) + } else if incompleteResults { + return .single(nil) + } else { + return .complete() + } + } + |> deliverOnMainQueue + |> beforeStarted { + updateActivity?(true) + } + |> afterCompleted { + updateActivity?(false) + } +} diff --git a/submodules/TelegramUI/Sources/GifPaneSearchContentNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/GifPaneSearchContentNode.swift similarity index 65% rename from submodules/TelegramUI/Sources/GifPaneSearchContentNode.swift rename to submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/GifPaneSearchContentNode.swift index 2e63132e34..cb49fd3cc7 100644 --- a/submodules/TelegramUI/Sources/GifPaneSearchContentNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/GifPaneSearchContentNode.swift @@ -7,117 +7,10 @@ import Postbox import TelegramCore import TelegramPresentationData import AccountContext -import WebSearchUI import AppBundle - -class PaneGifSearchForQueryResult { - let files: [MultiplexedVideoNodeFile] - let nextOffset: String? - let isComplete: Bool - let isStale: Bool - - init(files: [MultiplexedVideoNodeFile], nextOffset: String?, isComplete: Bool, isStale: Bool) { - self.files = files - self.nextOffset = nextOffset - self.isComplete = isComplete - self.isStale = isStale - } -} - -func paneGifSearchForQuery(context: AccountContext, query: String, offset: String?, incompleteResults: Bool = false, staleCachedResults: Bool = false, delayRequest: Bool = true, updateActivity: ((Bool) -> Void)?) -> Signal { - let contextBot = context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.SearchBots()) - |> mapToSignal { searchBots -> Signal in - let botName = searchBots.gifBotUsername ?? "gif" - return context.engine.peers.resolvePeerByName(name: botName) - } - |> mapToSignal { peer -> Signal<(ChatPresentationInputQueryResult?, Bool, Bool), NoError> in - if case let .user(user) = peer, let botInfo = user.botInfo, let _ = botInfo.inlinePlaceholder { - let results = requestContextResults(context: context, botId: user.id, query: query, peerId: context.account.peerId, offset: offset ?? "", incompleteResults: incompleteResults, staleCachedResults: staleCachedResults, limit: 1) - |> map { results -> (ChatPresentationInputQueryResult?, Bool, Bool) in - return (.contextRequestResult(.user(user), results?.results), results != nil, results?.isStale ?? false) - } - - let maybeDelayedContextResults: Signal<(ChatPresentationInputQueryResult?, Bool, Bool), NoError> - if delayRequest { - maybeDelayedContextResults = results |> delay(0.4, queue: Queue.concurrentDefaultQueue()) - } else { - maybeDelayedContextResults = results - } - - return maybeDelayedContextResults - } else { - return .single((nil, true, false)) - } - } - return contextBot - |> mapToSignal { result -> Signal in - if let r = result.0, case let .contextRequestResult(_, maybeCollection) = r, let collection = maybeCollection { - let results = collection.results - var references: [MultiplexedVideoNodeFile] = [] - for result in results { - switch result { - case let .externalReference(externalReference): - var imageResource: TelegramMediaResource? - var thumbnailResource: TelegramMediaResource? - var thumbnailIsVideo: Bool = false - var uniqueId: Int64? - if let content = externalReference.content { - imageResource = content.resource - if let resource = content.resource as? WebFileReferenceMediaResource { - uniqueId = Int64(HashFunctions.murMurHash32(resource.url)) - } - } - if let thumbnail = externalReference.thumbnail { - thumbnailResource = thumbnail.resource - if thumbnail.mimeType.hasPrefix("video/") { - thumbnailIsVideo = true - } - } - - if externalReference.type == "gif", let resource = imageResource, let content = externalReference.content, let dimensions = content.dimensions { - var previews: [TelegramMediaImageRepresentation] = [] - var videoThumbnails: [TelegramMediaFile.VideoThumbnail] = [] - if let thumbnailResource = thumbnailResource { - if thumbnailIsVideo { - videoThumbnails.append(TelegramMediaFile.VideoThumbnail( - dimensions: dimensions, - resource: thumbnailResource - )) - } else { - previews.append(TelegramMediaImageRepresentation( - dimensions: dimensions, - resource: thumbnailResource, - progressiveSizes: [], - immediateThumbnailData: nil, - hasVideo: false, - isPersonal: false - )) - } - } - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: uniqueId ?? 0), partialReference: nil, resource: resource, previewRepresentations: previews, videoThumbnails: videoThumbnails, immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [])]) - references.append(MultiplexedVideoNodeFile(file: FileMediaReference.standalone(media: file), contextResult: (collection, result))) - } - case let .internalReference(internalReference): - if let file = internalReference.file { - references.append(MultiplexedVideoNodeFile(file: FileMediaReference.standalone(media: file), contextResult: (collection, result))) - } - } - } - return .single(PaneGifSearchForQueryResult(files: references, nextOffset: collection.nextOffset, isComplete: result.1, isStale: result.2)) - } else if incompleteResults { - return .single(nil) - } else { - return .complete() - } - } - |> deliverOnMainQueue - |> beforeStarted { - updateActivity?(true) - } - |> afterCompleted { - updateActivity?(false) - } -} +import ChatControllerInteraction +import MultiplexedVideoNode +import ChatPresentationInterfaceState final class GifPaneSearchContentNode: ASDisplayNode & PaneSearchContentNode { private let context: AccountContext diff --git a/submodules/TelegramUI/Sources/PaneSearchBarNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/PaneSearchBarNode.swift similarity index 99% rename from submodules/TelegramUI/Sources/PaneSearchBarNode.swift rename to submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/PaneSearchBarNode.swift index 490394469a..64754e7388 100644 --- a/submodules/TelegramUI/Sources/PaneSearchBarNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/PaneSearchBarNode.swift @@ -6,6 +6,7 @@ import Display import TelegramPresentationData import ActivityIndicator import AppBundle +import FeaturedStickersScreen private func generateLoupeIcon(color: UIColor) -> UIImage? { return generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Loupe"), color: color) diff --git a/submodules/TelegramUI/Sources/PaneSearchContainerNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/PaneSearchContainerNode.swift similarity index 83% rename from submodules/TelegramUI/Sources/PaneSearchContainerNode.swift rename to submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/PaneSearchContainerNode.swift index 13244d5c08..c645036b57 100644 --- a/submodules/TelegramUI/Sources/PaneSearchContainerNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/PaneSearchContainerNode.swift @@ -9,10 +9,13 @@ import TelegramPresentationData import AccountContext import ChatPresentationInterfaceState import EntityKeyboard +import ChatControllerInteraction +import MultiplexedVideoNode +import FeaturedStickersScreen private let searchBarHeight: CGFloat = 52.0 -protocol PaneSearchContentNode { +public protocol PaneSearchContentNode { var ready: Signal { get } var deactivateSearchBar: (() -> Void)? { get set } var updateActivity: ((Bool) -> Void)? { get set } @@ -28,7 +31,7 @@ protocol PaneSearchContentNode { func itemAt(point: CGPoint) -> (ASDisplayNode, Any)? } -final class PaneSearchContainerNode: ASDisplayNode, EntitySearchContainerNode { +public final class PaneSearchContainerNode: ASDisplayNode, EntitySearchContainerNode { private let context: AccountContext private let mode: ChatMediaInputSearchMode public private(set) var contentNode: PaneSearchContentNode & ASDisplayNode @@ -40,15 +43,15 @@ final class PaneSearchContainerNode: ASDisplayNode, EntitySearchContainerNode { private var validLayout: CGSize? - var onCancel: (() -> Void)? + public var onCancel: (() -> Void)? - var openGifContextMenu: ((MultiplexedVideoNodeFile, ASDisplayNode, CGRect, ContextGesture, Bool) -> Void)? + public var openGifContextMenu: ((MultiplexedVideoNodeFile, ASDisplayNode, CGRect, ContextGesture, Bool) -> Void)? - var ready: Signal { + public var ready: Signal { return self.contentNode.ready } - init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, controllerInteraction: ChatControllerInteraction, inputNodeInteraction: ChatMediaInputNodeInteraction, mode: ChatMediaInputSearchMode, trendingGifsPromise: Promise, cancel: @escaping () -> Void) { + public init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, controllerInteraction: ChatControllerInteraction, inputNodeInteraction: ChatMediaInputNodeInteraction, mode: ChatMediaInputSearchMode, trendingGifsPromise: Promise, cancel: @escaping () -> Void) { self.context = context self.mode = mode self.controllerInteraction = controllerInteraction @@ -102,7 +105,7 @@ final class PaneSearchContainerNode: ASDisplayNode, EntitySearchContainerNode { } } - func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { + public func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { self.backgroundNode.backgroundColor = theme.chat.inputMediaPanel.stickersBackgroundColor.withAlphaComponent(1.0) self.contentNode.updateThemeAndStrings(theme: theme, strings: strings) self.searchBar.updateThemeAndStrings(theme: theme, strings: strings) @@ -117,15 +120,15 @@ final class PaneSearchContainerNode: ASDisplayNode, EntitySearchContainerNode { self.searchBar.placeholderString = NSAttributedString(string: placeholder, font: Font.regular(17.0), textColor: theme.chat.inputMediaPanel.stickersSearchPlaceholderColor) } - func updateQuery(_ query: String) { + public func updateQuery(_ query: String) { self.searchBar.updateQuery(query) } - func itemAt(point: CGPoint) -> (ASDisplayNode, Any)? { + public func itemAt(point: CGPoint) -> (ASDisplayNode, Any)? { return self.contentNode.itemAt(point: CGPoint(x: point.x, y: point.y - searchBarHeight)) } - func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, inputHeight: CGFloat, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition) { + public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, inputHeight: CGFloat, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition) { self.validLayout = size transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size)) @@ -138,11 +141,11 @@ final class PaneSearchContainerNode: ASDisplayNode, EntitySearchContainerNode { self.contentNode.updateLayout(size: contentFrame.size, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, inputHeight: inputHeight, deviceMetrics: deviceMetrics, transition: transition) } - func deactivate() { + public func deactivate() { self.searchBar.deactivate(clear: true) } - func animateIn(from placeholder: PaneSearchBarPlaceholderNode?, anchorTop: CGPoint, anhorTopView: UIView, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) { + public func animateIn(from placeholder: PaneSearchBarPlaceholderNode?, anchorTop: CGPoint, anhorTopView: UIView, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) { var verticalOrigin: CGFloat = anhorTopView.convert(anchorTop, to: self.view).y if let placeholder = placeholder { let placeholderFrame = placeholder.view.convert(placeholder.bounds, to: self.view) @@ -170,7 +173,7 @@ final class PaneSearchContainerNode: ASDisplayNode, EntitySearchContainerNode { } } - func animateOut(to placeholder: PaneSearchBarPlaceholderNode, animateOutSearchBar: Bool, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) { + public func animateOut(to placeholder: PaneSearchBarPlaceholderNode, animateOutSearchBar: Bool, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) { if case let .animated(duration, curve) = transition { if let size = self.validLayout { let placeholderFrame = placeholder.view.convert(placeholder.bounds, to: self.view) diff --git a/submodules/TelegramUI/Sources/StickerPaneSearchContentNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/StickerPaneSearchContentNode.swift similarity index 97% rename from submodules/TelegramUI/Sources/StickerPaneSearchContentNode.swift rename to submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/StickerPaneSearchContentNode.swift index 8430ee3fda..78aa4ab462 100644 --- a/submodules/TelegramUI/Sources/StickerPaneSearchContentNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/StickerPaneSearchContentNode.swift @@ -16,20 +16,10 @@ import Emoji import AppBundle import OverlayStatusController import UndoUI - -final class StickerPaneSearchInteraction { - let open: (StickerPackCollectionInfo) -> Void - let install: (StickerPackCollectionInfo, [ItemCollectionItem], Bool) -> Void - let sendSticker: (FileMediaReference, UIView, CGRect) -> Void - let getItemIsPreviewed: (StickerPackItem) -> Bool - - init(open: @escaping (StickerPackCollectionInfo) -> Void, install: @escaping (StickerPackCollectionInfo, [ItemCollectionItem], Bool) -> Void, sendSticker: @escaping (FileMediaReference, UIView, CGRect) -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool) { - self.open = open - self.install = install - self.sendSticker = sendSticker - self.getItemIsPreviewed = getItemIsPreviewed - } -} +import ChatControllerInteraction +import FeaturedStickersScreen +import ChatPresentationInterfaceState +import FeaturedStickersScreen private enum StickerSearchEntryId: Equatable, Hashable { case sticker(String?, Int64) diff --git a/submodules/TelegramUI/Components/ChatInputNode/BUILD b/submodules/TelegramUI/Components/ChatInputNode/BUILD new file mode 100644 index 0000000000..1b45fe19a3 --- /dev/null +++ b/submodules/TelegramUI/Components/ChatInputNode/BUILD @@ -0,0 +1,21 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatInputNode", + module_name = "ChatInputNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/ChatPresentationInterfaceState:ChatPresentationInterfaceState", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/ChatInputNode/Sources/ChatInputNode.swift b/submodules/TelegramUI/Components/ChatInputNode/Sources/ChatInputNode.swift new file mode 100644 index 0000000000..95f9bf353a --- /dev/null +++ b/submodules/TelegramUI/Components/ChatInputNode/Sources/ChatInputNode.swift @@ -0,0 +1,34 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import SwiftSignalKit +import ChatPresentationInterfaceState + +open class ChatInputNode: ASDisplayNode { + public var interfaceInteraction: ChatPanelInterfaceInteraction? + open var ready: Signal { + return .single(Void()) + } + + open var externalTopPanelContainer: UIView? { + return nil + } + + public var topBackgroundExtension: CGFloat = 0.0 + public var topBackgroundExtensionUpdated: ((ContainedViewLayoutTransition) -> Void)? + + public var hideInput: Bool = false + public var adjustLayoutForHiddenInput: Bool = false + public var hideInputUpdated: ((ContainedViewLayoutTransition) -> Void)? + + public var followsDefaultHeight: Bool = false + + open func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) { + + } + + open func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, deviceMetrics: DeviceMetrics, isVisible: Bool, isExpanded: Bool) -> (CGFloat, CGFloat) { + return (0.0, 0.0) + } +} diff --git a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift index d20bbd009d..9aea09e27c 100644 --- a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift +++ b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift @@ -165,11 +165,14 @@ public final class EmojiStatusSelectionComponent: Component { topPanelInsets: UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0), emojiContent: component.emojiContent, stickerContent: nil, + maskContent: nil, gifContent: nil, hasRecentGifs: false, availableGifSearchEmojies: [], defaultToEmojiTab: true, externalTopPanelContainer: self.panelHostView, + externalBottomPanelContainer: nil, + displayTopPanelBackground: true, topPanelExtensionUpdated: { _, _ in }, hideInputUpdated: { _, _, _ in }, hideTopPanelUpdated: { [weak self] hideTopPanel, transition in diff --git a/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift b/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift index 4a3215b924..372da8ea61 100644 --- a/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift +++ b/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift @@ -482,3 +482,69 @@ public final class EmojiTextAttachmentView: UIView { self.contentLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: self.bounds.width, height: self.bounds.height)) } } + +public final class CustomEmojiContainerView: UIView { + private let emojiViewProvider: (ChatTextInputTextCustomEmojiAttribute) -> UIView? + + private var emojiLayers: [InlineStickerItemLayer.Key: UIView] = [:] + + public init(emojiViewProvider: @escaping (ChatTextInputTextCustomEmojiAttribute) -> UIView?) { + self.emojiViewProvider = emojiViewProvider + + super.init(frame: CGRect()) + } + + required init(coder: NSCoder) { + preconditionFailure() + } + + public func update(fontSize: CGFloat, textColor: UIColor, emojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute)]) { + var nextIndexById: [Int64: Int] = [:] + + var validKeys = Set() + for (rect, emoji) in emojiRects { + let index: Int + if let nextIndex = nextIndexById[emoji.fileId] { + index = nextIndex + } else { + index = 0 + } + nextIndexById[emoji.fileId] = index + 1 + + let key = InlineStickerItemLayer.Key(id: emoji.fileId, index: index) + + let view: UIView + if let current = self.emojiLayers[key] { + view = current + } else if let newView = self.emojiViewProvider(emoji) { + view = newView + self.addSubview(newView) + self.emojiLayers[key] = view + } else { + continue + } + + if let view = view as? EmojiTextAttachmentView { + view.updateTextColor(textColor) + } + + let itemSize: CGFloat = floor(24.0 * fontSize / 17.0) + let size = CGSize(width: itemSize, height: itemSize) + + view.frame = CGRect(origin: CGPoint(x: floor(rect.midX - size.width / 2.0), y: floor(rect.midY - size.height / 2.0) + 1.0), size: size) + + validKeys.insert(key) + } + + var removeKeys: [InlineStickerItemLayer.Key] = [] + for (key, view) in self.emojiLayers { + if !validKeys.contains(key) { + removeKeys.append(key) + view.removeFromSuperview() + } + } + for key in removeKeys { + self.emojiLayers.removeValue(forKey: key) + } + } +} diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift index 6421a421b7..3ce1285f90 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift @@ -2101,8 +2101,8 @@ public final class EmojiPagerContentComponent: Component { public final class InputInteraction { public let performItemAction: (AnyHashable, Item, UIView, CGRect, CALayer, Bool) -> Void - public let deleteBackwards: () -> Void - public let openStickerSettings: () -> Void + public let deleteBackwards: (() -> Void)? + public let openStickerSettings: (() -> Void)? public let openFeatured: () -> Void public let openSearch: () -> Void public let addGroupAction: (AnyHashable, Bool) -> Void @@ -2122,8 +2122,8 @@ public final class EmojiPagerContentComponent: Component { public init( performItemAction: @escaping (AnyHashable, Item, UIView, CGRect, CALayer, Bool) -> Void, - deleteBackwards: @escaping () -> Void, - openStickerSettings: @escaping () -> Void, + deleteBackwards: (() -> Void)?, + openStickerSettings: (() -> Void)?, openFeatured: @escaping () -> Void, openSearch: @escaping () -> Void, addGroupAction: @escaping (AnyHashable, Bool) -> Void, @@ -6310,7 +6310,8 @@ public final class EmojiPagerContentComponent: Component { selectedItems: Set = Set(), topStatusTitle: String? = nil, topicTitle: String? = nil, - topicColor: Int32? = nil + topicColor: Int32? = nil, + hasSearch: Bool = true ) -> Signal { let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) let isPremiumDisabled = premiumConfiguration.isPremiumDisabled @@ -7088,15 +7089,17 @@ public final class EmojiPagerContentComponent: Component { var displaySearchWithPlaceholder: String? var searchInitiallyHidden = true - if isReactionSelection { - displaySearchWithPlaceholder = strings.EmojiSearch_SearchReactionsPlaceholder - } else if isStatusSelection { - displaySearchWithPlaceholder = strings.EmojiSearch_SearchStatusesPlaceholder - } else if isTopicIconSelection { - displaySearchWithPlaceholder = strings.EmojiSearch_SearchTopicIconsPlaceholder - } else if isEmojiSelection { - displaySearchWithPlaceholder = strings.EmojiSearch_SearchEmojiPlaceholder - searchInitiallyHidden = false + if hasSearch { + if isReactionSelection { + displaySearchWithPlaceholder = strings.EmojiSearch_SearchReactionsPlaceholder + } else if isStatusSelection { + displaySearchWithPlaceholder = strings.EmojiSearch_SearchStatusesPlaceholder + } else if isTopicIconSelection { + displaySearchWithPlaceholder = strings.EmojiSearch_SearchTopicIconsPlaceholder + } else if isEmojiSelection { + displaySearchWithPlaceholder = strings.EmojiSearch_SearchEmojiPlaceholder + searchInitiallyHidden = false + } } return EmojiPagerContentComponent( @@ -7160,7 +7163,9 @@ public final class EmojiPagerContentComponent: Component { animationRenderer: MultiAnimationRenderer, stickerNamespaces: [ItemCollectionId.Namespace], stickerOrderedItemListCollectionIds: [Int32], - chatPeerId: EnginePeer.Id? + chatPeerId: EnginePeer.Id?, + hasSearch: Bool, + hasTrending: Bool ) -> Signal { let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) let isPremiumDisabled = premiumConfiguration.isPremiumDisabled @@ -7212,7 +7217,7 @@ public final class EmojiPagerContentComponent: Component { return combineLatest( context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: stickerOrderedItemListCollectionIds, namespaces: stickerNamespaces, aroundIndex: nil, count: 10000000), hasPremium(context: context, chatPeerId: chatPeerId, premiumIfSavedMessages: false), - context.account.viewTracker.featuredStickerPacks(), + hasTrending ? context.account.viewTracker.featuredStickerPacks() : .single([]), context.engine.data.get(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: Namespaces.CachedItemCollection.featuredStickersConfiguration, id: ValueBoxKey(length: 0))), ApplicationSpecificNotice.dismissedTrendingStickerPacks(accountManager: context.sharedContext.accountManager), peerSpecificPack @@ -7674,8 +7679,8 @@ public final class EmojiPagerContentComponent: Component { itemLayoutType: .detailed, itemContentUniqueId: nil, warpContentsOnEdges: false, - displaySearchWithPlaceholder: strings.StickersSearch_SearchStickersPlaceholder, - searchInitiallyHidden: false, + displaySearchWithPlaceholder: hasSearch ? strings.StickersSearch_SearchStickersPlaceholder : nil, + searchInitiallyHidden: true, searchIsPlaceholderOnly: true, emptySearchResults: nil, enableLongPress: false, diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift index 9ad2a68437..6e2eebf303 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift @@ -58,6 +58,7 @@ public final class EntityKeyboardComponent: Component { public enum ReorderCategory { case stickers case emoji + case masks } public struct GifSearchEmoji: Equatable { @@ -92,11 +93,14 @@ public final class EntityKeyboardComponent: Component { public let topPanelInsets: UIEdgeInsets public let emojiContent: EmojiPagerContentComponent? public let stickerContent: EmojiPagerContentComponent? + public let maskContent: EmojiPagerContentComponent? public let gifContent: GifPagerContentComponent? public let hasRecentGifs: Bool public let availableGifSearchEmojies: [GifSearchEmoji] public let defaultToEmojiTab: Bool public let externalTopPanelContainer: PagerExternalTopPanelContainer? + public let externalBottomPanelContainer: PagerExternalTopPanelContainer? + public let displayTopPanelBackground: Bool public let topPanelExtensionUpdated: (CGFloat, Transition) -> Void public let hideInputUpdated: (Bool, Bool, Transition) -> Void public let hideTopPanelUpdated: (Bool, Transition) -> Void @@ -118,11 +122,14 @@ public final class EntityKeyboardComponent: Component { topPanelInsets: UIEdgeInsets, emojiContent: EmojiPagerContentComponent?, stickerContent: EmojiPagerContentComponent?, + maskContent: EmojiPagerContentComponent?, gifContent: GifPagerContentComponent?, hasRecentGifs: Bool, availableGifSearchEmojies: [GifSearchEmoji], defaultToEmojiTab: Bool, externalTopPanelContainer: PagerExternalTopPanelContainer?, + externalBottomPanelContainer: PagerExternalTopPanelContainer?, + displayTopPanelBackground: Bool, topPanelExtensionUpdated: @escaping (CGFloat, Transition) -> Void, hideInputUpdated: @escaping (Bool, Bool, Transition) -> Void, hideTopPanelUpdated: @escaping (Bool, Transition) -> Void, @@ -143,11 +150,14 @@ public final class EntityKeyboardComponent: Component { self.topPanelInsets = topPanelInsets self.emojiContent = emojiContent self.stickerContent = stickerContent + self.maskContent = maskContent self.gifContent = gifContent self.hasRecentGifs = hasRecentGifs self.availableGifSearchEmojies = availableGifSearchEmojies self.defaultToEmojiTab = defaultToEmojiTab self.externalTopPanelContainer = externalTopPanelContainer + self.externalBottomPanelContainer = externalBottomPanelContainer + self.displayTopPanelBackground = displayTopPanelBackground self.topPanelExtensionUpdated = topPanelExtensionUpdated self.hideInputUpdated = hideInputUpdated self.hideTopPanelUpdated = hideTopPanelUpdated @@ -184,6 +194,9 @@ public final class EntityKeyboardComponent: Component { if lhs.stickerContent != rhs.stickerContent { return false } + if lhs.maskContent != rhs.maskContent { + return false + } if lhs.gifContent != rhs.gifContent { return false } @@ -199,6 +212,9 @@ public final class EntityKeyboardComponent: Component { if lhs.externalTopPanelContainer != rhs.externalTopPanelContainer { return false } + if lhs.displayTopPanelBackground != rhs.displayTopPanelBackground { + return false + } if lhs.deviceMetrics != rhs.deviceMetrics { return false } @@ -268,11 +284,96 @@ public final class EntityKeyboardComponent: Component { let gifsContentItemIdUpdated = ActionSlot<(AnyHashable, AnyHashable?, Transition)>() let stickersContentItemIdUpdated = ActionSlot<(AnyHashable, AnyHashable?, Transition)>() + let masksContentItemIdUpdated = ActionSlot<(AnyHashable, AnyHashable?, Transition)>() if transition.userData(MarkInputCollapsed.self) != nil { self.searchComponent = nil } + if let maskContent = component.maskContent { + var topMaskItems: [EntityKeyboardTopPanelComponent.Item] = [] + + for itemGroup in maskContent.itemGroups { + if let id = itemGroup.supergroupId.base as? String { + let iconMapping: [String: EntityKeyboardIconTopPanelComponent.Icon] = [ + "saved": .saved, + "recent": .recent, + "premium": .premium + ] + let titleMapping: [String: String] = [ + "saved": component.strings.Stickers_Favorites, + "recent": component.strings.Stickers_Recent, + "premium": component.strings.EmojiInput_PanelTitlePremium + ] + if let icon = iconMapping[id], let title = titleMapping[id] { + topMaskItems.append(EntityKeyboardTopPanelComponent.Item( + id: itemGroup.supergroupId, + isReorderable: false, + content: AnyComponent(EntityKeyboardIconTopPanelComponent( + icon: icon, + theme: component.theme, + useAccentColor: false, + title: title, + pressed: { [weak self] in + self?.scrollToItemGroup(contentId: "masks", groupId: itemGroup.supergroupId, subgroupId: nil) + } + )) + )) + } + } else { + if !itemGroup.items.isEmpty { + if let animationData = itemGroup.items[0].animationData { + topMaskItems.append(EntityKeyboardTopPanelComponent.Item( + id: itemGroup.supergroupId, + isReorderable: !itemGroup.isFeatured, + content: AnyComponent(EntityKeyboardAnimationTopPanelComponent( + context: maskContent.context, + item: itemGroup.headerItem ?? animationData, + isFeatured: itemGroup.isFeatured, + isPremiumLocked: itemGroup.isPremiumLocked, + animationCache: maskContent.animationCache, + animationRenderer: maskContent.animationRenderer, + theme: component.theme, + title: itemGroup.title ?? "", + pressed: { [weak self] in + self?.scrollToItemGroup(contentId: "masks", groupId: itemGroup.supergroupId, subgroupId: nil) + } + )) + )) + } + } + } + } + contents.append(AnyComponentWithIdentity(id: "masks", component: AnyComponent(maskContent))) + contentTopPanels.append(AnyComponentWithIdentity(id: "masks", component: AnyComponent(EntityKeyboardTopPanelComponent( + id: "masks", + theme: component.theme, + items: topMaskItems, + containerSideInset: component.containerInsets.left + component.topPanelInsets.left, + defaultActiveItemId: maskContent.itemGroups.first?.groupId, + activeContentItemIdUpdated: masksContentItemIdUpdated, + reorderItems: { [weak self] items in + guard let strongSelf = self else { + return + } + strongSelf.reorderPacks(category: .stickers, items: items) + } + )))) + contentIcons.append(PagerComponentContentIcon(id: "masks", imageName: "Chat/Input/Media/EntityInputMasksIcon")) + if let _ = component.maskContent?.inputInteractionHolder.inputInteraction?.openStickerSettings { + contentAccessoryRightButtons.append(AnyComponentWithIdentity(id: "masks", component: AnyComponent(Button( + content: AnyComponent(BundleIconComponent( + name: "Chat/Input/Media/EntityInputSettingsIcon", + tintColor: component.theme.chat.inputMediaPanel.panelIconColor, + maxSize: nil + )), + action: { + maskContent.inputInteractionHolder.inputInteraction?.openStickerSettings?() + } + ).minSize(CGSize(width: 38.0, height: 38.0))))) + } + } + if let gifContent = component.gifContent { contents.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(gifContent))) var topGifItems: [EntityKeyboardTopPanelComponent.Item] = [] @@ -345,16 +446,6 @@ public final class EntityKeyboardComponent: Component { } )))) contentIcons.append(PagerComponentContentIcon(id: "gifs", imageName: "Chat/Input/Media/EntityInputGifsIcon")) -// contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(Button( -// content: AnyComponent(BundleIconComponent( -// name: "Chat/Input/Media/EntityInputSearchIcon", -// tintColor: component.theme.chat.inputMediaPanel.panelIconColor, -// maxSize: nil -// )), -// action: { [weak self] in -// self?.openSearch() -// } -// ).minSize(CGSize(width: 38.0, height: 38.0))))) } if let stickerContent = component.stickerContent { @@ -459,28 +550,22 @@ public final class EntityKeyboardComponent: Component { } )))) contentIcons.append(PagerComponentContentIcon(id: "stickers", imageName: "Chat/Input/Media/EntityInputStickersIcon")) -// contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(Button( -// content: AnyComponent(BundleIconComponent( -// name: "Chat/Input/Media/EntityInputSearchIcon", -// tintColor: component.theme.chat.inputMediaPanel.panelIconColor, -// maxSize: nil -// )), -// action: { [weak self] in -// self?.openSearch() -// } -// ).minSize(CGSize(width: 38.0, height: 38.0))))) - contentAccessoryRightButtons.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(Button( - content: AnyComponent(BundleIconComponent( - name: "Chat/Input/Media/EntityInputSettingsIcon", - tintColor: component.theme.chat.inputMediaPanel.panelIconColor, - maxSize: nil - )), - action: { - stickerContent.inputInteractionHolder.inputInteraction?.openStickerSettings() - } - ).minSize(CGSize(width: 38.0, height: 38.0))))) + if let _ = component.stickerContent?.inputInteractionHolder.inputInteraction?.openStickerSettings { + contentAccessoryRightButtons.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(Button( + content: AnyComponent(BundleIconComponent( + name: "Chat/Input/Media/EntityInputSettingsIcon", + tintColor: component.theme.chat.inputMediaPanel.panelIconColor, + maxSize: nil + )), + action: { + stickerContent.inputInteractionHolder.inputInteraction?.openStickerSettings?() + } + ).minSize(CGSize(width: 38.0, height: 38.0))))) + } } + let deleteBackwards = component.emojiContent?.inputInteractionHolder.inputInteraction?.deleteBackwards + let emojiContentItemIdUpdated = ActionSlot<(AnyHashable, AnyHashable?, Transition)>() if let emojiContent = component.emojiContent { contents.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(emojiContent))) @@ -564,36 +649,39 @@ public final class EntityKeyboardComponent: Component { } )))) contentIcons.append(PagerComponentContentIcon(id: "emoji", imageName: "Chat/Input/Media/EntityInputEmojiIcon")) - contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(Button( + if let _ = deleteBackwards { + contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(Button( + content: AnyComponent(BundleIconComponent( + name: "Chat/Input/Media/EntityInputGlobeIcon", + tintColor: component.theme.chat.inputMediaPanel.panelIconColor, + maxSize: nil + )), + action: { [weak self] in + guard let strongSelf = self, let component = strongSelf.component else { + return + } + component.switchToTextInput() + } + ).minSize(CGSize(width: 38.0, height: 38.0))))) + } + } + + if let _ = deleteBackwards { + contentAccessoryRightButtons.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(Button( content: AnyComponent(BundleIconComponent( - name: "Chat/Input/Media/EntityInputGlobeIcon", + name: "Chat/Input/Media/EntityInputClearIcon", tintColor: component.theme.chat.inputMediaPanel.panelIconColor, maxSize: nil )), - action: { [weak self] in - guard let strongSelf = self, let component = strongSelf.component else { - return - } - component.switchToTextInput() + action: { + deleteBackwards?() + AudioServicesPlaySystemSound(1155) } - ).minSize(CGSize(width: 38.0, height: 38.0))))) - } - - let deleteBackwards = component.emojiContent?.inputInteractionHolder.inputInteraction?.deleteBackwards - contentAccessoryRightButtons.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(Button( - content: AnyComponent(BundleIconComponent( - name: "Chat/Input/Media/EntityInputClearIcon", - tintColor: component.theme.chat.inputMediaPanel.panelIconColor, - maxSize: nil - )), - action: { + ).withHoldAction({ deleteBackwards?() AudioServicesPlaySystemSound(1155) - } - ).withHoldAction({ - deleteBackwards?() - AudioServicesPlaySystemSound(1155) - }).minSize(CGSize(width: 38.0, height: 38.0))))) + }).minSize(CGSize(width: 38.0, height: 38.0))))) + } let panelHideBehavior: PagerComponentPanelHideBehavior if self.searchComponent != nil { @@ -618,17 +706,18 @@ public final class EntityKeyboardComponent: Component { topPanel: AnyComponent(EntityKeyboardTopContainerPanelComponent( theme: component.theme, overflowHeight: component.hiddenInputHeight, - displayBackground: component.externalTopPanelContainer == nil + displayBackground: component.externalTopPanelContainer == nil && component.displayTopPanelBackground )), externalTopPanelContainer: component.externalTopPanelContainer, bottomPanel: component.displayBottomPanel ? AnyComponent(EntityKeyboardBottomPanelComponent( theme: component.theme, containerInsets: component.containerInsets, deleteBackwards: { [weak self] in - self?.component?.emojiContent?.inputInteractionHolder.inputInteraction?.deleteBackwards() + self?.component?.emojiContent?.inputInteractionHolder.inputInteraction?.deleteBackwards?() AudioServicesPlaySystemSound(0x451) } )) : nil, + externalBottomPanelContainer: component.externalBottomPanelContainer, panelStateUpdated: { [weak self] panelState, transition in guard let strongSelf = self else { return @@ -661,6 +750,8 @@ public final class EntityKeyboardComponent: Component { return stickersContentItemIdUpdated } else if id == AnyHashable("emoji") { return emojiContentItemIdUpdated + } else if id == AnyHashable("masks") { + return masksContentItemIdUpdated } return nil } @@ -670,7 +761,7 @@ public final class EntityKeyboardComponent: Component { ) transition.setFrame(view: self.pagerView, frame: CGRect(origin: CGPoint(), size: pagerSize)) - let accountContext = component.emojiContent?.context ?? component.stickerContent?.context + let accountContext = component.emojiContent?.context ?? component.maskContent?.context if let searchComponent = self.searchComponent, let accountContext = accountContext { var animateIn = false let searchView: ComponentHostView diff --git a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift index 3a5dc7880c..b645ad8c4a 100644 --- a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift +++ b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift @@ -401,11 +401,14 @@ private final class TopicIconSelectionComponent: Component { topPanelInsets: UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0), emojiContent: component.emojiContent, stickerContent: nil, + maskContent: nil, gifContent: nil, hasRecentGifs: false, availableGifSearchEmojies: [], defaultToEmojiTab: true, externalTopPanelContainer: self.panelHostView, + externalBottomPanelContainer: nil, + displayTopPanelBackground: true, topPanelExtensionUpdated: { _, _ in }, hideInputUpdated: { _, _, _ in }, hideTopPanelUpdated: { _, _ in }, diff --git a/submodules/TelegramUI/Components/MultiplexedVideoNode/BUILD b/submodules/TelegramUI/Components/MultiplexedVideoNode/BUILD new file mode 100644 index 0000000000..dbbd2364a6 --- /dev/null +++ b/submodules/TelegramUI/Components/MultiplexedVideoNode/BUILD @@ -0,0 +1,26 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "MultiplexedVideoNode", + module_name = "MultiplexedVideoNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/ContextUI:ContextUI", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/ShimmerEffect:ShimmerEffect", + "//submodules/SoftwareVideo:SoftwareVideo", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/MultiplexedVideoNode.swift b/submodules/TelegramUI/Components/MultiplexedVideoNode/Sources/MultiplexedVideoNode.swift similarity index 93% rename from submodules/TelegramUI/Sources/MultiplexedVideoNode.swift rename to submodules/TelegramUI/Components/MultiplexedVideoNode/Sources/MultiplexedVideoNode.swift index 80ba7707d0..eca0ec6b47 100644 --- a/submodules/TelegramUI/Sources/MultiplexedVideoNode.swift +++ b/submodules/TelegramUI/Components/MultiplexedVideoNode/Sources/MultiplexedVideoNode.swift @@ -74,24 +74,24 @@ private final class VisibleVideoItem { } } -final class MultiplexedVideoNodeFile { - let file: FileMediaReference - let contextResult: (ChatContextResultCollection, ChatContextResult)? +public final class MultiplexedVideoNodeFile { + public let file: FileMediaReference + public let contextResult: (ChatContextResultCollection, ChatContextResult)? - init(file: FileMediaReference, contextResult: (ChatContextResultCollection, ChatContextResult)?) { + public init(file: FileMediaReference, contextResult: (ChatContextResultCollection, ChatContextResult)?) { self.file = file self.contextResult = contextResult } } -final class MultiplexedVideoNodeFiles { - let saved: [MultiplexedVideoNodeFile] - let trending: [MultiplexedVideoNodeFile] - let isSearch: Bool - let canLoadMore: Bool - let isStale: Bool +public final class MultiplexedVideoNodeFiles { + public let saved: [MultiplexedVideoNodeFile] + public let trending: [MultiplexedVideoNodeFile] + public let isSearch: Bool + public let canLoadMore: Bool + public let isStale: Bool - init(saved: [MultiplexedVideoNodeFile], trending: [MultiplexedVideoNodeFile], isSearch: Bool, canLoadMore: Bool, isStale: Bool) { + public init(saved: [MultiplexedVideoNodeFile], trending: [MultiplexedVideoNodeFile], isSearch: Bool, canLoadMore: Bool, isStale: Bool) { self.saved = saved self.trending = trending self.isSearch = isSearch @@ -100,36 +100,37 @@ final class MultiplexedVideoNodeFiles { } } -final class MultiplexedVideoNode: ASDisplayNode, UIScrollViewDelegate { +public final class MultiplexedVideoNode: ASDisplayNode, UIScrollViewDelegate { private let account: Account private var theme: PresentationTheme private var strings: PresentationStrings private let trackingNode: MultiplexedVideoTrackingNode - var didScroll: ((CGFloat, CGFloat) -> Void)? - var didEndScrolling: (() -> Void)? - var reactionSelected: ((String) -> Void)? - var topInset: CGFloat = 0.0 { + public var didScroll: ((CGFloat, CGFloat) -> Void)? + public var didEndScrolling: (() -> Void)? + public var reactionSelected: ((String) -> Void)? + + public var topInset: CGFloat = 0.0 { didSet { self.setNeedsLayout() } } - var bottomInset: CGFloat = 0.0 { + public var bottomInset: CGFloat = 0.0 { didSet { self.setNeedsLayout() } } - var idealHeight: CGFloat = 93.0 { + public var idealHeight: CGFloat = 93.0 { didSet { self.setNeedsLayout() } } - private(set) var files: MultiplexedVideoNodeFiles = MultiplexedVideoNodeFiles(saved: [], trending: [], isSearch: false, canLoadMore: false, isStale: false) + public private(set) var files: MultiplexedVideoNodeFiles = MultiplexedVideoNodeFiles(saved: [], trending: [], isSearch: false, canLoadMore: false, isStale: false) - func setFiles(files: MultiplexedVideoNodeFiles, synchronous: Bool, resetScrollingToOffset: CGFloat?) { + public func setFiles(files: MultiplexedVideoNodeFiles, synchronous: Bool, resetScrollingToOffset: CGFloat?) { self.files = files self.ignoreDidScroll = true @@ -145,7 +146,7 @@ final class MultiplexedVideoNode: ASDisplayNode, UIScrollViewDelegate { private var visiblePlaceholderNodes: [Int: MultiplexedVideoPlaceholderNode] = [:] private let contextContainerNode: ContextControllerSourceNode - let scrollNode: ASScrollNode + public let scrollNode: ASScrollNode private var visibleLayers: [VisibleVideoItem.Id: (SoftwareVideoLayerFrameManager, SampleBufferLayer)] = [:] @@ -157,14 +158,14 @@ final class MultiplexedVideoNode: ASDisplayNode, UIScrollViewDelegate { private let timebase: CMTimebase - var fileSelected: ((MultiplexedVideoNodeFile, ASDisplayNode, CGRect) -> Void)? - var fileContextMenu: ((MultiplexedVideoNodeFile, ASDisplayNode, CGRect, ContextGesture, Bool) -> Void)? - var enableVideoNodes = false + public var fileSelected: ((MultiplexedVideoNodeFile, ASDisplayNode, CGRect) -> Void)? + public var fileContextMenu: ((MultiplexedVideoNodeFile, ASDisplayNode, CGRect, ContextGesture, Bool) -> Void)? + public var enableVideoNodes = false private var currentActivatingId: VisibleVideoItem.Id? private var isFinishingActivation = false - init(account: Account, theme: PresentationTheme, strings: PresentationStrings) { + public init(account: Account, theme: PresentationTheme, strings: PresentationStrings) { self.account = account self.theme = theme self.strings = strings @@ -343,7 +344,7 @@ final class MultiplexedVideoNode: ASDisplayNode, UIScrollViewDelegate { } private var validSize: CGSize? - func updateLayout(theme: PresentationTheme, strings: PresentationStrings, size: CGSize, transition: ContainedViewLayoutTransition) { + public func updateLayout(theme: PresentationTheme, strings: PresentationStrings, size: CGSize, transition: ContainedViewLayoutTransition) { self.theme = theme self.strings = strings if self.validSize == nil || !self.validSize!.equalTo(size) { @@ -359,18 +360,18 @@ final class MultiplexedVideoNode: ASDisplayNode, UIScrollViewDelegate { private var ignoreDidScroll: Bool = false - func scrollViewDidScroll(_ scrollView: UIScrollView) { + public func scrollViewDidScroll(_ scrollView: UIScrollView) { if !self.ignoreDidScroll { self.updateImmediatelyVisibleItems() self.didScroll?(scrollView.contentOffset.y, scrollView.contentSize.height) } } - func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { self.didEndScrolling?() } - func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { if !decelerate { self.didEndScrolling?() } @@ -738,7 +739,7 @@ final class MultiplexedVideoNode: ASDisplayNode, UIScrollViewDelegate { } } - func frameForItem(_ id: MediaId) -> CGRect? { + public func frameForItem(_ id: MediaId) -> CGRect? { for item in self.displayItems { if item.file.file.media.fileId == id { return item.frame @@ -747,7 +748,7 @@ final class MultiplexedVideoNode: ASDisplayNode, UIScrollViewDelegate { return nil } - func fileAt(point: CGPoint) -> (MultiplexedVideoNodeFile, CGRect, Bool)? { + public func fileAt(point: CGPoint) -> (MultiplexedVideoNodeFile, CGRect, Bool)? { if let result = self.internalFileAt(point: point) { return (result.1, result.2, result.3) } else { diff --git a/submodules/TelegramUI/Sources/SoftwareVideoThumbnailLayer.swift b/submodules/TelegramUI/Components/MultiplexedVideoNode/Sources/SoftwareVideoThumbnailLayer.swift similarity index 90% rename from submodules/TelegramUI/Sources/SoftwareVideoThumbnailLayer.swift rename to submodules/TelegramUI/Components/MultiplexedVideoNode/Sources/SoftwareVideoThumbnailLayer.swift index e54058df92..f84319127b 100644 --- a/submodules/TelegramUI/Sources/SoftwareVideoThumbnailLayer.swift +++ b/submodules/TelegramUI/Components/MultiplexedVideoNode/Sources/SoftwareVideoThumbnailLayer.swift @@ -13,7 +13,7 @@ private final class SoftwareVideoThumbnailLayerNullAction: NSObject, CAAction { } } -final class SoftwareVideoThumbnailNode: ASDisplayNode { +public final class SoftwareVideoThumbnailNode: ASDisplayNode { private let usePlaceholder: Bool private var placeholder: MultiplexedVideoPlaceholderNode? private var theme: PresentationTheme? @@ -21,7 +21,7 @@ final class SoftwareVideoThumbnailNode: ASDisplayNode { var disposable = MetaDisposable() - var ready: (() -> Void)? { + public var ready: (() -> Void)? { didSet { if self.layer.contents != nil { self.ready?() @@ -29,6 +29,10 @@ final class SoftwareVideoThumbnailNode: ASDisplayNode { } } + public convenience init(account: Account, fileReference: FileMediaReference, synchronousLoad: Bool, usePlaceholder: Bool = false) { + self.init(account: account, fileReference: fileReference, synchronousLoad: synchronousLoad, existingPlaceholder: nil) + } + init(account: Account, fileReference: FileMediaReference, synchronousLoad: Bool, usePlaceholder: Bool = false, existingPlaceholder: MultiplexedVideoPlaceholderNode? = nil) { self.usePlaceholder = usePlaceholder if usePlaceholder { @@ -102,7 +106,7 @@ final class SoftwareVideoThumbnailNode: ASDisplayNode { self.disposable.dispose() } - func update(theme: PresentationTheme, size: CGSize) { + public func update(theme: PresentationTheme, size: CGSize) { if self.usePlaceholder { self.theme = theme } @@ -112,7 +116,7 @@ final class SoftwareVideoThumbnailNode: ASDisplayNode { } } - func updateAbsoluteRect(_ absoluteRect: CGRect, within containerSize: CGSize) { + public func updateAbsoluteRect(_ absoluteRect: CGRect, within containerSize: CGSize) { self.asolutePosition = (absoluteRect, containerSize) if let placeholder = self.placeholder { placeholder.updateAbsoluteRect(absoluteRect, within: containerSize) diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputMasksIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputMasksIcon.imageset/Contents.json new file mode 100644 index 0000000000..96db762e39 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputMasksIcon.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "mask.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputMasksIcon.imageset/mask.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputMasksIcon.imageset/mask.pdf new file mode 100644 index 0000000000000000000000000000000000000000..bf429af6c9c564a2d20bdfe8942cecf30af79878 GIT binary patch literal 5801 zcmajjc{o(<-vIEzWH0+pO|lcSSR!QKvy-vUn89Eu#-6P#*|Kj_k}ZZvkv034h-A-} z7_w!Jkb1}Sd!G0A{Fe86&vmXj=lb5?`+LrHf9H?Sxdn|iw5~!VVYGr>#9iWg$==fs zT|Klg5E$fzbfZ;N1W9ROJaA}l($xcjLu;U&yijP6lmXh)1?LKaLaxbxl$B|HaNcMH zmNtMaF<9Qp=X8Raj-a`(6)FNFF8iCMjfWg2u%z-@!{X2?w4wO89Ooq>!$lB*o+s(`QY#bH zma^=Eaa4!SdL?$9LBdkkWDOv6%%fN_v+=dJF&IQP+k{)(7Y@E*s$pi!D|o=3J=CIk zsx&r1VLc+(DP_GmamELBQS#fS^;pokHlfO1vl$+~k191|5JSn6?j|m(6v@hMguhhe zF3QHtITe?a!@)mZ!F_JLxhl~2ZTuq^wS^hx!f^>y`UyC)JHY?yr9vlqo_M*(4L#4D zP;L^GeV(`Kl$%R}?K0gRUJP&RtyZ#-OBX;XC`F%7GV>b}($%zJd~8F_=YR2a@p=b$ z?s9EwEYK9_=>`7q--Q+vy{p?kv^T!fGS%Z6Y}EKAL#aq3fmN@8S}+N8C_DzeeohmB zo@^1Ze@eP zLl{+D5{8va6Pr`~(*cjbVY*7kW1!#Um(!aWn?ft5_7uPcb5q)L_=&)JYQ;ng^{tM{ zIvvZKoxwiIQe97_-Cw@^`ou(ZNj9ZcZ&f?l2^MGWa%j#>?V#W*%tFuXBXw+{>6|>p zGZ@av^@R+^F=LG7ctzAucwf`r5`v}yM9=7Kr+eW~Wp5w?&PNvafD~W+yef114B7*YueN@#f+0s;0Y3Lky2=1voi1VUz z1c*fh3TN`sBkeH~kSS4~>_9N#dU^BiqmN|<@12>qG`<@o#yujhl_4;iibb4{Z-L^UQPciGS zb!^Z=%{rkSUcOwFtCa%Z%O4_v^wapdbi2wNk*)(4D5l{&e{H|s$qnJ8^>3E6+7DX? z9l}mx4Sp)hM~2;iEHfoUTckSpqA=0v`%%NWm+0+_XA&2udzU$MD1Hb1I%=J6YBbl86a7Kz`d%gN8AiK1c2bl$bWEFeQ~rM>+MJkDc-eK5R? zvwx-2#J%s^N99s{UhD(*zU@<(DXzO}!~Temz9ZY3iYXp1)$Hbg~C1?qnD&rs4)m=5e|6sMT*2tSi^H6mbyA5#nYz_kth z&gjp1$&E`VK}$ndDR)(C($8s%&xegTji~W3?VY)vqTBe}xEJ+n8cx$Q`_#ddKqtB+ zA&p~wuU+uP^9#=ZJovsSZoP6CKT0FhS6uzTr=rFae`O>bmZ950U}vO!41ZOLDc#2~ zrh0qrc`7JmcQu%Q7fWQlhx*q4)v5Ci$HmJ<(;C{q8tQIigUMROMCax48mY)&-)86G zt1mk!$uIPT94cqc(M60!+PJ$a#Xdj1lftR1V=&fuE9I4@+DPbnTud4Nsn8W0xat;9Rk;e#cSD!Pssc1BZ^0u9c!ymhyx@3Pu4wwg|3 zW#*0t#ckm;UkW+$$XTwd1iOQ9`tU%<;eN?>o!Y-Gi`>Qt|Qlr9lWigKuLtD zV6ulR;8PR;X(D~_CW9_1UF+SlojHirlYP-HfcNv=`>O(wSXzV2c3raP+$O4ud|9Iz z#zMy%k_$Fk2xODEq=S5}*z}sVV)0bXaS`E-NAh+nA&$~dyOK+7yPmd?nHz)}wmdbp z^sMN&7+58E++Ew9N-5uG6Db5WdA1Mq-WC;KOS8K26?1H(OGEmIU=%?|D#wZyN?<;~RZpsWNMy}EtmPa_;zw%otU^+4C zjZAsgE7$$LR%%Gkli&oUxn3*TtrS|5PidUTU3FT9sZt4Xq8gxb| zbsq>w=Fb9ut0DWh8iu4NNU5Gl%ag<`kdy}6595S3)mHnbwPYdC|E{sW11vYw-`s49 zJ!Ys`uK5EqKS*$&JBSAK@wyHh&?Htj+?YH%Qpiu^oE9sSs3pq0jZ8a+-$|2|?*2XG z1kEm^aZkI6h9+?~!JTHSLS}Ccf7%P5pIWJ#m~z<)nP>zU|GWq6z*&)nOXpj$ep_$1 zWoGH`Iwa?0B)ecm*0$j64AlOiK|cDkSt%!{RHvu<;G1MPs%cuH)mX@9{C8Liznr=N zC4g3QOja>eh=WL;ZdTpR1^o?tx1S*jjfOzk^Jxm6^DlP2@Hi-07H{gy&JtS6=Y|h* zy^(&B&}2M^7hEVz0Os3f3x0Mfyi0{{HrK@ih=(F0BR?J+0Klv{4g)Jf`5v)QO;)aN zvAg5|>hv!8H{KWGkK|pS00N%5EX4t1b&=1QCbBWauWy}?cdE^fkPz|o&YKGl0t|-s zYv><>Il^CfWonWM!m&B7DVr6%o15_QW!IzDsriecW~WMTgH9}Vgf@nrs3tst&wFe< zmuhAQ90*uH;;U}SumIFxDOHFI&yBV~fZcO|nz_e&7o$DwQ@g&<3)97KYgxaqiIO+2*QI*v zr36sFLLuOJBZxd?-G&iD`?lqcYemWnpCo9|ua)CVbhdGx3kItEX3GckNhoi@0tv3d z`ACm;a^_?z+*4ZV2uU!xgyVVMaM*2%IS?Q+T!)b&lR{F59HRn!rK&s$pjCxTlKI_t zyA2qP2>JjtyeE5`(iV8D?fh*z`fzO(+URzDFxgxLhWg$%BU^!LSu%SOLyp=wBabz8 zpt`E@HCBpl_1I*_RF$I-LZzHy_sjCtjg|JV;J6#9?d~5xrTPGpraUDJiDb$qA3>a- z1k{F4RbA($*6!&4tP#NWn2MuqdO@$AUHL*?2gd@>Hb5fEp(Bl25l=&+PRgAn3d)##L^daT57%N0JYfN8i5N%ffyc6)vMmib zmSvq8VG&37ijiY4pIHe%zu`wC%k(JmYL}&BlA}#k)=P>>#Kv{r3%zYzUwCha)^plI zJ!L9^bqo^;P2uaE=NE)_0^W$tv2NXCX!qGh-o4yH8^#-UaX#r%Ppfb;jWy8y9>e8K zkbsAPIU_gq3iW9uQ-`!kM5z&uKbkSH%N8m6^5LZF8(m%@!!%aiXaNpW0nwbA2y_tEA@zoFx`+^~kshz6+LWQZUV}=YN}zhN>G@L6 zQLD1-#$0QoW{nWN5Gn06Sk_+FVqZmCd5U|+dRDNYQTjxdf0lz}p0LB^Fuh;Te%6zl zw5zn6MHE(9=4&m~bSOpXYT^qrkPnb}WGwRj7nyDo!8fT9sddao1eMjQkAlUQxz$FB z13%W++^kXfg#HBP)0Qhb8A)61-oNy zri7$D>E(u@#i4207a05BZ$vYW#5~3BX7Fb?XOLwil^Ixgjj@j9mSM|`M!{}EZV0z2 zx05l+^2DK%A^#^sxen65mwv{qRW-&2)gQZk?_5(_OXJ3Kw{UZDTX1Jd_sDSJJ@I{b zB7Q}>x5m|)?lsVwXua~<9-NW$ZNS`FusU8ozoK%p=AGMd}$K|&oo*)Vr zoUW9mln;v)S{GjLx#m=RCvHc1(MG5$Hdi-QH?i-g+2rV)xntu{%@Do&va67ExlB`U zLDdW#{#)~b#>KXu{V z?VLh-qumyrst2FVF2AlIT%Gr8-3+;ox2u|EZ!&BeI=FDae-FP-n-hsEeqGqE*-53T)A|!}27QJU?fBmGlC%}>Av*g9Subv-+xkjRAy=MYbg(r`hhqlKlMAF-J956Ii5H%0$u|?r};`-1k?grQYq6x zX_(LX0z2Eu+MDiOlKSW%U^~iAuJ%*iFG`U{hTe|t*oRY6Li)MTMqs@~KxlwJ@G4SM zL%pj>C+Y3Sx7zWE?E38fT7BBxnq%5GHG?&?H1)HrB;eIcS_#$(pl3?#mw4EM1^m1T zhaYG4V-`2Q&@O(HgxO|RZ+!B8O(gUWRVF*M!|#Y)k~*HRqy@&Umhty>Y4Q6N{j*)>oBM@|sPRhM z=>|i8yY|L~?=9;|SP)icYP(;nKM~C{?-@5YYVy>is>RW@dTY5ytwGIXq50~d%>|Ec z4>pg@S?B5sx9Kc?`{D1%{x5M~j09&=*XQ9Ow~l594k9<3iw}xV{6G6q`_Ig-Htt#F zAmR2?A1XiFRS*Yu3wG<8Zf&$!B5dwBGGLNWNj1>YSIv4a+_c9^){535xRdb9&1=DR zo4KEQrzcwHv0)=*i4@&b$`@IA--NwAGOG4VZ#sLwafB5UwVIB+Im8- zI29NdQno65ns-q9!8*=5e|%;l#{?6s0h^IXM zCfO4F+kQrAHDvB|n2Nw)e`6r%+NtZQ)5cBXr(-`RoC(eQ8&4-3hpHdA zBt_S&Oa0C`dSTRgZip}>u2rM8rd^>ulyH` zvkS7XYukNzJ?DnrTxpB^@%RCD1@(1l#2>TutR!x(yyeD0#A*JC(|&Kt>W0w|ql~=7 z8~%4`e(nYcVV#%f4S_odGzxTPRa21NU;I7u@?ZS@C#QWt zQa8~k3_{H-0AvFur9h=g=N}&SB_SIElG4YZd`M0{(>STx-z+Tm9|hGBIE06n%Riv* z^QX}N3Gi!wn?M3M%GZgsNhyLwI2oCNEYaRR7%xu{1S%;DvK3MD#dx4ddzIP{h>Wc$ z$ix@njSB*isQ!oU18|}wp_BG6ail34Bz)6~XedCzP$&cnA(0y*2ZLIJ!NR1Ily}n$ zb(RA9zf=DCh5_DaXId~wo)-MS2S`>%1||b?2K^a>La&jQ>FfY`{uPsf$dMZGuQ4#` zb^bjDg~|Sxxzhg;gGxh5YxS@7WS}ynhW=X&2L9V~aoz}w2ip6O9bhv|5E>*;`a1IR z!hy~jMfy_G^>p?Eo%P`CUW3$9MR_C)bxj^}%}E9YkwKzi5E+CtR1S=mN5jz9U take(1) |> mapToSignal { basicData -> Signal<(Peer?, DeviceContactExtendedData?), NoError> in var stableId: String? - let queryPhoneNumber = formatPhoneNumber(phoneNumber) + let queryPhoneNumber = formatPhoneNumber(context: context, number: phoneNumber) outer: for (id, data) in basicData { for phoneNumber in data.phoneNumbers { - if formatPhoneNumber(phoneNumber.value) == queryPhoneNumber { + if formatPhoneNumber(context: context, number: phoneNumber.value) == queryPhoneNumber { stableId = id break outer } @@ -13199,10 +13209,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G |> take(1) |> mapToSignal { basicData -> Signal<(Peer?, DeviceContactExtendedData?), NoError> in var stableId: String? - let queryPhoneNumber = formatPhoneNumber(phoneNumber) + let queryPhoneNumber = formatPhoneNumber(context: context, number: phoneNumber) outer: for (id, data) in basicData { for phoneNumber in data.phoneNumbers { - if formatPhoneNumber(phoneNumber.value) == queryPhoneNumber { + if formatPhoneNumber(context: context, number: phoneNumber.value) == queryPhoneNumber { stableId = id break outer } diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index e839dfc6d6..9be0656d2c 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -21,6 +21,9 @@ import ChatPresentationInterfaceState import ChatInputPanelContainer import PremiumUI import ChatTitleView +import ChatInputNode +import ChatEntityKeyboardInputNode +import ChatControllerInteraction final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem { let itemNode: OverlayMediaItemNode diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 05f8ab7822..a58f5d75a0 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -21,6 +21,7 @@ import ComponentFlow import ReactionSelectionNode import ChatPresentationInterfaceState import TelegramNotices +import ChatControllerInteraction extension ChatReplyThreadMessage { var effectiveTopId: MessageId { diff --git a/submodules/TelegramUI/Sources/ChatHistorySearchContainerNode.swift b/submodules/TelegramUI/Sources/ChatHistorySearchContainerNode.swift index 66fccca20c..f6797a8a97 100644 --- a/submodules/TelegramUI/Sources/ChatHistorySearchContainerNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistorySearchContainerNode.swift @@ -11,6 +11,7 @@ import AccountContext import SearchUI import TelegramUIPreferences import ListMessageItem +import ChatControllerInteraction private enum ChatHistorySearchEntryStableId: Hashable { case messageId(MessageId) diff --git a/submodules/TelegramUI/Sources/ChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/ChatInputContextPanelNode.swift index d669d813d4..2ad8826833 100644 --- a/submodules/TelegramUI/Sources/ChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatInputContextPanelNode.swift @@ -7,6 +7,7 @@ import TelegramPresentationData import TelegramUIPreferences import AccountContext import ChatPresentationInterfaceState +import ChatControllerInteraction enum ChatInputContextPanelPlacement { case overPanels diff --git a/submodules/TelegramUI/Sources/ChatInputNode.swift b/submodules/TelegramUI/Sources/ChatInputNode.swift deleted file mode 100644 index bc62c8c9a0..0000000000 --- a/submodules/TelegramUI/Sources/ChatInputNode.swift +++ /dev/null @@ -1,34 +0,0 @@ -import Foundation -import UIKit -import Display -import AsyncDisplayKit -import SwiftSignalKit -import ChatPresentationInterfaceState - -class ChatInputNode: ASDisplayNode { - var interfaceInteraction: ChatPanelInterfaceInteraction? - var ready: Signal { - return .single(Void()) - } - - var externalTopPanelContainer: UIView? { - return nil - } - - var topBackgroundExtension: CGFloat = 0.0 - var topBackgroundExtensionUpdated: ((ContainedViewLayoutTransition) -> Void)? - - var hideInput: Bool = false - var adjustLayoutForHiddenInput: Bool = false - var hideInputUpdated: ((ContainedViewLayoutTransition) -> Void)? - - var followsDefaultHeight: Bool = false - - func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) { - - } - - func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, deviceMetrics: DeviceMetrics, isVisible: Bool, isExpanded: Bool) -> (CGFloat, CGFloat) { - return (0.0, 0.0) - } -} diff --git a/submodules/TelegramUI/Sources/ChatInterfaceInputContextPanels.swift b/submodules/TelegramUI/Sources/ChatInterfaceInputContextPanels.swift index a41ea42c4f..65d1497013 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceInputContextPanels.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceInputContextPanels.swift @@ -3,6 +3,7 @@ import UIKit import TelegramCore import AccountContext import ChatPresentationInterfaceState +import ChatControllerInteraction private func inputQueryResultPriority(_ result: ChatPresentationInputQueryResult) -> (Int, Bool) { switch result { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift b/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift index 1da4d9b0ae..ec08704f02 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift @@ -5,6 +5,9 @@ import TelegramCore import Postbox import AccountContext import ChatPresentationInterfaceState +import ChatControllerInteraction +import ChatInputNode +import ChatEntityKeyboardInputNode func inputNodeForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentNode: ChatInputNode?, interfaceInteraction: ChatPanelInterfaceInteraction?, inputMediaNode: ChatMediaInputNode?, controllerInteraction: ChatControllerInteraction, inputPanelNode: ChatInputPanelNode?, makeMediaInputNode: () -> ChatInputNode?) -> ChatInputNode? { if let inputPanelNode = inputPanelNode, !(inputPanelNode is ChatTextInputPanelNode) { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift index 7e5e257aa2..24c2418e41 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift @@ -4,6 +4,7 @@ import AsyncDisplayKit import TelegramCore import AccountContext import ChatPresentationInterfaceState +import ChatControllerInteraction func accessoryPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: AccessoryPanelNode?, chatControllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?) -> AccessoryPanelNode? { if let _ = chatPresentationInterfaceState.interfaceState.selectionState { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index e44425b188..faafe2ecb8 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -32,6 +32,7 @@ import Pasteboard import SettingsUI import PremiumUI import TextNodeWithEntities +import ChatControllerInteraction private struct MessageContextMenuData { let starStatus: Bool? diff --git a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift index 6a66e43666..eb4c315302 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift @@ -3,6 +3,7 @@ import UIKit import TelegramCore import AccountContext import ChatPresentationInterfaceState +import ChatControllerInteraction func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatTitleAccessoryPanelNode?, controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?) -> ChatTitleAccessoryPanelNode? { if case .overlay = chatPresentationInterfaceState.mode { diff --git a/submodules/TelegramUI/Sources/ChatMediaInputGifPane.swift b/submodules/TelegramUI/Sources/ChatMediaInputGifPane.swift index f8d1b0a914..062b1196af 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputGifPane.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputGifPane.swift @@ -9,6 +9,10 @@ import TelegramPresentationData import ContextUI import AccountContext import ChatPresentationInterfaceState +import ChatControllerInteraction +import MultiplexedVideoNode +import FeaturedStickersScreen +import ChatEntityKeyboardInputNode private func fixListScrolling(_ multiplexedNode: MultiplexedVideoNode) { let searchBarHeight: CGFloat = 56.0 @@ -25,16 +29,6 @@ private func fixListScrolling(_ multiplexedNode: MultiplexedVideoNode) { } } -final class ChatMediaInputGifPaneTrendingState { - let files: [MultiplexedVideoNodeFile] - let nextOffset: String? - - init(files: [MultiplexedVideoNodeFile], nextOffset: String?) { - self.files = files - self.nextOffset = nextOffset - } -} - final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate { private let context: AccountContext private var theme: PresentationTheme diff --git a/submodules/TelegramUI/Sources/ChatMediaInputGridEntries.swift b/submodules/TelegramUI/Sources/ChatMediaInputGridEntries.swift index c0502f3a5c..195234e36b 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputGridEntries.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputGridEntries.swift @@ -6,6 +6,8 @@ import Display import TelegramPresentationData import MergeLists import ChatPresentationInterfaceState +import ChatControllerInteraction +import FeaturedStickersScreen enum ChatMediaInputGridEntryStableId: Equatable, Hashable { case search diff --git a/submodules/TelegramUI/Sources/ChatMediaInputMetaSectionItemNode.swift b/submodules/TelegramUI/Sources/ChatMediaInputMetaSectionItemNode.swift index 0c4ee80922..1af29e0298 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputMetaSectionItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputMetaSectionItemNode.swift @@ -8,6 +8,7 @@ import Postbox import TelegramPresentationData import AnimatedStickerNode import TelegramAnimatedStickerNode +import ChatPresentationInterfaceState enum ChatMediaInputMetaSectionItemType: Equatable { case savedStickers diff --git a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift index 9910024c28..b85b0db557 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift @@ -22,6 +22,12 @@ import ChatInterfaceState import ChatPresentationInterfaceState import UndoUI import PremiumUI +import ChatControllerInteraction +import FeaturedStickersScreen +import ChatInputNode +import FeaturedStickersScreen +import MultiplexedVideoNode +import ChatEntityKeyboardInputNode struct PeerSpecificPackData { let peer: Peer @@ -433,47 +439,6 @@ enum StickerPacksCollectionUpdate { case navigate(ItemCollectionViewEntryIndex?, ItemCollectionId?) } -enum ChatMediaInputGifMode: Equatable { - case recent - case trending - case emojiSearch(String) -} - -final class ChatMediaInputNodeInteraction { - let navigateToCollectionId: (ItemCollectionId) -> Void - let navigateBackToStickers: () -> Void - let setGifMode: (ChatMediaInputGifMode) -> Void - let openSettings: () -> Void - let openTrending: (ItemCollectionId?) -> Void - let dismissTrendingPacks: ([ItemCollectionId]) -> Void - let toggleSearch: (Bool, ChatMediaInputSearchMode?, String) -> Void - let openPeerSpecificSettings: () -> Void - let dismissPeerSpecificSettings: () -> Void - let clearRecentlyUsedStickers: () -> Void - - var stickerSettings: ChatInterfaceStickerSettings? - var highlightedStickerItemCollectionId: ItemCollectionId? - var highlightedItemCollectionId: ItemCollectionId? - var highlightedGifMode: ChatMediaInputGifMode = .recent - var previewedStickerPackItem: StickerPreviewPeekItem? - var appearanceTransition: CGFloat = 1.0 - var displayStickerPlaceholder = true - var displayStickerPackManageControls = true - - init(navigateToCollectionId: @escaping (ItemCollectionId) -> Void, navigateBackToStickers: @escaping () -> Void, setGifMode: @escaping (ChatMediaInputGifMode) -> Void, openSettings: @escaping () -> Void, openTrending: @escaping (ItemCollectionId?) -> Void, dismissTrendingPacks: @escaping ([ItemCollectionId]) -> Void, toggleSearch: @escaping (Bool, ChatMediaInputSearchMode?, String) -> Void, openPeerSpecificSettings: @escaping () -> Void, dismissPeerSpecificSettings: @escaping () -> Void, clearRecentlyUsedStickers: @escaping () -> Void) { - self.navigateToCollectionId = navigateToCollectionId - self.navigateBackToStickers = navigateBackToStickers - self.setGifMode = setGifMode - self.openSettings = openSettings - self.openTrending = openTrending - self.dismissTrendingPacks = dismissTrendingPacks - self.toggleSearch = toggleSearch - self.openPeerSpecificSettings = openPeerSpecificSettings - self.dismissPeerSpecificSettings = dismissPeerSpecificSettings - self.clearRecentlyUsedStickers = clearRecentlyUsedStickers - } -} - func clipScrollPosition(_ position: StickerPacksCollectionPosition) -> StickerPacksCollectionPosition { switch position { case let .scroll(index): diff --git a/submodules/TelegramUI/Sources/ChatMediaInputPane.swift b/submodules/TelegramUI/Sources/ChatMediaInputPane.swift index 012627463c..183d06f694 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputPane.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputPane.swift @@ -3,6 +3,7 @@ import UIKit import AsyncDisplayKit import Display import TelegramPresentationData +import ChatPresentationInterfaceState struct ChatMediaInputPaneScrollState { let absoluteOffset: CGFloat? diff --git a/submodules/TelegramUI/Sources/ChatMediaInputPanelEntries.swift b/submodules/TelegramUI/Sources/ChatMediaInputPanelEntries.swift index 307b7020ca..56a8ba81de 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputPanelEntries.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputPanelEntries.swift @@ -6,6 +6,7 @@ import Display import TelegramPresentationData import MergeLists import AccountContext +import ChatPresentationInterfaceState enum ChatMediaInputPanelAuxiliaryNamespace: Int32 { case savedStickers = 2 diff --git a/submodules/TelegramUI/Sources/ChatMediaInputPeerSpecificItem.swift b/submodules/TelegramUI/Sources/ChatMediaInputPeerSpecificItem.swift index b2a7894921..1177f8871a 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputPeerSpecificItem.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputPeerSpecificItem.swift @@ -8,6 +8,7 @@ import Postbox import TelegramPresentationData import AvatarNode import AccountContext +import ChatPresentationInterfaceState final class ChatMediaInputPeerSpecificItem: ListViewItem { let context: AccountContext diff --git a/submodules/TelegramUI/Sources/ChatMediaInputRecentGifsItem.swift b/submodules/TelegramUI/Sources/ChatMediaInputRecentGifsItem.swift index c2d3ac7817..0e1ececabd 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputRecentGifsItem.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputRecentGifsItem.swift @@ -6,6 +6,7 @@ import TelegramCore import SwiftSignalKit import Postbox import TelegramPresentationData +import ChatPresentationInterfaceState final class ChatMediaInputRecentGifsItem: ListViewItem { let inputNodeInteraction: ChatMediaInputNodeInteraction diff --git a/submodules/TelegramUI/Sources/ChatMediaInputSettingsItem.swift b/submodules/TelegramUI/Sources/ChatMediaInputSettingsItem.swift index b374d760d8..f8f1ecb181 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputSettingsItem.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputSettingsItem.swift @@ -6,6 +6,7 @@ import TelegramCore import SwiftSignalKit import Postbox import TelegramPresentationData +import ChatPresentationInterfaceState final class ChatMediaInputSettingsItem: ListViewItem { let inputNodeInteraction: ChatMediaInputNodeInteraction diff --git a/submodules/TelegramUI/Sources/ChatMediaInputStickerGridItem.swift b/submodules/TelegramUI/Sources/ChatMediaInputStickerGridItem.swift index d55f0eeebb..c4af1c4ec5 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputStickerGridItem.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputStickerGridItem.swift @@ -11,6 +11,8 @@ import AccountContext import AnimatedStickerNode import TelegramAnimatedStickerNode import ShimmerEffect +import ChatControllerInteraction +import ChatPresentationInterfaceState enum ChatMediaInputStickerGridSectionAccessory { case none diff --git a/submodules/TelegramUI/Sources/ChatMediaInputStickerPackItem.swift b/submodules/TelegramUI/Sources/ChatMediaInputStickerPackItem.swift index cd351e0c90..2c904a2d45 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputStickerPackItem.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputStickerPackItem.swift @@ -11,6 +11,7 @@ import ItemListStickerPackItem import AnimatedStickerNode import TelegramAnimatedStickerNode import ShimmerEffect +import ChatPresentationInterfaceState final class ChatMediaInputStickerPackItem: ListViewItem { let account: Account diff --git a/submodules/TelegramUI/Sources/ChatMediaInputStickerPane.swift b/submodules/TelegramUI/Sources/ChatMediaInputStickerPane.swift index 024ede6514..8c10f5497e 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputStickerPane.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputStickerPane.swift @@ -6,6 +6,8 @@ import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData +import ChatInputNode +import FeaturedStickersScreen private func fixGridScrolling(_ gridNode: GridNode) { var searchItemNode: GridItemNode? diff --git a/submodules/TelegramUI/Sources/ChatMediaInputTrendingItem.swift b/submodules/TelegramUI/Sources/ChatMediaInputTrendingItem.swift index 5a440d6254..03824271eb 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputTrendingItem.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputTrendingItem.swift @@ -6,6 +6,7 @@ import TelegramCore import SwiftSignalKit import Postbox import TelegramPresentationData +import ChatPresentationInterfaceState final class ChatMediaInputTrendingItem: ListViewItem { let inputNodeInteraction: ChatMediaInputNodeInteraction diff --git a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift index 3350e302f1..235c9ab0f4 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -28,6 +28,7 @@ import AppBundle import LottieMeshSwift import ChatPresentationInterfaceState import TextNodeWithEntities +import ChatControllerInteraction private let nameFont = Font.medium(14.0) private let inlineBotPrefixFont = Font.regular(14.0) @@ -699,7 +700,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { if let overlayMeshAnimationNode = self.overlayMeshAnimationNode { self.overlayMeshAnimationNode = nil - if let transitionNode = item.controllerInteraction.getMessageTransitionNode() { + if let transitionNode = item.controllerInteraction.getMessageTransitionNode() as? ChatMessageTransitionNode { transitionNode.remove(decorationNode: overlayMeshAnimationNode) } } @@ -1943,7 +1944,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { guard let item = self.item else { return } - guard let transitionNode = item.controllerInteraction.getMessageTransitionNode() else { + guard let transitionNode = item.controllerInteraction.getMessageTransitionNode() as? ChatMessageTransitionNode else { return } diff --git a/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift index f3fb5f65a8..f611be8edb 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift @@ -18,6 +18,7 @@ import GalleryData import TextNodeWithEntities import AnimationCache import MultiAnimationRenderer +import ChatControllerInteraction private let buttonFont = Font.semibold(13.0) diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift index a470c66ff6..9b8df19d73 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift @@ -8,6 +8,7 @@ import TelegramUIPreferences import TelegramPresentationData import AccountContext import ChatMessageBackground +import ChatControllerInteraction enum ChatMessageBubbleContentBackgroundHiding { case never diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index 95deeb7290..911d9172f8 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -28,6 +28,7 @@ import AnimationCache import MultiAnimationRenderer import ComponentFlow import EmojiStatusComponent +import ChatControllerInteraction enum InternalBubbleTapAction { case action(() -> Void) @@ -306,19 +307,6 @@ private enum ContentNodeOperation { case insert(index: Int, node: ChatMessageBubbleContentNode) } -class ChatPresentationContext { - weak var backgroundNode: WallpaperBackgroundNode? - let animationCache: AnimationCache - let animationRenderer: MultiAnimationRenderer - - init(context: AccountContext, backgroundNode: WallpaperBackgroundNode?) { - self.backgroundNode = backgroundNode - - self.animationCache = context.animationCache - self.animationRenderer = context.animationRenderer - } -} - private func mapVisibility(_ visibility: ListViewItemNodeVisibility, boundsSize: CGSize, insets: UIEdgeInsets, to contentNode: ChatMessageBubbleContentNode) -> ListViewItemNodeVisibility { switch visibility { case .none: diff --git a/submodules/TelegramUI/Sources/ChatMessageDateHeader.swift b/submodules/TelegramUI/Sources/ChatMessageDateHeader.swift index 17c792ff9c..6a6e2bbfbd 100644 --- a/submodules/TelegramUI/Sources/ChatMessageDateHeader.swift +++ b/submodules/TelegramUI/Sources/ChatMessageDateHeader.swift @@ -13,6 +13,7 @@ import UniversalMediaPlayer import GalleryUI import HierarchyTrackingLayer import WallpaperBackgroundNode +import ChatControllerInteraction private let timezoneOffset: Int32 = { let nowTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) diff --git a/submodules/TelegramUI/Sources/ChatMessageGiftItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageGiftItemNode.swift index 3e59b91bdb..f3367076ce 100644 --- a/submodules/TelegramUI/Sources/ChatMessageGiftItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageGiftItemNode.swift @@ -16,6 +16,7 @@ import WallpaperBackgroundNode import ReactionSelectionNode import AnimatedStickerNode import TelegramAnimatedStickerNode +import ChatControllerInteraction private func attributedServiceMessageString(theme: ChatPresentationThemeData, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: Message, accountPeerId: PeerId) -> NSAttributedString? { return universalServiceMessageString(presentationData: (theme.theme, theme.wallpaper), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: EngineMessage(message), accountPeerId: accountPeerId, forChatList: false, forForumOverview: false) diff --git a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift index 5b41b6856b..e46afc445e 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift @@ -12,6 +12,7 @@ import AccountContext import LocalizedPeerData import ContextUI import Markdown +import ChatControllerInteraction private let nameFont = Font.medium(14.0) diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift index 55078a1eb3..1db709ccfb 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift @@ -28,6 +28,7 @@ import TextSelectionNode import AudioTranscriptionPendingIndicatorComponent import UndoUI import TelegramNotices +import ChatControllerInteraction private struct FetchControls { let fetch: (Bool) -> Void diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift index 80b73fc822..fa1ee99b87 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift @@ -23,6 +23,7 @@ import WallpaperResources import ChatMessageInteractiveMediaBadge import ContextUI import InvisibleInkDustNode +import ChatControllerInteraction private struct FetchControls { let fetch: (Bool) -> Void diff --git a/submodules/TelegramUI/Sources/ChatMessageItem.swift b/submodules/TelegramUI/Sources/ChatMessageItem.swift index 04c8afb817..a878a6dd5e 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItem.swift +++ b/submodules/TelegramUI/Sources/ChatMessageItem.swift @@ -10,6 +10,7 @@ import TelegramUIPreferences import AccountContext import Emoji import PersistentStringHash +import ChatControllerInteraction public enum ChatMessageItemContent: Sequence { case message(message: Message, read: Bool, selection: ChatHistoryMessageSelection, attributes: ChatMessageEntryAttributes, location: MessageHistoryEntryLocation?) diff --git a/submodules/TelegramUI/Sources/ChatMessageItemView.swift b/submodules/TelegramUI/Sources/ChatMessageItemView.swift index 99fc9c78a8..f1847dd852 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItemView.swift +++ b/submodules/TelegramUI/Sources/ChatMessageItemView.swift @@ -10,6 +10,7 @@ import ContextUI import ChatListUI import TelegramPresentationData import SwiftSignalKit +import ChatControllerInteraction struct ChatMessageItemWidthFill { var compactInset: CGFloat diff --git a/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift index b6860844a4..3e6a921804 100644 --- a/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift @@ -9,6 +9,7 @@ import TelegramUIPreferences import TelegramPresentationData import AccountContext import GridMessageSelectionNode +import ChatControllerInteraction class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { override var supportsMosaic: Bool { diff --git a/submodules/TelegramUI/Sources/ChatMessageReactionsFooterContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageReactionsFooterContentNode.swift index 84ba80dde4..d6b74442ad 100644 --- a/submodules/TelegramUI/Sources/ChatMessageReactionsFooterContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageReactionsFooterContentNode.swift @@ -12,6 +12,7 @@ import AnimatedAvatarSetNode import ReactionButtonListComponent import AccountContext import WallpaperBackgroundNode +import ChatControllerInteraction func canViewMessageReactionList(message: Message) -> Bool { var found = false diff --git a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift index da5210afb2..9ffcd8ab58 100644 --- a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift @@ -13,6 +13,7 @@ import ContextUI import Markdown import ShimmerEffect import WallpaperBackgroundNode +import ChatControllerInteraction private let nameFont = Font.medium(14.0) private let inlineBotPrefixFont = Font.regular(14.0) diff --git a/submodules/TelegramUI/Sources/ChatMessageSwipeToReplyNode.swift b/submodules/TelegramUI/Sources/ChatMessageSwipeToReplyNode.swift index 6ac4bedecb..97aac1c46e 100644 --- a/submodules/TelegramUI/Sources/ChatMessageSwipeToReplyNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageSwipeToReplyNode.swift @@ -4,6 +4,7 @@ import Display import AsyncDisplayKit import AppBundle import WallpaperBackgroundNode +import ChatControllerInteraction private let size = CGSize(width: 33.0, height: 33.0) diff --git a/submodules/TelegramUI/Sources/ChatMessageThreadInfoNode.swift b/submodules/TelegramUI/Sources/ChatMessageThreadInfoNode.swift index 3fe396bfb1..e761792d15 100644 --- a/submodules/TelegramUI/Sources/ChatMessageThreadInfoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageThreadInfoNode.swift @@ -18,6 +18,7 @@ import MultiAnimationRenderer import ComponentFlow import EmojiStatusComponent import WallpaperBackgroundNode +import ChatControllerInteraction private func generateRectsImage(color: UIColor, rects: [CGRect], inset: CGFloat, outerRadius: CGFloat, innerRadius: CGFloat) -> (CGPoint, UIImage?) { enum CornerType { diff --git a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift index 8476cc2a3d..25f4e00e88 100644 --- a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift @@ -9,6 +9,8 @@ import ContextUI import Postbox import TelegramCore import ReactionSelectionNode +import ChatControllerInteraction +import FeaturedStickersScreen private func convertAnimatingSourceRect(_ rect: CGRect, fromView: UIView, toView: UIView?) -> CGRect { if let presentationLayer = fromView.layer.presentation() { @@ -93,7 +95,7 @@ private final class OverlayTransitionContainerController: ViewController, Standa } } -public final class ChatMessageTransitionNode: ASDisplayNode { +public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransitionProtocol { static let animationDuration: Double = 0.3 static let verticalAnimationControlPoints: (Float, Float, Float, Float) = (0.19919472913616398, 0.010644531250000006, 0.27920937042459737, 0.91025390625) diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift index 04261503ed..1b04d3319c 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift @@ -26,6 +26,8 @@ import WallpaperBackgroundNode import BotPaymentsUI import ContextUI import Pasteboard +import ChatControllerInteraction +import ChatPresentationInterfaceState private final class ChatRecentActionsListOpaqueState { let entries: [ChatRecentActionsEntry] diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift b/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift index b05558eedd..f503332409 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift @@ -6,6 +6,7 @@ import Postbox import TelegramPresentationData import MergeLists import AccountContext +import ChatControllerInteraction enum ChatRecentActionsEntryContentIndex: Int32 { case header = 0 diff --git a/submodules/TelegramUI/Sources/ChatReplyCountItem.swift b/submodules/TelegramUI/Sources/ChatReplyCountItem.swift index 2bf23adc86..ec4627f166 100644 --- a/submodules/TelegramUI/Sources/ChatReplyCountItem.swift +++ b/submodules/TelegramUI/Sources/ChatReplyCountItem.swift @@ -7,6 +7,7 @@ import SwiftSignalKit import TelegramPresentationData import AccountContext import WallpaperBackgroundNode +import ChatControllerInteraction private let titleFont = UIFont.systemFont(ofSize: 13.0) diff --git a/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift b/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift index e97bcfff6a..c8de534d20 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift @@ -7,6 +7,7 @@ import TelegramPresentationData import ContextUI import ChatPresentationInterfaceState import ChatMessageBackground +import ChatControllerInteraction final class ChatTextInputActionButtonsNode: ASDisplayNode { private let presentationContext: ChatPresentationContext? diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index 874a7f1293..f7f9f87129 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -29,6 +29,7 @@ import LottieAnimationComponent import ComponentFlow import EmojiSuggestionsComponent import AudioToolbox +import ChatControllerInteraction private let accessoryButtonFont = Font.medium(14.0) private let counterFont = Font.with(size: 14.0, design: .regular, traits: [.monospacedNumbers]) @@ -458,72 +459,6 @@ final class ChatTextViewForOverlayContent: UIView, ChatInputPanelViewForOverlayC } } -final class CustomEmojiContainerView: UIView { - private let emojiViewProvider: (ChatTextInputTextCustomEmojiAttribute) -> UIView? - - private var emojiLayers: [InlineStickerItemLayer.Key: UIView] = [:] - - init(emojiViewProvider: @escaping (ChatTextInputTextCustomEmojiAttribute) -> UIView?) { - self.emojiViewProvider = emojiViewProvider - - super.init(frame: CGRect()) - } - - required init(coder: NSCoder) { - preconditionFailure() - } - - func update(fontSize: CGFloat, textColor: UIColor, emojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute)]) { - var nextIndexById: [Int64: Int] = [:] - - var validKeys = Set() - for (rect, emoji) in emojiRects { - let index: Int - if let nextIndex = nextIndexById[emoji.fileId] { - index = nextIndex - } else { - index = 0 - } - nextIndexById[emoji.fileId] = index + 1 - - let key = InlineStickerItemLayer.Key(id: emoji.fileId, index: index) - - let view: UIView - if let current = self.emojiLayers[key] { - view = current - } else if let newView = self.emojiViewProvider(emoji) { - view = newView - self.addSubview(newView) - self.emojiLayers[key] = view - } else { - continue - } - - if let view = view as? EmojiTextAttachmentView { - view.updateTextColor(textColor) - } - - let itemSize: CGFloat = floor(24.0 * fontSize / 17.0) - let size = CGSize(width: itemSize, height: itemSize) - - view.frame = CGRect(origin: CGPoint(x: floor(rect.midX - size.width / 2.0), y: floor(rect.midY - size.height / 2.0) + 1.0), size: size) - - validKeys.insert(key) - } - - var removeKeys: [InlineStickerItemLayer.Key] = [] - for (key, view) in self.emojiLayers { - if !validKeys.contains(key) { - removeKeys.append(key) - view.removeFromSuperview() - } - } - for key in removeKeys { - self.emojiLayers.removeValue(forKey: key) - } - } -} - class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { let clippingNode: ASDisplayNode var textPlaceholderNode: ImmediateTextNode diff --git a/submodules/TelegramUI/Sources/ChatUnreadItem.swift b/submodules/TelegramUI/Sources/ChatUnreadItem.swift index 84378321ba..64b0a52b1b 100644 --- a/submodules/TelegramUI/Sources/ChatUnreadItem.swift +++ b/submodules/TelegramUI/Sources/ChatUnreadItem.swift @@ -7,6 +7,7 @@ import SwiftSignalKit import TelegramPresentationData import AccountContext import WallpaperBackgroundNode +import ChatControllerInteraction private let titleFont = UIFont.systemFont(ofSize: 13.0) diff --git a/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift index 8b9f2f0345..8cb47032d5 100644 --- a/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift @@ -9,6 +9,7 @@ import TelegramUIPreferences import MergeLists import AccountContext import ChatPresentationInterfaceState +import ChatControllerInteraction private struct CommandChatInputContextPanelEntryStableId: Hashable { let command: PeerCommand diff --git a/submodules/TelegramUI/Sources/CommandMenuChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/CommandMenuChatInputContextPanelNode.swift index de6d79d3bc..250c514c00 100644 --- a/submodules/TelegramUI/Sources/CommandMenuChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/CommandMenuChatInputContextPanelNode.swift @@ -10,6 +10,7 @@ import TelegramUIPreferences import MergeLists import AccountContext import ChatPresentationInterfaceState +import ChatControllerInteraction private struct CommandMenuChatInputContextPanelEntryStableId: Hashable { let command: PeerCommand diff --git a/submodules/TelegramUI/Sources/DisabledContextResultsChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/DisabledContextResultsChatInputContextPanelNode.swift index 8a558045eb..ff4f17d0b7 100644 --- a/submodules/TelegramUI/Sources/DisabledContextResultsChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/DisabledContextResultsChatInputContextPanelNode.swift @@ -8,6 +8,7 @@ import TelegramStringFormatting import TelegramUIPreferences import AccountContext import ChatPresentationInterfaceState +import ChatControllerInteraction final class DisabledContextResultsChatInputContextPanelNode: ChatInputContextPanelNode { private let containerNode: ASDisplayNode diff --git a/submodules/TelegramUI/Sources/DrawingStickersScreen.swift b/submodules/TelegramUI/Sources/DrawingStickersScreen.swift index dc40c81abe..ae51c091e5 100644 --- a/submodules/TelegramUI/Sources/DrawingStickersScreen.swift +++ b/submodules/TelegramUI/Sources/DrawingStickersScreen.swift @@ -17,6 +17,9 @@ import UndoUI import SegmentedControlNode import LegacyComponents import ChatPresentationInterfaceState +import FeaturedStickersScreen +import ChatControllerInteraction +import ChatEntityKeyboardInputNode private enum DrawingPaneType { case stickers diff --git a/submodules/TelegramUI/Sources/EmojisChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/EmojisChatInputContextPanelNode.swift index d8039260c7..5793ab3096 100644 --- a/submodules/TelegramUI/Sources/EmojisChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/EmojisChatInputContextPanelNode.swift @@ -13,6 +13,7 @@ import ChatPresentationInterfaceState import AnimationCache import MultiAnimationRenderer import TextFormat +import ChatControllerInteraction private enum EmojisChatInputContextPanelEntryStableId: Hashable, Equatable { case symbol(String) diff --git a/submodules/TelegramUI/Sources/GridMessageItem.swift b/submodules/TelegramUI/Sources/GridMessageItem.swift index b5aa53490b..562af25d21 100644 --- a/submodules/TelegramUI/Sources/GridMessageItem.swift +++ b/submodules/TelegramUI/Sources/GridMessageItem.swift @@ -14,6 +14,7 @@ import PhotoResources import GridMessageSelectionNode import ContextUI import ChatMessageInteractiveMediaBadge +import ChatControllerInteraction private func mediaForMessage(_ message: Message) -> Media? { for media in message.media { diff --git a/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift index da5d7bc8a0..77912f98fe 100644 --- a/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift @@ -11,6 +11,7 @@ import AccountContext import AccountContext import ItemListUI import ChatPresentationInterfaceState +import ChatControllerInteraction private struct HashtagChatInputContextPanelEntryStableId: Hashable { let text: String diff --git a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift index e716cad5c0..0348c7b5fa 100644 --- a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift @@ -15,6 +15,7 @@ import ContextUI import ChatPresentationInterfaceState import UndoUI import PremiumUI +import ChatControllerInteraction private struct ChatContextResultStableId: Hashable { let result: ChatContextResult diff --git a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift index e6379e9325..a78301e57c 100644 --- a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift +++ b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift @@ -15,6 +15,7 @@ import TelegramPresentationData import AccountContext import ShimmerEffect import SoftwareVideo +import MultiplexedVideoNode final class HorizontalListContextResultsChatInputPanelItem: ListViewItem { let account: Account diff --git a/submodules/TelegramUI/Sources/HorizontalStickersChatContextPanelNode.swift b/submodules/TelegramUI/Sources/HorizontalStickersChatContextPanelNode.swift index 828551c40f..952a9b0ea2 100755 --- a/submodules/TelegramUI/Sources/HorizontalStickersChatContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/HorizontalStickersChatContextPanelNode.swift @@ -15,6 +15,7 @@ import ContextUI import ChatPresentationInterfaceState import PremiumUI import UndoUI +import ChatControllerInteraction final class HorizontalStickersChatContextPanelInteraction { var previewedStickerItem: TelegramMediaFile? diff --git a/submodules/TelegramUI/Sources/InlineReactionSearchPanel.swift b/submodules/TelegramUI/Sources/InlineReactionSearchPanel.swift index 193604a501..e999aaea28 100644 --- a/submodules/TelegramUI/Sources/InlineReactionSearchPanel.swift +++ b/submodules/TelegramUI/Sources/InlineReactionSearchPanel.swift @@ -14,6 +14,7 @@ import ContextUI import ChatPresentationInterfaceState import PremiumUI import UndoUI +import ChatControllerInteraction private final class InlineReactionSearchStickersNode: ASDisplayNode, UIScrollViewDelegate { private final class DisplayItem { diff --git a/submodules/TelegramUI/Sources/MentionChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/MentionChatInputContextPanelNode.swift index 2c78873718..ed144a72d0 100644 --- a/submodules/TelegramUI/Sources/MentionChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/MentionChatInputContextPanelNode.swift @@ -11,6 +11,7 @@ import AccountContext import LocalizedPeerData import ItemListUI import ChatPresentationInterfaceState +import ChatControllerInteraction private struct MentionChatInputContextPanelEntry: Comparable, Identifiable { let index: Int diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index 2f4c04b354..dc5728da27 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -10,6 +10,7 @@ import TelegramUIPreferences import AccountContext import DirectionalPanGesture import ChatPresentationInterfaceState +import ChatControllerInteraction final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestureRecognizerDelegate { let ready = Promise() diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoGroupsInCommonPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoGroupsInCommonPaneNode.swift index f3c856d0e8..98586b44c1 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoGroupsInCommonPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoGroupsInCommonPaneNode.swift @@ -11,6 +11,7 @@ import TelegramUIPreferences import ItemListPeerItem import MergeLists import ItemListUI +import ChatControllerInteraction private struct GroupsInCommonListTransaction { let deletions: [ListViewDeleteItem] diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift index 5b9fb9fef7..a44175621a 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift @@ -15,6 +15,7 @@ import OverlayStatusController import ListMessageItem import UndoUI import ChatPresentationInterfaceState +import ChatControllerInteraction final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { private let context: AccountContext diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift index 100d99c963..81e0a62198 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift @@ -23,6 +23,7 @@ import TelegramNotices import TelegramUIPreferences import CheckNode import AppBundle +import ChatControllerInteraction private final class FrameSequenceThumbnailNode: ASDisplayNode { private let context: AccountContext diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index 30d719f700..441ba65869 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -3433,6 +3433,11 @@ final class PeerInfoHeaderNode: ASDisplayNode { return nil } + let setByFrame = self.avatarListNode.listContainerNode.setByYouNode.view.convert(self.avatarListNode.listContainerNode.setByYouNode.bounds, to: self.view).insetBy(dx: -44.0, dy: 0.0) + if self.avatarListNode.listContainerNode.setByYouNode.alpha > 0.0, setByFrame.contains(point) { + return self.avatarListNode.listContainerNode.setByYouNode.view + } + if !(self.state?.isEditing ?? false) { switch self.currentCredibilityIcon { case .premium, .emojiStatus: diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoPaneContainerNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoPaneContainerNode.swift index ed2435ca9a..b1d9e55017 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoPaneContainerNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoPaneContainerNode.swift @@ -8,6 +8,7 @@ import Postbox import TelegramCore import AccountContext import ContextUI +import ChatControllerInteraction protocol PeerInfoPaneNode: ASDisplayNode { var isReady: Signal { get } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 13c4625a37..9cb14b5b54 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -83,6 +83,15 @@ import ChatTimerScreen import NotificationPeerExceptionController import StickerPackPreviewUI import ChatListHeaderComponent +import ChatControllerInteraction + +enum PeerInfoAvatarEditingMode { + case generic + case accept + case suggest + case custom + case fallback +} protocol PeerInfoScreenItem: AnyObject { var id: AnyHashable { get } @@ -6740,7 +6749,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } } - fileprivate func updateProfilePhoto(_ image: UIImage, mode: AvatarEditingMode) { + fileprivate func updateProfilePhoto(_ image: UIImage, mode: PeerInfoAvatarEditingMode) { guard let data = image.jpegData(compressionQuality: 0.6) else { return } @@ -6791,6 +6800,18 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations) }) } + + var dismissStatus: (() -> Void)? + if [.suggest, .fallback].contains(mode) { + let statusController = OverlayStatusController(theme: self.presentationData.theme, type: .loading(cancelled: { [weak self] in + self?.updateAvatarDisposable.set(nil) + dismissStatus?() + })) + dismissStatus = { [weak statusController] in + statusController?.dismiss() + } + self.controller?.presentInGlobalOverlay(statusController) + } self.updateAvatarDisposable.set((signal |> deliverOnMainQueue).start(next: { [weak self] result in @@ -6807,18 +6828,34 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) } - if case .complete = result, case .custom = mode { + if case .complete = result { + dismissStatus?() + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.peerId)) |> deliverOnMainQueue).start(next: { [weak self] peer in if let strongSelf = self, let peer { - strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, text: strongSelf.presentationData.strings.UserInfo_SetCustomPhoto_SuccessPhotoText(peer.compactDisplayTitle).string, action: nil, duration: 5), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + switch mode { + case .fallback: + (strongSelf.controller?.parentController?.topViewController as? ViewController)?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .image(image: image, title: nil, text: strongSelf.presentationData.strings.Privacy_ProfilePhoto_PublicPhotoSuccess, round: true, undo: false), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + case .custom: + strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, text: strongSelf.presentationData.strings.UserInfo_SetCustomPhoto_SuccessPhotoText(peer.compactDisplayTitle).string, action: nil, duration: 5), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + case .suggest: + if let navigationController = (strongSelf.controller?.navigationController as? NavigationController) { + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), keepStack: .default, completion: { _ in + })) + } + case .accept: + (strongSelf.controller?.parentController?.topViewController as? ViewController)?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .image(image: image, title: nil, text: strongSelf.presentationData.strings.Conversation_SuggestedPhotoSuccess, round: true, undo: false), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + default: + break + } } }) } })) } - fileprivate func updateProfileVideo(_ image: UIImage, asset: Any?, adjustments: TGVideoEditAdjustments?, mode: AvatarEditingMode) { + fileprivate func updateProfileVideo(_ image: UIImage, asset: Any?, adjustments: TGVideoEditAdjustments?, mode: PeerInfoAvatarEditingMode) { guard let data = image.jpegData(compressionQuality: 0.6) else { return } @@ -6930,6 +6967,18 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } } + var dismissStatus: (() -> Void)? + if [.suggest, .fallback].contains(mode) { + let statusController = OverlayStatusController(theme: self.presentationData.theme, type: .loading(cancelled: { [weak self] in + self?.updateAvatarDisposable.set(nil) + dismissStatus?() + })) + dismissStatus = { [weak statusController] in + statusController?.dismiss() + } + self.controller?.presentInGlobalOverlay(statusController) + } + let peerId = self.peerId let isSettings = self.isSettings self.updateAvatarDisposable.set((signal @@ -6972,25 +7021,34 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) } - if case .complete = result, case .custom = mode { + if case .complete = result { + dismissStatus?() + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.peerId)) |> deliverOnMainQueue).start(next: { [weak self] peer in if let strongSelf = self, let peer { - strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, text: strongSelf.presentationData.strings.UserInfo_SetCustomPhoto_SuccessVideoText(peer.compactDisplayTitle).string, action: nil, duration: 5), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + switch mode { + case .fallback: + (strongSelf.controller?.parentController?.topViewController as? ViewController)?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .image(image: image, title: nil, text: strongSelf.presentationData.strings.Privacy_ProfilePhoto_PublicVideoSuccess, round: true, undo: false), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + case .custom: + strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, text: strongSelf.presentationData.strings.UserInfo_SetCustomPhoto_SuccessVideoText(peer.compactDisplayTitle).string, action: nil, duration: 5), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + case .suggest: + if let navigationController = (strongSelf.controller?.navigationController as? NavigationController) { + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), keepStack: .default, completion: { _ in + })) + } + case .accept: + (strongSelf.controller?.parentController?.topViewController as? ViewController)?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .image(image: image, title: nil, text: strongSelf.presentationData.strings.Conversation_SuggestedVideoSuccess, round: true, undo: false), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + default: + break + } } }) } })) } - - fileprivate enum AvatarEditingMode { - case generic - case suggest - case custom - case fallback - } - - fileprivate func openAvatarForEditing(mode: AvatarEditingMode = .generic, fromGallery: Bool = false, completion: @escaping () -> Void = {}) { + + fileprivate func openAvatarForEditing(mode: PeerInfoAvatarEditingMode = .generic, fromGallery: Bool = false, completion: @escaping () -> Void = {}) { guard let peer = self.data?.peer, mode != .generic || canEditPeerInfo(context: self.context, peer: peer, chatLocation: self.chatLocation, threadData: self.data?.threadData) else { return } @@ -7156,7 +7214,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate }) } - fileprivate func openAvatarRemoval(mode: AvatarEditingMode, peer: EnginePeer? = nil, item: PeerInfoAvatarListItem? = nil) { + fileprivate func openAvatarRemoval(mode: PeerInfoAvatarEditingMode, peer: EnginePeer? = nil, item: PeerInfoAvatarListItem? = nil) { let proceed = { [weak self] in guard let strongSelf = self else { return @@ -8962,6 +9020,8 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc private let chatLocation: ChatLocation private let chatLocationContextHolder = Atomic(value: nil) + weak var parentController: TelegramRootController? + fileprivate var presentationData: PresentationData private var presentationDataDisposable: Disposable? private let cachedDataPromise = Promise() @@ -9374,18 +9434,18 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc } } - func updateProfilePhoto(_ image: UIImage, fallback: Bool = false) { + func updateProfilePhoto(_ image: UIImage, mode: PeerInfoAvatarEditingMode) { if !self.isNodeLoaded { self.loadDisplayNode() } - self.controllerNode.updateProfilePhoto(image, mode: fallback ? .fallback : .generic) + self.controllerNode.updateProfilePhoto(image, mode: mode) } - func updateProfileVideo(_ image: UIImage, asset: Any?, adjustments: TGVideoEditAdjustments?, fallback: Bool = false) { + func updateProfileVideo(_ image: UIImage, mode: PeerInfoAvatarEditingMode, asset: Any?, adjustments: TGVideoEditAdjustments?, fallback: Bool = false) { if !self.isNodeLoaded { self.loadDisplayNode() } - self.controllerNode.updateProfileVideo(image, asset: asset, adjustments: adjustments, mode: fallback ? .fallback : .generic) + self.controllerNode.updateProfileVideo(image, asset: asset, adjustments: adjustments, mode: mode) } static func displayChatNavigationMenu(context: AccountContext, chatNavigationStack: [ChatNavigationStackItem], nextFolderId: Int32?, parentController: ViewController, backButtonView: UIView, navigationController: NavigationController, gesture: ContextGesture) { diff --git a/submodules/TelegramUI/Sources/PeerInfoGifPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfoGifPaneNode.swift index 098d790c02..fce20a99a6 100644 --- a/submodules/TelegramUI/Sources/PeerInfoGifPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfoGifPaneNode.swift @@ -14,6 +14,7 @@ import UniversalMediaPlayer import ListMessageItem import ChatMessageInteractiveMediaBadge import SoftwareVideo +import ChatControllerInteraction private final class FrameSequenceThumbnailNode: ASDisplayNode { private let context: AccountContext diff --git a/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift b/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift index 7e6659bace..d516efebf7 100644 --- a/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift +++ b/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift @@ -5,6 +5,7 @@ import TelegramCore import Display import MergeLists import AccountContext +import ChatControllerInteraction func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toView: ChatHistoryView, reason: ChatHistoryViewTransitionReason, reverse: Bool, chatLocation: ChatLocation, controllerInteraction: ChatControllerInteraction, scrollPosition: ChatHistoryViewScrollPosition?, scrollAnimationCurve: ListViewAnimationCurve?, initialData: InitialMessageHistoryData?, keyboardButtonsMessage: Message?, cachedData: CachedPeerData?, cachedDataMessages: [MessageId: Message]?, readStateData: [PeerId: ChatHistoryCombinedInitialReadStateData]?, flashIndicators: Bool, updatedMessageSelection: Bool, messageTransitionNode: ChatMessageTransitionNode?, allUpdated: Bool) -> ChatHistoryViewTransition { var mergeResult: (deleteIndices: [Int], indicesAndItems: [(Int, ChatHistoryEntry, Int?)], updateIndices: [(Int, ChatHistoryEntry, Int)]) diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 89bec87cd2..802a86a466 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -27,6 +27,8 @@ import WallpaperBackgroundNode import InAppPurchaseManager import PremiumUI import StickerPackPreviewUI +import ChatControllerInteraction +import ChatPresentationInterfaceState private final class AccountUserInterfaceInUseContext { let subscribers = Bag<(Bool) -> Void>() @@ -1509,6 +1511,10 @@ public final class SharedAccountContextImpl: SharedAccountContext { public func makeProxySettingsController(sharedContext: SharedAccountContext, account: UnauthorizedAccount) -> ViewController { return proxySettingsController(accountManager: sharedContext.accountManager, postbox: account.postbox, network: account.network, mode: .modal, presentationData: sharedContext.currentPresentationData.with { $0 }, updatedPresentationData: sharedContext.presentationData) } + + public func makeInstalledStickerPacksController(context: AccountContext, mode: InstalledStickerPacksControllerMode) -> ViewController { + return installedStickerPacksController(context: context, mode: mode) + } } private func peerInfoControllerImpl(context: AccountContext, updatedPresentationData: (PresentationData, Signal)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, requestsContext: PeerInvitationImportersContext? = nil) -> ViewController? { diff --git a/submodules/TelegramUI/Sources/StickerPaneTrendingListGridItem.swift b/submodules/TelegramUI/Sources/StickerPaneTrendingListGridItem.swift index a7793b474b..6fc95c6d82 100644 --- a/submodules/TelegramUI/Sources/StickerPaneTrendingListGridItem.swift +++ b/submodules/TelegramUI/Sources/StickerPaneTrendingListGridItem.swift @@ -12,6 +12,7 @@ import AnimatedStickerNode import TelegramAnimatedStickerNode import ShimmerEffect import MergeLists +import ChatPresentationInterfaceState private let boundingSize = CGSize(width: 41.0, height: 41.0) private let boundingImageSize = CGSize(width: 28.0, height: 28.0) diff --git a/submodules/TelegramUI/Sources/StickersChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/StickersChatInputContextPanelNode.swift index 116aaec010..6c2ea057b0 100644 --- a/submodules/TelegramUI/Sources/StickersChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/StickersChatInputContextPanelNode.swift @@ -15,6 +15,7 @@ import ContextUI import ChatPresentationInterfaceState import PremiumUI import UndoUI +import ChatControllerInteraction private struct StickersChatInputContextPanelEntryStableId: Hashable { let ids: [MediaId] diff --git a/submodules/TelegramUI/Sources/TelegramRootController.swift b/submodules/TelegramUI/Sources/TelegramRootController.swift index 72330d4912..6e8cd5698d 100644 --- a/submodules/TelegramUI/Sources/TelegramRootController.swift +++ b/submodules/TelegramUI/Sources/TelegramRootController.swift @@ -131,8 +131,9 @@ public final class TelegramRootController: NavigationController { } strongSelf.pushViewController(debugController(sharedContext: strongSelf.context.sharedContext, context: strongSelf.context)) } + accountSettingsController.parentController = self controllers.append(accountSettingsController) - + tabBarController.setControllers(controllers, selectedIndex: restoreSettignsController != nil ? (controllers.count - 1) : (controllers.count - 2)) self.contactsController = contactsController diff --git a/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputContextPanelNode.swift index 4574657846..b6ad34f95a 100644 --- a/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputContextPanelNode.swift @@ -10,6 +10,7 @@ import MergeLists import AccountContext import SwiftSignalKit import ChatPresentationInterfaceState +import ChatControllerInteraction private enum VerticalChatContextResultsEntryStableId: Hashable { case action diff --git a/submodules/TextFormat/Sources/ChatTextInputAttributes.swift b/submodules/TextFormat/Sources/ChatTextInputAttributes.swift index c2707160b8..711273b010 100644 --- a/submodules/TextFormat/Sources/ChatTextInputAttributes.swift +++ b/submodules/TextFormat/Sources/ChatTextInputAttributes.swift @@ -192,11 +192,19 @@ public final class ChatTextInputTextUrlAttribute: NSObject { } } -public final class ChatTextInputTextCustomEmojiAttribute: NSObject { +public final class ChatTextInputTextCustomEmojiAttribute: NSObject, Codable { + private enum CodingKeys: String, CodingKey { + case interactivelySelectedFromPackId + case fileId + case file + case topicId + case topicInfo + } + public let interactivelySelectedFromPackId: ItemCollectionId? public let fileId: Int64 - public let topicInfo: (Int64, EngineMessageHistoryThread.Info)? public let file: TelegramMediaFile? + public let topicInfo: (Int64, EngineMessageHistoryThread.Info)? public init(interactivelySelectedFromPackId: ItemCollectionId?, fileId: Int64, file: TelegramMediaFile?, topicInfo: (Int64, EngineMessageHistoryThread.Info)? = nil) { self.interactivelySelectedFromPackId = interactivelySelectedFromPackId @@ -207,6 +215,29 @@ public final class ChatTextInputTextCustomEmojiAttribute: NSObject { super.init() } + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.interactivelySelectedFromPackId = try container.decodeIfPresent(ItemCollectionId.self, forKey: .interactivelySelectedFromPackId) + self.fileId = try container.decode(Int64.self, forKey: .fileId) + self.file = try container.decodeIfPresent(TelegramMediaFile.self, forKey: .file) + if let topicId = try container.decodeIfPresent(Int64.self, forKey: .topicId), let topicInfo = try container.decodeIfPresent(EngineMessageHistoryThread.Info.self, forKey: .topicInfo) { + self.topicInfo = (topicId, topicInfo) + } else { + self.topicInfo = nil + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(self.interactivelySelectedFromPackId, forKey: .interactivelySelectedFromPackId) + try container.encode(self.fileId, forKey: .fileId) + try container.encodeIfPresent(self.file, forKey: .file) + if let (topicId, topicInfo) = self.topicInfo { + try container.encode(topicId, forKey: .topicId) + try container.encode(topicInfo, forKey: .topicInfo) + } + } + override public func isEqual(_ object: Any?) -> Bool { if let other = object as? ChatTextInputTextCustomEmojiAttribute { return self === other diff --git a/submodules/UndoUI/Sources/UndoOverlayController.swift b/submodules/UndoUI/Sources/UndoOverlayController.swift index a64c9bb671..da49acb4a5 100644 --- a/submodules/UndoUI/Sources/UndoOverlayController.swift +++ b/submodules/UndoUI/Sources/UndoOverlayController.swift @@ -39,7 +39,7 @@ public enum UndoOverlayContent { case mediaSaved(text: String) case paymentSent(currencyValue: String, itemTitle: String) case inviteRequestSent(title: String, text: String) - case image(image: UIImage, title: String?, text: String, undo: Bool) + case image(image: UIImage, title: String?, text: String, round: Bool, undo: Bool) case notificationSoundAdded(title: String, text: String, action: (() -> Void)?) case universal(animation: String, scale: CGFloat, colors: [String: UIColor], title: String?, text: String, customUndoText: String?) case peers(context: AccountContext, peers: [EnginePeer], title: String?, text: String, customUndoText: String?) diff --git a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift index 35b3a240a8..c7dbb84e53 100644 --- a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift +++ b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift @@ -860,13 +860,13 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { } else { displayUndo = false } - case let .image(image, title, text, undo): + case let .image(image, title, text, round, undo): self.avatarNode = nil self.iconNode = ASImageNode() self.iconNode?.clipsToBounds = true self.iconNode?.contentMode = .scaleAspectFill self.iconNode?.image = image - self.iconNode?.cornerRadius = 4.0 + self.iconNode?.cornerRadius = round ? 16.0 : 4.0 self.iconImageSize = CGSize(width: 32.0, height: 32.0) self.iconCheckNode = nil self.animationNode = nil @@ -1100,7 +1100,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { self.content = content switch content { - case let .image(image, title, text, _): + case let .image(image, title, text, _, _): self.iconNode?.image = image if let title = title { self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white) diff --git a/submodules/WebSearchUI/Sources/LegacyWebSearchGallery.swift b/submodules/WebSearchUI/Sources/LegacyWebSearchGallery.swift index d1598463f5..0e38dd8d8f 100644 --- a/submodules/WebSearchUI/Sources/LegacyWebSearchGallery.swift +++ b/submodules/WebSearchUI/Sources/LegacyWebSearchGallery.swift @@ -432,16 +432,7 @@ public func legacyEnqueueWebSearchMessages(_ selectionState: TGMediaSelectionCon for result in results { let editableItem = LegacyWebSearchItem(result: result) if let adjustments = editingState.adjustments(for: editableItem) { - var animated = false - if let entities = adjustments.paintingData?.entities { - for entity in entities { - if let paintEntity = entity as? TGPhotoPaintEntity, paintEntity.animated { - animated = true - break - } - } - } - + let animated = adjustments.paintingData?.hasAnimation ?? false if let imageSignal = editingState.imageSignal(for: editableItem) { let signal = imageSignal.map { image -> Any in if let image = image as? UIImage { diff --git a/submodules/WebSearchUI/Sources/WebSearchController.swift b/submodules/WebSearchUI/Sources/WebSearchController.swift index 79695f3c85..ca14d0c501 100644 --- a/submodules/WebSearchUI/Sources/WebSearchController.swift +++ b/submodules/WebSearchUI/Sources/WebSearchController.swift @@ -10,51 +10,6 @@ import TelegramPresentationData import AccountContext import AttachmentUI -public func requestContextResults(context: AccountContext, botId: EnginePeer.Id, query: String, peerId: EnginePeer.Id, offset: String = "", existingResults: ChatContextResultCollection? = nil, incompleteResults: Bool = false, staleCachedResults: Bool = false, limit: Int = 60) -> Signal { - return context.engine.messages.requestChatContextResults(botId: botId, peerId: peerId, query: query, offset: offset, incompleteResults: incompleteResults, staleCachedResults: staleCachedResults) - |> `catch` { error -> Signal in - return .single(nil) - } - |> mapToSignal { resultsStruct -> Signal in - let results = resultsStruct?.results - - var collection = existingResults - var updated: Bool = false - if let existingResults = existingResults, let results = results { - var newResults: [ChatContextResult] = [] - var existingIds = Set() - for result in existingResults.results { - newResults.append(result) - existingIds.insert(result.id) - } - for result in results.results { - if !existingIds.contains(result.id) { - newResults.append(result) - existingIds.insert(result.id) - updated = true - } - } - collection = ChatContextResultCollection(botId: existingResults.botId, peerId: existingResults.peerId, query: existingResults.query, geoPoint: existingResults.geoPoint, queryId: results.queryId, nextOffset: results.nextOffset, presentation: existingResults.presentation, switchPeer: existingResults.switchPeer, results: newResults, cacheTimeout: existingResults.cacheTimeout) - } else { - collection = results - updated = true - } - if let collection = collection, collection.results.count < limit, let nextOffset = collection.nextOffset, updated { - let nextResults = requestContextResults(context: context, botId: botId, query: query, peerId: peerId, offset: nextOffset, existingResults: collection, limit: limit) - if collection.results.count > 10 { - return .single(RequestChatContextResultsResult(results: collection, isStale: resultsStruct?.isStale ?? false)) - |> then(nextResults) - } else { - return nextResults - } - } else if let collection = collection { - return .single(RequestChatContextResultsResult(results: collection, isStale: resultsStruct?.isStale ?? false)) - } else { - return .single(nil) - } - } -} - public enum WebSearchMode { case media case avatar @@ -497,7 +452,7 @@ public final class WebSearchController: ViewController { } |> mapToSignal { peer -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> in if case let .user(user) = peer, let botInfo = user.botInfo, let _ = botInfo.inlinePlaceholder { - let results = requestContextResults(context: context, botId: user.id, query: query, peerId: peerId, limit: 64) + let results = requestContextResults(engine: context.engine, botId: user.id, query: query, peerId: peerId, limit: 64) |> map { results -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in return { _ in return .contextRequestResult(.user(user), results?.results) diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 631fbb1c85..8856dd373c 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -593,6 +593,8 @@ public final class WebAppController: ViewController, AttachmentContainable { private let hapticFeedback = HapticFeedback() + private weak var currentQrCodeScannerScreen: QrCodeScanScreen? + private var delayedScriptMessage: WKScriptMessage? private func handleScriptMessage(_ message: WKScriptMessage) { guard let controller = self.controller else { @@ -829,7 +831,13 @@ public final class WebAppController: ViewController, AttachmentContainable { strongSelf.sendQrCodeScannedEvent(data: result) } } + self.currentQrCodeScannerScreen = controller self.controller?.present(controller, in: .window(.root)) + case "web_app_close_scan_qr_popup": + if let controller = self.currentQrCodeScannerScreen { + self.currentQrCodeScannerScreen = nil + controller.dismissAnimated() + } case "web_app_read_text_from_clipboard": if let json = json, let requestId = json["req_id"] as? String { let currentTimestamp = CACurrentMediaTime() @@ -972,7 +980,11 @@ public final class WebAppController: ViewController, AttachmentContainable { fileprivate func sendQrCodeScannedEvent(data: String?) { let paramsString = data.flatMap { "{data: \"\($0)\"}" } ?? "{}" - self.webView?.sendEvent(name: "scan_qr_popup_closed", data: paramsString) + self.webView?.sendEvent(name: "qr_text_received", data: paramsString) + } + + fileprivate func sendQrCodeScannerClosedEvent(data: String?) { + self.webView?.sendEvent(name: "scan_qr_popup_closed", data: nil) } fileprivate func sendClipboardTextEvent(requestId: String, fillData: Bool) {