mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various improvements
This commit is contained in:
parent
fa93715135
commit
f720277d29
@ -14094,3 +14094,5 @@ Sorry for the inconvenience.";
|
||||
"Gift.Unpin.Title" = "Too Manu Pinned Gifts";
|
||||
"Gift.Unpin.Subtitle" = "Select a gift to unpin below:";
|
||||
"Gift.Unpin.Unpin" = "Unpin";
|
||||
|
||||
"ChatList.Search.Ad" = "Ad";
|
||||
|
@ -6219,7 +6219,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
title: title,
|
||||
options: options,
|
||||
completed: {
|
||||
//removeAd?(adAttribute.opaqueId)
|
||||
}
|
||||
)
|
||||
)
|
||||
@ -6236,9 +6235,20 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
guard let navigationController = self?.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
c?.dismiss(completion: {
|
||||
if context.isPremium && !"".isEmpty {
|
||||
//removeAd?(adAttribute.opaqueId)
|
||||
c?.dismiss(completion: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if context.isPremium {
|
||||
self.present(UndoOverlayController(presentationData: self.presentationData, content: .actionSucceeded(title: nil, text: self.presentationData.strings.ReportAd_Hidden, cancel: nil, destructive: false), elevatedLayout: false, action: { _ in
|
||||
return true
|
||||
}), in: .current)
|
||||
|
||||
let _ = self.context.engine.accountData.updateAdMessagesEnabled(enabled: false).start()
|
||||
|
||||
if let searchContentNode = self.chatListDisplayNode.searchDisplayController?.contentNode as? ChatListSearchContainerNode {
|
||||
searchContentNode.removeAds()
|
||||
}
|
||||
} else {
|
||||
var replaceImpl: ((ViewController) -> Void)?
|
||||
let demoController = context.sharedContext.makePremiumDemoController(context: context, subject: .noAds, forceDark: false, action: {
|
||||
|
@ -570,6 +570,12 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
self.selectionPanelNode?.selectedMessages = self.stateValue.selectedMessageIds ?? []
|
||||
}
|
||||
|
||||
public func removeAds() {
|
||||
for pane in self.paneContainerNode.currentPanes.values {
|
||||
pane.node.removeAds()
|
||||
}
|
||||
}
|
||||
|
||||
private var currentSearchOptions: ChatListSearchOptions {
|
||||
return self.searchOptionsValue ?? ChatListSearchOptions(peer: nil, date: nil)
|
||||
}
|
||||
|
@ -858,19 +858,12 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
context.engine.messages.markAdAction(opaqueId: peer.opaqueId, media: false, fullscreen: 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
|
||||
}, animationCache: interaction.animationCache, animationRenderer: interaction.animationRenderer, storyStats: nil, adButtonAction: { node in
|
||||
interaction.openAdInfo(node, peer)
|
||||
}, visibilityUpdated: { isVisible in
|
||||
if isVisible {
|
||||
context.engine.messages.markAdAsSeen(opaqueId: peer.opaqueId)
|
||||
}
|
||||
})
|
||||
case let .localPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType, storyStats, requiresPremiumForMessaging, isSelf):
|
||||
let primaryPeer: EnginePeer
|
||||
@ -1613,6 +1606,13 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
|
||||
private var deletedMessagesDisposable: Disposable?
|
||||
|
||||
private var adsHiddenPromise = ValuePromise<Bool>(false)
|
||||
private var adsHidden = false {
|
||||
didSet {
|
||||
self.adsHiddenPromise.set(self.adsHidden)
|
||||
}
|
||||
}
|
||||
|
||||
private var searchQueryValue: String?
|
||||
private var searchOptionsValue: ChatListSearchOptions?
|
||||
|
||||
@ -1954,6 +1954,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
let previousRecentlySearchedPeersState = Atomic<SearchedPeersState?>(value: nil)
|
||||
let hadAnySearchMessages = Atomic<Bool>(value: false)
|
||||
|
||||
let adsHiddenPromise = self.adsHiddenPromise
|
||||
|
||||
let foundItems: Signal<([ChatListSearchEntry], Bool)?, NoError> = combineLatest(queue: .mainQueue(), searchQuery, searchOptions, self.searchScopePromise.get(), downloadItems)
|
||||
|> mapToSignal { [weak self] query, options, searchScope, downloadItems -> Signal<([ChatListSearchEntry], Bool)?, NoError> in
|
||||
if query == nil && options == nil && [.chats, .topics, .channels, .apps].contains(key) {
|
||||
@ -2726,9 +2728,10 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
selectionPromise.get(),
|
||||
resolvedMessage,
|
||||
fixedRecentlySearchedPeers,
|
||||
foundThreads
|
||||
foundThreads,
|
||||
adsHiddenPromise.get()
|
||||
)
|
||||
|> 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, adsHidden -> ([ChatListSearchEntry], Bool)? in
|
||||
let isSearching = foundRemotePeers.3 || foundRemoteMessages.1 || foundPublicMessages.1
|
||||
var entries: [ChatListSearchEntry] = []
|
||||
var index = 0
|
||||
@ -3046,11 +3049,13 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
|
||||
var numberOfGlobalPeers = 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 !adsHidden {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3448,6 +3453,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
let previousSearchItems = Atomic<[ChatListSearchEntry]?>(value: nil)
|
||||
let previousSelectedMessages = Atomic<Set<EngineMessage.Id>?>(value: nil)
|
||||
let previousExpandGlobalSearch = Atomic<Bool>(value: false)
|
||||
let previousAdsHidden = Atomic<Bool>(value: false)
|
||||
|
||||
self.searchQueryDisposable = (searchQuery
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self, weak listInteraction, weak chatListInteraction] query in
|
||||
@ -3536,6 +3542,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
if let strongSelf = self {
|
||||
let previousSelectedMessageIds = previousSelectedMessages.swap(strongSelf.selectedMessages)
|
||||
let previousExpandGlobalSearch = previousExpandGlobalSearch.swap(strongSelf.searchStateValue.expandGlobalSearch)
|
||||
let previousAdsHidden = previousAdsHidden.swap(strongSelf.adsHidden)
|
||||
|
||||
var entriesAndFlags = foundItems?.0
|
||||
|
||||
@ -3572,8 +3579,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
|
||||
let selectionChanged = (previousSelectedMessageIds == nil) != (strongSelf.selectedMessages == nil)
|
||||
let expandGlobalSearchChanged = previousExpandGlobalSearch != strongSelf.searchStateValue.expandGlobalSearch
|
||||
let adsHiddenChanged = previousAdsHidden != strongSelf.adsHidden
|
||||
|
||||
let animated = selectionChanged || expandGlobalSearchChanged
|
||||
let animated = selectionChanged || expandGlobalSearchChanged || adsHiddenChanged
|
||||
let firstTime = previousEntries == nil
|
||||
var transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: newEntries, displayingResults: entriesAndFlags != nil, isEmpty: !isSearching && (entriesAndFlags?.isEmpty ?? false), isLoading: isSearching, animated: animated, context: context, presentationData: strongSelf.presentationData, enableHeaders: true, filter: peersFilter, requestPeerType: requestPeerType, location: location, key: strongSelf.key, tagMask: tagMask, interaction: chatListInteraction, listInteraction: listInteraction, peerContextAction: { message, node, rect, gesture, location in
|
||||
interaction.peerContextAction?(message, node, rect, gesture, location)
|
||||
@ -4909,6 +4917,10 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
self.mediaNode.updateSelectedMessages(animated: animated)
|
||||
}
|
||||
|
||||
func removeAds() {
|
||||
self.adsHidden = true
|
||||
}
|
||||
|
||||
private func enqueueRecentTransition(_ transition: ChatListSearchContainerRecentTransition, firstTime: Bool) {
|
||||
self.enqueuedRecentTransitions.append((transition, firstTime))
|
||||
|
||||
|
@ -23,6 +23,7 @@ protocol ChatListSearchPaneNode: ASDisplayNode {
|
||||
func updateSelectedMessages(animated: Bool)
|
||||
func previewViewAndActionAtLocation(_ location: CGPoint) -> (UIView, CGRect, Any)?
|
||||
func didBecomeFocused()
|
||||
func removeAds()
|
||||
var searchCurrentMessages: [EngineMessage]? { get }
|
||||
}
|
||||
|
||||
|
@ -210,6 +210,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
|
||||
let storyStats: (total: Int, unseen: Int, hasUnseenCloseFriends: Bool)?
|
||||
let openStories: ((ContactsPeerItemPeer, ASDisplayNode) -> Void)?
|
||||
let adButtonAction: ((ASDisplayNode) -> Void)?
|
||||
let visibilityUpdated: ((Bool) -> Void)?
|
||||
|
||||
public let selectable: Bool
|
||||
|
||||
@ -254,7 +255,8 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
|
||||
animationRenderer: MultiAnimationRenderer? = nil,
|
||||
storyStats: (total: Int, unseen: Int, hasUnseenCloseFriends: Bool)? = nil,
|
||||
openStories: ((ContactsPeerItemPeer, ASDisplayNode) -> Void)? = nil,
|
||||
adButtonAction: ((ASDisplayNode) -> Void)? = nil
|
||||
adButtonAction: ((ASDisplayNode) -> Void)? = nil,
|
||||
visibilityUpdated: ((Bool) -> Void)? = nil
|
||||
) {
|
||||
self.presentationData = presentationData
|
||||
self.style = style
|
||||
@ -294,6 +296,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
|
||||
self.storyStats = storyStats
|
||||
self.openStories = openStories
|
||||
self.adButtonAction = adButtonAction
|
||||
self.visibilityUpdated = visibilityUpdated
|
||||
|
||||
if let index = index {
|
||||
var letter: String = "#"
|
||||
@ -538,6 +541,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
)
|
||||
}
|
||||
self.statusNode.visibilityRect = self.visibilityStatus == false ? CGRect.zero : CGRect.infinite
|
||||
|
||||
self.item?.visibilityUpdated?(self.visibilityStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1799,14 +1804,17 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
adButton = current
|
||||
} else {
|
||||
adButton = HighlightableButtonNode()
|
||||
adButton.setImage(UIImage(bundleImageName: "Components/AdMock"), for: .normal)
|
||||
strongSelf.addSubnode(adButton)
|
||||
strongSelf.adButton = adButton
|
||||
|
||||
adButton.addTarget(strongSelf, action: #selector(strongSelf.adButtonPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
adButton.frame = CGRect(origin: CGPoint(x: params.width - 20.0 - 31.0 - 13.0, y: 11.0), size: CGSize(width: 31.0, height: 15.0))
|
||||
if updatedTheme != nil || adButton.image(for: .normal) == nil {
|
||||
adButton.setImage(PresentationResourcesChatList.searchAdIcon(item.presentationData.theme, strings: item.presentationData.strings), for: .normal)
|
||||
}
|
||||
if let icon = adButton.image(for: .normal) {
|
||||
adButton.frame = CGRect(origin: CGPoint(x: params.width - 20.0 - icon.size.width - 13.0, y: 11.0), size: icon.size).insetBy(dx: -11.0, dy: -11.0)
|
||||
}
|
||||
} else if let adButton = strongSelf.adButton {
|
||||
strongSelf.adButton = nil
|
||||
adButton.removeFromSupernode()
|
||||
|
@ -308,6 +308,7 @@ private enum PreferencesKeyValues: Int32 {
|
||||
case botBiometricsState = 39
|
||||
case businessLinks = 40
|
||||
case starGifts = 41
|
||||
case botStorageState = 42
|
||||
}
|
||||
|
||||
public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey {
|
||||
@ -538,6 +539,13 @@ public struct PreferencesKeys {
|
||||
key.setInt32(0, value: PreferencesKeyValues.starGifts.rawValue)
|
||||
return key
|
||||
}
|
||||
|
||||
public static func botStorageState(peerId: PeerId) -> ValueBoxKey {
|
||||
let key = ValueBoxKey(length: 4 + 8)
|
||||
key.setInt32(0, value: PreferencesKeyValues.botStorageState.rawValue)
|
||||
key.setInt64(4, value: peerId.toInt64())
|
||||
return key
|
||||
}
|
||||
}
|
||||
|
||||
private enum SharedDataKeyValues: Int32 {
|
||||
|
@ -2093,6 +2093,37 @@ public extension TelegramEngine.EngineData.Item {
|
||||
}
|
||||
}
|
||||
|
||||
public struct BotStorageValue: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
|
||||
public typealias Result = String?
|
||||
|
||||
fileprivate var id: EnginePeer.Id
|
||||
fileprivate var storageKey: String
|
||||
|
||||
public var mapKey: EnginePeer.Id {
|
||||
return self.id
|
||||
}
|
||||
|
||||
public init(id: EnginePeer.Id, key: String) {
|
||||
self.id = id
|
||||
self.storageKey = key
|
||||
}
|
||||
|
||||
var key: PostboxViewKey {
|
||||
return .preferences(keys: Set([PreferencesKeys.botStorageState(peerId: self.id)]))
|
||||
}
|
||||
|
||||
func extract(view: PostboxView) -> Result {
|
||||
guard let view = view as? PreferencesView else {
|
||||
preconditionFailure()
|
||||
}
|
||||
if let state = view.values[PreferencesKeys.botStorageState(peerId: self.id)]?.get(TelegramBotStorageState.self) {
|
||||
return state.data[self.storageKey]
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct BusinessChatLinks: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
|
||||
public typealias Result = TelegramBusinessChatLinks?
|
||||
|
||||
|
@ -617,10 +617,11 @@ func _internal_markAdAction(account: Account, opaqueId: Data, media: Bool, fulls
|
||||
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)))
|
||||
func _internal_markAdAsSeen(account: Account, opaqueId: Data) {
|
||||
let signal = account.network.request(Api.functions.messages.viewSponsoredMessage(randomId: Buffer(data: opaqueId)))
|
||||
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
||||
return .single(.boolFalse)
|
||||
}
|
||||
|> ignoreValues
|
||||
let _ = signal.start()
|
||||
}
|
||||
|
@ -416,6 +416,86 @@ func _internal_invokeBotCustomMethod(postbox: Postbox, network: Network, botId:
|
||||
|> switchToLatest
|
||||
}
|
||||
|
||||
private let maxBotStorageSize = 5 * 1024 * 1024
|
||||
public struct TelegramBotStorageState: Codable, Equatable {
|
||||
public struct KeyValue: Codable, Equatable {
|
||||
var key: String
|
||||
var value: String
|
||||
}
|
||||
|
||||
public var data: [String: String]
|
||||
|
||||
public init(
|
||||
data: [String: String]
|
||||
) {
|
||||
self.data = data
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: StringCodingKey.self)
|
||||
|
||||
let values = try container.decode([KeyValue].self, forKey: "data")
|
||||
var data: [String: String] = [:]
|
||||
for pair in values {
|
||||
data[pair.key] = pair.value
|
||||
}
|
||||
self.data = data
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: StringCodingKey.self)
|
||||
|
||||
var values: [KeyValue] = []
|
||||
for (key, value) in self.data {
|
||||
values.append(KeyValue(key: key, value: value))
|
||||
}
|
||||
try container.encode(values, forKey: "data")
|
||||
}
|
||||
}
|
||||
|
||||
private func _internal_updateBotStorageState(account: Account, peerId: EnginePeer.Id, update: @escaping (TelegramBotStorageState?) -> TelegramBotStorageState) -> Signal<Never, BotStorageError> {
|
||||
return account.postbox.transaction { transaction -> Signal<Never, BotStorageError> in
|
||||
let previousState = transaction.getPreferencesEntry(key: PreferencesKeys.botStorageState(peerId: peerId))?.get(TelegramBotStorageState.self)
|
||||
let updatedState = update(previousState)
|
||||
|
||||
var totalSize = 0
|
||||
for (_, value) in updatedState.data {
|
||||
totalSize += value.utf8.count
|
||||
}
|
||||
guard totalSize <= maxBotStorageSize else {
|
||||
return .fail(.quotaExceeded)
|
||||
}
|
||||
|
||||
transaction.setPreferencesEntry(key: PreferencesKeys.botStorageState(peerId: peerId), value: PreferencesEntry(updatedState))
|
||||
return .never()
|
||||
}
|
||||
|> castError(BotStorageError.self)
|
||||
|> switchToLatest
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
public enum BotStorageError {
|
||||
case quotaExceeded
|
||||
}
|
||||
|
||||
func _internal_setBotStorageValue(account: Account, peerId: EnginePeer.Id, key: String, value: String?) -> Signal<Never, BotStorageError> {
|
||||
return _internal_updateBotStorageState(account: account, peerId: peerId, update: { current in
|
||||
var data = current?.data ?? [:]
|
||||
if let value {
|
||||
data[key] = value
|
||||
} else {
|
||||
data.removeValue(forKey: key)
|
||||
}
|
||||
return TelegramBotStorageState(data: data)
|
||||
})
|
||||
}
|
||||
|
||||
func _internal_clearBotStorage(account: Account, peerId: EnginePeer.Id) -> Signal<Never, BotStorageError> {
|
||||
return _internal_updateBotStorageState(account: account, peerId: peerId, update: { _ in
|
||||
return TelegramBotStorageState(data: [:])
|
||||
})
|
||||
}
|
||||
|
||||
public struct TelegramBotBiometricsState: Codable, Equatable {
|
||||
public struct OpaqueToken: Codable, Equatable {
|
||||
public let publicKey: Data
|
||||
|
@ -1520,6 +1520,10 @@ public extension TelegramEngine {
|
||||
_internal_markAdAction(account: self.account, opaqueId: opaqueId, media: media, fullscreen: fullscreen)
|
||||
}
|
||||
|
||||
public func markAdAsSeen(opaqueId: Data) {
|
||||
_internal_markAdAsSeen(account: self.account, opaqueId: opaqueId)
|
||||
}
|
||||
|
||||
public func getAllLocalChannels(count: Int) -> Signal<[EnginePeer.Id], NoError> {
|
||||
return self.account.postbox.transaction { transaction -> [EnginePeer.Id] in
|
||||
var result: [EnginePeer.Id] = []
|
||||
|
@ -1529,11 +1529,7 @@ public extension TelegramEngine {
|
||||
public func searchAdPeers(query: String) -> Signal<[AdPeer], NoError> {
|
||||
return _internal_searchAdPeers(account: self.account, query: query)
|
||||
}
|
||||
|
||||
public func markAsSeen(ad opaqueId: Data) -> Signal<Never, NoError> {
|
||||
return _internal_markAsSeen(account: self.account, opaqueId: opaqueId)
|
||||
}
|
||||
|
||||
|
||||
public func isPremiumRequiredToContact(_ peerIds: [EnginePeer.Id]) -> Signal<[EnginePeer.Id: RequirementToContact], NoError> {
|
||||
return _internal_updateIsPremiumRequiredToContact(account: self.account, peerIds: peerIds)
|
||||
}
|
||||
@ -1673,6 +1669,14 @@ public extension TelegramEngine {
|
||||
return _internal_botsWithBiometricState(account: self.account)
|
||||
}
|
||||
|
||||
public func setBotStorageValue(peerId: EnginePeer.Id, key: String, value: String?) -> Signal<Never, BotStorageError> {
|
||||
return _internal_setBotStorageValue(account: self.account, peerId: peerId, key: key, value: value)
|
||||
}
|
||||
|
||||
public func clearBotStorage(peerId: EnginePeer.Id) -> Signal<Never, BotStorageError> {
|
||||
return _internal_clearBotStorage(account: self.account, peerId: peerId)
|
||||
}
|
||||
|
||||
public func toggleChatManagingBotIsPaused(chatId: EnginePeer.Id) {
|
||||
let _ = _internal_toggleChatManagingBotIsPaused(account: self.account, chatId: chatId).startStandalone()
|
||||
}
|
||||
|
@ -128,6 +128,8 @@ public enum PresentationResourceKey: Int32 {
|
||||
|
||||
case chatListGeneralTopicIcon
|
||||
case chatListGeneralTopicSmallIcon
|
||||
|
||||
case searchAdIcon
|
||||
|
||||
case chatTitleLockIcon
|
||||
case chatTitleMuteIcon
|
||||
|
@ -535,4 +535,38 @@ public struct PresentationResourcesChatList {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public static func searchAdIcon(_ theme: PresentationTheme, strings: PresentationStrings) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.searchAdIcon.rawValue, { theme in
|
||||
let titleString = NSAttributedString(string: strings.ChatList_Search_Ad, font: Font.regular(11.0), textColor: theme.list.itemAccentColor, paragraphAlignment: .center)
|
||||
let stringRect = titleString.boundingRect(with: CGSize(width: 200.0, height: 20.0), options: .usesLineFragmentOrigin, context: nil)
|
||||
|
||||
return generateImage(CGSize(width: floor(stringRect.width) + 18.0, height: 15.0), rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
|
||||
context.setFillColor(theme.list.itemAccentColor.withMultipliedAlpha(0.1).cgColor)
|
||||
context.addPath(UIBezierPath(roundedRect: bounds, cornerRadius: size.height / 2.0).cgPath)
|
||||
context.fillPath()
|
||||
|
||||
context.setFillColor(theme.list.itemAccentColor.cgColor)
|
||||
|
||||
let circleSize = CGSize(width: 2.0 - UIScreenPixel, height: 2.0 - UIScreenPixel)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - 8.0, y: 3.0 + UIScreenPixel), size: circleSize))
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - 8.0, y: 7.0 - UIScreenPixel), size: circleSize))
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - 8.0, y: 10.0), size: circleSize))
|
||||
|
||||
let textRect = CGRect(
|
||||
x: 5.0,
|
||||
y: (size.height - stringRect.height) / 2.0 - UIScreenPixel,
|
||||
width: stringRect.width,
|
||||
height: stringRect.height
|
||||
)
|
||||
|
||||
UIGraphicsPushContext(context)
|
||||
titleString.draw(in: textRect)
|
||||
UIGraphicsPopContext()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "admock.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 4.4 KiB |
@ -1,12 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "MockSMS.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 86 KiB |
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user