Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

# Conflicts:
#	submodules/TelegramCore/Sources/TelegramEngine/Peers/AdPeers.swift
This commit is contained in:
Mikhail Filimonov 2025-03-18 14:48:20 +04:00
commit fd20831e92
47 changed files with 1127 additions and 458 deletions

View File

@ -14034,3 +14034,26 @@ Sorry for the inconvenience.";
"Privacy.Gifts.PremiumToast.Action" = "Open"; "Privacy.Gifts.PremiumToast.Action" = "Open";
"Gift.Send.ErrorDisallowed" = "**%@** doesn't accept this kind of gifts."; "Gift.Send.ErrorDisallowed" = "**%@** doesn't accept this kind of gifts.";
"ChatbotSetup.Rights.ManageMessages" = "Manage Messages";
"ChatbotSetup.Rights.ReadMessages" = "Read Messages";
"ChatbotSetup.Rights.ReplyToMessages" = "Reply to Messages";
"ChatbotSetup.Rights.MarkAsRead" = "Mark Messages as Read";
"ChatbotSetup.Rights.DeleteSentMessages" = "Delete Sent Messages";
"ChatbotSetup.Rights.DeleteReceivedMessages" = "Delete Received Messages";
"ChatbotSetup.Rights.ManageProfile" = "Manage Profile";
"ChatbotSetup.Rights.EditName" = "Edit Name";
"ChatbotSetup.Rights.EditBio" = "Edit Bio";
"ChatbotSetup.Rights.EditProfilePhoto" = "Edit Profile Photo";
"ChatbotSetup.Rights.EditUsername" = "Edit Username";
"ChatbotSetup.Rights.ManageGiftsAndStars" = "Manage Gifts and Stars";
"ChatbotSetup.Rights.ViewGifts" = "View Gifts";
"ChatbotSetup.Rights.SellGifts" = "Sell Gifts";
"ChatbotSetup.Rights.ChangeGiftSettings" = "Change Gift Settings";
"ChatbotSetup.Rights.TransferAndUpgradeGifts" = "Transfer and Upgrade Gifts";
"ChatbotSetup.Rights.TransferStars" = "Transfer Stars";
"ChatbotSetup.Rights.ManageStories" = "Manage Stories";
"Gift.Send.Upgrade.ForcedInfo" = "%1$@ accepts only unique gifts. [Learn More >]()";

View File

@ -114,6 +114,7 @@ swift_library(
"//submodules/TelegramUI/Components/LottieComponent", "//submodules/TelegramUI/Components/LottieComponent",
"//submodules/TelegramUI/Components/AvatarUploadToastScreen", "//submodules/TelegramUI/Components/AvatarUploadToastScreen",
"//submodules/TelegramUI/Components/Ads/AdsInfoScreen", "//submodules/TelegramUI/Components/Ads/AdsInfoScreen",
"//submodules/TelegramUI/Components/Ads/AdsReportScreen",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -54,6 +54,7 @@ import OldChannelsController
import TextFormat import TextFormat
import AvatarUploadToastScreen import AvatarUploadToastScreen
import AdsInfoScreen import AdsInfoScreen
import AdsReportScreen
private final class ContextControllerContentSourceImpl: ContextControllerContentSource { private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
let controller: ViewController let controller: ViewController
@ -6131,15 +6132,16 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
self.push(controller) self.push(controller)
} }
func openAdInfo(_ node: ASDisplayNode) { func openAdInfo(node: ASDisplayNode, adPeer: AdPeer) {
let controller = self let controller = self
let referenceView = node.view let referenceView = node.view
let context = self.context let context = self.context
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
//TODO:localize
var actions: [ContextMenuItem] = [] var actions: [ContextMenuItem] = []
//if adAttribute.sponsorInfo != nil || adAttribute.additionalInfo != nil { if adPeer.sponsorInfo != nil || adPeer.additionalInfo != nil {
actions.append(.action(ContextMenuActionItem(text: presentationData.strings.Chat_ContextMenu_AdSponsorInfo, textColor: .primary, icon: { theme in actions.append(.action(ContextMenuActionItem(text: presentationData.strings.Chat_ContextMenu_AdSponsorInfo, textColor: .primary, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Channels"), color: theme.actionSheet.primaryTextColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Channels"), color: theme.actionSheet.primaryTextColor)
}, iconSource: nil, action: { [weak self] c, _ in }, iconSource: nil, action: { [weak self] c, _ in
@ -6154,32 +6156,40 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
subItems.append(.separator) subItems.append(.separator)
// if let sponsorInfo = adAttribute.sponsorInfo { if let sponsorInfo = adPeer.sponsorInfo {
// subItems.append(.action(ContextMenuActionItem(text: sponsorInfo, textColor: .primary, textLayout: .multiline, textFont: .custom(font: Font.regular(floor(presentationData.listsFontSize.baseDisplaySize * 0.8)), height: nil, verticalOffset: nil), badge: nil, icon: { theme in subItems.append(.action(ContextMenuActionItem(text: sponsorInfo, textColor: .primary, textLayout: .multiline, textFont: .custom(font: Font.regular(floor(presentationData.listsFontSize.baseDisplaySize * 0.8)), height: nil, verticalOffset: nil), badge: nil, icon: { theme in
// return nil return nil
// }, iconSource: nil, action: { [weak self] c, _ in }, iconSource: nil, action: { [weak self] c, _ in
// c?.dismiss(completion: { c?.dismiss(completion: {
// UIPasteboard.general.string = sponsorInfo UIPasteboard.general.string = sponsorInfo
//
// self?.displayUndo(.copy(text: presentationData.strings.Chat_ContextMenu_AdSponsorInfoCopied)) if let self {
// }) self.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Chat_ContextMenu_AdSponsorInfoCopied), elevatedLayout: false, action: { _ in
// }))) return true
// } }), in: .current)
// if let additionalInfo = adAttribute.additionalInfo { }
// subItems.append(.action(ContextMenuActionItem(text: additionalInfo, textColor: .primary, textLayout: .multiline, textFont: .custom(font: Font.regular(floor(presentationData.listsFontSize.baseDisplaySize * 0.8)), height: nil, verticalOffset: nil), badge: nil, icon: { theme in })
// return nil })))
// }, iconSource: nil, action: { [weak self] c, _ in }
// c?.dismiss(completion: { if let additionalInfo = adPeer.additionalInfo {
// UIPasteboard.general.string = additionalInfo subItems.append(.action(ContextMenuActionItem(text: additionalInfo, textColor: .primary, textLayout: .multiline, textFont: .custom(font: Font.regular(floor(presentationData.listsFontSize.baseDisplaySize * 0.8)), height: nil, verticalOffset: nil), badge: nil, icon: { theme in
// return nil
// self?.displayUndo(.copy(text: presentationData.strings.Chat_ContextMenu_AdSponsorInfoCopied)) }, iconSource: nil, action: { [weak self] c, _ in
// }) c?.dismiss(completion: {
// }))) UIPasteboard.general.string = additionalInfo
// }
if let self {
self.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Chat_ContextMenu_AdSponsorInfoCopied), elevatedLayout: false, action: { _ in
return true
}), in: .current)
}
})
})))
}
c?.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) c?.pushItems(items: .single(ContextController.Items(content: .list(subItems))))
}))) })))
//} }
actions.append(.action(ContextMenuActionItem(text: "About These Ads", textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in actions.append(.action(ContextMenuActionItem(text: "About These Ads", textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.actionSheet.primaryTextColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.actionSheet.primaryTextColor)
@ -6190,70 +6200,59 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
} }
}))) })))
if "".isEmpty { actions.append(.action(ContextMenuActionItem(text: presentationData.strings.Chat_ContextMenu_ReportAd, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in
actions.append(.action(ContextMenuActionItem(text: presentationData.strings.Chat_ContextMenu_ReportAd, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.actionSheet.primaryTextColor)
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.actionSheet.primaryTextColor) }, iconSource: nil, action: { [weak self] _, f in
}, iconSource: nil, action: { [weak self] _, f in f(.default)
f(.default)
guard let navigationController = self?.navigationController as? NavigationController else {
guard let navigationController = self?.navigationController as? NavigationController else { return
return }
let _ = (context.engine.messages.reportAdMessage(opaqueId: adPeer.opaqueId, option: nil)
|> deliverOnMainQueue).start(next: { [weak navigationController] result in
if case let .options(title, options) = result {
Queue.mainQueue().after(0.2) {
navigationController?.pushViewController(
AdsReportScreen(
context: context,
opaqueId: adPeer.opaqueId,
title: title,
options: options,
completed: {
//removeAd?(adAttribute.opaqueId)
}
)
)
}
} }
let _ = navigationController })
//.dismiss(animated: true) })))
// let _ = (context.engine.messages.reportAdMessage(peerId: message.id.peerId, opaqueId: adAttribute.opaqueId, option: nil) actions.append(.separator)
// |> deliverOnMainQueue).start(next: { [weak navigationController] result in
// if case let .options(title, options) = result { actions.append(.action(ContextMenuActionItem(text: presentationData.strings.Chat_ContextMenu_RemoveAd, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in
// Queue.mainQueue().after(0.2) { return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.primaryTextColor)
// navigationController?.pushViewController( }, iconSource: nil, action: { [weak self] c, _ in
// AdsReportScreen( guard let navigationController = self?.navigationController as? NavigationController else {
// context: context, return
// peerId: message.id.peerId, }
// opaqueId: adAttribute.opaqueId, c?.dismiss(completion: {
// title: title, if context.isPremium && !"".isEmpty {
// options: options, //removeAd?(adAttribute.opaqueId)
// completed: { } else {
// removeAd?(adAttribute.opaqueId) var replaceImpl: ((ViewController) -> Void)?
// } let demoController = context.sharedContext.makePremiumDemoController(context: context, subject: .noAds, forceDark: false, action: {
// ) let controller = context.sharedContext.makePremiumIntroController(context: context, source: .ads, forceDark: false, dismissed: nil)
// ) replaceImpl?(controller)
// } }, dismissed: nil)
// } replaceImpl = { [weak demoController] c in
// }) demoController?.replace(with: c)
}))) }
navigationController.pushViewController(demoController)
actions.append(.separator) }
})
actions.append(.action(ContextMenuActionItem(text: presentationData.strings.Chat_ContextMenu_RemoveAd, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in })))
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.primaryTextColor)
}, iconSource: nil, action: { [weak self] c, _ in
c?.dismiss(completion: {
let _ = self
// if context.isPremium {
// removeAd?(adAttribute.opaqueId)
// } else {
// self?.presentNoAdsDemo()
// }
})
})))
}
// } else {
// if !actions.isEmpty {
// actions.append(.separator)
// }
// actions.append(.action(ContextMenuActionItem(text: presentationData.strings.SponsoredMessageMenu_Hide, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in
// return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.primaryTextColor)
// }, iconSource: nil, action: { [weak self] c, _ in
// c?.dismiss(completion: {
// if context.isPremium {
// removeAd?(adAttribute.opaqueId)
// } else {
// self?.presentNoAdsDemo()
// }
// })
// })))
// }
let contextController = ContextController(presentationData: presentationData, source: .reference(AdsInfoContextReferenceContentSource(controller: controller, sourceView: referenceView, insets: .zero, contentInsets: .zero)), items: .single(ContextController.Items(content: .list(actions))), gesture: nil) let contextController = ContextController(presentationData: presentationData, source: .reference(AdsInfoContextReferenceContentSource(controller: controller, sourceView: referenceView, insets: .zero, contentInsets: .zero)), items: .single(ContextController.Items(content: .list(actions))), gesture: nil)
controller.presentInGlobalOverlay(contextController) controller.presentInGlobalOverlay(contextController)

View File

@ -1684,8 +1684,8 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
contentNode.dismissSearch = { [weak self] in contentNode.dismissSearch = { [weak self] in
self?.dismissSearch?() self?.dismissSearch?()
} }
contentNode.openAdInfo = { [weak self] node in contentNode.openAdInfo = { [weak self] node, adPeer in
self?.controller?.openAdInfo(node) self?.controller?.openAdInfo(node: node, adPeer: adPeer)
} }
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .list, contentNode: contentNode, cancel: { [weak self] in self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .list, contentNode: contentNode, cancel: { [weak self] in

View File

@ -62,9 +62,9 @@ final class ChatListSearchInteraction {
let openStories: ((PeerId, ASDisplayNode) -> Void)? let openStories: ((PeerId, ASDisplayNode) -> Void)?
let switchToFilter: (ChatListSearchPaneKey) -> Void let switchToFilter: (ChatListSearchPaneKey) -> Void
let dismissSearch: () -> Void let dismissSearch: () -> Void
let openAdInfo: (ASDisplayNode) -> Void let openAdInfo: (ASDisplayNode, AdPeer) -> Void
init(openPeer: @escaping (EnginePeer, EnginePeer?, Int64?, Bool) -> Void, openDisabledPeer: @escaping (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void, openMessage: @escaping (EnginePeer, Int64?, EngineMessage.Id, Bool) -> Void, openUrl: @escaping (String) -> Void, clearRecentSearch: @escaping () -> Void, addContact: @escaping (String) -> Void, toggleMessageSelection: @escaping (EngineMessage.Id, Bool) -> Void, messageContextAction: @escaping ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int64, isFirstInList: Bool)?) -> Void), mediaMessageContextAction: @escaping ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void), peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, getSelectedMessageIds: @escaping () -> Set<EngineMessage.Id>?, openStories: ((PeerId, ASDisplayNode) -> Void)?, switchToFilter: @escaping (ChatListSearchPaneKey) -> Void, dismissSearch: @escaping () -> Void, openAdInfo: @escaping (ASDisplayNode) -> Void) { init(openPeer: @escaping (EnginePeer, EnginePeer?, Int64?, Bool) -> Void, openDisabledPeer: @escaping (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void, openMessage: @escaping (EnginePeer, Int64?, EngineMessage.Id, Bool) -> Void, openUrl: @escaping (String) -> Void, clearRecentSearch: @escaping () -> Void, addContact: @escaping (String) -> Void, toggleMessageSelection: @escaping (EngineMessage.Id, Bool) -> Void, messageContextAction: @escaping ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int64, isFirstInList: Bool)?) -> Void), mediaMessageContextAction: @escaping ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void), peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, getSelectedMessageIds: @escaping () -> Set<EngineMessage.Id>?, openStories: ((PeerId, ASDisplayNode) -> Void)?, switchToFilter: @escaping (ChatListSearchPaneKey) -> Void, dismissSearch: @escaping () -> Void, openAdInfo: @escaping (ASDisplayNode, AdPeer) -> Void) {
self.openPeer = openPeer self.openPeer = openPeer
self.openDisabledPeer = openDisabledPeer self.openDisabledPeer = openDisabledPeer
self.openMessage = openMessage self.openMessage = openMessage
@ -105,7 +105,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
private let navigationController: NavigationController? private let navigationController: NavigationController?
var dismissSearch: (() -> Void)? var dismissSearch: (() -> Void)?
var openAdInfo: ((ASDisplayNode) -> Void)? var openAdInfo: ((ASDisplayNode, AdPeer) -> Void)?
private let dimNode: ASDisplayNode private let dimNode: ASDisplayNode
let filterContainerNode: ChatListSearchFiltersContainerNode let filterContainerNode: ChatListSearchFiltersContainerNode
@ -307,8 +307,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
} }
}, dismissSearch: { [weak self] in }, dismissSearch: { [weak self] in
self?.dismissSearch?() self?.dismissSearch?()
}, openAdInfo: { [weak self] node in }, openAdInfo: { [weak self] node, adPeer in
self?.openAdInfo?(node) self?.openAdInfo?(node, adPeer)
}) })
self.paneContainerNode.interaction = interaction self.paneContainerNode.interaction = interaction

View File

