[WIP] Stickers editor

This commit is contained in:
Ilya Laktyushin 2024-03-15 10:55:40 +04:00
parent bd8d299a58
commit 6906df0243
31 changed files with 381 additions and 140 deletions

View File

@ -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

View File

@ -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
}

View File

@ -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?)
}

View File

@ -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 {

View File

@ -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<EnginePeer.Id>, 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<EnginePeer.Id>, 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<RecentPeers, NoError>
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<ContactsListNodeTransition, NoError> in
|> mapToQueue { view, chatListPeers, selectionState, pendingRemovalPeerIds, presentationData, authorizationStatus, warningSuppressed, storySubscriptions, topPeers -> Signal<ContactsListNodeTransition, NoError> in
let signal = deferred { () -> Signal<ContactsListNodeTransition, NoError> 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)

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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

View File

@ -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)
}

View File

@ -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

View File

@ -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

View File

@ -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
)

View File

@ -297,8 +297,8 @@ public func preloadedStickerPackThumbnail(account: Account, info: StickerPackCol
let signal = Signal<Bool, NoError> { 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()

View File

@ -1140,7 +1140,7 @@ private final class StickerPackContainer: ASDisplayNode {
private let stickerPickerInputData = Promise<StickerPickerInput>()
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
}

View File

@ -392,7 +392,7 @@ final class EmojiStickerAccessoryNode: SparseNode, PeekControllerAccessoryNode {
items.append(reaction)
}
let selectedItems = ValuePromise<Set<MessageReaction.Reaction>>()
let selectedItems = ValuePromise<Set<MessageReaction.Reaction>>(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

View File

@ -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)

View File

@ -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
}
}

View File

@ -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

View File

@ -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)
}

View File

@ -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

View File

@ -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: ""

View File

@ -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)
}
}
}

View File

@ -0,0 +1,8 @@
//
// PeerInfoScreenBirthdatePickerItem.swift
// MediaEditorScreen
//
// Created by Ilya Laktyushin on 15.03.2024.
//
import Foundation

View File

@ -38,9 +38,10 @@ final class PeerInfoScreenDisclosureItem: PeerInfoScreenItem {
let text: String
let icon: UIImage?
let iconSignal: Signal<UIImage?, NoError>?
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<UIImage?, NoError>? = nil, action: (() -> Void)?) {
init(id: AnyHashable, label: Label = .none, additionalBadgeLabel: String? = nil, additionalBadgeIcon: UIImage? = nil, text: String, icon: UIImage? = nil, iconSignal: Signal<UIImage?, NoError>? = 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)

View File

@ -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

View File

@ -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?()
}

View File

@ -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()

View File

@ -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)

View File

@ -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)

View File

@ -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
)

View File

@ -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)
}