From 6906df02430f8758cf1eaa5a897d4fbd5fa442b4 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 15 Mar 2024 10:55:40 +0400 Subject: [PATCH] [WIP] Stickers editor --- .../Sources/AccountContext.swift | 2 +- .../ContactMultiselectionController.swift | 2 +- .../AccountContext/Sources/Premium.swift | 2 +- .../Sources/Node/ChatListNode.swift | 2 +- .../Sources/ContactListNode.swift | 66 ++++-- .../Sources/ContactsControllerNode.swift | 2 +- .../ContextUI/Sources/ContextController.swift | 6 +- .../ContextControllerActionsStackNode.swift | 22 +- .../ImportStickerPackControllerNode.swift | 4 +- .../Sources/ItemListStickerPackItem.swift | 6 +- .../Sources/MediaPickerScreen.swift | 5 +- .../Sources/ReactionContextNode.swift | 3 +- .../StickerPackPreviewController.swift | 4 +- .../Sources/StickerPackScreen.swift | 12 +- .../Sources/StickerPreviewPeekContent.swift | 3 +- .../SyncCore_StickerPackCollectionInfo.swift | 8 - .../SyncCore_TelegramMediaImage.swift | 23 ++- .../Stickers/ImportStickers.swift | 6 +- .../TelegramEngine/Stickers/StickerPack.swift | 30 +-- .../Sources/EmojiPagerContentSignals.swift | 5 - .../Sources/MediaEditorScreen.swift | 46 +---- .../Sources/StickerPackListContextItem.swift | 191 ++++++++++++++++++ .../PeerInfoScreenBirthdatePickerItem.swift | 8 + .../PeerInfoScreenDisclosureItem.swift | 8 +- .../Sources/PeerSelectionControllerNode.swift | 2 +- .../StickerPackEditTitleController.swift | 14 +- .../Sources/ComposeControllerNode.swift | 2 +- .../ContactMultiselectionControllerNode.swift | 14 +- .../ContactSelectionControllerNode.swift | 2 +- .../Sources/SharedAccountContext.swift | 17 +- .../Sources/UndoOverlayControllerNode.swift | 4 +- 31 files changed, 381 insertions(+), 140 deletions(-) create mode 100644 submodules/TelegramUI/Components/MediaEditorScreen/Sources/StickerPackListContextItem.swift create mode 100644 submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenBirthdatePickerItem.swift diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index d76ab65342..3c002c7548 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -997,7 +997,7 @@ public protocol SharedAccountContext: AnyObject { func makeMediaPickerScreen(context: AccountContext, hasSearch: Bool, completion: @escaping (Any) -> Void) -> ViewController - func makeStickerEditorScreen(context: AccountContext, source: Any, transitionArguments: (UIView, CGRect, UIImage?)?, completion: @escaping (TelegramMediaFile) -> Void) -> ViewController + func makeStickerEditorScreen(context: AccountContext, source: Any, transitionArguments: (UIView, CGRect, UIImage?)?, completion: @escaping (TelegramMediaFile, @escaping () -> Void) -> Void) -> ViewController func makeStickerMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController func makeStoryMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void, groupsPresented: @escaping () -> Void) -> ViewController diff --git a/submodules/AccountContext/Sources/ContactMultiselectionController.swift b/submodules/AccountContext/Sources/ContactMultiselectionController.swift index 45cbfa89ff..0d9f444f23 100644 --- a/submodules/AccountContext/Sources/ContactMultiselectionController.swift +++ b/submodules/AccountContext/Sources/ContactMultiselectionController.swift @@ -72,7 +72,7 @@ public enum ContactMultiselectionControllerMode { case peerSelection(searchChatList: Bool, searchGroups: Bool, searchChannels: Bool) case channelCreation case chatSelection(ChatSelection) - case premiumGifting + case premiumGifting(topSectionTitle: String?, topSectionPeers: [EnginePeer.Id]) case requestedUsersSelection } diff --git a/submodules/AccountContext/Sources/Premium.swift b/submodules/AccountContext/Sources/Premium.swift index fc27194fcd..4000f84c53 100644 --- a/submodules/AccountContext/Sources/Premium.swift +++ b/submodules/AccountContext/Sources/Premium.swift @@ -45,7 +45,7 @@ public enum PremiumGiftSource: Equatable { case profile case attachMenu case settings - case chatList + case chatList([EnginePeer.Id]) case channelBoost case deeplink(String?) } diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index bbf477b2fb..0bc6ae5aa3 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -1697,7 +1697,7 @@ public final class ChatListNode: ListView { guard let self else { return } - let controller = self.context.sharedContext.makePremiumGiftController(context: self.context, source: .chatList, completion: nil) + let controller = self.context.sharedContext.makePremiumGiftController(context: self.context, source: .chatList(peerIds), completion: nil) self.push?(controller) }, openActiveSessions: { [weak self] in guard let self else { diff --git a/submodules/ContactListUI/Sources/ContactListNode.swift b/submodules/ContactListUI/Sources/ContactListNode.swift index ef2b12b7b0..0d58ac6686 100644 --- a/submodules/ContactListUI/Sources/ContactListNode.swift +++ b/submodules/ContactListUI/Sources/ContactListNode.swift @@ -369,7 +369,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable { } } -private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactListPeer], presences: [EnginePeer.Id: EnginePeer.Presence], presentation: ContactListPresentation, selectionState: ContactListNodeGroupSelectionState?, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, disabledPeerIds: Set, peerRequiresPremiumForMessaging: [EnginePeer.Id: Bool], authorizationStatus: AccessType, warningSuppressed: (Bool, Bool), displaySortOptions: Bool, displayCallIcons: Bool, storySubscriptions: EngineStorySubscriptions?, topPeers: [EnginePeer], interaction: ContactListNodeInteraction) -> [ContactListNodeEntry] { +private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactListPeer], presences: [EnginePeer.Id: EnginePeer.Presence], presentation: ContactListPresentation, selectionState: ContactListNodeGroupSelectionState?, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, disabledPeerIds: Set, peerRequiresPremiumForMessaging: [EnginePeer.Id: Bool], authorizationStatus: AccessType, warningSuppressed: (Bool, Bool), displaySortOptions: Bool, displayCallIcons: Bool, storySubscriptions: EngineStorySubscriptions?, topPeers: [EnginePeer], topSectionTitle: String?, interaction: ContactListNodeInteraction) -> [ContactListNodeEntry] { var entries: [ContactListNodeEntry] = [] var commonHeader: ListViewItemHeader? @@ -528,7 +528,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis if !topPeers.isEmpty { let hasDeselectAll = !(selectionState?.selectedPeerIndices ?? [:]).isEmpty - let header: ListViewItemHeader? = ChatListSearchItemHeader(type: .text(strings.Premium_Gift_ContactSelection_FrequentContacts.uppercased(), AnyHashable(hasDeselectAll ? 1 : 0)), theme: theme, strings: strings, actionTitle: hasDeselectAll ? strings.Premium_Gift_ContactSelection_DeselectAll.uppercased() : nil, action: { + let header: ListViewItemHeader? = ChatListSearchItemHeader(type: .text(topSectionTitle ?? strings.Premium_Gift_ContactSelection_FrequentContacts.uppercased(), AnyHashable(hasDeselectAll ? 1 : 0)), theme: theme, strings: strings, actionTitle: hasDeselectAll ? strings.Premium_Gift_ContactSelection_DeselectAll.uppercased() : nil, action: { interaction.deselectAll() }) @@ -722,8 +722,14 @@ public enum ContactListPresentation { } } + public enum TopPeers { + case none + case recent + case custom(title: String, peerIds: [EnginePeer.Id]) + } + case orderedByPresence(options: [ContactListAdditionalOption]) - case natural(options: [ContactListAdditionalOption], includeChatList: Bool, topPeers: Bool) + case natural(options: [ContactListAdditionalOption], includeChatList: Bool, topPeers: TopPeers) case search(Search) public var sortOrder: ContactsSortOrder? { @@ -1110,11 +1116,11 @@ public final class ContactListNode: ASDisplayNode { |> mapToSignal { presentation in var generateSections = false var includeChatList = false - var displayTopPeers = false - if case let .natural(_, includeChatListValue, displayTopPeersValue) = presentation { + var displayTopPeers: ContactListPresentation.TopPeers = .none + if case let .natural(_, includeChatListValue, topPeersValue) = presentation { generateSections = true includeChatList = includeChatListValue - displayTopPeers = displayTopPeersValue + displayTopPeers = topPeersValue } if case let .search(search) = presentation { @@ -1421,7 +1427,7 @@ public final class ContactListNode: ASDisplayNode { peers.append(.deviceContact(stableId, contact.0)) } - let entries = contactListNodeEntries(accountPeer: nil, peers: peers, presences: localPeersAndStatuses.1, presentation: presentation, selectionState: selectionState, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, disabledPeerIds: disabledPeerIds, peerRequiresPremiumForMessaging: peerRequiresPremiumForMessaging, authorizationStatus: .allowed, warningSuppressed: (true, true), displaySortOptions: false, displayCallIcons: displayCallIcons, storySubscriptions: nil, topPeers: [], interaction: interaction) + let entries = contactListNodeEntries(accountPeer: nil, peers: peers, presences: localPeersAndStatuses.1, presentation: presentation, selectionState: selectionState, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, disabledPeerIds: disabledPeerIds, peerRequiresPremiumForMessaging: peerRequiresPremiumForMessaging, authorizationStatus: .allowed, warningSuppressed: (true, true), displaySortOptions: false, displayCallIcons: displayCallIcons, storySubscriptions: nil, topPeers: [], topSectionTitle: nil, interaction: interaction) let previous = previousEntries.swap(entries) return .single(preparedContactListNodeTransition(context: context, presentationData: presentationData, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, isEmpty: false, generateIndexSections: generateSections, animation: .none, isSearch: isSearch)) } @@ -1481,11 +1487,36 @@ public final class ContactListNode: ASDisplayNode { chatListSignal = .single([]) } - let recentPeers: Signal - if displayTopPeers { - recentPeers = context.engine.peers.recentPeers() - } else { - recentPeers = .single(.disabled) + let topPeers: Signal<[EnginePeer], NoError> + let topPeersSectionTitle: String? + switch displayTopPeers { + case .recent: + topPeers = context.engine.peers.recentPeers() + |> map { recentPeers -> [EnginePeer] in + var topPeers: [EnginePeer] = [] + if case let .peers(peers) = recentPeers { + topPeers = peers.map(EnginePeer.init) + } + return topPeers + } + topPeersSectionTitle = nil + case let .custom(title, peerIds): + topPeers = context.engine.data.get( + EngineDataMap(peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:))) + ) + |> map { peers in + var result: [EnginePeer] = [] + for peer in peers.values { + if let peer { + result.append(peer) + } + } + return result + } + topPeersSectionTitle = title + case .none: + topPeers = .single([]) + topPeersSectionTitle = nil } return (combineLatest( @@ -1497,9 +1528,9 @@ public final class ContactListNode: ASDisplayNode { contactsAuthorization.get(), contactsWarningSuppressed.get(), self.storySubscriptions.get(), - recentPeers + topPeers ) - |> mapToQueue { view, chatListPeers, selectionState, pendingRemovalPeerIds, presentationData, authorizationStatus, warningSuppressed, storySubscriptions, recentPeers -> Signal in + |> mapToQueue { view, chatListPeers, selectionState, pendingRemovalPeerIds, presentationData, authorizationStatus, warningSuppressed, storySubscriptions, topPeers -> Signal in let signal = deferred { () -> Signal in if !view.2.isEmpty { context.account.viewTracker.refreshCanSendMessagesForPeerIds(peerIds: Array(view.2.keys)) @@ -1540,16 +1571,11 @@ public final class ContactListNode: ASDisplayNode { } } - var topPeers: [EnginePeer] = [] - if case let .peers(peers) = recentPeers { - topPeers = peers.map(EnginePeer.init) - } - var isEmpty = false if (authorizationStatus == .notDetermined || authorizationStatus == .denied) && peers.isEmpty { isEmpty = true } - let entries = contactListNodeEntries(accountPeer: view.1, peers: peers, presences: view.0.presences, presentation: presentation, selectionState: selectionState, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, disabledPeerIds: disabledPeerIds, peerRequiresPremiumForMessaging: view.2, authorizationStatus: authorizationStatus, warningSuppressed: warningSuppressed, displaySortOptions: displaySortOptions, displayCallIcons: displayCallIcons, storySubscriptions: storySubscriptions, topPeers: topPeers, interaction: interaction) + let entries = contactListNodeEntries(accountPeer: view.1, peers: peers, presences: view.0.presences, presentation: presentation, selectionState: selectionState, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, disabledPeerIds: disabledPeerIds, peerRequiresPremiumForMessaging: view.2, authorizationStatus: authorizationStatus, warningSuppressed: warningSuppressed, displaySortOptions: displaySortOptions, displayCallIcons: displayCallIcons, storySubscriptions: storySubscriptions, topPeers: topPeers, topSectionTitle: topPeersSectionTitle, interaction: interaction) let previous = previousEntries.swap(entries) let previousSelection = previousSelectionState.swap(selectionState) let previousPendingRemovalPeerIds = previousPendingRemovalPeerIds.swap(pendingRemovalPeerIds) diff --git a/submodules/ContactListUI/Sources/ContactsControllerNode.swift b/submodules/ContactListUI/Sources/ContactsControllerNode.swift index 32642969bf..b23c24310c 100644 --- a/submodules/ContactListUI/Sources/ContactsControllerNode.swift +++ b/submodules/ContactListUI/Sources/ContactsControllerNode.swift @@ -108,7 +108,7 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { case .presence: return .orderedByPresence(options: options) case .natural: - return .natural(options: options, includeChatList: false, topPeers: false) + return .natural(options: options, includeChatList: false, topPeers: .none) } } diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index 2986c8bd50..ba800ab20b 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -48,8 +48,6 @@ public enum ContextMenuActionItemTextColor { public enum ContextMenuActionResult { case `default` case dismissWithoutContent - /// Temporary - static var safeStreamRecordingDismissWithoutContent: ContextMenuActionResult { .dismissWithoutContent } case custom(ContainedViewLayoutTransition) } @@ -116,9 +114,11 @@ public final class ContextMenuActionItem { public struct IconAnimation: Equatable { public var name: String + public var loop: Bool - public init(name: String) { + public init(name: String, loop: Bool = false) { self.name = name + self.loop = loop } } diff --git a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift index eeac765ad8..fca0ee07ae 100644 --- a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift @@ -74,7 +74,7 @@ public protocol ContextControllerActionsStackItem: AnyObject { var dismissed: (() -> Void)? { get } } -protocol ContextControllerActionsListItemNode: ASDisplayNode { +public protocol ContextControllerActionsListItemNode: ASDisplayNode { func update(presentationData: PresentationData, constrainedSize: CGSize) -> (minSize: CGSize, apply: (_ size: CGSize, _ transition: ContainedViewLayoutTransition) -> Void) func canBeHighlighted() -> Bool @@ -82,7 +82,7 @@ protocol ContextControllerActionsListItemNode: ASDisplayNode { func performAction() } -private final class ContextControllerActionsListActionItemNode: HighlightTrackingButtonNode, ContextControllerActionsListItemNode { +public final class ContextControllerActionsListActionItemNode: HighlightTrackingButtonNode, ContextControllerActionsListItemNode { private let getController: () -> ContextControllerProtocol? private let requestDismiss: (ContextMenuActionResult) -> Void private let requestUpdateAction: (AnyHashable, ContextMenuActionItem) -> Void @@ -103,7 +103,7 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin private var iconDisposable: Disposable? - init( + public init( getController: @escaping () -> ContextControllerProtocol?, requestDismiss: @escaping (ContextMenuActionResult) -> Void, requestUpdateAction: @escaping (AnyHashable, ContextMenuActionItem) -> Void, @@ -168,7 +168,7 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin self.iconDisposable?.dispose() } - override func didLoad() { + public override func didLoad() { super.didLoad() self.view.isExclusiveTouch = true @@ -196,19 +196,19 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin )) } - func canBeHighlighted() -> Bool { + public func canBeHighlighted() -> Bool { return self.item.action != nil } - func updateIsHighlighted(isHighlighted: Bool) { + public func updateIsHighlighted(isHighlighted: Bool) { self.highlightBackgroundNode.alpha = isHighlighted ? 1.0 : 0.0 } - func performAction() { + public func performAction() { self.pressed() } - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if self.titleLabelNode.tapAttributeAction != nil { if let result = self.titleLabelNode.hitTest(self.view.convert(point, to: self.titleLabelNode.view), with: event) { return result @@ -223,7 +223,7 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin self.accessibilityLabel = item.text } - func update(presentationData: PresentationData, constrainedSize: CGSize) -> (minSize: CGSize, apply: (_ size: CGSize, _ transition: ContainedViewLayoutTransition) -> Void) { + public func update(presentationData: PresentationData, constrainedSize: CGSize) -> (minSize: CGSize, apply: (_ size: CGSize, _ transition: ContainedViewLayoutTransition) -> Void) { let sideInset: CGFloat = 16.0 let verticalInset: CGFloat = 11.0 let titleSubtitleSpacing: CGFloat = 1.0 @@ -365,8 +365,8 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin component: AnyComponent(LottieComponent( content: LottieComponent.AppBundleContent(name: iconAnimation.name), color: titleColor, - startingPosition: .end, - loop: false + startingPosition: iconAnimation.loop ? .begin : .end, + loop: iconAnimation.loop )), environment: {}, containerSize: animatedIconSize diff --git a/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift b/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift index 6e39581e13..2e0fb376bd 100644 --- a/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift +++ b/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift @@ -719,7 +719,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll @objc private func createActionButtonPressed() { var proceedImpl: ((String, String?) -> Void)? - let titleController = stickerPackEditTitleController(context: self.context, title: self.presentationData.strings.ImportStickerPack_ChooseName, text: self.presentationData.strings.ImportStickerPack_ChooseNameDescription, placeholder: self.presentationData.strings.ImportStickerPack_NamePlaceholder, value: nil, maxLength: 128, apply: { [weak self] title in + let titleController = stickerPackEditTitleController(context: self.context, title: self.presentationData.strings.ImportStickerPack_ChooseName, text: self.presentationData.strings.ImportStickerPack_ChooseNameDescription, placeholder: self.presentationData.strings.ImportStickerPack_NamePlaceholder, value: nil, maxLength: 64, apply: { [weak self] title in if let strongSelf = self, let title = title { strongSelf.shortNameSuggestionDisposable.set((strongSelf.context.engine.stickers.getStickerSetShortNameSuggestion(title: title) |> deliverOnMainQueue).start(next: { suggestedShortName in @@ -735,7 +735,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll guard let strongSelf = self else { return } - let controller = importStickerPackShortNameController(context: strongSelf.context, title: strongSelf.presentationData.strings.ImportStickerPack_ChooseLink, text: strongSelf.presentationData.strings.ImportStickerPack_ChooseLinkDescription, placeholder: "", value: suggestedShortName, maxLength: 60, existingAlertController: titleController, apply: { [weak self] shortName in + let controller = importStickerPackShortNameController(context: strongSelf.context, title: strongSelf.presentationData.strings.ImportStickerPack_ChooseLink, text: strongSelf.presentationData.strings.ImportStickerPack_ChooseLinkDescription, placeholder: "", value: suggestedShortName, maxLength: 64, existingAlertController: titleController, apply: { [weak self] shortName in if let shortName = shortName { self?.createStickerSet(title: title, shortName: shortName) } diff --git a/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift b/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift index 43710530f4..c86a2fa029 100644 --- a/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift +++ b/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift @@ -488,8 +488,8 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { var thumbnailItem: StickerPackThumbnailItem? var resourceReference: MediaResourceReference? if let thumbnail = item.packInfo.thumbnail { - if item.packInfo.flags.contains(.isAnimated) || item.packInfo.flags.contains(.isVideo) { - thumbnailItem = .animated(thumbnail.resource, thumbnail.dimensions, item.packInfo.flags.contains(.isVideo), item.packInfo.flags.contains(.isCustomTemplateEmoji)) + if thumbnail.typeHint != .generic { + thumbnailItem = .animated(thumbnail.resource, thumbnail.dimensions, thumbnail.typeHint == .video, item.packInfo.flags.contains(.isCustomTemplateEmoji)) } else { thumbnailItem = .still(thumbnail) } @@ -844,7 +844,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { var imageSize = PixelDimensions(width: 512, height: 512) var immediateThumbnailData: Data? if let data = item.packInfo.immediateThumbnailData { - if item.packInfo.flags.contains(.isVideo) { + if item.packInfo.thumbnail?.typeHint == .video || item.topItem?.file.isVideoSticker == true { imageSize = PixelDimensions(width: 100, height: 100) } immediateThumbnailData = data diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index c4859c5482..d04d19a559 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -2248,7 +2248,10 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { if !items.isEmpty { items.append(.separator) } - items.append(.action(ContextMenuActionItem(text: hasGeneric ? strings.Attachment_EnableSpoiler : strings.Attachment_DisableSpoiler, icon: { _ in return nil }, animationName: "anim_spoiler", action: { [weak self] _, f in + items.append(.action(ContextMenuActionItem(text: hasGeneric ? strings.Attachment_EnableSpoiler : strings.Attachment_DisableSpoiler, icon: { _ in return nil }, iconAnimation: ContextMenuActionItem.IconAnimation( + name: "anim_spoiler", + loop: true + ), action: { [weak self] _, f in f(.default) guard let strongSelf = self else { return diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index edaffa43d0..3ff0e43f50 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -332,6 +332,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { public var displayTail: Bool = true public var forceTailToRight: Bool = false public var forceDark: Bool = false + public var hideBackground: Bool = false private var didAnimateIn: Bool = false public private(set) var isAnimatingOut: Bool = false @@ -1900,7 +1901,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { externalExpansionView: self.view, customContentView: nil, useOpaqueTheme: false, - hideBackground: false, + hideBackground: self.hideBackground, stateContext: nil, addImage: nil ) diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewController.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewController.swift index ba685bfdb4..1ba3c3e66d 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewController.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewController.swift @@ -297,8 +297,8 @@ public func preloadedStickerPackThumbnail(account: Account, info: StickerPackCol let signal = Signal { subscriber in let fetched = fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: .stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource)).start() let dataDisposable: Disposable - if info.flags.contains(.isAnimated) || info.flags.contains(.isVideo) { - dataDisposable = chatMessageAnimationData(mediaBox: account.postbox.mediaBox, resource: thumbnail.resource, isVideo: info.flags.contains(.isVideo), width: 80, height: 80, synchronousLoad: false).start(next: { data in + if thumbnail.typeHint != .generic { + dataDisposable = chatMessageAnimationData(mediaBox: account.postbox.mediaBox, resource: thumbnail.resource, isVideo: thumbnail.typeHint == .video, width: 80, height: 80, synchronousLoad: false).start(next: { data in if data.complete { subscriber.putNext(true) subscriber.putCompletion() diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift index d8dd924dd8..cf1eb65ab7 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift @@ -1140,7 +1140,7 @@ private final class StickerPackContainer: ASDisplayNode { private let stickerPickerInputData = Promise() private func presentAddStickerOptions() { - + //TODO:localize let actionSheet = ActionSheetController(presentationData: self.presentationData) var items: [ActionSheetItem] = [] items.append(ActionSheetButtonItem(title: "Create a New Sticker", color: .accent, action: { [weak actionSheet, weak self] in @@ -1207,7 +1207,7 @@ private final class StickerPackContainer: ASDisplayNode { context: context, source: result, transitionArguments: (transitionView, transitionRect, transitionImage), - completion: { file in + completion: { file, commit in dismissImpl?() let sticker = ImportSticker( resource: file.resource, @@ -1219,6 +1219,8 @@ private final class StickerPackContainer: ASDisplayNode { let packReference: StickerPackReference = .id(id: info.id.id, accessHash: info.accessHash) let _ = (context.engine.stickers.addStickerToStickerSet(packReference: packReference, sticker: sticker) |> deliverOnMainQueue).start(completed: { + commit() + let packController = StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [], parentNavigationController: navigationController, sendSticker: nil, sendEmoji: nil, actionPerformed: nil, dismissed: nil, getSourceRect: nil) (navigationController?.viewControllers.last as? ViewController)?.present(packController, in: .window(.root)) @@ -1283,7 +1285,7 @@ private final class StickerPackContainer: ASDisplayNode { context: context, source: initialFile, transitionArguments: nil, - completion: { file in + completion: { file, commit in let sticker = ImportSticker( resource: file.resource, emojis: ["😀"], @@ -1295,6 +1297,8 @@ private final class StickerPackContainer: ASDisplayNode { let _ = (context.engine.stickers.replaceSticker(previousSticker: .stickerPack(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), media: initialFile), sticker: sticker) |> deliverOnMainQueue).start(completed: { + commit() + let packController = StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [], parentNavigationController: navigationController, sendSticker: nil, sendEmoji: nil, actionPerformed: nil, dismissed: nil, getSourceRect: nil) (navigationController?.viewControllers.last as? ViewController)?.present(packController, in: .window(.root)) }) @@ -1310,7 +1314,7 @@ private final class StickerPackContainer: ASDisplayNode { let context = self.context //TODO:localize var dismissImpl: (() -> Void)? - let controller = stickerPackEditTitleController(context: context, title: "Edit Sticker Set Name", text: "Choose a new name for your set.", placeholder: self.presentationData.strings.ImportStickerPack_NamePlaceholder, actionTitle: presentationData.strings.Common_Done, value: self.updatedTitle ?? info.title, maxLength: 128, apply: { [weak self] title in + let controller = stickerPackEditTitleController(context: context, title: "Edit Sticker Set Name", text: "Choose a new name for your set.", placeholder: self.presentationData.strings.ImportStickerPack_NamePlaceholder, actionTitle: presentationData.strings.Common_Done, value: self.updatedTitle ?? info.title, maxLength: 64, apply: { [weak self] title in guard let self, let title else { return } diff --git a/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift b/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift index 02b443717c..8f0ec3483c 100644 --- a/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift +++ b/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift @@ -392,7 +392,7 @@ final class EmojiStickerAccessoryNode: SparseNode, PeekControllerAccessoryNode { items.append(reaction) } - let selectedItems = ValuePromise>() + let selectedItems = ValuePromise>(Set()) //TODO:localize let reactionContextNode = ReactionContextNode( context: self.context, @@ -440,6 +440,7 @@ final class EmojiStickerAccessoryNode: SparseNode, PeekControllerAccessoryNode { layoutImpl?(transition) } ) + reactionContextNode.hideBackground = true reactionContextNode.displayTail = true reactionContextNode.forceTailToRight = true reactionContextNode.forceDark = true diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_StickerPackCollectionInfo.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_StickerPackCollectionInfo.swift index 1823edd5ff..a9f717f43d 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_StickerPackCollectionInfo.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_StickerPackCollectionInfo.swift @@ -21,12 +21,6 @@ public struct StickerPackCollectionInfoFlags: OptionSet { if flags.contains(StickerPackCollectionInfoFlags.isOfficial) { rawValue |= StickerPackCollectionInfoFlags.isOfficial.rawValue } - if flags.contains(StickerPackCollectionInfoFlags.isAnimated) { - rawValue |= StickerPackCollectionInfoFlags.isAnimated.rawValue - } - if flags.contains(StickerPackCollectionInfoFlags.isVideo) { - rawValue |= StickerPackCollectionInfoFlags.isVideo.rawValue - } if flags.contains(StickerPackCollectionInfoFlags.isEmoji) { rawValue |= StickerPackCollectionInfoFlags.isEmoji.rawValue } @@ -39,8 +33,6 @@ public struct StickerPackCollectionInfoFlags: OptionSet { public static let isMasks = StickerPackCollectionInfoFlags(rawValue: 1 << 0) public static let isOfficial = StickerPackCollectionInfoFlags(rawValue: 1 << 1) - public static let isAnimated = StickerPackCollectionInfoFlags(rawValue: 1 << 2) - public static let isVideo = StickerPackCollectionInfoFlags(rawValue: 1 << 3) public static let isEmoji = StickerPackCollectionInfoFlags(rawValue: 1 << 4) public static let isAvailableAsChannelStatus = StickerPackCollectionInfoFlags(rawValue: 1 << 5) public static let isCustomTemplateEmoji = StickerPackCollectionInfoFlags(rawValue: 1 << 6) diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaImage.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaImage.swift index d98ebeecdd..c16f7d8f6d 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaImage.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaImage.swift @@ -360,20 +360,36 @@ public final class TelegramMediaImage: Media, Equatable, Codable { } public final class TelegramMediaImageRepresentation: PostboxCoding, Equatable, CustomStringConvertible { + public enum TypeHint: Int32 { + case generic + case animated + case video + } + public let dimensions: PixelDimensions public let resource: TelegramMediaResource public let progressiveSizes: [Int32] public let immediateThumbnailData: Data? public let hasVideo: Bool public let isPersonal: Bool + public let typeHint: TypeHint - public init(dimensions: PixelDimensions, resource: TelegramMediaResource, progressiveSizes: [Int32], immediateThumbnailData: Data?, hasVideo: Bool, isPersonal: Bool) { + public init( + dimensions: PixelDimensions, + resource: TelegramMediaResource, + progressiveSizes: [Int32], + immediateThumbnailData: Data?, + hasVideo: Bool = false, + isPersonal: Bool = false, + typeHint: TypeHint = .generic + ) { self.dimensions = dimensions self.resource = resource self.progressiveSizes = progressiveSizes self.immediateThumbnailData = immediateThumbnailData self.hasVideo = hasVideo self.isPersonal = isPersonal + self.typeHint = typeHint } public init(decoder: PostboxDecoder) { @@ -383,6 +399,7 @@ public final class TelegramMediaImageRepresentation: PostboxCoding, Equatable, C self.immediateThumbnailData = decoder.decodeDataForKey("th") self.hasVideo = decoder.decodeBoolForKey("hv", orElse: false) self.isPersonal = decoder.decodeBoolForKey("ip", orElse: false) + self.typeHint = TypeHint(rawValue: decoder.decodeInt32ForKey("th", orElse: 0)) ?? .generic } public func encode(_ encoder: PostboxEncoder) { @@ -397,6 +414,7 @@ public final class TelegramMediaImageRepresentation: PostboxCoding, Equatable, C } encoder.encodeBool(self.hasVideo, forKey: "hv") encoder.encodeBool(self.isPersonal, forKey: "ip") + encoder.encodeInt32(self.typeHint.rawValue, forKey: "th") } public var description: String { @@ -422,6 +440,9 @@ public final class TelegramMediaImageRepresentation: PostboxCoding, Equatable, C if self.isPersonal != other.isPersonal { return false } + if self.typeHint != other.typeHint { + return false + } return true } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift index a6fb320ca7..e37fca3718 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift @@ -194,7 +194,7 @@ func _internal_createStickerSet(account: Account, title: String, shortName: Stri flags |= (1 << 1) } - inputStickers.append(.inputStickerSetItem(flags: flags, document: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), emoji: sticker.emojis.first ?? "", maskCoords: nil, keywords: sticker.keywords)) + inputStickers.append(.inputStickerSetItem(flags: flags, document: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), emoji: sticker.emojis.joined(), maskCoords: nil, keywords: sticker.keywords)) } var thumbnailDocument: Api.InputDocument? if thumbnail != nil, let resource = resources.last { @@ -307,7 +307,7 @@ func _internal_addStickerToStickerSet(account: Account, packReference: StickerPa if sticker.keywords.count > 0 { flags |= (1 << 1) } - let inputSticker: Api.InputStickerSetItem = .inputStickerSetItem(flags: flags, document: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), emoji: sticker.emojis.first ?? "", maskCoords: nil, keywords: sticker.keywords) + let inputSticker: Api.InputStickerSetItem = .inputStickerSetItem(flags: flags, document: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), emoji: sticker.emojis.joined(), maskCoords: nil, keywords: sticker.keywords) return account.network.request(Api.functions.stickers.addStickerToSet(stickerset: packReference.apiInputStickerSet, sticker: inputSticker)) |> mapError { error -> AddStickerToSetError in @@ -416,7 +416,7 @@ func _internal_replaceSticker(account: Account, previousSticker: FileMediaRefere if sticker.keywords.count > 0 { flags |= (1 << 1) } - let inputSticker: Api.InputStickerSetItem = .inputStickerSetItem(flags: flags, document: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), emoji: sticker.emojis.first ?? "", maskCoords: nil, keywords: sticker.keywords) + let inputSticker: Api.InputStickerSetItem = .inputStickerSetItem(flags: flags, document: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), emoji: sticker.emojis.joined(), maskCoords: nil, keywords: sticker.keywords) return account.network.request(Api.functions.stickers.replaceSticker(sticker: .inputDocument(id: previousResource.fileId, accessHash: previousResource.accessHash, fileReference: Buffer(data: previousResource.fileReference ?? Data())), newSticker: inputSticker)) |> mapError { error -> ReplaceStickerError in diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerPack.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerPack.swift index a8e6444d67..bce8775167 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerPack.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerPack.swift @@ -5,19 +5,31 @@ import SwiftSignalKit import MtProtoKit func telegramStickerPackThumbnailRepresentationFromApiSizes(datacenterId: Int32, thumbVersion: Int32?, sizes: [Api.PhotoSize]) -> (immediateThumbnail: Data?, representations: [TelegramMediaImageRepresentation]) { + func stickerTypeHint(for type: String) -> TelegramMediaImageRepresentation.TypeHint { + switch type { + case "s": + return .generic + case "a": + return .animated + case "v": + return .video + default: + return .generic + } + } var immediateThumbnailData: Data? var representations: [TelegramMediaImageRepresentation] = [] for size in sizes { switch size { - case let .photoCachedSize(_, w, h, _): + case let .photoCachedSize(type, w, h, _): let resource = CloudStickerPackThumbnailMediaResource(datacenterId: datacenterId, thumbVersion: thumbVersion, volumeId: nil, localId: nil) - representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)) - case let .photoSize(_, w, h, _): + representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, typeHint: stickerTypeHint(for: type))) + case let .photoSize(type, w, h, _): let resource = CloudStickerPackThumbnailMediaResource(datacenterId: datacenterId, thumbVersion: thumbVersion, volumeId: nil, localId: nil) - representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)) - case let .photoSizeProgressive(_, w, h, sizes): + representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, typeHint: stickerTypeHint(for: type))) + case let .photoSizeProgressive(type, w, h, sizes): let resource = CloudStickerPackThumbnailMediaResource(datacenterId: datacenterId, thumbVersion: thumbVersion, volumeId: nil, localId: nil) - representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: sizes, immediateThumbnailData: nil, hasVideo: false, isPersonal: false)) + representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: sizes, immediateThumbnailData: nil, typeHint: stickerTypeHint(for: type))) case let .photoPathSize(_, data): immediateThumbnailData = data.makeData() case .photoStrippedSize: @@ -40,12 +52,6 @@ extension StickerPackCollectionInfo { if (flags & (1 << 3)) != 0 { setFlags.insert(.isMasks) } - if (flags & (1 << 5)) != 0 { - setFlags.insert(.isAnimated) - } - if (flags & (1 << 6)) != 0 { - setFlags.insert(.isVideo) - } if (flags & (1 << 7)) != 0 { setFlags.insert(.isEmoji) } diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift index 227217651d..4195137f15 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift @@ -1162,11 +1162,6 @@ public extension EmojiPagerContentComponent { } } else if case .stickerAlt = subject { for reactionItem in topReactionItems { -// if existingIds.contains(reactionItem.reaction) { -// continue -// } -// existingIds.insert(reactionItem.reaction) - let icon: EmojiPagerContentComponent.Item.Icon if case .reaction(onlyTop: true) = subject { icon = .none diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index de6ec17e31..7c8a5e342d 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -5676,11 +5676,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate if let navigationController = self.navigationController as? NavigationController { navigationController.updateRootContainerTransitionOffset(0.0, transition: .immediate) } - -// mediaEditor.stop() -// mediaEditor.invalidate() -// self.node.entitiesView.invalidate() - + let entities = self.node.entitiesView.entities.filter { !($0 is DrawingMediaEntity) } let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView) mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities) @@ -5772,36 +5768,12 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate } }))) - let thumbSize = CGSize(width: 24.0, height: 24.0) - for (pack, firstItem) in self.myStickerPacks { - let thumbnailResource = pack.thumbnail?.resource ?? firstItem?.file.resource - let thumbnailIconSource: ContextMenuActionItemIconSource? - if let thumbnailResource { - var resourceId: Int64 = 0 - if let resource = thumbnailResource as? CloudDocumentMediaResource { - resourceId = resource.fileId - } - let thumbnailFile = firstItem?.file ?? TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: resourceId), partialReference: nil, resource: thumbnailResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "image/webp", size: thumbnailResource.size ?? 0, attributes: []) - - let _ = freeMediaFileInteractiveFetched(account: self.context.account, userLocation: .other, fileReference: .stickerPack(stickerPack: .id(id: pack.id.id, accessHash: pack.accessHash), media: thumbnailFile)).start() - thumbnailIconSource = ContextMenuActionItemIconSource( - size: thumbSize, - signal: chatMessageStickerPackThumbnail(postbox: self.context.account.postbox, resource: thumbnailResource) - |> map { generator -> UIImage? in - return generator(TransformImageArguments(corners: ImageCorners(), imageSize: thumbSize, boundingSize: thumbSize, intrinsicInsets: .zero))?.generateImage() - } - ) - } else { - thumbnailIconSource = nil + contextItems.append(.custom(StickerPackListContextItem(context: self.context, packs: self.myStickerPacks, packSelected: { [weak self] pack in + guard let self else { + return } - contextItems.append(.action(ContextMenuActionItem(text: pack.title, icon: { _ in return nil }, iconSource: thumbnailIconSource, iconPosition: .left, action: { [weak self] _, f in - guard let self else { - return - } - f(.default) - self.uploadSticker(file, action: .addToStickerPack(pack: .id(id: pack.id.id, accessHash: pack.accessHash), title: pack.title)) - }))) - } + self.uploadSticker(file, action: .addToStickerPack(pack: .id(id: pack.id.id, accessHash: pack.accessHash), title: pack.title)) + }), false)) let items = ContextController.Items( id: 1, @@ -5877,7 +5849,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme) var dismissImpl: (() -> Void)? - let controller = stickerPackEditTitleController(context: self.context, forceDark: true, title: "New Sticker Set", text: "Choose a name for your sticker set.", placeholder: presentationData.strings.ImportStickerPack_NamePlaceholder, actionTitle: presentationData.strings.Common_Done, value: nil, maxLength: 128, apply: { [weak self] title in + let controller = stickerPackEditTitleController(context: self.context, forceDark: true, title: "New Sticker Set", text: "Choose a name for your sticker set.", placeholder: presentationData.strings.ImportStickerPack_NamePlaceholder, actionTitle: presentationData.strings.Common_Done, value: nil, maxLength: 64, apply: { [weak self] title in guard let self else { return } @@ -5972,7 +5944,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate case let .createStickerPack(title): let sticker = ImportSticker( resource: resource, - emojis: ["😀"], + emojis: ["😀😂"], dimensions: dimensions, mimeType: "image/webp", keywords: "" @@ -5991,7 +5963,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate case let .addToStickerPack(pack, _): let sticker = ImportSticker( resource: resource, - emojis: ["😀"], + emojis: ["😀😂"], dimensions: dimensions, mimeType: "image/webp", keywords: "" diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StickerPackListContextItem.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StickerPackListContextItem.swift new file mode 100644 index 0000000000..aca3995de1 --- /dev/null +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StickerPackListContextItem.swift @@ -0,0 +1,191 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import SwiftSignalKit +import Postbox +import TelegramCore +import AccountContext +import TelegramPresentationData +import StickerResources +import ContextUI + +final class StickerPackListContextItem: ContextMenuCustomItem { + let context: AccountContext + let packs: [(StickerPackCollectionInfo, StickerPackItem?)] + let packSelected: (StickerPackCollectionInfo) -> Void + + init(context: AccountContext, packs: [(StickerPackCollectionInfo, StickerPackItem?)], packSelected: @escaping (StickerPackCollectionInfo) -> Void) { + self.context = context + self.packs = packs + self.packSelected = packSelected + } + + func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode { + return StickerPackListContextItemNode(presentationData: presentationData, item: self, getController: getController, actionSelected: actionSelected) + } +} + +private final class StickerPackListContextItemNode: ASDisplayNode, ContextMenuCustomNode, ContextActionNodeProtocol, UIScrollViewDelegate { + private let item: StickerPackListContextItem + private let presentationData: PresentationData + private let getController: () -> ContextControllerProtocol? + private let actionSelected: (ContextMenuActionResult) -> Void + + private let scrollNode: ASScrollNode + private let actionNodes: [ContextControllerActionsListActionItemNode] + private let separatorNodes: [ASDisplayNode] + + init(presentationData: PresentationData, item: StickerPackListContextItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) { + self.item = item + self.presentationData = presentationData + self.getController = getController + self.actionSelected = actionSelected + + self.scrollNode = ASScrollNode() + + var actionNodes: [ContextControllerActionsListActionItemNode] = [] + var separatorNodes: [ASDisplayNode] = [] + + var i = 0 + for (pack, topItem) in item.packs { + let thumbSize = CGSize(width: 24.0, height: 24.0) + let thumbnailResource = pack.thumbnail?.resource ?? topItem?.file.resource + let thumbnailIconSource: ContextMenuActionItemIconSource? + if let thumbnailResource { + var resourceId: Int64 = 0 + if let resource = thumbnailResource as? CloudDocumentMediaResource { + resourceId = resource.fileId + } + let thumbnailFile = topItem?.file ?? TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: resourceId), partialReference: nil, resource: thumbnailResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "image/webp", size: thumbnailResource.size ?? 0, attributes: []) + + let _ = freeMediaFileInteractiveFetched(account: item.context.account, userLocation: .other, fileReference: .stickerPack(stickerPack: .id(id: pack.id.id, accessHash: pack.accessHash), media: thumbnailFile)).start() + thumbnailIconSource = ContextMenuActionItemIconSource( + size: thumbSize, + signal: chatMessageStickerPackThumbnail(postbox: item.context.account.postbox, resource: thumbnailResource) + |> map { generator -> UIImage? in + return generator(TransformImageArguments(corners: ImageCorners(), imageSize: thumbSize, boundingSize: thumbSize, intrinsicInsets: .zero))?.generateImage() + } + ) + } else { + thumbnailIconSource = nil + } + + let action = ContextMenuActionItem(text: pack.title, textLayout: .singleLine, icon: { _ in nil }, iconSource: thumbnailIconSource, iconPosition: .left, action: { _, f in + f(.dismissWithoutContent) + + item.packSelected(pack) + }) + let actionNode = ContextControllerActionsListActionItemNode(getController: getController, requestDismiss: actionSelected, requestUpdateAction: { _, _ in }, item: action) + actionNodes.append(actionNode) + if actionNodes.count != item.packs.count { + let separatorNode = ASDisplayNode() + separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor + separatorNodes.append(separatorNode) + } + i += 1 + } + self.actionNodes = actionNodes + self.separatorNodes = separatorNodes + + super.init() + + self.addSubnode(self.scrollNode) + for separatorNode in self.separatorNodes { + self.scrollNode.addSubnode(separatorNode) + } + for actionNode in self.actionNodes { + self.scrollNode.addSubnode(actionNode) + } + } + + override func didLoad() { + super.didLoad() + + self.scrollNode.view.delegate = self + self.scrollNode.view.alwaysBounceVertical = false + self.scrollNode.view.showsHorizontalScrollIndicator = false + self.scrollNode.view.scrollIndicatorInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 5.0, right: 0.0) + } + + func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) { + let minActionsWidth: CGFloat = 250.0 + let maxActionsWidth: CGFloat = 300.0 + let constrainedWidth = min(constrainedWidth, maxActionsWidth) + var maxWidth: CGFloat = 0.0 + var contentHeight: CGFloat = 0.0 + var heightsAndCompletions: [(CGFloat, (CGSize, ContainedViewLayoutTransition) -> Void)?] = [] + for i in 0 ..< self.actionNodes.count { + let itemNode = self.actionNodes[i] + let (minSize, complete) = itemNode.update(presentationData: self.presentationData, constrainedSize: CGSize(width: constrainedWidth, height: constrainedHeight)) + maxWidth = max(maxWidth, minSize.width) + heightsAndCompletions.append((minSize.height, complete)) + contentHeight += minSize.height + } + + maxWidth = max(maxWidth, minActionsWidth) + + let maxHeight: CGFloat = min(155.0, constrainedHeight - 108.0) + + return (CGSize(width: maxWidth, height: min(maxHeight, contentHeight)), { size, transition in + var verticalOffset: CGFloat = 0.0 + for i in 0 ..< heightsAndCompletions.count { + let itemNode = self.actionNodes[i] + if let (itemHeight, itemCompletion) = heightsAndCompletions[i] { + let itemSize = CGSize(width: maxWidth, height: itemHeight) + transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: itemSize)) + itemCompletion(itemSize, transition) + verticalOffset += itemHeight + } + + if i < self.actionNodes.count - 1 { + let separatorNode = self.separatorNodes[i] + separatorNode.frame = CGRect(x: 0, y: verticalOffset, width: size.width, height: UIScreenPixel) + } + } + transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: size)) + self.scrollNode.view.contentSize = CGSize(width: size.width, height: contentHeight) + + }) + } + + func updateTheme(presentationData: PresentationData) { +// for actionNode in self.actionNodes { +// actionNode.updateTheme(presentationData: presentationData) +// } + } + + var isActionEnabled: Bool { + return true + } + + func performAction() { + } + + func setIsHighlighted(_ value: Bool) { + } + + func canBeHighlighted() -> Bool { + return self.isActionEnabled + } + + func updateIsHighlighted(isHighlighted: Bool) { + self.setIsHighlighted(isHighlighted) + } + + func actionNode(at point: CGPoint) -> ContextActionNodeProtocol { +// for actionNode in self.actionNodes { +// let frame = actionNode.convert(actionNode.bounds, to: self) +// if frame.contains(point) { +// return actionNode +// } +// } + return self + } + + func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + for actionNode in self.actionNodes { + actionNode.updateIsHighlighted(isHighlighted: false) + } + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenBirthdatePickerItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenBirthdatePickerItem.swift new file mode 100644 index 0000000000..11c8cee150 --- /dev/null +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenBirthdatePickerItem.swift @@ -0,0 +1,8 @@ +// +// PeerInfoScreenBirthdatePickerItem.swift +// MediaEditorScreen +// +// Created by Ilya Laktyushin on 15.03.2024. +// + +import Foundation diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenDisclosureItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenDisclosureItem.swift index 369f19bb3e..63c1d698cf 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenDisclosureItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenDisclosureItem.swift @@ -38,9 +38,10 @@ final class PeerInfoScreenDisclosureItem: PeerInfoScreenItem { let text: String let icon: UIImage? let iconSignal: Signal? + let hasArrow: Bool let action: (() -> Void)? - init(id: AnyHashable, label: Label = .none, additionalBadgeLabel: String? = nil, additionalBadgeIcon: UIImage? = nil, text: String, icon: UIImage? = nil, iconSignal: Signal? = nil, action: (() -> Void)?) { + init(id: AnyHashable, label: Label = .none, additionalBadgeLabel: String? = nil, additionalBadgeIcon: UIImage? = nil, text: String, icon: UIImage? = nil, iconSignal: Signal? = nil, hasArrow: Bool = true, action: (() -> Void)?) { self.id = id self.label = label self.additionalBadgeLabel = additionalBadgeLabel @@ -48,6 +49,7 @@ final class PeerInfoScreenDisclosureItem: PeerInfoScreenItem { self.text = text self.icon = icon self.iconSignal = iconSignal + self.hasArrow = hasArrow self.action = action } @@ -139,7 +141,7 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode { let sideInset: CGFloat = 16.0 + safeInsets.left let leftInset = (item.icon == nil && item.iconSignal == nil ? sideInset : sideInset + 29.0 + 16.0) - let rightInset = sideInset + 18.0 + let rightInset = sideInset + (item.hasArrow ? 18.0 : 0.0) let separatorInset = item.icon == nil && item.iconSignal == nil ? sideInset : leftInset - 1.0 let titleFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize) @@ -206,7 +208,7 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode { self.iconNode.removeFromSupernode() } - if let arrowImage = PresentationResourcesItemList.disclosureArrowImage(presentationData.theme) { + if item.hasArrow, let arrowImage = PresentationResourcesItemList.disclosureArrowImage(presentationData.theme) { self.arrowNode.image = arrowImage let arrowFrame = CGRect(origin: CGPoint(x: width - 7.0 - arrowImage.size.width - safeInsets.right, y: floorToScreenPixels((height - arrowImage.size.height) / 2.0)), size: arrowImage.size) transition.updateFrame(node: self.arrowNode, frame: arrowFrame) diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift index 72859b0b55..cb557759d0 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift @@ -1354,7 +1354,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { self.controller?.containerLayoutUpdated(layout, transition: .immediate) } } else { - let contactListNode = ContactListNode(context: self.context, updatedPresentationData: self.updatedPresentationData, presentation: .single(.natural(options: [], includeChatList: false, topPeers: false)), onlyWriteable: self.filter.contains(.onlyWriteable)) + let contactListNode = ContactListNode(context: self.context, updatedPresentationData: self.updatedPresentationData, presentation: .single(.natural(options: [], includeChatList: false, topPeers: .none)), onlyWriteable: self.filter.contains(.onlyWriteable)) self.contactListNode = contactListNode contactListNode.enableUpdates = true contactListNode.selectionStateUpdated = { [weak self] selectionState in diff --git a/submodules/TelegramUI/Components/Stickers/StickerPackEditTitleController/Sources/StickerPackEditTitleController.swift b/submodules/TelegramUI/Components/Stickers/StickerPackEditTitleController/Sources/StickerPackEditTitleController.swift index c94161c26b..12339fecda 100644 --- a/submodules/TelegramUI/Components/Stickers/StickerPackEditTitleController/Sources/StickerPackEditTitleController.swift +++ b/submodules/TelegramUI/Components/Stickers/StickerPackEditTitleController/Sources/StickerPackEditTitleController.swift @@ -255,7 +255,7 @@ private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, UITextF private let maxLength: Int - init(theme: PresentationTheme, placeholder: String, maxLength: Int, keyboardType: UIKeyboardType = .default, returnKeyType: UIReturnKeyType = .done) { + init(theme: PresentationTheme, placeholder: String, maxLength: Int, keyboardType: UIKeyboardType = .default, returnKeyType: UIReturnKeyType = .done, hasClearButton: Bool = false) { self.theme = theme self.maxLength = maxLength @@ -370,6 +370,10 @@ private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, UITextF return false } + if string == " " && updatedText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + return false + } + if self.textInputNode.keyboardType == .asciiCapable { var cleanString = string.folding(options: .diacriticInsensitive, locale: .current).replacingOccurrences(of: " ", with: "_") @@ -506,7 +510,7 @@ private final class ImportStickerPackTitleAlertContentNode: AlertContentNode { return self.isUserInteractionEnabled } - init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], title: String, text: String, placeholder: String, value: String?, maxLength: Int, asciiOnly: Bool = false) { + init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], title: String, text: String, placeholder: String, value: String?, maxLength: Int, asciiOnly: Bool = false, hasClearButton: Bool) { self.strings = strings self.alertTheme = theme self.theme = ptheme @@ -524,7 +528,7 @@ private final class ImportStickerPackTitleAlertContentNode: AlertContentNode { self.activityIndicator = ActivityIndicator(type: .custom(ptheme.rootController.navigationBar.secondaryTextColor, 20.0, 1.5, false), speed: .slow) self.activityIndicator.isHidden = true - self.inputFieldNode = ImportStickerPackTitleInputFieldNode(theme: ptheme, placeholder: placeholder, maxLength: maxLength, keyboardType: asciiOnly ? .asciiCapable : .default, returnKeyType: asciiOnly ? .done : .next) + self.inputFieldNode = ImportStickerPackTitleInputFieldNode(theme: ptheme, placeholder: placeholder, maxLength: maxLength, keyboardType: asciiOnly ? .asciiCapable : .default, returnKeyType: asciiOnly ? .done : .next, hasClearButton: hasClearButton) if asciiOnly { self.inputFieldNode.prefix = "t.me/addstickers/" } @@ -743,7 +747,7 @@ public func stickerPackEditTitleController(context: AccountContext, forceDark: B applyImpl?() })] - let contentNode = ImportStickerPackTitleAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, title: title, text: text, placeholder: placeholder, value: value, maxLength: maxLength) + let contentNode = ImportStickerPackTitleAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, title: title, text: text, placeholder: placeholder, value: value, maxLength: maxLength, hasClearButton: false) contentNode.complete = { applyImpl?() } @@ -805,7 +809,7 @@ public func importStickerPackShortNameController(context: AccountContext, title: applyImpl?() })] - let contentNode = ImportStickerPackTitleAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, title: title, text: text, placeholder: placeholder, value: value, maxLength: maxLength, asciiOnly: true) + let contentNode = ImportStickerPackTitleAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, title: title, text: text, placeholder: placeholder, value: value, maxLength: maxLength, asciiOnly: true, hasClearButton: true) contentNode.complete = { applyImpl?() } diff --git a/submodules/TelegramUI/Sources/ComposeControllerNode.swift b/submodules/TelegramUI/Sources/ComposeControllerNode.swift index 9213c2fd39..59c8835503 100644 --- a/submodules/TelegramUI/Sources/ComposeControllerNode.swift +++ b/submodules/TelegramUI/Sources/ComposeControllerNode.swift @@ -52,7 +52,7 @@ final class ComposeControllerNode: ASDisplayNode { ContactListAdditionalOption(title: self.presentationData.strings.Compose_NewChannel, icon: .generic(UIImage(bundleImageName: "Contact List/CreateChannelActionIcon")!), action: { openCreateNewChannelImpl?() }) - ], includeChatList: false, topPeers: false)), onlyWriteable: false, displayPermissionPlaceholder: false) + ], includeChatList: false, topPeers: .none)), onlyWriteable: false, displayPermissionPlaceholder: false) super.init() diff --git a/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift b/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift index 177d376c49..798de3e5d8 100644 --- a/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift @@ -169,11 +169,17 @@ final class ContactMultiselectionControllerNode: ASDisplayNode { } self.contentNode = .chats(chatListNode) } else { - var displayTopPeers = false - if case .premiumGifting = mode { - displayTopPeers = true + let displayTopPeers: ContactListPresentation.TopPeers + if case let .premiumGifting(topSectionTitle, topSectionPeers) = mode { + if let topSectionTitle { + displayTopPeers = .custom(title: topSectionTitle, peerIds: topSectionPeers) + } else { + displayTopPeers = .recent + } } else if case .requestedUsersSelection = mode { - displayTopPeers = true + displayTopPeers = .recent + } else { + displayTopPeers = .none } let contactListNode = ContactListNode(context: context, presentation: .single(.natural(options: options, includeChatList: includeChatList, topPeers: displayTopPeers)), filters: filters, onlyWriteable: onlyWriteable, selectionState: ContactListNodeGroupSelectionState()) self.contentNode = .contacts(contactListNode) diff --git a/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift b/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift index 99084f28bd..0a446de4a4 100644 --- a/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift @@ -68,7 +68,7 @@ final class ContactSelectionControllerNode: ASDisplayNode { self.filters = filters var contextActionImpl: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? - self.contactListNode = ContactListNode(context: context, updatedPresentationData: (presentationData, self.presentationDataPromise.get()), presentation: .single(.natural(options: options, includeChatList: false, topPeers: false)), filters: filters, onlyWriteable: false, displayCallIcons: displayCallIcons, contextAction: multipleSelection ? { peer, node, gesture, _, _ in + self.contactListNode = ContactListNode(context: context, updatedPresentationData: (presentationData, self.presentationDataPromise.get()), presentation: .single(.natural(options: options, includeChatList: false, topPeers: .none)), filters: filters, onlyWriteable: false, displayCallIcons: displayCallIcons, contextAction: multipleSelection ? { peer, node, gesture, _, _ in contextActionImpl?(peer, node, gesture, nil) } : nil, multipleSelection: multipleSelection) diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 48b0935967..7f573537a9 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -2110,7 +2110,15 @@ public final class SharedAccountContextImpl: SharedAccountContext { let limit: Int32 = 10 var reachedLimitImpl: ((Int32) -> Void)? - let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .premiumGifting, options: [], isPeerEnabled: { peer in + + let mode: ContactMultiselectionControllerMode + if case let .chatList(peerIds) = source { + mode = .premiumGifting(topSectionTitle: "🎂 BIRTHDAY TODAY", topSectionPeers: peerIds) + } else { + mode = .premiumGifting(topSectionTitle: nil, topSectionPeers: []) + } + + let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: mode, options: [], isPeerEnabled: { peer in if case let .user(user) = peer, user.botInfo == nil && !peer.isService && !user.flags.contains(.isSupport) { return true } else { @@ -2309,7 +2317,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { return StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: mainStickerPack, stickerPacks: stickerPacks, loadedStickerPacks: loadedStickerPacks, isEditing: isEditing, parentNavigationController: parentNavigationController, sendSticker: sendSticker) } - public func makeStickerEditorScreen(context: AccountContext, source: Any, transitionArguments: (UIView, CGRect, UIImage?)?, completion: @escaping (TelegramMediaFile) -> Void) -> ViewController { + public func makeStickerEditorScreen(context: AccountContext, source: Any, transitionArguments: (UIView, CGRect, UIImage?)?, completion: @escaping (TelegramMediaFile, @escaping () -> Void) -> Void) -> ViewController { let subject: MediaEditorScreen.Subject let mode: MediaEditorScreen.Mode.StickerEditorMode if let file = source as? TelegramMediaFile { @@ -2342,9 +2350,10 @@ public final class SharedAccountContextImpl: SharedAccountContext { } return nil }, completion: { result, commit in - commit({}) if case let .sticker(file) = result.media { - completion(file) + completion(file, { + commit({}) + }) } } as (MediaEditorScreen.Result, @escaping (@escaping () -> Void) -> Void) -> Void ) diff --git a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift index 16b60c0dad..7e703d8abd 100644 --- a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift +++ b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift @@ -441,8 +441,8 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { var resourceReference: MediaResourceReference? if let thumbnail = info.thumbnail { - if info.flags.contains(.isAnimated) || info.flags.contains(.isVideo) { - thumbnailItem = .animated(EngineMediaResource(thumbnail.resource), thumbnail.dimensions, info.flags.contains(.isVideo)) + if thumbnail.typeHint != .generic { + thumbnailItem = .animated(EngineMediaResource(thumbnail.resource), thumbnail.dimensions, thumbnail.typeHint == .video) } else { thumbnailItem = .still(thumbnail) }