@ -439,8 +439,9 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
case topic(EnginePeer, ChatListItemContent.ThreadInfo, Int, PresentationTheme, PresentationStrings, ChatListSearchSectionExpandType) case topic(EnginePeer, ChatListItemContent.ThreadInfo, Int, PresentationTheme, PresentationStrings, ChatListSearchSectionExpandType)
case recentlySearchedPeer(EnginePeer, EnginePeer?, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, PeerStoryStats?, Bool) case recentlySearchedPeer(EnginePeer, EnginePeer?, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, PeerStoryStats?, Bool)
case adPeer(AdPeer, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType, String?)
case localPeer(EnginePeer, EnginePeer?, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType, PeerStoryStats?, Bool, Bool) case localPeer(EnginePeer, EnginePeer?, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType, PeerStoryStats?, Bool, Bool)
case globalPeer(FoundPeer, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType, PeerStoryStats?, Bool, String?, Bool) case globalPeer(FoundPeer, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType, PeerStoryStats?, Bool, String?)
case message(EngineMessage, EngineRenderedPeer, EnginePeerReadCounters?, EngineMessageHistoryThread.Info?, ChatListPresentationData, Int32, Bool?, Bool, MessageOrderingKey, (id: String, size: Int64, isFirstInList: Bool)?, MessageSection, Bool, PeerStoryStats?, Bool, TelegramSearchPeersScope) case message(EngineMessage, EngineRenderedPeer, EnginePeerReadCounters?, EngineMessageHistoryThread.Info?, ChatListPresentationData, Int32, Bool?, Bool, MessageOrderingKey, (id: String, size: Int64, isFirstInList: Bool)?, MessageSection, Bool, PeerStoryStats?, Bool, TelegramSearchPeersScope)
case messagePlaceholder(Int32, ChatListPresentationData, TelegramSearchPeersScope) case messagePlaceholder(Int32, ChatListPresentationData, TelegramSearchPeersScope)
case emptyMessagesFooter(ChatListPresentationData, TelegramSearchPeersScope, String?) case emptyMessagesFooter(ChatListPresentationData, TelegramSearchPeersScope, String?)
@ -454,7 +455,9 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
return .localPeerId(peer.id) return .localPeerId(peer.id)
case let .localPeer(peer, _, _, _, _, _, _, _, _, _, _, _): case let .localPeer(peer, _, _, _, _, _, _, _, _, _, _, _):
return .localPeerId(peer.id) return .localPeerId(peer.id)
case let .globalPeer(peer, _, _, _, _, _, _, _, _, _, _, _): case let .adPeer(peer, _, _, _, _, _, _, _):
return .globalPeerId(peer.peer.id)
case let .globalPeer(peer, _, _, _, _, _, _, _, _, _, _):
return .globalPeerId(peer.peer.id) return .globalPeerId(peer.peer.id)
case let .message(message, _, _, _, _, _, _, _, _, _, section, _, _, _, _): case let .message(message, _, _, _, _, _, _, _, _, _, section, _, _, _, _):
return .messageId(message.id, section) return .messageId(message.id, section)
@ -487,8 +490,14 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
} else { } else {
return false return false
} }
case let .globalPeer(lhsPeer, lhsUnreadBadge, lhsIndex, lhsTheme, lhsStrings, lhsSortOrder, lhsDisplayOrder, lhsExpandType, lhsStoryStats, lhsRequiresPremiumForMessaging, lhsQuery, lhsIsAd): case let .adPeer(lhsPeer, lhsIndex, lhsTheme, lhsStrings, lhsSortOrder, lhsDisplayOrder, lhsExpandType, lhsQuery):
if case let .globalPeer(rhsPeer, rhsUnreadBadge, rhsIndex, rhsTheme, rhsStrings, rhsSortOrder, rhsDisplayOrder, rhsExpandType, rhsStoryStats, rhsRequiresPremiumForMessaging, rhsQuery, rhsIsAd) = rhs, lhsPeer == rhsPeer && lhsIndex == rhsIndex && lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsSortOrder == rhsSortOrder && lhsDisplayOrder == rhsDisplayOrder && lhsUnreadBadge?.0 == rhsUnreadBadge?.0 && lhsUnreadBadge?.1 == rhsUnreadBadge?.1 && lhsExpandType == rhsExpandType && lhsStoryStats == rhsStoryStats && lhsRequiresPremiumForMessaging == rhsRequiresPremiumForMessaging, lhsQuery == rhsQuery, lhsIsAd == rhsIsAd { if case let .adPeer(rhsPeer, rhsIndex, rhsTheme, rhsStrings, rhsSortOrder, rhsDisplayOrder, rhsExpandType, rhsQuery) = rhs, lhsPeer == rhsPeer && lhsIndex == rhsIndex && lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsSortOrder == rhsSortOrder && lhsDisplayOrder == rhsDisplayOrder && lhsExpandType == rhsExpandType && lhsQuery == rhsQuery {
return true
} else {
return false
}
case let .globalPeer(lhsPeer, lhsUnreadBadge, lhsIndex, lhsTheme, lhsStrings, lhsSortOrder, lhsDisplayOrder, lhsExpandType, lhsStoryStats, lhsRequiresPremiumForMessaging, lhsQuery):
if case let .globalPeer(rhsPeer, rhsUnreadBadge, rhsIndex, rhsTheme, rhsStrings, rhsSortOrder, rhsDisplayOrder, rhsExpandType, rhsStoryStats, rhsRequiresPremiumForMessaging, rhsQuery) = rhs, lhsPeer == rhsPeer && lhsIndex == rhsIndex && lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsSortOrder == rhsSortOrder && lhsDisplayOrder == rhsDisplayOrder && lhsUnreadBadge?.0 == rhsUnreadBadge?.0 && lhsUnreadBadge?.1 == rhsUnreadBadge?.1 && lhsExpandType == rhsExpandType && lhsStoryStats == rhsStoryStats && lhsRequiresPremiumForMessaging == rhsRequiresPremiumForMessaging, lhsQuery == rhsQuery {
return true return true
} else { } else {
return false return false
@ -620,14 +629,23 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
return false return false
case let .localPeer(_, _, _, rhsIndex, _, _, _, _, _, _, _, _): case let .localPeer(_, _, _, rhsIndex, _, _, _, _, _, _, _, _):
return lhsIndex <= rhsIndex return lhsIndex <= rhsIndex
case .globalPeer, .message, .messagePlaceholder, .emptyMessagesFooter, .addContact: case .adPeer, .globalPeer, .message, .messagePlaceholder, .emptyMessagesFooter, .addContact:
return true return true
} }
case let .globalPeer(_, _, lhsIndex, _, _, _, _, _, _, _, _, _): case let .adPeer(_, lhsIndex, _, _, _, _, _, _):
switch rhs { switch rhs {
case .topic, .recentlySearchedPeer, .localPeer: case .topic, .recentlySearchedPeer, .localPeer:
return false return false
case let .globalPeer(_, _, rhsIndex, _, _, _, _, _, _, _, _, _): case let .adPeer(_, rhsIndex, _, _, _, _, _, _):
return lhsIndex <= rhsIndex
case .globalPeer, .message, .messagePlaceholder, .emptyMessagesFooter, .addContact:
return true
}
case let .globalPeer(_, _, lhsIndex, _, _, _, _, _, _, _, _):
switch rhs {
case .topic, .recentlySearchedPeer, .localPeer, .adPeer:
return false
case let .globalPeer(_, _, rhsIndex, _, _, _, _, _, _, _, _):
return lhsIndex <= rhsIndex return lhsIndex <= rhsIndex
case .message, .messagePlaceholder, .emptyMessagesFooter, .addContact: case .message, .messagePlaceholder, .emptyMessagesFooter, .addContact:
return true return true
@ -808,6 +826,51 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
openStories(peer.id, sourceNode.avatarNode) openStories(peer.id, sourceNode.avatarNode)
} }
}) })
case let .adPeer(peer, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType, _):
let enabled = true
var suffixString = ""
if let subscribers = peer.subscribers, subscribers != 0 {
if case .user = peer.peer {
suffixString = ", \(strings.Conversation_StatusBotSubscribers(subscribers))"
} else if case let .channel(channel) = peer.peer, case .broadcast = channel.info {
suffixString = ", \(strings.Conversation_StatusSubscribers(subscribers))"
} else {
suffixString = ", \(strings.Conversation_StatusMembers(subscribers))"
}
}
let header: ChatListSearchItemHeader?
let actionTitle: String?
switch expandType {
case .none:
actionTitle = nil
case .expand:
actionTitle = strings.ChatList_Search_ShowMore
case .collapse:
actionTitle = strings.ChatList_Search_ShowLess
}
header = ChatListSearchItemHeader(type: .globalPeers, theme: theme, strings: strings, actionTitle: actionTitle, action: actionTitle == nil ? nil : { _ in
toggleExpandGlobalResults()
})
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .generalSearch(isSavedMessages: false), peer: .peer(peer: peer.peer, chatPeer: peer.peer), status: .addressName(suffixString), badge: nil, requiresPremiumForMessaging: false, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, searchQuery: nil, isAd: true, action: { _ in
interaction.peerSelected(peer.peer, nil, nil, nil, false)
}, disabledAction: { _ in
interaction.disabledPeerSelected(peer.peer, nil, .generic)
}, contextAction: peerContextAction.flatMap { peerContextAction in
return { node, gesture, location in
peerContextAction(peer.peer, .search(nil), node, gesture, location)
}
}, animationCache: interaction.animationCache, animationRenderer: interaction.animationRenderer, storyStats: nil, openStories: { itemPeer, sourceNode in
guard case let .peer(_, chatPeer) = itemPeer, let peer = chatPeer else {
return
}
if let sourceNode = sourceNode as? ContactsPeerItemNode {
openStories(peer.id, sourceNode.avatarNode)
}
}, adButtonAction: { node in
interaction.openAdInfo(node, peer)
})
case let .localPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType, storyStats, requiresPremiumForMessaging, isSelf): case let .localPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType, storyStats, requiresPremiumForMessaging, isSelf):
let primaryPeer: EnginePeer let primaryPeer: EnginePeer
var chatPeer: EnginePeer? var chatPeer: EnginePeer?
@ -942,7 +1005,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
openStories(peer.id, sourceNode.avatarNode) openStories(peer.id, sourceNode.avatarNode)
} }
}) })
case let .globalPeer(peer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType, storyStats, requiresPremiumForMessaging, query, isAd): case let .globalPeer(peer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType, storyStats, requiresPremiumForMessaging, query):
var enabled = true var enabled = true
if filter.contains(.onlyWriteable) { if filter.contains(.onlyWriteable) {
enabled = canSendMessagesToPeer(peer.peer) enabled = canSendMessagesToPeer(peer.peer)
@ -1002,7 +1065,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
isSavedMessages = true isSavedMessages = true
} }
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .generalSearch(isSavedMessages: isSavedMessages), peer: .peer(peer: EnginePeer(peer.peer), chatPeer: EnginePeer(peer.peer)), status: .addressName(suffixString), badge: badge, requiresPremiumForMessaging: requiresPremiumForMessaging, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, searchQuery: query, isAd: isAd, action: { _ in return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .generalSearch(isSavedMessages: isSavedMessages), peer: .peer(peer: EnginePeer(peer.peer), chatPeer: EnginePeer(peer.peer)), status: .addressName(suffixString), badge: badge, requiresPremiumForMessaging: requiresPremiumForMessaging, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, searchQuery: query, isAd: false, action: { _ in
interaction.peerSelected(EnginePeer(peer.peer), nil, nil, nil, false) interaction.peerSelected(EnginePeer(peer.peer), nil, nil, nil, false)
}, disabledAction: { _ in }, disabledAction: { _ in
interaction.disabledPeerSelected(EnginePeer(peer.peer), nil, requiresPremiumForMessaging ? .premiumRequired : .generic) interaction.disabledPeerSelected(EnginePeer(peer.peer), nil, requiresPremiumForMessaging ? .premiumRequired : .generic)
@ -1019,8 +1082,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
if let sourceNode = sourceNode as? ContactsPeerItemNode { if let sourceNode = sourceNode as? ContactsPeerItemNode {
openStories(peer.id, sourceNode.avatarNode) openStories(peer.id, sourceNode.avatarNode)
} }
}, adButtonAction: { node in }, adButtonAction: { _ in
interaction.openAdInfo(node)
}) })
case let .message(message, peer, readState, threadInfo, presentationData, _, selected, displayCustomHeader, orderingKey, _, section, allPaused, storyStats, requiresPremiumForMessaging, searchScope): case let .message(message, peer, readState, threadInfo, presentationData, _, selected, displayCustomHeader, orderingKey, _, section, allPaused, storyStats, requiresPremiumForMessaging, searchScope):
let header: ChatListSearchItemHeader let header: ChatListSearchItemHeader
@ -1791,7 +1853,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
self.mediaNode.isHidden = true self.mediaNode.isHidden = true
self.recentListNode.isHidden = peersFilter.contains(.excludeRecent) self.recentListNode.isHidden = peersFilter.contains(.excludeRecent)
let currentRemotePeers = Atomic<([FoundPeer], [FoundPeer])?>(value: nil) let currentRemotePeers = Atomic<([FoundPeer], [FoundPeer], [AdPeer])?>(value: nil)
let presentationDataPromise = self.presentationDataPromise let presentationDataPromise = self.presentationDataPromise
let searchStatePromise = self.searchStatePromise let searchStatePromise = self.searchStatePromise
let selectionPromise = self.selectedMessagesPromise let selectionPromise = self.selectedMessagesPromise
@ -2351,35 +2413,40 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
let _ = previousRecentlySearchedPeersState.swap(nil) let _ = previousRecentlySearchedPeersState.swap(nil)
} }
let foundRemotePeers: Signal<([FoundPeer], [FoundPeer], Bool), NoError> let foundRemotePeers: Signal<([FoundPeer], [FoundPeer], [AdPeer], Bool), NoError>
let currentRemotePeersValue: ([FoundPeer], [FoundPeer]) = currentRemotePeers.with { $0 } ?? ([], []) let currentRemotePeersValue: ([FoundPeer], [FoundPeer], [AdPeer]) = currentRemotePeers.with { $0 } ?? ([], [], [])
if case .savedMessagesChats = location { if case .savedMessagesChats = location {
foundRemotePeers = .single(([], [], false)) foundRemotePeers = .single(([], [], [], false))
} else if let query = query, case .chats = key { } else if let query = query, case .chats = key {
if query.hasPrefix("#") { if query.hasPrefix("#") {
foundRemotePeers = .single(([], [], false)) foundRemotePeers = .single(([], [], [], false))
} else { } else {
foundRemotePeers = ( foundRemotePeers = (
.single((currentRemotePeersValue.0, currentRemotePeersValue.1, true)) .single((currentRemotePeersValue.0, currentRemotePeersValue.1, currentRemotePeersValue.2, true))
|> then( |> then(
globalPeerSearchContext.searchRemotePeers(engine: context.engine, query: query) globalPeerSearchContext.searchRemotePeers(engine: context.engine, query: query)
|> map { ($0.0, $0.1, false) } |> mapToSignal { result in
return context.engine.peers.searchAdPeers(query: query)
|> map { adPeers in
return (result.0, result.1, adPeers, false)
}
}
) )
) )
} }
} else if let query = query, case .channels = key { } else if let query = query, case .channels = key {
foundRemotePeers = ( foundRemotePeers = (
.single((currentRemotePeersValue.0, currentRemotePeersValue.1, true)) .single((currentRemotePeersValue.0, currentRemotePeersValue.1, currentRemotePeersValue.2, true))
|> then( |> then(
globalPeerSearchContext.searchRemotePeers(engine: context.engine, query: query, scope: .channels) globalPeerSearchContext.searchRemotePeers(engine: context.engine, query: query, scope: .channels)
|> map { ($0.0, $0.1, false) } |> map { ($0.0, $0.1, [], false) }
) )
) )
} else if let query, case .apps = key { } else if let query, case .apps = key {
let _ = query let _ = query
foundRemotePeers = .single(([], [], false)) foundRemotePeers = .single(([], [], [], false))
} else { } else {
foundRemotePeers = .single(([], [], false)) foundRemotePeers = .single(([], [], [], false))
} }
let searchLocations: [SearchMessagesLocation] let searchLocations: [SearchMessagesLocation]
if let options = options { if let options = options {
@ -2661,7 +2728,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
foundThreads foundThreads
) )
|> map { accountPeer, foundLocalPeers, foundRemotePeers, foundRemoteMessages, foundPublicMessages, presentationData, searchState, selectionState, resolvedMessage, recentPeers, allAndFoundThreads -> ([ChatListSearchEntry], Bool)? in |> map { accountPeer, foundLocalPeers, foundRemotePeers, foundRemoteMessages, foundPublicMessages, presentationData, searchState, selectionState, resolvedMessage, recentPeers, allAndFoundThreads -> ([ChatListSearchEntry], Bool)? in
let isSearching = foundRemotePeers.2 || foundRemoteMessages.1 || foundPublicMessages.1 let isSearching = foundRemotePeers.3 || foundRemoteMessages.1 || foundPublicMessages.1
var entries: [ChatListSearchEntry] = [] var entries: [ChatListSearchEntry] = []
var index = 0 var index = 0
@ -2677,7 +2744,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
recentPeers = [] recentPeers = []
} }
let _ = currentRemotePeers.swap((foundRemotePeers.0, foundRemotePeers.1)) let _ = currentRemotePeers.swap((foundRemotePeers.0, foundRemotePeers.1, foundRemotePeers.2))
let filteredPeer: (EnginePeer, EnginePeer) -> Bool = { peer, accountPeer in let filteredPeer: (EnginePeer, EnginePeer) -> Bool = { peer, accountPeer in
if let requestPeerType { if let requestPeerType {
@ -2882,7 +2949,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
index += 1 index += 1
} }
} }
if peersFilter.contains(.includeSelf) { if peersFilter.contains(.includeSelf) {
for renderedPeer in foundLocalPeers.peers { for renderedPeer in foundLocalPeers.peers {
if renderedPeer.peerId == context.account.peerId, let peer = renderedPeer.peers[renderedPeer.peerId], filteredPeer(peer, EnginePeer(accountPeer)) { if renderedPeer.peerId == context.account.peerId, let peer = renderedPeer.peers[renderedPeer.peerId], filteredPeer(peer, EnginePeer(accountPeer)) {
@ -2962,7 +3029,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
} }
} }
} }
for peer in foundRemotePeers.0 { for peer in foundRemotePeers.0 {
if case .expand = localExpandType, numberOfLocalPeers >= 3 { if case .expand = localExpandType, numberOfLocalPeers >= 3 {
break break
@ -2978,6 +3045,14 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
var numberOfGlobalPeers = 0 var numberOfGlobalPeers = 0
index = 0 index = 0
for peer in foundRemotePeers.2 {
if !existingPeerIds.contains(peer.peer.id) {
existingPeerIds.insert(peer.peer.id)
entries.append(.adPeer(peer, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, globalExpandType, finalQuery))
index += 1
}
}
if let _ = tagMask { if let _ = tagMask {
} else { } else {
for peer in foundRemotePeers.1 { for peer in foundRemotePeers.1 {
@ -2986,14 +3061,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
} }
if !existingPeerIds.contains(peer.peer.id), filteredPeer(EnginePeer(peer.peer), EnginePeer(accountPeer)) { if !existingPeerIds.contains(peer.peer.id), filteredPeer(EnginePeer(peer.peer), EnginePeer(accountPeer)) {
//TODO:unmock
var isAd = !"".isEmpty
#if DEBUG
isAd = numberOfGlobalPeers == 0
#endif
existingPeerIds.insert(peer.peer.id) existingPeerIds.insert(peer.peer.id)
entries.append(.globalPeer(peer, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, globalExpandType, nil, false, finalQuery, isAd)) entries.append(.globalPeer(peer, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, globalExpandType, nil, false, finalQuery))
index += 1 index += 1
numberOfGlobalPeers += 1 numberOfGlobalPeers += 1
} }
@ -3013,7 +3082,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
} }
var firstHeaderId: Int64? var firstHeaderId: Int64?
if !foundRemotePeers.2 { if !foundRemotePeers.3 {
index = 0 index = 0
var existingPostIds = Set<MessageId>() var existingPostIds = Set<MessageId>()
for foundPublicMessageSet in foundPublicMessages.0 { for foundPublicMessageSet in foundPublicMessages.0 {
@ -3279,8 +3348,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}, editPeer: { _ in }, editPeer: { _ in
}, openWebApp: { _ in }, openWebApp: { _ in
}, openPhotoSetup: { }, openPhotoSetup: {
}, openAdInfo: { node in }, openAdInfo: { node, adPeer in
interaction.openAdInfo(node) interaction.openAdInfo(node, adPeer)
}, openAccountFreezeInfo: { }, openAccountFreezeInfo: {
}) })
chatListInteraction.isSearchMode = true chatListInteraction.isSearchMode = true
@ -3410,7 +3479,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
if case let .user(user) = peer, user.flags.contains(.requirePremium) { if case let .user(user) = peer, user.flags.contains(.requirePremium) {
requiresPremiumForMessagingPeerIds.append(peer.id) requiresPremiumForMessagingPeerIds.append(peer.id)
} }
case let .globalPeer(foundPeer, _, _, _, _, _, _, _, _, _, _, _): case let .globalPeer(foundPeer, _, _, _, _, _, _, _, _, _, _):
storyStatsIds.append(foundPeer.peer.id) storyStatsIds.append(foundPeer.peer.id)
if let user = foundPeer.peer as? TelegramUser, user.flags.contains(.requirePremium) { if let user = foundPeer.peer as? TelegramUser, user.flags.contains(.requirePremium) {
requiresPremiumForMessagingPeerIds.append(foundPeer.peer.id) requiresPremiumForMessagingPeerIds.append(foundPeer.peer.id)
@ -3451,8 +3520,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
mappedItems[i] = .recentlySearchedPeer(peer, associatedPeer, unreadBadge, index, theme, strings, sortOrder, displayOrder, stats[peer.id] ?? nil, requiresPremiumForMessaging[peer.id] ?? false) mappedItems[i] = .recentlySearchedPeer(peer, associatedPeer, unreadBadge, index, theme, strings, sortOrder, displayOrder, stats[peer.id] ?? nil, requiresPremiumForMessaging[peer.id] ?? false)
case let .localPeer(peer, associatedPeer, unreadBadge, index, theme, strings, sortOrder, displayOrder, expandType, _, _, isSelf): case let .localPeer(peer, associatedPeer, unreadBadge, index, theme, strings, sortOrder, displayOrder, expandType, _, _, isSelf):
mappedItems[i] = .localPeer(peer, associatedPeer, unreadBadge, index, theme, strings, sortOrder, displayOrder, expandType, stats[peer.id] ?? nil, requiresPremiumForMessaging[peer.id] ?? false, isSelf) mappedItems[i] = .localPeer(peer, associatedPeer, unreadBadge, index, theme, strings, sortOrder, displayOrder, expandType, stats[peer.id] ?? nil, requiresPremiumForMessaging[peer.id] ?? false, isSelf)
case let .globalPeer(peer, unreadBadge, index, theme, strings, sortOrder, displayOrder, expandType, _, _, searchQuery, isAd): case let .globalPeer(peer, unreadBadge, index, theme, strings, sortOrder, displayOrder, expandType, _, _, searchQuery):
mappedItems[i] = .globalPeer(peer, unreadBadge, index, theme, strings, sortOrder, displayOrder, expandType, stats[peer.peer.id] ?? nil, requiresPremiumForMessaging[peer.peer.id] ?? false, searchQuery, isAd) mappedItems[i] = .globalPeer(peer, unreadBadge, index, theme, strings, sortOrder, displayOrder, expandType, stats[peer.peer.id] ?? nil, requiresPremiumForMessaging[peer.peer.id] ?? false, searchQuery)
case let .message(message, peer, combinedPeerReadState, threadInfo, presentationData, totalCount, selected, displayCustomHeader, key, resourceId, section, allPaused, _, _, searchScope): case let .message(message, peer, combinedPeerReadState, threadInfo, presentationData, totalCount, selected, displayCustomHeader, key, resourceId, section, allPaused, _, _, searchScope):
mappedItems[i] = .message(message, peer, combinedPeerReadState, threadInfo, presentationData, totalCount, selected, displayCustomHeader, key, resourceId, section, allPaused, stats[peer.peerId] ?? nil, requiresPremiumForMessaging[peer.peerId] ?? false, searchScope) mappedItems[i] = .message(message, peer, combinedPeerReadState, threadInfo, presentationData, totalCount, selected, displayCustomHeader, key, resourceId, section, allPaused, stats[peer.peerId] ?? nil, requiresPremiumForMessaging[peer.peerId] ?? false, searchScope)
default: default:
@ -5263,7 +5332,7 @@ public final class ChatListSearchShimmerNode: ASDisplayNode {
}, editPeer: { _ in }, editPeer: { _ in
}, openWebApp: { _ in }, openWebApp: { _ in
}, openPhotoSetup: { }, openPhotoSetup: {
}, openAdInfo: { _ in }, openAdInfo: { _, _ in
}, openAccountFreezeInfo: { }, openAccountFreezeInfo: {
}) })
var isInlineMode = false var isInlineMode = false

View File

@ -161,7 +161,7 @@ public final class ChatListShimmerNode: ASDisplayNode {
}, editPeer: { _ in }, editPeer: { _ in
}, openWebApp: { _ in }, openWebApp: { _ in
}, openPhotoSetup: { }, openPhotoSetup: {
}, openAdInfo: { _ in }, openAdInfo: { _, _ in
}, openAccountFreezeInfo: { }, openAccountFreezeInfo: {
}) })
interaction.isInlineMode = isInlineMode interaction.isInlineMode = isInlineMode

View File

@ -114,7 +114,7 @@ public final class ChatListNodeInteraction {
let editPeer: (ChatListItem) -> Void let editPeer: (ChatListItem) -> Void
let openWebApp: (TelegramUser) -> Void let openWebApp: (TelegramUser) -> Void
let openPhotoSetup: () -> Void let openPhotoSetup: () -> Void
let openAdInfo: (ASDisplayNode) -> Void let openAdInfo: (ASDisplayNode, AdPeer) -> Void
let openAccountFreezeInfo: () -> Void let openAccountFreezeInfo: () -> Void
public var searchTextHighightState: String? public var searchTextHighightState: String?
@ -174,7 +174,7 @@ public final class ChatListNodeInteraction {
editPeer: @escaping (ChatListItem) -> Void, editPeer: @escaping (ChatListItem) -> Void,
openWebApp: @escaping (TelegramUser) -> Void, openWebApp: @escaping (TelegramUser) -> Void,
openPhotoSetup: @escaping () -> Void, openPhotoSetup: @escaping () -> Void,
openAdInfo: @escaping (ASDisplayNode) -> Void, openAdInfo: @escaping (ASDisplayNode, AdPeer) -> Void,
openAccountFreezeInfo: @escaping () -> Void openAccountFreezeInfo: @escaping () -> Void
) { ) {
self.activateSearch = activateSearch self.activateSearch = activateSearch
@ -1245,7 +1245,7 @@ public final class ChatListNode: ListView {
public var openStarsTopup: ((Int64?) -> Void)? public var openStarsTopup: ((Int64?) -> Void)?
public var openWebApp: ((TelegramUser) -> Void)? public var openWebApp: ((TelegramUser) -> Void)?
public var openPhotoSetup: (() -> Void)? public var openPhotoSetup: (() -> Void)?
public var openAdInfo: ((ASDisplayNode) -> Void)? public var openAdInfo: ((ASDisplayNode, AdPeer) -> Void)?
public var openAccountFreezeInfo: (() -> Void)? public var openAccountFreezeInfo: (() -> Void)?
private var theme: PresentationTheme private var theme: PresentationTheme
@ -1905,8 +1905,8 @@ public final class ChatListNode: ListView {
return return
} }
self.openPhotoSetup?() self.openPhotoSetup?()
}, openAdInfo: { [weak self] node in }, openAdInfo: { [weak self] node, adPeer in
self?.openAdInfo?(node) self?.openAdInfo?(node, adPeer)
}, openAccountFreezeInfo: { [weak self] in }, openAccountFreezeInfo: { [weak self] in
self?.openAccountFreezeInfo?() self?.openAccountFreezeInfo?()
}) })

View File

@ -569,14 +569,13 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
}, iconSource: nil, action: { [weak self] _, f in }, iconSource: nil, action: { [weak self] _, f in
f(.default) f(.default)
let _ = (context.engine.messages.reportAdMessage(peerId: message.id.peerId, opaqueId: adAttribute.opaqueId, option: nil) let _ = (context.engine.messages.reportAdMessage(opaqueId: adAttribute.opaqueId, option: nil)
|> deliverOnMainQueue).start(next: { [weak self] result in |> deliverOnMainQueue).start(next: { [weak self] result in
if case let .options(title, options) = result { if case let .options(title, options) = result {
if let navigationController = self?.baseNavigationController() as? NavigationController { if let navigationController = self?.baseNavigationController() as? NavigationController {
navigationController.pushViewController( navigationController.pushViewController(
AdsReportScreen( AdsReportScreen(
context: context, context: context,
peerId: message.id.peerId,
opaqueId: adAttribute.opaqueId, opaqueId: adAttribute.opaqueId,
title: title, title: title,
options: options, options: options,

View File

@ -3131,14 +3131,13 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
}, iconSource: nil, action: { [weak self] _, f in }, iconSource: nil, action: { [weak self] _, f in
f(.default) f(.default)
let _ = (context.engine.messages.reportAdMessage(peerId: message.id.peerId, opaqueId: adAttribute.opaqueId, option: nil) let _ = (context.engine.messages.reportAdMessage(opaqueId: adAttribute.opaqueId, option: nil)
|> deliverOnMainQueue).start(next: { [weak self] result in |> deliverOnMainQueue).start(next: { [weak self] result in
if case let .options(title, options) = result { if case let .options(title, options) = result {
if let navigationController = self?.baseNavigationController() as? NavigationController { if let navigationController = self?.baseNavigationController() as? NavigationController {
navigationController.pushViewController( navigationController.pushViewController(
AdsReportScreen( AdsReportScreen(
context: context, context: context,
peerId: message.id.peerId,
opaqueId: adAttribute.opaqueId, opaqueId: adAttribute.opaqueId,
title: title, title: title,
options: options, options: options,

View File

@ -174,8 +174,9 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
case disallowedGiftsUnlimited(PresentationTheme, String, Bool, Bool) case disallowedGiftsUnlimited(PresentationTheme, String, Bool, Bool)
case disallowedGiftsLimited(PresentationTheme, String, Bool, Bool) case disallowedGiftsLimited(PresentationTheme, String, Bool, Bool)
case disallowedGiftsUnique(PresentationTheme, String, Bool, Bool) case disallowedGiftsUnique(PresentationTheme, String, Bool, Bool)
case disallowedGiftsPremium(PresentationTheme, String, Bool, Bool)
case disallowedGiftsInfo(PresentationTheme, String) case disallowedGiftsInfo(PresentationTheme, String)
case showGiftButton(PresentationTheme, String, Bool, Bool) case showGiftButton(PresentationTheme, String, Bool, Bool, Bool)
case showGiftButtonInfo(PresentationTheme, String) case showGiftButtonInfo(PresentationTheme, String)
var section: ItemListSectionId { var section: ItemListSectionId {
@ -202,7 +203,7 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
return SelectivePrivacySettingsSection.hideReadTime.rawValue return SelectivePrivacySettingsSection.hideReadTime.rawValue
case .subscribeToPremium, .subscribeToPremiumInfo: case .subscribeToPremium, .subscribeToPremiumInfo:
return SelectivePrivacySettingsSection.premium.rawValue return SelectivePrivacySettingsSection.premium.rawValue
case .disallowedGiftsHeader, .disallowedGiftsUnlimited, .disallowedGiftsLimited, .disallowedGiftsUnique, .disallowedGiftsInfo: case .disallowedGiftsHeader, .disallowedGiftsUnlimited, .disallowedGiftsLimited, .disallowedGiftsUnique, .disallowedGiftsPremium, .disallowedGiftsInfo:
return SelectivePrivacySettingsSection.disallowedGifts.rawValue return SelectivePrivacySettingsSection.disallowedGifts.rawValue
case .showGiftButton, .showGiftButtonInfo: case .showGiftButton, .showGiftButtonInfo:
return SelectivePrivacySettingsSection.giftButton.rawValue return SelectivePrivacySettingsSection.giftButton.rawValue
@ -285,12 +286,14 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
return 35 return 35
case .disallowedGiftsUnique: case .disallowedGiftsUnique:
return 36 return 36
case .disallowedGiftsInfo: case .disallowedGiftsPremium:
return 37 return 37
case .showGiftButton: case .disallowedGiftsInfo:
return 38 return 38
case .showGiftButtonInfo: case .showGiftButton:
return 39 return 39
case .showGiftButtonInfo:
return 40
} }
} }
@ -518,14 +521,20 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .disallowedGiftsPremium(lhsTheme, lhsText, lhsEnabled, lhsValue):
if case let .disallowedGiftsPremium(rhsTheme, rhsText, rhsEnabled, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsEnabled == rhsEnabled, lhsValue == rhsValue {
return true
} else {
return false
}
case let .disallowedGiftsInfo(lhsTheme, lhsText): case let .disallowedGiftsInfo(lhsTheme, lhsText):
if case let .disallowedGiftsInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { if case let .disallowedGiftsInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true return true
} else { } else {
return false return false
} }
case let .showGiftButton(lhsTheme, lhsText, lhsEnabled, lhsValue): case let .showGiftButton(lhsTheme, lhsText, lhsEnabled, lhsValue, lhsAvailable):
if case let .showGiftButton(rhsTheme, rhsText, rhsEnabled, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsEnabled == rhsEnabled, lhsValue == rhsValue { if case let .showGiftButton(rhsTheme, rhsText, rhsEnabled, rhsValue, rhsAvailable) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsEnabled == rhsEnabled, lhsValue == rhsValue, lhsAvailable == rhsAvailable {
return true return true
} else { } else {
return false return false
@ -696,18 +705,28 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
}, activatedWhileDisabled: { }, activatedWhileDisabled: {
arguments.displayLockedGiftsInfo() arguments.displayLockedGiftsInfo()
}) })
case let .disallowedGiftsInfo(_, text): case let .disallowedGiftsPremium(_, text, isLocked, value):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .showGiftButton(_, text, isLocked, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enableInteractiveChanges: !isLocked, enabled: true, displayLocked: isLocked, sectionId: self.section, style: .blocks, updated: { updatedValue in return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enableInteractiveChanges: !isLocked, enabled: true, displayLocked: isLocked, sectionId: self.section, style: .blocks, updated: { updatedValue in
if !isLocked { if !isLocked {
arguments.updateShowGiftButton?(updatedValue) arguments.updateDisallowedGifts?(.premium, !updatedValue)
} else { } else {
arguments.displayLockedGiftsInfo() arguments.displayLockedGiftsInfo()
} }
}, activatedWhileDisabled: { }, activatedWhileDisabled: {
arguments.displayLockedGiftsInfo() arguments.displayLockedGiftsInfo()
}) })
case let .disallowedGiftsInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .showGiftButton(_, text, isLocked, value, available):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enableInteractiveChanges: !isLocked, enabled: available, displayLocked: isLocked, sectionId: self.section, style: .blocks, updated: { updatedValue in
if !isLocked {
arguments.updateShowGiftButton?(updatedValue)
} else if available {
arguments.displayLockedGiftsInfo()
}
}, activatedWhileDisabled: {
arguments.displayLockedGiftsInfo()
})
case let .showGiftButtonInfo(_, text): case let .showGiftButtonInfo(_, text):
let attributedString = NSMutableAttributedString(string: text, font: Font.regular(presentationData.fontSize.itemListBaseHeaderFontSize), textColor: presentationData.theme.list.freeTextColor) let attributedString = NSMutableAttributedString(string: text, font: Font.regular(presentationData.fontSize.itemListBaseHeaderFontSize), textColor: presentationData.theme.list.freeTextColor)
if let range = attributedString.string.range(of: "#") { if let range = attributedString.string.range(of: "#") {
@ -1125,8 +1144,9 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present
entries.append(.disallowedGiftsUnlimited(presentationData.theme, "Unlimited", !isPremium, !(state.disallowedGifts?.contains(.unlimited) ?? false))) entries.append(.disallowedGiftsUnlimited(presentationData.theme, "Unlimited", !isPremium, !(state.disallowedGifts?.contains(.unlimited) ?? false)))
entries.append(.disallowedGiftsLimited(presentationData.theme, "Limited-Edition", !isPremium, !(state.disallowedGifts?.contains(.limited) ?? false))) entries.append(.disallowedGiftsLimited(presentationData.theme, "Limited-Edition", !isPremium, !(state.disallowedGifts?.contains(.limited) ?? false)))
entries.append(.disallowedGiftsUnique(presentationData.theme, "Unique", !isPremium, !(state.disallowedGifts?.contains(.unique) ?? false))) entries.append(.disallowedGiftsUnique(presentationData.theme, "Unique", !isPremium, !(state.disallowedGifts?.contains(.unique) ?? false)))
entries.append(.disallowedGiftsPremium(presentationData.theme, "Premium Subscriptions", !isPremium, !(state.disallowedGifts?.contains(.premium) ?? false)))
entries.append(.disallowedGiftsInfo(presentationData.theme, "Choose the types of gifts that you allow others to send you.")) entries.append(.disallowedGiftsInfo(presentationData.theme, "Choose the types of gifts that you allow others to send you."))
entries.append(.showGiftButton(presentationData.theme, "Show Gift Icon in Chats", !isPremium, state.showGiftButton == true)) entries.append(.showGiftButton(presentationData.theme, "Show Gift Icon in Chats", !isPremium, state.showGiftButton == true && state.disallowedGifts != TelegramDisallowedGifts.All, state.disallowedGifts != TelegramDisallowedGifts.All))
entries.append(.showGiftButtonInfo(presentationData.theme, "Display the # Gift icon in the message input field for both participants in all chats.")) entries.append(.showGiftButtonInfo(presentationData.theme, "Display the # Gift icon in the message input field for both participants in all chats."))
} }
@ -1603,6 +1623,12 @@ public func selectivePrivacySettingsController(
} else { } else {
updatedDisallowedGifts.remove(.unique) updatedDisallowedGifts.remove(.unique)
} }
case .premium:
if value {
updatedDisallowedGifts.insert(.premium)
} else {
updatedDisallowedGifts.remove(.premium)
}
default: default:
break break
} }
@ -1713,7 +1739,11 @@ public func selectivePrivacySettingsController(
disallowedGifts = value disallowedGifts = value
} }
if let value = state.showGiftButton { if let value = state.showGiftButton {
showGiftButton = value if disallowedGifts != TelegramDisallowedGifts.All {
showGiftButton = value
} else {
showGiftButton = false
}
} }
} }

View File

@ -232,7 +232,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, ASScrollView
}, editPeer: { _ in }, editPeer: { _ in
}, openWebApp: { _ in }, openWebApp: { _ in
}, openPhotoSetup: { }, openPhotoSetup: {
}, openAdInfo: { _ in }, openAdInfo: { _, _ in
}, openAccountFreezeInfo: { }, openAccountFreezeInfo: {
}) })

View File

@ -381,7 +381,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, ASScrollViewDelegate {
}, editPeer: { _ in }, editPeer: { _ in
}, openWebApp: { _ in }, openWebApp: { _ in
}, openPhotoSetup: { }, openPhotoSetup: {
}, openAdInfo: { _ in }, openAdInfo: { _, _ in
}, openAccountFreezeInfo: { }, openAccountFreezeInfo: {
}) })

View File

@ -241,7 +241,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[2004110666] = { return Api.DialogFilterSuggested.parse_dialogFilterSuggested($0) } dict[2004110666] = { return Api.DialogFilterSuggested.parse_dialogFilterSuggested($0) }
dict[-445792507] = { return Api.DialogPeer.parse_dialogPeer($0) } dict[-445792507] = { return Api.DialogPeer.parse_dialogPeer($0) }
dict[1363483106] = { return Api.DialogPeer.parse_dialogPeerFolder($0) } dict[1363483106] = { return Api.DialogPeer.parse_dialogPeerFolder($0) }
dict[1653721450] = { return Api.DisallowedStarGiftsSettings.parse_disallowedStarGiftsSettings($0) } dict[1911715524] = { return Api.DisallowedGiftsSettings.parse_disallowedGiftsSettings($0) }
dict[-1881881384] = { return Api.Document.parse_document($0) } dict[-1881881384] = { return Api.Document.parse_document($0) }
dict[922273905] = { return Api.Document.parse_documentEmpty($0) } dict[922273905] = { return Api.Document.parse_documentEmpty($0) }
dict[297109817] = { return Api.DocumentAttribute.parse_documentAttributeAnimated($0) } dict[297109817] = { return Api.DocumentAttribute.parse_documentAttributeAnimated($0) }
@ -300,7 +300,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1297942941] = { return Api.GeoPoint.parse_geoPoint($0) } dict[-1297942941] = { return Api.GeoPoint.parse_geoPoint($0) }
dict[286776671] = { return Api.GeoPoint.parse_geoPointEmpty($0) } dict[286776671] = { return Api.GeoPoint.parse_geoPointEmpty($0) }
dict[-565420653] = { return Api.GeoPointAddress.parse_geoPointAddress($0) } dict[-565420653] = { return Api.GeoPointAddress.parse_geoPointAddress($0) }
dict[-715184062] = { return Api.GlobalPrivacySettings.parse_globalPrivacySettings($0) } dict[-29248689] = { return Api.GlobalPrivacySettings.parse_globalPrivacySettings($0) }
dict[-839330845] = { return Api.GroupCall.parse_groupCall($0) } dict[-839330845] = { return Api.GroupCall.parse_groupCall($0) }
dict[2004925620] = { return Api.GroupCall.parse_groupCallDiscarded($0) } dict[2004925620] = { return Api.GroupCall.parse_groupCallDiscarded($0) }
dict[-341428482] = { return Api.GroupCallParticipant.parse_groupCallParticipant($0) } dict[-341428482] = { return Api.GroupCallParticipant.parse_groupCallParticipant($0) }
@ -1149,7 +1149,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1831650802] = { return Api.UrlAuthResult.parse_urlAuthResultRequest($0) } dict[-1831650802] = { return Api.UrlAuthResult.parse_urlAuthResultRequest($0) }
dict[34280482] = { return Api.User.parse_user($0) } dict[34280482] = { return Api.User.parse_user($0) }
dict[-742634630] = { return Api.User.parse_userEmpty($0) } dict[-742634630] = { return Api.User.parse_userEmpty($0) }
dict[791719153] = { return Api.UserFull.parse_userFull($0) } dict[-1712881595] = { return Api.UserFull.parse_userFull($0) }
dict[-2100168954] = { return Api.UserProfilePhoto.parse_userProfilePhoto($0) } dict[-2100168954] = { return Api.UserProfilePhoto.parse_userProfilePhoto($0) }
dict[1326562017] = { return Api.UserProfilePhoto.parse_userProfilePhotoEmpty($0) } dict[1326562017] = { return Api.UserProfilePhoto.parse_userProfilePhotoEmpty($0) }
dict[164646985] = { return Api.UserStatus.parse_userStatusEmpty($0) } dict[164646985] = { return Api.UserStatus.parse_userStatusEmpty($0) }
@ -1685,7 +1685,7 @@ public extension Api {
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.DialogPeer: case let _1 as Api.DialogPeer:
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.DisallowedStarGiftsSettings: case let _1 as Api.DisallowedGiftsSettings:
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.Document: case let _1 as Api.Document:
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)

View File

@ -614,13 +614,13 @@ public extension Api {
} }
public extension Api { public extension Api {
enum UserFull: TypeConstructorDescription { enum UserFull: TypeConstructorDescription {
case userFull(flags: Int32, flags2: Int32, id: Int64, about: String?, settings: Api.PeerSettings, personalPhoto: Api.Photo?, profilePhoto: Api.Photo?, fallbackPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, botInfo: Api.BotInfo?, pinnedMsgId: Int32?, commonChatsCount: Int32, folderId: Int32?, ttlPeriod: Int32?, themeEmoticon: String?, privateForwardName: String?, botGroupAdminRights: Api.ChatAdminRights?, botBroadcastAdminRights: Api.ChatAdminRights?, wallpaper: Api.WallPaper?, stories: Api.PeerStories?, businessWorkHours: Api.BusinessWorkHours?, businessLocation: Api.BusinessLocation?, businessGreetingMessage: Api.BusinessGreetingMessage?, businessAwayMessage: Api.BusinessAwayMessage?, businessIntro: Api.BusinessIntro?, birthday: Api.Birthday?, personalChannelId: Int64?, personalChannelMessage: Int32?, stargiftsCount: Int32?, starrefProgram: Api.StarRefProgram?, botVerification: Api.BotVerification?, sendPaidMessagesStars: Int64?, disallowedStargifts: Api.DisallowedStarGiftsSettings?) case userFull(flags: Int32, flags2: Int32, id: Int64, about: String?, settings: Api.PeerSettings, personalPhoto: Api.Photo?, profilePhoto: Api.Photo?, fallbackPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, botInfo: Api.BotInfo?, pinnedMsgId: Int32?, commonChatsCount: Int32, folderId: Int32?, ttlPeriod: Int32?, themeEmoticon: String?, privateForwardName: String?, botGroupAdminRights: Api.ChatAdminRights?, botBroadcastAdminRights: Api.ChatAdminRights?, wallpaper: Api.WallPaper?, stories: Api.PeerStories?, businessWorkHours: Api.BusinessWorkHours?, businessLocation: Api.BusinessLocation?, businessGreetingMessage: Api.BusinessGreetingMessage?, businessAwayMessage: Api.BusinessAwayMessage?, businessIntro: Api.BusinessIntro?, birthday: Api.Birthday?, personalChannelId: Int64?, personalChannelMessage: Int32?, stargiftsCount: Int32?, starrefProgram: Api.StarRefProgram?, botVerification: Api.BotVerification?, sendPaidMessagesStars: Int64?, disallowedGifts: Api.DisallowedGiftsSettings?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let themeEmoticon, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let wallpaper, let stories, let businessWorkHours, let businessLocation, let businessGreetingMessage, let businessAwayMessage, let businessIntro, let birthday, let personalChannelId, let personalChannelMessage, let stargiftsCount, let starrefProgram, let botVerification, let sendPaidMessagesStars, let disallowedStargifts): case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let themeEmoticon, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let wallpaper, let stories, let businessWorkHours, let businessLocation, let businessGreetingMessage, let businessAwayMessage, let businessIntro, let birthday, let personalChannelId, let personalChannelMessage, let stargiftsCount, let starrefProgram, let botVerification, let sendPaidMessagesStars, let disallowedGifts):
if boxed { if boxed {
buffer.appendInt32(791719153) buffer.appendInt32(-1712881595)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(flags2, buffer: buffer, boxed: false) serializeInt32(flags2, buffer: buffer, boxed: false)
@ -654,15 +654,15 @@ public extension Api {
if Int(flags2) & Int(1 << 11) != 0 {starrefProgram!.serialize(buffer, true)} if Int(flags2) & Int(1 << 11) != 0 {starrefProgram!.serialize(buffer, true)}
if Int(flags2) & Int(1 << 12) != 0 {botVerification!.serialize(buffer, true)} if Int(flags2) & Int(1 << 12) != 0 {botVerification!.serialize(buffer, true)}
if Int(flags2) & Int(1 << 14) != 0 {serializeInt64(sendPaidMessagesStars!, buffer: buffer, boxed: false)} if Int(flags2) & Int(1 << 14) != 0 {serializeInt64(sendPaidMessagesStars!, buffer: buffer, boxed: false)}
if Int(flags2) & Int(1 << 15) != 0 {disallowedStargifts!.serialize(buffer, true)} if Int(flags2) & Int(1 << 15) != 0 {disallowedGifts!.serialize(buffer, true)}
break break
} }
} }
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { switch self {
case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let themeEmoticon, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let wallpaper, let stories, let businessWorkHours, let businessLocation, let businessGreetingMessage, let businessAwayMessage, let businessIntro, let birthday, let personalChannelId, let personalChannelMessage, let stargiftsCount, let starrefProgram, let botVerification, let sendPaidMessagesStars, let disallowedStargifts): case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let themeEmoticon, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let wallpaper, let stories, let businessWorkHours, let businessLocation, let businessGreetingMessage, let businessAwayMessage, let businessIntro, let birthday, let personalChannelId, let personalChannelMessage, let stargiftsCount, let starrefProgram, let botVerification, let sendPaidMessagesStars, let disallowedGifts):
return ("userFull", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("about", about as Any), ("settings", settings as Any), ("personalPhoto", personalPhoto as Any), ("profilePhoto", profilePhoto as Any), ("fallbackPhoto", fallbackPhoto as Any), ("notifySettings", notifySettings as Any), ("botInfo", botInfo as Any), ("pinnedMsgId", pinnedMsgId as Any), ("commonChatsCount", commonChatsCount as Any), ("folderId", folderId as Any), ("ttlPeriod", ttlPeriod as Any), ("themeEmoticon", themeEmoticon as Any), ("privateForwardName", privateForwardName as Any), ("botGroupAdminRights", botGroupAdminRights as Any), ("botBroadcastAdminRights", botBroadcastAdminRights as Any), ("wallpaper", wallpaper as Any), ("stories", stories as Any), ("businessWorkHours", businessWorkHours as Any), ("businessLocation", businessLocation as Any), ("businessGreetingMessage", businessGreetingMessage as Any), ("businessAwayMessage", businessAwayMessage as Any), ("businessIntro", businessIntro as Any), ("birthday", birthday as Any), ("personalChannelId", personalChannelId as Any), ("personalChannelMessage", personalChannelMessage as Any), ("stargiftsCount", stargiftsCount as Any), ("starrefProgram", starrefProgram as Any), ("botVerification", botVerification as Any), ("sendPaidMessagesStars", sendPaidMessagesStars as Any), ("disallowedStargifts", disallowedStargifts as Any)]) return ("userFull", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("about", about as Any), ("settings", settings as Any), ("personalPhoto", personalPhoto as Any), ("profilePhoto", profilePhoto as Any), ("fallbackPhoto", fallbackPhoto as Any), ("notifySettings", notifySettings as Any), ("botInfo", botInfo as Any), ("pinnedMsgId", pinnedMsgId as Any), ("commonChatsCount", commonChatsCount as Any), ("folderId", folderId as Any), ("ttlPeriod", ttlPeriod as Any), ("themeEmoticon", themeEmoticon as Any), ("privateForwardName", privateForwardName as Any), ("botGroupAdminRights", botGroupAdminRights as Any), ("botBroadcastAdminRights", botBroadcastAdminRights as Any), ("wallpaper", wallpaper as Any), ("stories", stories as Any), ("businessWorkHours", businessWorkHours as Any), ("businessLocation", businessLocation as Any), ("businessGreetingMessage", businessGreetingMessage as Any), ("businessAwayMessage", businessAwayMessage as Any), ("businessIntro", businessIntro as Any), ("birthday", birthday as Any), ("personalChannelId", personalChannelId as Any), ("personalChannelMessage", personalChannelMessage as Any), ("stargiftsCount", stargiftsCount as Any), ("starrefProgram", starrefProgram as Any), ("botVerification", botVerification as Any), ("sendPaidMessagesStars", sendPaidMessagesStars as Any), ("disallowedGifts", disallowedGifts as Any)])
} }
} }
@ -767,9 +767,9 @@ public extension Api {
} } } }
var _32: Int64? var _32: Int64?
if Int(_2!) & Int(1 << 14) != 0 {_32 = reader.readInt64() } if Int(_2!) & Int(1 << 14) != 0 {_32 = reader.readInt64() }
var _33: Api.DisallowedStarGiftsSettings? var _33: Api.DisallowedGiftsSettings?
if Int(_2!) & Int(1 << 15) != 0 {if let signature = reader.readInt32() { if Int(_2!) & Int(1 << 15) != 0 {if let signature = reader.readInt32() {
_33 = Api.parse(reader, signature: signature) as? Api.DisallowedStarGiftsSettings _33 = Api.parse(reader, signature: signature) as? Api.DisallowedGiftsSettings
} } } }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
@ -805,7 +805,7 @@ public extension Api {
let _c32 = (Int(_2!) & Int(1 << 14) == 0) || _32 != nil let _c32 = (Int(_2!) & Int(1 << 14) == 0) || _32 != nil
let _c33 = (Int(_2!) & Int(1 << 15) == 0) || _33 != nil let _c33 = (Int(_2!) & Int(1 << 15) == 0) || _33 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 && _c33 { if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 && _c33 {
return Api.UserFull.userFull(flags: _1!, flags2: _2!, id: _3!, about: _4, settings: _5!, personalPhoto: _6, profilePhoto: _7, fallbackPhoto: _8, notifySettings: _9!, botInfo: _10, pinnedMsgId: _11, commonChatsCount: _12!, folderId: _13, ttlPeriod: _14, themeEmoticon: _15, privateForwardName: _16, botGroupAdminRights: _17, botBroadcastAdminRights: _18, wallpaper: _19, stories: _20, businessWorkHours: _21, businessLocation: _22, businessGreetingMessage: _23, businessAwayMessage: _24, businessIntro: _25, birthday: _26, personalChannelId: _27, personalChannelMessage: _28, stargiftsCount: _29, starrefProgram: _30, botVerification: _31, sendPaidMessagesStars: _32, disallowedStargifts: _33) return Api.UserFull.userFull(flags: _1!, flags2: _2!, id: _3!, about: _4, settings: _5!, personalPhoto: _6, profilePhoto: _7, fallbackPhoto: _8, notifySettings: _9!, botInfo: _10, pinnedMsgId: _11, commonChatsCount: _12!, folderId: _13, ttlPeriod: _14, themeEmoticon: _15, privateForwardName: _16, botGroupAdminRights: _17, botBroadcastAdminRights: _18, wallpaper: _19, stories: _20, businessWorkHours: _21, businessLocation: _22, businessGreetingMessage: _23, businessAwayMessage: _24, businessIntro: _25, birthday: _26, personalChannelId: _27, personalChannelMessage: _28, stargiftsCount: _29, starrefProgram: _30, botVerification: _31, sendPaidMessagesStars: _32, disallowedGifts: _33)
} }
else { else {
return nil return nil

View File

@ -1425,14 +1425,14 @@ public extension Api {
} }
} }
public extension Api { public extension Api {
enum DisallowedStarGiftsSettings: TypeConstructorDescription { enum DisallowedGiftsSettings: TypeConstructorDescription {
case disallowedStarGiftsSettings(flags: Int32) case disallowedGiftsSettings(flags: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
case .disallowedStarGiftsSettings(let flags): case .disallowedGiftsSettings(let flags):
if boxed { if boxed {
buffer.appendInt32(1653721450) buffer.appendInt32(1911715524)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
break break
@ -1441,17 +1441,17 @@ public extension Api {
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { switch self {
case .disallowedStarGiftsSettings(let flags): case .disallowedGiftsSettings(let flags):
return ("disallowedStarGiftsSettings", [("flags", flags as Any)]) return ("disallowedGiftsSettings", [("flags", flags as Any)])
} }
} }
public static func parse_disallowedStarGiftsSettings(_ reader: BufferReader) -> DisallowedStarGiftsSettings? { public static func parse_disallowedGiftsSettings(_ reader: BufferReader) -> DisallowedGiftsSettings? {
var _1: Int32? var _1: Int32?
_1 = reader.readInt32() _1 = reader.readInt32()
let _c1 = _1 != nil let _c1 = _1 != nil
if _c1 { if _c1 {
return Api.DisallowedStarGiftsSettings.disallowedStarGiftsSettings(flags: _1!) return Api.DisallowedGiftsSettings.disallowedGiftsSettings(flags: _1!)
} }
else { else {
return nil return nil

View File

@ -946,25 +946,25 @@ public extension Api {
} }
public extension Api { public extension Api {
enum GlobalPrivacySettings: TypeConstructorDescription { enum GlobalPrivacySettings: TypeConstructorDescription {
case globalPrivacySettings(flags: Int32, noncontactPeersPaidStars: Int64?, disallowedStargifts: Api.DisallowedStarGiftsSettings?) case globalPrivacySettings(flags: Int32, noncontactPeersPaidStars: Int64?, disallowedGifts: Api.DisallowedGiftsSettings?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
case .globalPrivacySettings(let flags, let noncontactPeersPaidStars, let disallowedStargifts): case .globalPrivacySettings(let flags, let noncontactPeersPaidStars, let disallowedGifts):
if boxed { if boxed {
buffer.appendInt32(-715184062) buffer.appendInt32(-29248689)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 5) != 0 {serializeInt64(noncontactPeersPaidStars!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 5) != 0 {serializeInt64(noncontactPeersPaidStars!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 6) != 0 {disallowedStargifts!.serialize(buffer, true)} if Int(flags) & Int(1 << 6) != 0 {disallowedGifts!.serialize(buffer, true)}
break break
} }
} }
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { switch self {
case .globalPrivacySettings(let flags, let noncontactPeersPaidStars, let disallowedStargifts): case .globalPrivacySettings(let flags, let noncontactPeersPaidStars, let disallowedGifts):
return ("globalPrivacySettings", [("flags", flags as Any), ("noncontactPeersPaidStars", noncontactPeersPaidStars as Any), ("disallowedStargifts", disallowedStargifts as Any)]) return ("globalPrivacySettings", [("flags", flags as Any), ("noncontactPeersPaidStars", noncontactPeersPaidStars as Any), ("disallowedGifts", disallowedGifts as Any)])
} }
} }
@ -973,15 +973,15 @@ public extension Api {
_1 = reader.readInt32() _1 = reader.readInt32()
var _2: Int64? var _2: Int64?
if Int(_1!) & Int(1 << 5) != 0 {_2 = reader.readInt64() } if Int(_1!) & Int(1 << 5) != 0 {_2 = reader.readInt64() }
var _3: Api.DisallowedStarGiftsSettings? var _3: Api.DisallowedGiftsSettings?
if Int(_1!) & Int(1 << 6) != 0 {if let signature = reader.readInt32() { if Int(_1!) & Int(1 << 6) != 0 {if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.DisallowedStarGiftsSettings _3 = Api.parse(reader, signature: signature) as? Api.DisallowedGiftsSettings
} } } }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = (Int(_1!) & Int(1 << 5) == 0) || _2 != nil let _c2 = (Int(_1!) & Int(1 << 5) == 0) || _2 != nil
let _c3 = (Int(_1!) & Int(1 << 6) == 0) || _3 != nil let _c3 = (Int(_1!) & Int(1 << 6) == 0) || _3 != nil
if _c1 && _c2 && _c3 { if _c1 && _c2 && _c3 {
return Api.GlobalPrivacySettings.globalPrivacySettings(flags: _1!, noncontactPeersPaidStars: _2, disallowedStargifts: _3) return Api.GlobalPrivacySettings.globalPrivacySettings(flags: _1!, noncontactPeersPaidStars: _2, disallowedGifts: _3)
} }
else { else {
return nil return nil

View File

@ -306,6 +306,14 @@ public struct TelegramDisallowedGifts: OptionSet, Codable {
public static let unlimited = TelegramDisallowedGifts(rawValue: 1 << 0) public static let unlimited = TelegramDisallowedGifts(rawValue: 1 << 0)
public static let limited = TelegramDisallowedGifts(rawValue: 1 << 1) public static let limited = TelegramDisallowedGifts(rawValue: 1 << 1)
public static let unique = TelegramDisallowedGifts(rawValue: 1 << 2) public static let unique = TelegramDisallowedGifts(rawValue: 1 << 2)
public static let premium = TelegramDisallowedGifts(rawValue: 1 << 3)
public static let All: TelegramDisallowedGifts = [
.unlimited,
.limited,
.unique,
.premium
]
public init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringCodingKey.self) let container = try decoder.container(keyedBy: StringCodingKey.self)

View File

@ -526,7 +526,7 @@ private class AdMessagesHistoryContextImpl {
} }
func markAction(opaqueId: Data, media: Bool, fullscreen: Bool) { func markAction(opaqueId: Data, media: Bool, fullscreen: Bool) {
_internal_markAdAction(account: self.account, peerId: self.peerId, opaqueId: opaqueId, media: media, fullscreen: fullscreen) _internal_markAdAction(account: self.account, opaqueId: opaqueId, media: media, fullscreen: fullscreen)
} }
func remove(opaqueId: Data) { func remove(opaqueId: Data) {
@ -601,7 +601,7 @@ public class AdMessagesHistoryContext {
} }
func _internal_markAdAction(account: Account, peerId: EnginePeer.Id, opaqueId: Data, media: Bool, fullscreen: Bool) { func _internal_markAdAction(account: Account, opaqueId: Data, media: Bool, fullscreen: Bool) {
var flags: Int32 = 0 var flags: Int32 = 0
if media { if media {
flags |= (1 << 0) flags |= (1 << 0)
@ -616,3 +616,14 @@ func _internal_markAdAction(account: Account, peerId: EnginePeer.Id, opaqueId: D
|> ignoreValues |> ignoreValues
let _ = signal.start() let _ = signal.start()
} }
func _internal_markAsSeen(account: Account, opaqueId: Data) -> Signal<Never, NoError> {
return account.network.request(Api.functions.messages.viewSponsoredMessage(randomId: Buffer(data: opaqueId)))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
|> ignoreValues
}

View File

@ -1125,6 +1125,23 @@ public struct TelegramBusinessBotRights: OptionSet, Codable {
public static let transferStars = TelegramBusinessBotRights(rawValue: 1 << 12) public static let transferStars = TelegramBusinessBotRights(rawValue: 1 << 12)
public static let manageStories = TelegramBusinessBotRights(rawValue: 1 << 13) public static let manageStories = TelegramBusinessBotRights(rawValue: 1 << 13)
public static let All: TelegramBusinessBotRights = [
.reply,
.readMessages,
.deleteSentMessages,
.deleteReceivedMessages,
.editName,
.editBio,
.editProfilePhoto,
.editUsername,
.viewGifts,
.sellGifts,
.changeGiftSettings,
.transferAndUpgradeGifts,
.transferStars,
.manageStories
]
public init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringCodingKey.self) let container = try decoder.container(keyedBy: StringCodingKey.self)
let value = try? container.decode(Int32.self, forKey: "v") let value = try? container.decode(Int32.self, forKey: "v")

View File

@ -19,7 +19,7 @@ public enum ReportAdMessageError {
case premiumRequired case premiumRequired
} }
func _internal_reportAdMessage(account: Account, peerId: EnginePeer.Id, opaqueId: Data, option: Data?) -> Signal<ReportAdMessageResult, ReportAdMessageError> { func _internal_reportAdMessage(account: Account, opaqueId: Data, option: Data?) -> Signal<ReportAdMessageResult, ReportAdMessageError> {
return account.network.request(Api.functions.messages.reportSponsoredMessage(randomId: Buffer(data: opaqueId), option: Buffer(data: option))) return account.network.request(Api.functions.messages.reportSponsoredMessage(randomId: Buffer(data: opaqueId), option: Buffer(data: option)))
|> mapError { error -> ReportAdMessageError in |> mapError { error -> ReportAdMessageError in
if error.errorDescription == "PREMIUM_ACCOUNT_REQUIRED" { if error.errorDescription == "PREMIUM_ACCOUNT_REQUIRED" {

View File

@ -1504,8 +1504,8 @@ public extension TelegramEngine {
}).startStandalone() }).startStandalone()
} }
public func reportAdMessage(peerId: EnginePeer.Id, opaqueId: Data, option: Data?) -> Signal<ReportAdMessageResult, ReportAdMessageError> { public func reportAdMessage(opaqueId: Data, option: Data?) -> Signal<ReportAdMessageResult, ReportAdMessageError> {
return _internal_reportAdMessage(account: self.account, peerId: peerId, opaqueId: opaqueId, option: option) return _internal_reportAdMessage(account: self.account, opaqueId: opaqueId, option: option)
} }
public func reportContent(subject: ReportContentSubject, option: Data?, message: String?) -> Signal<ReportContentResult, ReportContentError> { public func reportContent(subject: ReportContentSubject, option: Data?, message: String?) -> Signal<ReportContentResult, ReportContentError> {
@ -1516,8 +1516,8 @@ public extension TelegramEngine {
return _internal_updateExtendedMedia(account: self.account, messageIds: messageIds) return _internal_updateExtendedMedia(account: self.account, messageIds: messageIds)
} }
public func markAdAction(peerId: EnginePeer.Id, opaqueId: Data, media: Bool, fullscreen: Bool) { public func markAdAction(opaqueId: Data, media: Bool, fullscreen: Bool) {
_internal_markAdAction(account: self.account, peerId: peerId, opaqueId: opaqueId, media: media, fullscreen: fullscreen) _internal_markAdAction(account: self.account, opaqueId: opaqueId, media: media, fullscreen: fullscreen)
} }
public func getAllLocalChannels(count: Int) -> Signal<[EnginePeer.Id], NoError> { public func getAllLocalChannels(count: Int) -> Signal<[EnginePeer.Id], NoError> {

View File

@ -6,12 +6,14 @@ import TelegramApi
public class AdPeer: Equatable { public class AdPeer: Equatable {
public let opaqueId: Data public let opaqueId: Data
public let peer: EnginePeer public let peer: EnginePeer
public let subscribers: Int32?
public let sponsorInfo: String? public let sponsorInfo: String?
public let additionalInfo: String? public let additionalInfo: String?
public init(opaqueId: Data, peer: EnginePeer, sponsorInfo: String?, additionalInfo: String?) { public init(opaqueId: Data, peer: EnginePeer, subscribers: Int32?, sponsorInfo: String?, additionalInfo: String?) {
self.opaqueId = opaqueId self.opaqueId = opaqueId
self.peer = peer self.peer = peer
self.subscribers = subscribers
self.sponsorInfo = sponsorInfo self.sponsorInfo = sponsorInfo
self.additionalInfo = additionalInfo self.additionalInfo = additionalInfo
} }
@ -23,6 +25,9 @@ public class AdPeer: Equatable {
if lhs.peer != rhs.peer { if lhs.peer != rhs.peer {
return false return false
} }
if lhs.subscribers != rhs.subscribers {
return false
}
if lhs.sponsorInfo != rhs.sponsorInfo { if lhs.sponsorInfo != rhs.sponsorInfo {
return false return false
} }
@ -49,6 +54,20 @@ func _internal_searchAdPeers(account: Account, query: String) -> Signal<[AdPeer]
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users) let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: parsedPeers) updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: parsedPeers)
var subscribers: [PeerId: Int32] = [:]
for chat in chats {
if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) {
switch chat {
case let .channel(_, _, _, _, _, _, _, _, _, _, _, _, participantsCount, _, _, _, _, _, _, _, _, _):
if let participantsCount = participantsCount {
subscribers[groupOrChannel.id] = participantsCount
}
default:
break
}
}
}
var result: [AdPeer] = [] var result: [AdPeer] = []
for peer in peers { for peer in peers {
switch peer { switch peer {
@ -60,6 +79,7 @@ func _internal_searchAdPeers(account: Account, query: String) -> Signal<[AdPeer]
AdPeer( AdPeer(
opaqueId: randomId.makeData(), opaqueId: randomId.makeData(),
peer: EnginePeer(peer), peer: EnginePeer(peer),
subscribers: subscribers[peer.id],
sponsorInfo: sponsorInfo, sponsorInfo: sponsorInfo,
additionalInfo: additionalInfo additionalInfo: additionalInfo
) )
@ -73,15 +93,3 @@ func _internal_searchAdPeers(account: Account, query: String) -> Signal<[AdPeer]
} }
} }
} }
func _internal_markAsSeen(account: Account, opaqueId: Data) -> Signal<Never, NoError> {
let signal: Signal<Never, NoError> = account.network.request(Api.functions.messages.viewSponsoredMessage(randomId: Buffer(data: opaqueId)))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
|> ignoreValues
return signal
}

View File

@ -397,7 +397,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
let sendPaidMessageStars = sendPaidMessageStars.flatMap { StarsAmount(value: $0, nanos: 0) } let sendPaidMessageStars = sendPaidMessageStars.flatMap { StarsAmount(value: $0, nanos: 0) }
var disallowedGifts: TelegramDisallowedGifts = [] var disallowedGifts: TelegramDisallowedGifts = []
if case let .disallowedStarGiftsSettings(giftFlags) = disallowedStarGifts { if case let .disallowedGiftsSettings(giftFlags) = disallowedStarGifts {
if (giftFlags & (1 << 0)) != 0 { if (giftFlags & (1 << 0)) != 0 {
disallowedGifts.insert(.unlimited) disallowedGifts.insert(.unlimited)
} }
@ -407,6 +407,9 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
if (giftFlags & (1 << 2)) != 0 { if (giftFlags & (1 << 2)) != 0 {
disallowedGifts.insert(.unique) disallowedGifts.insert(.unique)
} }
if (giftFlags & (1 << 3)) != 0 {
disallowedGifts.insert(.premium)
}
} }
return previous.withUpdatedAbout(userFullAbout) return previous.withUpdatedAbout(userFullAbout)

View File

@ -34,7 +34,7 @@ func _internal_updateGlobalPrivacySettings(account: Account) -> Signal<Never, No
} }
var disallowedGifts: TelegramDisallowedGifts = [] var disallowedGifts: TelegramDisallowedGifts = []
if case let .disallowedStarGiftsSettings(giftFlags) = disallowedStarGifts { if case let .disallowedGiftsSettings(giftFlags) = disallowedStarGifts {
if (giftFlags & (1 << 0)) != 0 { if (giftFlags & (1 << 0)) != 0 {
disallowedGifts.insert(.unlimited) disallowedGifts.insert(.unlimited)
} }
@ -44,6 +44,9 @@ func _internal_updateGlobalPrivacySettings(account: Account) -> Signal<Never, No
if (giftFlags & (1 << 2)) != 0 { if (giftFlags & (1 << 2)) != 0 {
disallowedGifts.insert(.unique) disallowedGifts.insert(.unique)
} }
if (giftFlags & (1 << 3)) != 0 {
disallowedGifts.insert(.premium)
}
} }
globalSettings = GlobalPrivacySettings( globalSettings = GlobalPrivacySettings(
@ -256,7 +259,7 @@ func _internal_requestAccountPrivacySettings(account: Account) -> Signal<Account
} }
var disallowedGifts: TelegramDisallowedGifts = [] var disallowedGifts: TelegramDisallowedGifts = []
if case let .disallowedStarGiftsSettings(giftFlags) = disallowedStarGifts { if case let .disallowedGiftsSettings(giftFlags) = disallowedStarGifts {
if (giftFlags & (1 << 0)) != 0 { if (giftFlags & (1 << 0)) != 0 {
disallowedGifts.insert(.unlimited) disallowedGifts.insert(.unlimited)
} }
@ -266,6 +269,9 @@ func _internal_requestAccountPrivacySettings(account: Account) -> Signal<Account
if (giftFlags & (1 << 2)) != 0 { if (giftFlags & (1 << 2)) != 0 {
disallowedGifts.insert(.unique) disallowedGifts.insert(.unique)
} }
if (giftFlags & (1 << 3)) != 0 {
disallowedGifts.insert(.premium)
}
} }
globalSettings = GlobalPrivacySettings( globalSettings = GlobalPrivacySettings(
@ -409,10 +415,10 @@ func _internal_updateGlobalPrivacySettings(account: Account, settings: GlobalPri
} }
} }
flags |= 1 << 6 flags |= 1 << 6
let disallowedStargifts = Api.DisallowedStarGiftsSettings.disallowedStarGiftsSettings(flags: giftFlags) let disallowedStargifts = Api.DisallowedGiftsSettings.disallowedGiftsSettings(flags: giftFlags)
return account.network.request(Api.functions.account.setGlobalPrivacySettings( return account.network.request(Api.functions.account.setGlobalPrivacySettings(
settings: .globalPrivacySettings(flags: flags, noncontactPeersPaidStars: noncontactPeersPaidStars, disallowedStargifts: disallowedStargifts) settings: .globalPrivacySettings(flags: flags, noncontactPeersPaidStars: noncontactPeersPaidStars, disallowedGifts: disallowedStargifts)
)) ))
|> retryRequest |> retryRequest
|> ignoreValues |> ignoreValues

View File

@ -1277,22 +1277,19 @@ public class AdsInfoScreen: ViewController {
guard let navigationController = self?.controller?.navigationController as? NavigationController else { guard let navigationController = self?.controller?.navigationController as? NavigationController else {
return return
} }
self?.controller?.dismiss(animated: true) let _ = (context.engine.messages.reportAdMessage(opaqueId: adAttribute.opaqueId, option: nil)
let _ = (context.engine.messages.reportAdMessage(peerId: message.id.peerId, opaqueId: adAttribute.opaqueId, option: nil)
|> deliverOnMainQueue).start(next: { [weak navigationController] result in |> deliverOnMainQueue).start(next: { [weak navigationController] result in
if case let .options(title, options) = result { if case let .options(title, options) = result {
Queue.mainQueue().after(0.2) { Queue.mainQueue().after(0.2) {
navigationController?.pushViewController( navigationController?.pushViewController(
AdsReportScreen( AdsReportScreen(
context: context, context: context,
peerId: message.id.peerId,
opaqueId: adAttribute.opaqueId, opaqueId: adAttribute.opaqueId,
title: title, title: title,
options: options, options: options,
completed: { completed: {
removeAd?(adAttribute.opaqueId) // removeAd?(adAttribute.opaqueId)
} }
) )
) )

View File

@ -250,7 +250,6 @@ private final class SheetContent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext let context: AccountContext
let peerId: EnginePeer.Id
let opaqueId: Data let opaqueId: Data
let title: String let title: String
let options: [ReportAdMessageResult.Option] let options: [ReportAdMessageResult.Option]
@ -262,7 +261,6 @@ private final class SheetContent: CombinedComponent {
init( init(
context: AccountContext, context: AccountContext,
peerId: EnginePeer.Id,
opaqueId: Data, opaqueId: Data,
title: String, title: String,
options: [ReportAdMessageResult.Option], options: [ReportAdMessageResult.Option],
@ -273,7 +271,6 @@ private final class SheetContent: CombinedComponent {
update: @escaping (ComponentTransition) -> Void update: @escaping (ComponentTransition) -> Void
) { ) {
self.context = context self.context = context
self.peerId = peerId
self.opaqueId = opaqueId self.opaqueId = opaqueId
self.title = title self.title = title
self.options = options self.options = options
@ -288,9 +285,6 @@ private final class SheetContent: CombinedComponent {
if lhs.context !== rhs.context { if lhs.context !== rhs.context {
return false return false
} }
if lhs.peerId != rhs.peerId {
return false
}
if lhs.opaqueId != rhs.opaqueId { if lhs.opaqueId != rhs.opaqueId {
return false return false
} }
@ -329,7 +323,6 @@ private final class SheetContent: CombinedComponent {
let update = component.update let update = component.update
let accountContext = component.context let accountContext = component.context
let peerId = component.peerId
let opaqueId = component.opaqueId let opaqueId = component.opaqueId
let complete = component.complete let complete = component.complete
let action: (SheetPageContent.Item) -> Void = { [weak state] item in let action: (SheetPageContent.Item) -> Void = { [weak state] item in
@ -337,7 +330,7 @@ private final class SheetContent: CombinedComponent {
return return
} }
state.disposable.set( state.disposable.set(
(accountContext.engine.messages.reportAdMessage(peerId: peerId, opaqueId: opaqueId, option: item.option) (accountContext.engine.messages.reportAdMessage(opaqueId: opaqueId, option: item.option)
|> deliverOnMainQueue).start(next: { [weak state] result in |> deliverOnMainQueue).start(next: { [weak state] result in
switch result { switch result {
case let .options(title, options): case let .options(title, options):
@ -423,7 +416,6 @@ private final class SheetContainerComponent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext let context: AccountContext
let peerId: EnginePeer.Id
let opaqueId: Data let opaqueId: Data
let title: String let title: String
let options: [ReportAdMessageResult.Option] let options: [ReportAdMessageResult.Option]
@ -432,7 +424,6 @@ private final class SheetContainerComponent: CombinedComponent {
init( init(
context: AccountContext, context: AccountContext,
peerId: EnginePeer.Id,
opaqueId: Data, opaqueId: Data,
title: String, title: String,
options: [ReportAdMessageResult.Option], options: [ReportAdMessageResult.Option],
@ -440,7 +431,6 @@ private final class SheetContainerComponent: CombinedComponent {
complete: @escaping (ReportResult) -> Void complete: @escaping (ReportResult) -> Void
) { ) {
self.context = context self.context = context
self.peerId = peerId
self.opaqueId = opaqueId self.opaqueId = opaqueId
self.title = title self.title = title
self.options = options self.options = options
@ -452,9 +442,6 @@ private final class SheetContainerComponent: CombinedComponent {
if lhs.context !== rhs.context { if lhs.context !== rhs.context {
return false return false
} }
if lhs.peerId != rhs.peerId {
return false
}
if lhs.opaqueId != rhs.opaqueId { if lhs.opaqueId != rhs.opaqueId {
return false return false
} }
@ -490,7 +477,6 @@ private final class SheetContainerComponent: CombinedComponent {
component: SheetComponent<EnvironmentType>( component: SheetComponent<EnvironmentType>(
content: AnyComponent<EnvironmentType>(SheetContent( content: AnyComponent<EnvironmentType>(SheetContent(
context: context.component.context, context: context.component.context,
peerId: context.component.peerId,
opaqueId: context.component.opaqueId, opaqueId: context.component.opaqueId,
title: context.component.title, title: context.component.title,
options: context.component.options, options: context.component.options,
@ -571,7 +557,6 @@ public final class AdsReportScreen: ViewControllerComponentContainer {
public init( public init(
context: AccountContext, context: AccountContext,
peerId: EnginePeer.Id,
opaqueId: Data, opaqueId: Data,
title: String, title: String,
options: [ReportAdMessageResult.Option], options: [ReportAdMessageResult.Option],
@ -585,7 +570,6 @@ public final class AdsReportScreen: ViewControllerComponentContainer {
context: context, context: context,
component: SheetContainerComponent( component: SheetContainerComponent(
context: context, context: context,
peerId: peerId,
opaqueId: opaqueId, opaqueId: opaqueId,
title: title, title: title,
options: options, options: options,

View File

@ -686,7 +686,7 @@ public final class ChatInlineSearchResultsListComponent: Component {
}, },
openPhotoSetup: { openPhotoSetup: {
}, },
openAdInfo: { _ in openAdInfo: { _, _ in
}, },
openAccountFreezeInfo: { openAccountFreezeInfo: {
} }

View File

@ -69,11 +69,19 @@ public final class GiftItemComponent: Component {
} }
} }
} }
public enum Font {
case generic
case monospaced
}
public let text: String public let text: String
public let font: Font
public let color: Color public let color: Color
public init(text: String, color: Color) { public init(text: String, font: Font = .generic, color: Color) {
self.text = text self.text = text
self.font = font
self.color = color self.color = color
} }
} }
@ -593,11 +601,19 @@ public final class GiftItemComponent: Component {
} else { } else {
ribbonFontSize = 10.0 ribbonFontSize = 10.0
} }
let ribbonFont: UIFont
switch ribbon.font {
case .generic:
ribbonFont = Font.semibold(ribbonFontSize)
case .monospaced:
ribbonFont = Font.with(size: 10.0, design: .monospace, weight: .semibold)
}
let ribbonTextSize = self.ribbonText.update( let ribbonTextSize = self.ribbonText.update(
transition: transition, transition: transition,
component: AnyComponent( component: AnyComponent(
MultilineTextComponent( MultilineTextComponent(
text: .plain(NSAttributedString(string: ribbon.text, font: Font.semibold(ribbonFontSize), textColor: .white)), text: .plain(NSAttributedString(string: ribbon.text, font: ribbonFont, textColor: .white)),
horizontalAlignment: .center horizontalAlignment: .center
) )
), ),
@ -778,7 +794,11 @@ public final class GiftItemComponent: Component {
} else { } else {
selectionLayer = SimpleShapeLayer() selectionLayer = SimpleShapeLayer()
self.selectionLayer = selectionLayer self.selectionLayer = selectionLayer
self.layer.addSublayer(selectionLayer) if self.ribbon.layer.superlayer != nil {
self.layer.insertSublayer(selectionLayer, below: self.ribbon.layer)
} else {
self.layer.addSublayer(selectionLayer)
}
selectionLayer.fillColor = UIColor.clear.cgColor selectionLayer.fillColor = UIColor.clear.cgColor
selectionLayer.strokeColor = UIColor.white.cgColor selectionLayer.strokeColor = UIColor.white.cgColor

View File

@ -430,10 +430,15 @@ final class GiftOptionsScreenComponent: Component {
) )
mainController.push(giftController) mainController.push(giftController)
} else { } else {
var forceUnique = false
if let disallowedGifts = self.state?.disallowedGifts, disallowedGifts.contains(.limited) && !disallowedGifts.contains(.unique) {
forceUnique = true
}
let giftController = GiftSetupScreen( let giftController = GiftSetupScreen(
context: component.context, context: component.context,
peerId: component.peerId, peerId: component.peerId,
subject: .starGift(gift), subject: .starGift(gift, forceUnique),
completion: component.completion completion: component.completion
) )
mainController.push(giftController) mainController.push(giftController)
@ -714,7 +719,7 @@ final class GiftOptionsScreenComponent: Component {
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 }) let premiumConfiguration = PremiumConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 })
let isPremiumDisabled = premiumConfiguration.isPremiumDisabled let isPremiumDisabled = premiumConfiguration.isPremiumDisabled || state.disallowedGifts?.contains(.premium) == true
let isSelfGift = component.peerId == component.context.account.peerId let isSelfGift = component.peerId == component.context.account.peerId
let isChannelGift = component.peerId.namespace == Namespaces.Peer.CloudChannel let isChannelGift = component.peerId.namespace == Namespaces.Peer.CloudChannel
@ -1425,7 +1430,11 @@ final class GiftOptionsScreenComponent: Component {
} }
if disallowedGifts.contains(.limited) { if disallowedGifts.contains(.limited) {
if gift.availability != nil { if gift.availability != nil {
return false if !disallowedGifts.contains(.unique) && gift.upgradeStars != nil {
} else {
return false
}
} }
} }
} }

View File

@ -371,7 +371,7 @@ final class GiftSetupScreenComponent: Component {
} else { } else {
fatalError() fatalError()
} }
case let .starGift(starGift): case let .starGift(starGift, _):
finalPrice = starGift.price finalPrice = starGift.price
if self.includeUpgrade, let upgradeStars = starGift.upgradeStars { if self.includeUpgrade, let upgradeStars = starGift.upgradeStars {
finalPrice += upgradeStars finalPrice += upgradeStars
@ -403,7 +403,7 @@ final class GiftSetupScreenComponent: Component {
return return
} }
if peerId.namespace == Namespaces.Peer.CloudChannel, case let .starGift(starGift) = component.subject { if peerId.namespace == Namespaces.Peer.CloudChannel, case let .starGift(starGift, _) = component.subject {
var controllers = navigationController.viewControllers var controllers = navigationController.viewControllers
controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) } controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) }
navigationController.setViewControllers(controllers, animated: true) navigationController.setViewControllers(controllers, animated: true)
@ -555,6 +555,10 @@ final class GiftSetupScreenComponent: Component {
self.hideName = true self.hideName = true
} }
if case let .starGift(gift, true) = component.subject, gift.upgradeStars != nil {
self.includeUpgrade = true
}
let _ = (component.context.engine.data.get( let _ = (component.context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: component.peerId), TelegramEngine.EngineData.Item.Peer.Peer(id: component.peerId),
TelegramEngine.EngineData.Item.Peer.Peer(id: component.context.account.peerId), TelegramEngine.EngineData.Item.Peer.Peer(id: component.context.account.peerId),
@ -684,7 +688,7 @@ final class GiftSetupScreenComponent: Component {
self.options = options self.options = options
}) })
if case let .starGift(gift) = component.subject { if case let .starGift(gift, _) = component.subject {
if let _ = gift.upgradeStars { if let _ = gift.upgradeStars {
self.previewPromise.set( self.previewPromise.set(
component.context.engine.payments.starGiftUpgradePreview(giftId: gift.id) component.context.engine.payments.starGiftUpgradePreview(giftId: gift.id)
@ -742,7 +746,7 @@ final class GiftSetupScreenComponent: Component {
contentHeight += environment.navigationHeight contentHeight += environment.navigationHeight
contentHeight += 26.0 contentHeight += 26.0
if case let .starGift(starGift) = component.subject, let availability = starGift.availability { if case let .starGift(starGift, _) = component.subject, let availability = starGift.availability {
let remains: Int32 = availability.remains let remains: Int32 = availability.remains
let total: Int32 = availability.total let total: Int32 = availability.total
let position = CGFloat(remains) / CGFloat(total) let position = CGFloat(remains) / CGFloat(total)
@ -909,7 +913,7 @@ final class GiftSetupScreenComponent: Component {
let (currency, amount) = product.storeProduct?.priceCurrencyAndAmount ?? ("USD", 1) let (currency, amount) = product.storeProduct?.priceCurrencyAndAmount ?? ("USD", 1)
subject = .premium(months: product.months, amount: amount, currency: currency) subject = .premium(months: product.months, amount: amount, currency: currency)
} }
case let .starGift(gift): case let .starGift(gift, _):
subject = .starGift(gift: gift) subject = .starGift(gift: gift)
upgradeStars = gift.upgradeStars upgradeStars = gift.upgradeStars
} }
@ -1061,13 +1065,17 @@ final class GiftSetupScreenComponent: Component {
contentHeight += starsSectionSize.height contentHeight += starsSectionSize.height
contentHeight += sectionSpacing contentHeight += sectionSpacing
} }
case let .starGift(gift): case let .starGift(gift, forceUnique):
if let upgradeStars = gift.upgradeStars, component.peerId != component.context.account.peerId { if let upgradeStars = gift.upgradeStars, component.peerId != component.context.account.peerId {
let upgradeFooterRawString: String let upgradeFooterRawString: String
if isChannelGift { if isChannelGift {
upgradeFooterRawString = environment.strings.Gift_SendChannel_Upgrade_Info(peerName).string upgradeFooterRawString = environment.strings.Gift_SendChannel_Upgrade_Info(peerName).string
} else { } else {
upgradeFooterRawString = environment.strings.Gift_Send_Upgrade_Info(peerName).string if forceUnique {
upgradeFooterRawString = environment.strings.Gift_Send_Upgrade_ForcedInfo(peerName).string
} else {
upgradeFooterRawString = environment.strings.Gift_Send_Upgrade_Info(peerName).string
}
} }
let parsedString = parseMarkdownIntoAttributedString(upgradeFooterRawString, attributes: footerAttributes) let parsedString = parseMarkdownIntoAttributedString(upgradeFooterRawString, attributes: footerAttributes)
@ -1136,8 +1144,8 @@ final class GiftSetupScreenComponent: Component {
) )
)), )),
], alignment: .left, spacing: 2.0)), ], alignment: .left, spacing: 2.0)),
accessory: .toggle(ListActionItemComponent.Toggle(style: .regular, isOn: self.includeUpgrade, action: { [weak self] _ in accessory: .toggle(ListActionItemComponent.Toggle(style: .regular, isOn: self.includeUpgrade, isEnabled: !forceUnique, action: { [weak self] _ in
guard let self else { guard let self, !forceUnique else {
return return
} }
self.includeUpgrade = !self.includeUpgrade self.includeUpgrade = !self.includeUpgrade
@ -1263,7 +1271,7 @@ final class GiftSetupScreenComponent: Component {
let amountString = product.price let amountString = product.price
buttonString = "\(environment.strings.Gift_Send_Send) \(amountString)" buttonString = "\(environment.strings.Gift_Send_Send) \(amountString)"
} }
case let .starGift(starGift): case let .starGift(starGift, _):
var finalPrice: Int64 = starGift.price var finalPrice: Int64 = starGift.price
if self.includeUpgrade, let upgradePrice = starGift.upgradeStars { if self.includeUpgrade, let upgradePrice = starGift.upgradeStars {
finalPrice += upgradePrice finalPrice += upgradePrice
@ -1685,7 +1693,7 @@ final class GiftSetupScreenComponent: Component {
public final class GiftSetupScreen: ViewControllerComponentContainer { public final class GiftSetupScreen: ViewControllerComponentContainer {
public enum Subject: Equatable { public enum Subject: Equatable {
case premium(PremiumGiftProduct) case premium(PremiumGiftProduct)
case starGift(StarGift.Gift) case starGift(StarGift.Gift, Bool)
} }
private let context: AccountContext private let context: AccountContext

View File

@ -141,3 +141,24 @@ private func generateCloseButtonImage() -> UIImage? {
context.strokePath() context.strokePath()
})?.withRenderingMode(.alwaysTemplate) })?.withRenderingMode(.alwaysTemplate)
} }
func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: UIColor) -> UIImage? {
return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(backgroundColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
context.setLineWidth(2.0)
context.setLineCap(.round)
context.setStrokeColor(foregroundColor.cgColor)
context.move(to: CGPoint(x: 10.0, y: 10.0))
context.addLine(to: CGPoint(x: 20.0, y: 20.0))
context.strokePath()
context.move(to: CGPoint(x: 20.0, y: 10.0))
context.addLine(to: CGPoint(x: 10.0, y: 20.0))
context.strokePath()
})
}

View File

@ -0,0 +1,381 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import SwiftSignalKit
import TelegramCore
import Markdown
import TextFormat
import TelegramPresentationData
import ViewControllerComponent
import SheetComponent
import BundleIconComponent
import BalancedTextComponent
import MultilineTextComponent
import ButtonComponent
import PlainButtonComponent
import GiftItemComponent
import AccountContext
private final class SheetContent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let gifts: [ProfileGiftsContext.State.StarGift]
let completion: (StarGiftReference) -> Void
let dismiss: () -> Void
init(
context: AccountContext,
gifts: [ProfileGiftsContext.State.StarGift],
completion: @escaping (StarGiftReference) -> Void,
dismiss: @escaping () -> Void
) {
self.context = context
self.gifts = gifts
self.completion = completion
self.dismiss = dismiss
}
static func ==(lhs: SheetContent, rhs: SheetContent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.gifts != rhs.gifts {
return false
}
return true
}
final class State: ComponentState {
var selectedGift: StarGiftReference?
}
func makeState() -> State {
return State()
}
static var body: Body {
let closeButton = Child(Button.self)
let title = Child(BalancedTextComponent.self)
let text = Child(BalancedTextComponent.self)
let gifts = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self)
let button = Child(ButtonComponent.self)
return { context in
let environment = context.environment[EnvironmentType.self]
let component = context.component
let state = context.state
let theme = environment.theme
let strings = environment.strings
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
let textSideInset: CGFloat = 32.0 + environment.safeInsets.left
let titleFont = Font.semibold(17.0)
let subtitleFont = Font.regular(12.0)
let textColor = theme.actionSheet.primaryTextColor
let secondaryTextColor = theme.actionSheet.secondaryTextColor
var contentSize = CGSize(width: context.availableSize.width, height: 10.0)
let closeButton = closeButton.update(
component: Button(
content: AnyComponent(Text(text: strings.Common_Cancel, font: Font.regular(17.0), color: theme.actionSheet.controlAccentColor)),
action: { [weak component] in
component?.dismiss()
}
),
availableSize: CGSize(width: 100.0, height: 30.0),
transition: .immediate
)
context.add(closeButton
.position(CGPoint(x: environment.safeInsets.left + 16.0 + closeButton.size.width / 2.0, y: 28.0))
)
let title = title.update(
component: BalancedTextComponent(
text: .plain(NSAttributedString(string: "Too Many Pinned Gifts", font: titleFont, textColor: textColor)),
horizontalAlignment: .center,
maximumNumberOfLines: 1,
lineSpacing: 0.1
),
availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height),
transition: .immediate
)
context.add(title
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0))
)
contentSize.height += title.size.height
let text = text.update(
component: BalancedTextComponent(
text: .plain(NSAttributedString(string: "Select a gift to unpin below:", font: subtitleFont, textColor: secondaryTextColor)),
horizontalAlignment: .center,
maximumNumberOfLines: 1,
lineSpacing: 0.2
),
availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height),
transition: .immediate
)
context.add(text
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + text.size.height / 2.0))
)
contentSize.height += text.size.height
contentSize.height += 17.0
let itemsSideInset = environment.safeInsets.left + 16.0
let spacing: CGFloat = 10.0
let itemsInRow = 3
let width = (context.availableSize.width - itemsSideInset * 2.0 - spacing * CGFloat(itemsInRow - 1)) / CGFloat(itemsInRow)
var updatedGifts: [_UpdatedChildComponent] = []
var index = 0
var nextOriginX = itemsSideInset
for gift in component.gifts {
guard case let .unique(uniqueGift) = gift.gift else {
continue
}
var ribbonColor: GiftItemComponent.Ribbon.Color = .blue
for attribute in uniqueGift.attributes {
if case let .backdrop(_, innerColor, outerColor, _, _, _) = attribute {
ribbonColor = .custom(outerColor, innerColor)
break
}
}
updatedGifts.append(
gifts[index].update(
component: AnyComponent(
PlainButtonComponent(
content: AnyComponent(
GiftItemComponent(
context: component.context,
theme: theme,
strings: strings,
subject: .uniqueGift(gift: uniqueGift),
ribbon: GiftItemComponent.Ribbon(text: "#\(uniqueGift.number)", font: .monospaced, color: ribbonColor),
isSelected: state.selectedGift == gift.reference,
mode: .grid
)
),
effectAlignment: .center,
action: { [weak state] in
guard let state else {
return
}
state.selectedGift = gift.reference
state.updated(transition: .spring(duration: 0.3))
},
animateAlpha: false
)
),
availableSize: CGSize(width: width, height: width),
transition: context.transition
)
)
context.add(updatedGifts[index]
.position(CGPoint(x: nextOriginX + updatedGifts[index].size.width / 2.0, y: contentSize.height + updatedGifts[index].size.height / 2.0))
)
nextOriginX += updatedGifts[index].size.width + spacing
if nextOriginX > context.availableSize.width - itemsSideInset {
contentSize.height += updatedGifts[index].size.height + spacing
nextOriginX = itemsSideInset
}
index += 1
}
contentSize.height += 14.0
let button = button.update(
component: ButtonComponent(
background: ButtonComponent.Background(
color: theme.list.itemCheckColors.fillColor,
foreground: theme.list.itemCheckColors.foregroundColor,
pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
),
content: AnyComponentWithIdentity(
id: AnyHashable("unpin"),
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "Unpin", font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))))
),
isEnabled: state.selectedGift != nil,
displaysProgress: false,
action: { [weak state] in
guard let state else {
return
}
if let selectedGift = state.selectedGift {
component.completion(selectedGift)
component.dismiss()
}
}
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0),
transition: context.transition
)
context.add(button
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + button.size.height / 2.0))
.cornerRadius(10.0)
)
contentSize.height += button.size.height
contentSize.height += 7.0
let effectiveBottomInset: CGFloat = environment.metrics.isTablet ? 0.0 : environment.safeInsets.bottom
contentSize.height += 5.0 + effectiveBottomInset
return contentSize
}
}
}
private final class SheetContainerComponent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let gifts: [ProfileGiftsContext.State.StarGift]
let completion: (StarGiftReference) -> Void
init(
context: AccountContext,
gifts: [ProfileGiftsContext.State.StarGift],
completion: @escaping (StarGiftReference) -> Void
) {
self.context = context
self.gifts = gifts
self.completion = completion
}
static func ==(lhs: SheetContainerComponent, rhs: SheetContainerComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.gifts != rhs.gifts {
return false
}
return true
}
static var body: Body {
let sheet = Child(SheetComponent<EnvironmentType>.self)
let animateOut = StoredActionSlot(Action<Void>.self)
let sheetExternalState = SheetComponent<EnvironmentType>.ExternalState()
return { context in
let environment = context.environment[EnvironmentType.self]
let controller = environment.controller
let sheet = sheet.update(
component: SheetComponent<EnvironmentType>(
content: AnyComponent<EnvironmentType>(SheetContent(
context: context.component.context,
gifts: context.component.gifts,
completion: context.component.completion,
dismiss: {
animateOut.invoke(Action { _ in
if let controller = controller() {
controller.dismiss(completion: nil)
}
})
}
)),
backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor),
followContentSizeChanges: true,
externalState: sheetExternalState,
animateOut: animateOut
),
environment: {
environment
SheetComponentEnvironment(
isDisplaying: environment.value.isVisible,
isCentered: environment.metrics.widthClass == .regular,
hasInputHeight: !environment.inputHeight.isZero,
regularMetricsSize: CGSize(width: 430.0, height: 900.0),
dismiss: { animated in
if animated {
animateOut.invoke(Action { _ in
if let controller = controller() {
controller.dismiss(completion: nil)
}
})
} else {
if let controller = controller() {
controller.dismiss(completion: nil)
}
}
}
)
},
availableSize: context.availableSize,
transition: context.transition
)
context.add(sheet
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
)
if let controller = controller(), !controller.automaticallyControlPresentationContextLayout {
let layout = ContainerViewLayout(
size: context.availableSize,
metrics: environment.metrics,
deviceMetrics: environment.deviceMetrics,
intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: max(environment.safeInsets.bottom, sheetExternalState.contentHeight), right: 0.0),
safeInsets: UIEdgeInsets(top: 0.0, left: environment.safeInsets.left, bottom: 0.0, right: environment.safeInsets.right),
additionalInsets: .zero,
statusBarHeight: environment.statusBarHeight,
inputHeight: nil,
inputHeightIsInteractivellyChanging: false,
inVoiceOver: false
)
controller.presentationContext.containerLayoutUpdated(layout, transition: context.transition.containedViewLayoutTransition)
}
return context.availableSize
}
}
}
public class GiftUnpinScreen: ViewControllerComponentContainer {
private let context: AccountContext
private let gifts: [ProfileGiftsContext.State.StarGift]
private let completion: (StarGiftReference) -> Void
public init(
context: AccountContext,
gifts: [ProfileGiftsContext.State.StarGift],
completion: @escaping (StarGiftReference) -> Void
) {
self.context = context
self.gifts = gifts
self.completion = completion
super.init(
context: context,
component: SheetContainerComponent(
context: context,
gifts: gifts,
completion: completion
),
navigationBarAppearance: .none,
statusBarStyle: .ignore,
theme: .default
)
self.navigationPresentation = .flatModal
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public func dismissAnimated() {
if let view = self.node.hostView.findTaggedView(tag: SheetComponent<ViewControllerComponentContainer.Environment>.View.Tag()) as? SheetComponent<ViewControllerComponentContainer.Environment>.View {
view.dismissAnimated()
}
}
}

View File

@ -3007,12 +3007,6 @@ public class GiftViewScreen: ViewControllerComponentContainer {
self.subject = .profileGift(peerId, gift.withPinnedToTop(false)) self.subject = .profileGift(peerId, gift.withPinnedToTop(false))
} }
} }
} else {
var maxPinnedCount: Int = 6
if let value = context.currentAppConfiguration.with({ $0 }).data?["stargifts_pinned_to_top_limit"] as? Double {
maxPinnedCount = Int(value)
}
self.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: strings.PeerInfo_Gifts_ToastPinLimit_Text(Int32(maxPinnedCount)), timeout: nil, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
} }
}) })
}))) })))

View File

@ -18,12 +18,14 @@ public final class ListActionItemComponent: Component {
public var style: ToggleStyle public var style: ToggleStyle
public var isOn: Bool public var isOn: Bool
public var isInteractive: Bool public var isInteractive: Bool
public var isEnabled: Bool
public var action: ((Bool) -> Void)? public var action: ((Bool) -> Void)?
public init(style: ToggleStyle, isOn: Bool, isInteractive: Bool = true, action: ((Bool) -> Void)? = nil) { public init(style: ToggleStyle, isOn: Bool, isInteractive: Bool = true, isEnabled: Bool = true, action: ((Bool) -> Void)? = nil) {
self.style = style self.style = style
self.isOn = isOn self.isOn = isOn
self.isInteractive = isInteractive self.isInteractive = isInteractive
self.isEnabled = isEnabled
self.action = action self.action = action
} }
@ -37,6 +39,9 @@ public final class ListActionItemComponent: Component {
if lhs.isInteractive != rhs.isInteractive { if lhs.isInteractive != rhs.isInteractive {
return false return false
} }
if lhs.isEnabled != rhs.isEnabled {
return false
}
if (lhs.action == nil) != (rhs.action == nil) { if (lhs.action == nil) != (rhs.action == nil) {
return false return false
} }
@ -648,7 +653,9 @@ public final class ListActionItemComponent: Component {
} }
} }
switchNode.isUserInteractionEnabled = toggle.isInteractive switchNode.isUserInteractionEnabled = toggle.isInteractive && toggle.isEnabled
switchNode.alpha = toggle.isEnabled ? 1.0 : 0.3
switchNode.layer.allowsGroupOpacity = !toggle.isEnabled
if updateSwitchTheme { if updateSwitchTheme {
switchNode.frameColor = component.theme.list.itemSwitchColors.frameColor switchNode.frameColor = component.theme.list.itemSwitchColors.frameColor

View File

@ -187,7 +187,7 @@ public final class LoadingOverlayNode: ASDisplayNode {
}, editPeer: { _ in }, editPeer: { _ in
}, openWebApp: { _ in }, openWebApp: { _ in
}, openPhotoSetup: { }, openPhotoSetup: {
}, openAdInfo: { _ in }, openAdInfo: { _, _ in
}, openAccountFreezeInfo: { }, openAccountFreezeInfo: {
}) })
@ -548,7 +548,7 @@ private final class PeerInfoScreenPersonalChannelItemNode: PeerInfoScreenItemNod
}, },
openPhotoSetup: { openPhotoSetup: {
}, },
openAdInfo: { _ in openAdInfo: { _, _ in
}, },
openAccountFreezeInfo: { openAccountFreezeInfo: {
} }

View File

@ -540,14 +540,18 @@ private final class PeerInfoPendingPane {
switch key { switch key {
case .gifts: case .gifts:
var canManage = false var canManage = false
var canGift = true
if let peer = data.peer { if let peer = data.peer {
if let cachedUserData = data.cachedData as? CachedUserData, cachedUserData.disallowedGifts == .All {
canGift = false
}
if let channel = peer as? TelegramChannel, case .broadcast = channel.info { if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
if channel.hasPermission(.sendSomething) { if channel.hasPermission(.sendSomething) {
canManage = true canManage = true
} }
} }
} }
paneNode = PeerInfoGiftsPaneNode(context: context, peerId: peerId, chatControllerInteraction: chatControllerInteraction, profileGifts: data.profileGiftsContext!, canManage: canManage) paneNode = PeerInfoGiftsPaneNode(context: context, peerId: peerId, chatControllerInteraction: chatControllerInteraction, profileGifts: data.profileGiftsContext!, canManage: canManage, canGift: canGift)
case .stories, .storyArchive, .botPreview: case .stories, .storyArchive, .botPreview:
var canManage = false var canManage = false
if let peer = data.peer { if let peer = data.peer {

View File

@ -6435,15 +6435,18 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
} }
if strongSelf.peerId.namespace == Namespaces.Peer.CloudUser, !user.isDeleted && user.botInfo == nil && !user.flags.contains(.isSupport) { if strongSelf.peerId.namespace == Namespaces.Peer.CloudUser, !user.isDeleted && user.botInfo == nil && !user.flags.contains(.isSupport) {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Profile_SendGift, icon: { theme in if let cachedData = data.cachedData as? CachedUserData, cachedData.disallowedGifts == .All {
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Gift"), color: theme.contextMenu.primaryColor) } else {
}, action: { [weak self] _, f in items.append(.action(ContextMenuActionItem(text: presentationData.strings.Profile_SendGift, icon: { theme in
f(.dismissWithoutContent) generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Gift"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in
if let self { f(.dismissWithoutContent)
self.openPremiumGift()
} if let self {
}))) self.openPremiumGift()
}
})))
}
} }
if let cachedData = data.cachedData as? CachedUserData, cachedData.flags.contains(.translationHidden) { if let cachedData = data.cachedData as? CachedUserData, cachedData.flags.contains(.translationHidden) {

View File

@ -33,6 +33,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
private let peerId: PeerId private let peerId: PeerId
private let profileGifts: ProfileGiftsContext private let profileGifts: ProfileGiftsContext
private let canManage: Bool private let canManage: Bool
private let canGift: Bool
private var dataDisposable: Disposable? private var dataDisposable: Disposable?
@ -101,12 +102,13 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
private let maxPinnedCount: Int private let maxPinnedCount: Int
public init(context: AccountContext, peerId: PeerId, chatControllerInteraction: ChatControllerInteraction, profileGifts: ProfileGiftsContext, canManage: Bool) { public init(context: AccountContext, peerId: PeerId, chatControllerInteraction: ChatControllerInteraction, profileGifts: ProfileGiftsContext, canManage: Bool, canGift: Bool) {
self.context = context self.context = context
self.peerId = peerId self.peerId = peerId
self.chatControllerInteraction = chatControllerInteraction self.chatControllerInteraction = chatControllerInteraction
self.profileGifts = profileGifts self.profileGifts = profileGifts
self.canManage = canManage self.canManage = canManage
self.canGift = canGift
self.backgroundNode = ASDisplayNode() self.backgroundNode = ASDisplayNode()
self.scrollNode = ASScrollNode() self.scrollNode = ASScrollNode()
@ -434,6 +436,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
let ribbonText: String? let ribbonText: String?
var ribbonColor: GiftItemComponent.Ribbon.Color = .blue var ribbonColor: GiftItemComponent.Ribbon.Color = .blue
var ribbonFont: GiftItemComponent.Ribbon.Font = .generic
switch product.gift { switch product.gift {
case let .generic(gift): case let .generic(gift):
if let availability = gift.availability { if let availability = gift.availability {
@ -442,7 +445,8 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
ribbonText = nil ribbonText = nil
} }
case let .unique(gift): case let .unique(gift):
ribbonText = params.presentationData.strings.PeerInfo_Gifts_OneOf(compactNumericCountString(Int(gift.availability.issued), decimalSeparator: params.presentationData.dateTimeFormat.decimalSeparator)).string ribbonFont = .monospaced
ribbonText = "#\(gift.number)"
for attribute in gift.attributes { for attribute in gift.attributes {
if case let .backdrop(_, innerColor, outerColor, _, _, _) = attribute { if case let .backdrop(_, innerColor, outerColor, _, _, _) = attribute {
ribbonColor = .custom(outerColor, innerColor) ribbonColor = .custom(outerColor, innerColor)
@ -471,7 +475,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
strings: params.presentationData.strings, strings: params.presentationData.strings,
peer: peer, peer: peer,
subject: subject, subject: subject,
ribbon: ribbonText.flatMap { GiftItemComponent.Ribbon(text: $0, color: ribbonColor) }, ribbon: ribbonText.flatMap { GiftItemComponent.Ribbon(text: $0, font: ribbonFont, color: ribbonColor) },
isHidden: !product.savedToProfile, isHidden: !product.savedToProfile,
isPinned: product.pinnedToTop, isPinned: product.pinnedToTop,
isEditing: self.isReordering, isEditing: self.isReordering,
@ -542,6 +546,19 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
return false return false
} }
if pinnedToTop && self.pinnedReferences.count >= self.maxPinnedCount { if pinnedToTop && self.pinnedReferences.count >= self.maxPinnedCount {
if let gifts = self.profileGifts.currentState?.gifts.filter({ $0.pinnedToTop }) {
let controller = GiftUnpinScreen(
context: context,
gifts: gifts,
completion: { [weak self] reference in
guard let self else {
return
}
self.profileGifts.updateStarGiftPinnedToTop(reference: reference, pinnedToTop: false)
}
)
self.parentController?.push(controller)
}
return false return false
} }
if let reference = product.reference { if let reference = product.reference {
@ -657,7 +674,10 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
let panelSeparator: ASDisplayNode let panelSeparator: ASDisplayNode
let panelButton: SolidRoundedButtonNode let panelButton: SolidRoundedButtonNode
let panelAlpha = params.expandProgress var panelAlpha = params.expandProgress
if !self.canGift {
panelAlpha = 0.0
}
if let current = self.panelBackground { if let current = self.panelBackground {
panelBackground = current panelBackground = current

View File

@ -212,7 +212,7 @@ final class GreetingMessageListItemComponent: Component {
}, },
openPhotoSetup: { openPhotoSetup: {
}, },
openAdInfo: { _ in openAdInfo: { _, _ in
}, },
openAccountFreezeInfo: { openAccountFreezeInfo: {
} }

View File

@ -233,7 +233,7 @@ final class QuickReplySetupScreenComponent: Component {
}, },
openPhotoSetup: { openPhotoSetup: {
}, },
openAdInfo: { _ in openAdInfo: { _, _ in
}, },
openAccountFreezeInfo: { openAccountFreezeInfo: {
} }

View File

@ -112,14 +112,24 @@ final class ChatbotSetupScreenComponent: Component {
final class Permission { final class Permission {
var id: String var id: String
var key: TelegramBusinessBotRights?
var title: String var title: String
var value: Bool? var value: Bool?
var enabled: Bool var enabled: Bool
var subpermissions: [Permission]? var subpermissions: [Permission]?
var expanded: Bool? var expanded: Bool?
init(id: String, title: String, value: Bool? = nil, enabled: Bool = true, subpermissions: [Permission]? = nil, expanded: Bool? = nil) { init(
id: String,
key: TelegramBusinessBotRights? = nil,
title: String,
value: Bool? = nil,
enabled: Bool = true,
subpermissions: [Permission]? = nil,
expanded: Bool? = nil
) {
self.id = id self.id = id
self.key = key
self.title = title self.title = title
self.value = value self.value = value
self.enabled = enabled self.enabled = enabled
@ -162,7 +172,6 @@ final class ChatbotSetupScreenComponent: Component {
) )
private var permissions: [Permission] = [] private var permissions: [Permission] = []
private var botRights: TelegramBusinessBotRights = [] private var botRights: TelegramBusinessBotRights = []
override init(frame: CGRect) { override init(frame: CGRect) {
@ -184,30 +193,6 @@ final class ChatbotSetupScreenComponent: Component {
self.addSubview(self.scrollView) self.addSubview(self.scrollView)
self.scrollView.layer.addSublayer(self.topOverscrollLayer) self.scrollView.layer.addSublayer(self.topOverscrollLayer)
self.permissions = [
Permission(id: "message", title: "Manage Messages", subpermissions: [
Permission(id: "read", title: "Read Messages", value: true, enabled: false),
Permission(id: "reply", title: "Reply to Messages", value: true),
Permission(id: "mark", title: "Mark Messages as Read", value: true),
Permission(id: "deleteSent", title: "Delete Sent Messages", value: true),
Permission(id: "deleteReceived", title: "Delete Received Messages", value: true)
], expanded: false),
Permission(id: "profile", title: "Manage Profile", subpermissions: [
Permission(id: "name", title: "Edit Name", value: true),
Permission(id: "bio", title: "Edit Bio", value: true),
Permission(id: "avatar", title: "Edit Profile Picture", value: true),
Permission(id: "username", title: "Edit Username", value: true)
], expanded: false),
Permission(id: "gifts", title: "Manage Gifts and Stars", subpermissions: [
Permission(id: "view", title: "View Gifts", value: true),
Permission(id: "sell", title: "Sell Gifts", value: true),
Permission(id: "settings", title: "Change Gift Settings", value: true),
Permission(id: "transfer", title: "Transfer and Upgrade Gifts", value: true),
Permission(id: "transferStars", title: "Transfer Stars", value: true)
], expanded: false),
Permission(id: "stories", title: "Manage Stories", value: true)
]
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
@ -250,7 +235,7 @@ final class ChatbotSetupScreenComponent: Component {
let _ = component.context.engine.accountData.setAccountConnectedBot(bot: TelegramAccountConnectedBot( let _ = component.context.engine.accountData.setAccountConnectedBot(bot: TelegramAccountConnectedBot(
id: peer.id, id: peer.id,
recipients: recipients, recipients: recipients,
rights: [] rights: self.botRights
)).startStandalone() )).startStandalone()
} else { } else {
let _ = component.context.engine.accountData.setAccountConnectedBot(bot: nil).startStandalone() let _ = component.context.engine.accountData.setAccountConnectedBot(bot: nil).startStandalone()
@ -526,13 +511,17 @@ final class ChatbotSetupScreenComponent: Component {
self.isUpdating = false self.isUpdating = false
} }
let environment = environment[EnvironmentType.self].value
let themeUpdated = self.environment?.theme !== environment.theme
self.environment = environment
if self.component == nil { if self.component == nil {
if let bot = component.initialData.bot, let botPeer = component.initialData.botPeer, let addressName = botPeer.addressName { if let bot = component.initialData.bot, let botPeer = component.initialData.botPeer, let addressName = botPeer.addressName {
self.botResolutionState = BotResolutionState(query: addressName, state: .found(peer: botPeer, isInstalled: true)) self.botResolutionState = BotResolutionState(query: addressName, state: .found(peer: botPeer, isInstalled: true))
self.resetQueryText = addressName.lowercased() self.resetQueryText = addressName.lowercased()
self.botRights = bot.rights self.botRights = bot.rights
let initialRecipients = bot.recipients let initialRecipients = bot.recipients
var mappedCategories = Set<AdditionalPeerList.Category>() var mappedCategories = Set<AdditionalPeerList.Category>()
@ -571,12 +560,32 @@ final class ChatbotSetupScreenComponent: Component {
self.hasAccessToAllChatsByDefault = initialRecipients.exclude self.hasAccessToAllChatsByDefault = initialRecipients.exclude
} }
self.permissions = [
Permission(id: "message", title: environment.strings.ChatbotSetup_Rights_ManageMessages, subpermissions: [
Permission(id: "read", title: environment.strings.ChatbotSetup_Rights_ReadMessages, value: true, enabled: false),
Permission(id: "reply", key: .reply, title: environment.strings.ChatbotSetup_Rights_ReplyToMessages),
Permission(id: "mark", key: .readMessages, title: environment.strings.ChatbotSetup_Rights_MarkAsRead),
Permission(id: "deleteSent", key: .deleteSentMessages, title: environment.strings.ChatbotSetup_Rights_DeleteSentMessages),
Permission(id: "deleteReceived", key: .deleteReceivedMessages, title: environment.strings.ChatbotSetup_Rights_DeleteReceivedMessages)
], expanded: false),
Permission(id: "profile", title: environment.strings.ChatbotSetup_Rights_ManageProfile, subpermissions: [
Permission(id: "name", key: .editName, title: environment.strings.ChatbotSetup_Rights_EditName),
Permission(id: "bio", key: .editBio, title: environment.strings.ChatbotSetup_Rights_EditBio),
Permission(id: "avatar", key: .editProfilePhoto, title: environment.strings.ChatbotSetup_Rights_EditProfilePhoto),
Permission(id: "username", key: .editUsername, title: environment.strings.ChatbotSetup_Rights_EditUsername)
], expanded: false),
Permission(id: "gifts", title: environment.strings.ChatbotSetup_Rights_ManageGiftsAndStars, subpermissions: [
Permission(id: "view", key: .viewGifts, title: environment.strings.ChatbotSetup_Rights_ViewGifts),
Permission(id: "sell", key: .sellGifts, title: environment.strings.ChatbotSetup_Rights_SellGifts),
Permission(id: "settings", key: .changeGiftSettings, title: environment.strings.ChatbotSetup_Rights_ChangeGiftSettings),
Permission(id: "transfer", key: .transferAndUpgradeGifts, title: environment.strings.ChatbotSetup_Rights_TransferAndUpgradeGifts),
Permission(id: "transferStars", key: .transferStars, title: environment.strings.ChatbotSetup_Rights_TransferStars)
], expanded: false),
Permission(id: "stories", key: .manageStories, title: environment.strings.ChatbotSetup_Rights_ManageStories)
]
} }
let environment = environment[EnvironmentType.self].value
let themeUpdated = self.environment?.theme !== environment.theme
self.environment = environment
self.component = component self.component = component
self.state = state self.state = state
@ -732,6 +741,7 @@ final class ChatbotSetupScreenComponent: Component {
if case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.isBusiness) { if case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.isBusiness) {
botResolutionState.state = .found(peer: peer, isInstalled: true) botResolutionState.state = .found(peer: peer, isInstalled: true)
self.botResolutionState = botResolutionState self.botResolutionState = botResolutionState
self.botRights = .All
self.state?.updated(transition: .spring(duration: 0.3)) self.state?.updated(transition: .spring(duration: 0.3))
} else { } else {
self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.ChatbotSetup_ErrorBotNotBusinessCapable, actions: [ self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.ChatbotSetup_ErrorBotNotBusinessCapable, actions: [
@ -1027,158 +1037,192 @@ final class ChatbotSetupScreenComponent: Component {
if !self.hasAccessToAllChatsByDefault { if !self.hasAccessToAllChatsByDefault {
contentHeight += excludedUsersContentHeight contentHeight += excludedUsersContentHeight
} }
var permissionsItems: [AnyComponentWithIdentity<Empty>] = []
for permission in self.permissions { if case .found(_, true) = self.botResolutionState?.state {
var value = permission.value == true var permissionsItems: [AnyComponentWithIdentity<Empty>] = []
for permission in self.permissions {
var titleItems: [AnyComponentWithIdentity<Empty>] = [] var value: Bool
titleItems.append( if let key = permission.key {
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( value = self.botRights.contains(key)
text: .plain(NSAttributedString( } else {
string: permission.title, value = permission.value == true
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
textColor: environment.theme.list.itemPrimaryTextColor
)),
maximumNumberOfLines: 1
)))
)
if let subpermissions = permission.subpermissions {
value = false
var selectedCount = 0
for subpermission in subpermissions {
if subpermission.value == true {
value = true
selectedCount += 1
}
} }
var titleItems: [AnyComponentWithIdentity<Empty>] = []
titleItems.append( titleItems.append(
AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent( AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
string: "\(selectedCount)/\(subpermissions.count)", string: permission.title,
font: Font.with(size: presentationData.listsFontSize.baseDisplaySize / 17.0 * 13.0, design: .round, weight: .semibold), font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
textColor: environment.theme.list.itemPrimaryTextColor textColor: environment.theme.list.itemPrimaryTextColor
)), )),
maximumNumberOfLines: 1 maximumNumberOfLines: 1
))) )))
) )
titleItems.append(
AnyComponentWithIdentity(id: AnyHashable(2), component: AnyComponent(BundleIconComponent( if let subpermissions = permission.subpermissions {
name: "Item List/ExpandingItemVerticalRegularArrow", value = false
tintColor: environment.theme.list.itemPrimaryTextColor, var selectedCount = 0
flipVertically: permission.expanded == true for subpermission in subpermissions {
))) if subpermission.value == true {
) value = true
} selectedCount += 1
permissionsItems.append(
AnyComponentWithIdentity(id: permission.id, component: AnyComponent(ListActionItemComponent(
theme: environment.theme,
title: AnyComponent(HStack(titleItems, spacing: 6.0)),
accessory: .toggle(ListActionItemComponent.Toggle(style: .icons, isOn: value, action: { [weak self] value in
guard let self else {
return
} }
if let subpermissions = permission.subpermissions { }
for subpermission in subpermissions { titleItems.append(
if subpermission.enabled { AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(
subpermission.value = value text: .plain(NSAttributedString(
} string: "\(selectedCount)/\(subpermissions.count)",
} font: Font.with(size: presentationData.listsFontSize.baseDisplaySize / 17.0 * 13.0, design: .round, weight: .semibold),
} else if let value = permission.value { textColor: environment.theme.list.itemPrimaryTextColor
permission.value = value )),
} maximumNumberOfLines: 1
self.state?.updated(transition: .spring(duration: 0.4)) )))
})), )
action: permission.subpermissions != nil ? { [weak self] _ in titleItems.append(
guard let self else { AnyComponentWithIdentity(id: AnyHashable(2), component: AnyComponent(BundleIconComponent(
return name: "Item List/ExpandingItemVerticalRegularArrow",
} tintColor: environment.theme.list.itemPrimaryTextColor,
var scrollToBottom = false flipVertically: permission.expanded == true
if let expanded = permission.expanded {
permission.expanded = !expanded
if !expanded {
scrollToBottom = true
}
}
self.state?.updated(transition: .spring(duration: 0.4))
if scrollToBottom {
self.scrollView.setContentOffset(CGPoint(x: 0.0, y: self.scrollView.contentSize.height - self.scrollView.bounds.height), animated: true)
}
} : nil
)))
)
if let subpermissions = permission.subpermissions, permission.expanded == true {
for subpermission in subpermissions {
permissionsItems.append(
AnyComponentWithIdentity(id: subpermission.id, component: AnyComponent(ListActionItemComponent(
theme: environment.theme,
title: AnyComponent(VStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: subpermission.title,
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
textColor: environment.theme.list.itemPrimaryTextColor
)),
maximumNumberOfLines: 1
))),
], alignment: .left, spacing: 2.0)),
leftIcon: .check(ListActionItemComponent.LeftIcon.Check(isSelected: subpermission.value == true, isEnabled: subpermission.enabled, toggle: nil)),
accessory: nil,
action: subpermission.enabled ? { [weak self] _ in
guard let self else {
return
}
if let value = subpermission.value {
subpermission.value = !value
}
self.state?.updated(transition: .spring(duration: 0.4))
} : nil
))) )))
) )
} }
//permissionsItems.append(AnyComponentWithIdentity(id: "\(permission.id)_sub", component: AnyComponent(VStack(stackItems, spacing: 0.0)))) permissionsItems.append(
AnyComponentWithIdentity(id: permission.id, component: AnyComponent(ListActionItemComponent(
theme: environment.theme,
title: AnyComponent(HStack(titleItems, spacing: 6.0)),
accessory: .toggle(ListActionItemComponent.Toggle(style: .icons, isOn: value, action: { [weak self] value in
guard let self else {
return
}
if let subpermissions = permission.subpermissions {
for subpermission in subpermissions {
if subpermission.enabled {
if let key = subpermission.key {
if value {
self.botRights.insert(key)
} else {
self.botRights.remove(key)
}
}
}
}
} else if let key = permission.key {
if value {
self.botRights.insert(key)
} else {
self.botRights.remove(key)
}
}
self.state?.updated(transition: .spring(duration: 0.4))
})),
action: permission.subpermissions != nil ? { [weak self] _ in
guard let self else {
return
}
var scrollToBottom = false
if let expanded = permission.expanded {
permission.expanded = !expanded
if !expanded {
scrollToBottom = true
}
}
self.state?.updated(transition: .spring(duration: 0.4))
if scrollToBottom {
self.scrollView.setContentOffset(CGPoint(x: 0.0, y: self.scrollView.contentSize.height - self.scrollView.bounds.height), animated: true)
}
} : nil
)))
)
if let subpermissions = permission.subpermissions, permission.expanded == true {
for subpermission in subpermissions {
var value = false
if let key = permission.key {
value = self.botRights.contains(key)
}
permissionsItems.append(
AnyComponentWithIdentity(id: subpermission.id, component: AnyComponent(ListActionItemComponent(
theme: environment.theme,
title: AnyComponent(VStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: subpermission.title,
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
textColor: environment.theme.list.itemPrimaryTextColor
)),
maximumNumberOfLines: 1
))),
], alignment: .left, spacing: 2.0)),
leftIcon: .check(ListActionItemComponent.LeftIcon.Check(isSelected: value, isEnabled: subpermission.enabled, toggle: nil)),
accessory: nil,
action: subpermission.enabled ? { [weak self] _ in
guard let self else {
return
}
if let key = subpermission.key {
if !value {
self.botRights.insert(key)
} else {
self.botRights.remove(key)
}
}
self.state?.updated(transition: .spring(duration: 0.4))
} : nil
)))
)
}
//permissionsItems.append(AnyComponentWithIdentity(id: "\(permission.id)_sub", component: AnyComponent(VStack(stackItems, spacing: 0.0))))
}
} }
}
var permissionsTransition = transition
if self.permissionsSection.view?.superview == nil {
permissionsTransition = .immediate
let permissionsSectionSize = self.permissionsSection.update(
transition: transition,
component: AnyComponent(ListSectionComponent(
theme: environment.theme,
header: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: environment.strings.ChatbotSetup_PermissionsSectionHeader,
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
textColor: environment.theme.list.freeTextColor
)),
maximumNumberOfLines: 0
)),
footer: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: environment.strings.ChatbotSetup_PermissionsSectionFooter,
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
textColor: environment.theme.list.freeTextColor
)),
maximumNumberOfLines: 0
)),
items: permissionsItems
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
)
let permissionsSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: permissionsSectionSize)
if let permissionsSectionView = self.permissionsSection.view {
if permissionsSectionView.superview == nil {
self.scrollView.addSubview(permissionsSectionView)
} }
transition.setFrame(view: permissionsSectionView, frame: permissionsSectionFrame)
let permissionsSectionSize = self.permissionsSection.update(
transition: permissionsTransition,
component: AnyComponent(ListSectionComponent(
theme: environment.theme,
header: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: environment.strings.ChatbotSetup_PermissionsSectionHeader,
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
textColor: environment.theme.list.freeTextColor
)),
maximumNumberOfLines: 0
)),
footer: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: environment.strings.ChatbotSetup_PermissionsSectionFooter,
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
textColor: environment.theme.list.freeTextColor
)),
maximumNumberOfLines: 0
)),
items: permissionsItems
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
)
let permissionsSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: permissionsSectionSize)
if let permissionsSectionView = self.permissionsSection.view {
if permissionsSectionView.superview == nil {
self.scrollView.addSubview(permissionsSectionView)
permissionsSectionView.alpha = 1.0
transition.animateAlpha(view: permissionsSectionView, from: 0.0, to: 1.0)
}
permissionsTransition.setFrame(view: permissionsSectionView, frame: permissionsSectionFrame)
}
contentHeight += permissionsSectionSize.height
} else if let permissionsSectionView = self.permissionsSection.view {
transition.setAlpha(view: permissionsSectionView, alpha: 0.0, completion: { _ in
permissionsSectionView.removeFromSuperview()
})
} }
contentHeight += permissionsSectionSize.height
contentHeight += bottomContentInset contentHeight += bottomContentInset
contentHeight += environment.safeInsets.bottom contentHeight += environment.safeInsets.bottom

View File

@ -875,7 +875,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, ASScrollViewDelegate
}, editPeer: { _ in }, editPeer: { _ in
}, openWebApp: { _ in }, openWebApp: { _ in
}, openPhotoSetup: { }, openPhotoSetup: {
}, openAdInfo: { _ in }, openAdInfo: { _, _ in
}, openAccountFreezeInfo: { }, openAccountFreezeInfo: {
}) })
let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true) let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)

View File

@ -9265,8 +9265,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
if let value = value { if let value = value {
self.present(UndoOverlayController(presentationData: self.presentationData, content: .dice(dice: dice, context: self.context, text: value, action: canSendMessagesToChat(self.presentationInterfaceState) ? self.presentationData.strings.Conversation_SendDice : nil), elevatedLayout: false, action: { [weak self] action in self.present(UndoOverlayController(presentationData: self.presentationData, content: .dice(dice: dice, context: self.context, text: value, action: canSendMessagesToChat(self.presentationInterfaceState) ? self.presentationData.strings.Conversation_SendDice : nil), elevatedLayout: false, action: { [weak self] action in
if let strongSelf = self, canSendMessagesToChat(strongSelf.presentationInterfaceState), action == .undo { if let self, canSendMessagesToChat(self.presentationInterfaceState), action == .undo {
strongSelf.sendMessages([.message(text: "", attributes: [], inlineStickers: [:], mediaReference: AnyMediaReference.standalone(media: TelegramMediaDice(emoji: dice.emoji)), threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]) self.presentPaidMessageAlertIfNeeded(completion: { [weak self] postpone in
guard let self else {
return
}
self.sendMessages([.message(text: "", attributes: [], inlineStickers: [:], mediaReference: AnyMediaReference.standalone(media: TelegramMediaDice(emoji: dice.emoji)), threadId: self.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])], postpone: postpone)
})
} }
return false return false
}), in: .current) }), in: .current)

View File

@ -559,13 +559,12 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
}, iconSource: nil, action: { _, f in }, iconSource: nil, action: { _, f in
f(.default) f(.default)
let _ = (context.engine.messages.reportAdMessage(peerId: message.id.peerId, opaqueId: adAttribute.opaqueId, option: nil) let _ = (context.engine.messages.reportAdMessage(opaqueId: adAttribute.opaqueId, option: nil)
|> deliverOnMainQueue).start(next: { result in |> deliverOnMainQueue).start(next: { result in
if case let .options(title, options) = result { if case let .options(title, options) = result {
controllerInteraction.navigationController()?.pushViewController( controllerInteraction.navigationController()?.pushViewController(
AdsReportScreen( AdsReportScreen(
context: context, context: context,
peerId: message.id.peerId,
opaqueId: adAttribute.opaqueId, opaqueId: adAttribute.opaqueId,
title: title, title: title,
options: options, options: options,

View File

@ -295,7 +295,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, ASScrollViewDe
}, editPeer: { _ in }, editPeer: { _ in
}, openWebApp: { _ in }, openWebApp: { _ in
}, openPhotoSetup: { }, openPhotoSetup: {
}, openAdInfo: { _ in }, openAdInfo: { _, _ in
}, openAccountFreezeInfo: { }, openAccountFreezeInfo: {
}) })
interaction.searchTextHighightState = searchQuery interaction.searchTextHighightState = searchQuery

View File

@ -182,7 +182,7 @@ private struct CommandChatInputContextPanelEntry: Comparable, Identifiable {
}, },
openPhotoSetup: { openPhotoSetup: {
}, },
openAdInfo: { _ in openAdInfo: { _, _ in
}, },
openAccountFreezeInfo: { openAccountFreezeInfo: {
} }