2
0
mirror of https://github.com/telegramdesktop/tdesktop synced 2025-08-22 18:27:17 +00:00

Implement paid global search requests.

This commit is contained in:
John Preston 2025-07-31 13:24:11 +04:00
parent 608481df38
commit b5c1046dca
10 changed files with 81 additions and 20 deletions

View File

@ -2945,6 +2945,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_small_balance_for_message" = "Buy **Stars** to send messages to {user}."; "lng_credits_small_balance_for_message" = "Buy **Stars** to send messages to {user}.";
"lng_credits_small_balance_for_messages" = "Buy **Stars** to send messages."; "lng_credits_small_balance_for_messages" = "Buy **Stars** to send messages.";
"lng_credits_small_balance_for_suggest" = "Buy **Stars** to suggest post to {channel}."; "lng_credits_small_balance_for_suggest" = "Buy **Stars** to suggest post to {channel}.";
"lng_credits_small_balance_for_search" = "Buy **Stars** to search through public posts.";
"lng_credits_small_balance_fallback" = "Buy **Stars** to unlock content and services on Telegram."; "lng_credits_small_balance_fallback" = "Buy **Stars** to unlock content and services on Telegram.";
"lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars."; "lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars.";
"lng_credits_enough" = "You have enough stars at the moment. {link}"; "lng_credits_enough" = "You have enough stars at the moment. {link}";

View File

@ -841,13 +841,13 @@ postsSearchIntroTitle: FlatLabel(defaultFlatLabel) {
} }
align: align(top); align: align(top);
} }
postsSearchIntroTitleMargin: margins(20px, 0px, 20px, 12px); postsSearchIntroTitleMargin: margins(20px, 0px, 20px, 4px);
postsSearchIntroSubtitle: FlatLabel(defaultFlatLabel) { postsSearchIntroSubtitle: FlatLabel(defaultFlatLabel) {
textFg: windowSubTextFg; textFg: windowSubTextFg;
minWidth: 64px; minWidth: 64px;
align: align(top); align: align(top);
} }
postsSearchIntroSubtitleMargin: margins(20px, 4px, 20px, 12px); postsSearchIntroSubtitleMargin: margins(20px, 4px, 20px, 16px);
postsSearchIntroButton: RoundButton(defaultActiveButton) { postsSearchIntroButton: RoundButton(defaultActiveButton) {
width: 200px; width: 200px;
height: 42px; height: 42px;

View File

@ -164,7 +164,8 @@ constexpr auto kPreviewPostsLimit = 3;
: state.fromPeer; : state.fromPeer;
const auto waiting = trimmed.isEmpty() const auto waiting = trimmed.isEmpty()
&& state.tags.empty() && state.tags.empty()
&& !fromPeer; && !fromPeer
&& state.tab != ChatSearchTab::PublicPosts;
const auto suggestAllChats = !waiting const auto suggestAllChats = !waiting
&& state.tab == ChatSearchTab::MyMessages && state.tab == ChatSearchTab::MyMessages
&& state.filter != ChatTypeFilter::All; && state.filter != ChatTypeFilter::All;

View File

@ -43,25 +43,38 @@ void PostsSearch::requestMore() {
} }
void PostsSearch::setQuery(const QString &query) { void PostsSearch::setQuery(const QString &query) {
if (_query == query) { const auto words = TextUtilities::PrepareSearchWords(query);
const auto prepared = words.isEmpty() ? QString() : words.join(' ');
if (_query == prepared) {
return; return;
} }
_query = query; _query = prepared;
const auto i = _entries.find(query); const auto i = _entries.find(prepared);
if (i != end(_entries)) { if (i != end(_entries)) {
pushStateUpdate(i->second); pushStateUpdate(i->second);
} else if (query.isEmpty()) { } else if (prepared.isEmpty()) {
applyQuery(); applyQuery();
} else { } else {
_timer.callOnce(kQueryDelay); _timer.callOnce(kQueryDelay);
} }
} }
void PostsSearch::setAllowedStars(int stars) { int PostsSearch::setAllowedStars(int stars) {
if (_query) { if (!_query) {
_entries[*_query].allowedStars = stars; return 0;
requestSearch(*_query); } else if (_floodState) {
if (_floodState->freeSearchesLeft > 0) {
stars = 0;
} else if (_floodState->nextFreeSearchTime > 0
&& _floodState->nextFreeSearchTime <= base::unixtime::now()) {
stars = 0;
} else {
stars = std::min(int(_floodState->starsPerPaidSearch), stars);
}
} }
_entries[*_query].allowedStars = stars;
requestSearch(*_query);
return stars;
} }
void PostsSearch::pushStateUpdate(const Entry &entry) { void PostsSearch::pushStateUpdate(const Entry &entry) {
@ -118,17 +131,20 @@ void PostsSearch::requestSearch(const QString &query) {
return; return;
} }
const auto useStars = entry.allowedStars;
entry.allowedStars = 0;
using Flag = MTPchannels_SearchPosts::Flag; using Flag = MTPchannels_SearchPosts::Flag;
entry.searchId = _api.request(MTPchannels_SearchPosts( entry.searchId = _api.request(MTPchannels_SearchPosts(
MTP_flags(Flag::f_query MTP_flags(Flag::f_query
| (entry.allowedStars ? Flag::f_allow_paid_stars : Flag())), | (useStars ? Flag::f_allow_paid_stars : Flag())),
MTP_string(), // hashtag MTP_string(), // hashtag
MTP_string(query), MTP_string(query),
MTP_int(entry.offsetRate), MTP_int(entry.offsetRate),
(entry.offsetPeer ? entry.offsetPeer->input : MTP_inputPeerEmpty()), (entry.offsetPeer ? entry.offsetPeer->input : MTP_inputPeerEmpty()),
MTP_int(entry.offsetId), MTP_int(entry.offsetId),
MTP_int(kPerPage), MTP_int(kPerPage),
MTP_long(entry.allowedStars) MTP_long(useStars)
)).done([=](const MTPmessages_Messages &result) { )).done([=](const MTPmessages_Messages &result) {
auto &entry = _entries[query]; auto &entry = _entries[query];
entry.searchId = 0; entry.searchId = 0;
@ -207,7 +223,11 @@ void PostsSearch::requestSearch(const QString &query) {
entry.pages.clear(); entry.pages.clear();
} }
entry.pages.push_back(std::move(messages)); entry.pages.push_back(std::move(messages));
const auto count = int(entry.pages.size()); const auto count = int(ranges::accumulate(
entry.pages,
size_type(),
ranges::plus(),
&std::vector<not_null<HistoryItem*>>::size));
const auto full = entry.loaded ? count : std::max(count, totalCount); const auto full = entry.loaded ? count : std::max(count, totalCount);
entry.totalCount = full; entry.totalCount = full;
if (initial && _query == query) { if (initial && _query == query) {
@ -229,7 +249,7 @@ void PostsSearch::requestSearch(const QString &query) {
void PostsSearch::setFloodStateFrom(const MTPDsearchPostsFlood &data) { void PostsSearch::setFloodStateFrom(const MTPDsearchPostsFlood &data) {
_recheckTimer.cancel(); _recheckTimer.cancel();
const auto left = data.vremains().v; const auto left = std::max(data.vremains().v, 0);
const auto next = data.vwait_till().value_or_empty(); const auto next = data.vwait_till().value_or_empty();
if (!left && next > 0) { if (!left && next > 0) {
const auto now = base::unixtime::now(); const auto now = base::unixtime::now();

View File

@ -31,7 +31,7 @@ public:
[[nodiscard]] rpl::producer<PostsSearchState> stateUpdates() const; [[nodiscard]] rpl::producer<PostsSearchState> stateUpdates() const;
void setQuery(const QString &query); void setQuery(const QString &query);
void setAllowedStars(int stars); int setAllowedStars(int stars);
void requestMore(); void requestMore();
private: private:

View File

@ -38,11 +38,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "settings/settings_common.h" #include "settings/settings_common.h"
#include "settings/settings_credits_graphics.h"
#include "settings/settings_premium.h" #include "settings/settings_premium.h"
#include "storage/storage_shared_media.h" #include "storage/storage_shared_media.h"
#include "ui/boxes/confirm_box.h" #include "ui/boxes/confirm_box.h"
#include "ui/controls/swipe_handler.h" #include "ui/controls/swipe_handler.h"
#include "ui/effects/ripple_animation.h" #include "ui/effects/ripple_animation.h"
#include "ui/toast/toast.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/widgets/menu/menu_add_action_callback_factory.h" #include "ui/widgets/menu/menu_add_action_callback_factory.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
@ -1821,8 +1823,9 @@ bool Suggestions::consumeSearchQuery(const QString &query) {
const auto tab = key.tab; const auto tab = key.tab;
const auto type = (key.tab == Tab::Media) ? key.mediaType : Type::kCount; const auto type = (key.tab == Tab::Media) ? key.mediaType : Type::kCount;
if (tab == Tab::Posts) { if (tab == Tab::Posts) {
const auto changed = (_searchQuery != query);
setPostsSearchQuery(query); setPostsSearchQuery(query);
return !query.isEmpty(); return changed || !query.isEmpty();
} else if (tab != Tab::Downloads } else if (tab != Tab::Downloads
&& type != Type::File && type != Type::File
&& type != Type::Link && type != Type::Link
@ -1881,6 +1884,7 @@ void Suggestions::setPostsSearchQuery(const QString &query) {
if (!_postsSearch) { if (!_postsSearch) {
setupPostsSearch(); setupPostsSearch();
} }
_searchQuery = query;
_searchQueryTimer.cancel(); _searchQueryTimer.cancel();
_postsSearch->setQuery(query); _postsSearch->setQuery(query);
} }
@ -1938,8 +1942,32 @@ void Suggestions::setupPostsIntro(const PostsSearchIntroState &intro) {
Settings::ShowPremium( Settings::ShowPremium(
_controller, _controller,
u"posts_search"_q); u"posts_search"_q);
} else if (!stars) {
_postsSearch->setAllowedStars(0);
} else { } else {
_postsSearch->setAllowedStars(stars); using namespace Settings;
const auto done = [=](Settings::SmallBalanceResult result) {
if (result == Settings::SmallBalanceResult::Success
|| result == Settings::SmallBalanceResult::Already) {
const auto spent = _postsSearch->setAllowedStars(stars);
if (spent > 0) {
_controller->showToast({
.text = tr::lng_posts_paid_spent(
tr::now,
lt_count,
spent,
Ui::Text::RichLangValue),
.attach = RectPart::Top,
.duration = Ui::Toast::kDefaultDuration * 2,
});
}
}
};
MaybeRequestBalanceIncrease(
_controller->uiShow(),
stars,
SmallBalanceForSearch{},
done);
} }
}, _postsSearchIntro->lifetime()); }, _postsSearchIntro->lifetime());

View File

@ -162,6 +162,7 @@ void PostsSearchIntro::setup() {
st::resaleButtonTitle, st::resaleButtonTitle,
st::resaleButtonSubtitle); st::resaleButtonSubtitle);
} }
_content->resizeToWidth(width());
}, _button->lifetime()); }, _button->lifetime());
} }

View File

@ -2493,7 +2493,7 @@ void SmallBalanceBox(
return owner->peer(peerFromChannel(value.channelId))->name(); return owner->peer(peerFromChannel(value.channelId))->name();
}, [](SmallBalanceSubscription value) { }, [](SmallBalanceSubscription value) {
return value.name; return value.name;
}, [](SmallBalanceDeepLink value) { }, [](SmallBalanceDeepLink) {
return QString(); return QString();
}, [&](SmallBalanceStarGift value) { }, [&](SmallBalanceStarGift value) {
return owner->peer(value.recipientId)->shortName(); return owner->peer(value.recipientId)->shortName();
@ -2505,6 +2505,8 @@ void SmallBalanceBox(
return value.recipientId return value.recipientId
? owner->peer(value.recipientId)->shortName() ? owner->peer(value.recipientId)->shortName()
: QString(); : QString();
}, [](SmallBalanceForSearch) {
return QString();
}); });
auto needed = show->session().credits().balanceValue( auto needed = show->session().credits().balanceValue(
@ -2556,6 +2558,9 @@ void SmallBalanceBox(
lt_channel, lt_channel,
rpl::single(Ui::Text::Bold(name)), rpl::single(Ui::Text::Bold(name)),
Ui::Text::RichLangValue) Ui::Text::RichLangValue)
: v::is<SmallBalanceForSearch>(source)
? tr::lng_credits_small_balance_for_search(
Ui::Text::RichLangValue)
: name.isEmpty() : name.isEmpty()
? tr::lng_credits_small_balance_fallback( ? tr::lng_credits_small_balance_fallback(
Ui::Text::RichLangValue) Ui::Text::RichLangValue)

View File

@ -242,6 +242,8 @@ struct SmallBalanceForMessage {
struct SmallBalanceForSuggest { struct SmallBalanceForSuggest {
PeerId recipientId; PeerId recipientId;
}; };
struct SmallBalanceForSearch {
};
struct SmallBalanceSource : std::variant< struct SmallBalanceSource : std::variant<
SmallBalanceBot, SmallBalanceBot,
SmallBalanceReaction, SmallBalanceReaction,
@ -249,7 +251,8 @@ struct SmallBalanceSource : std::variant<
SmallBalanceDeepLink, SmallBalanceDeepLink,
SmallBalanceStarGift, SmallBalanceStarGift,
SmallBalanceForMessage, SmallBalanceForMessage,
SmallBalanceForSuggest> { SmallBalanceForSuggest,
SmallBalanceForSearch> {
using variant::variant; using variant::variant;
}; };

View File

@ -22,10 +22,12 @@ void SetButtonTwoLabels(
button, button,
std::move(title), std::move(title),
st); st);
buttonTitle->show();
const auto buttonSubtitle = Ui::CreateChild<Ui::FlatLabel>( const auto buttonSubtitle = Ui::CreateChild<Ui::FlatLabel>(
button, button,
std::move(subtitle), std::move(subtitle),
subst); subst);
buttonSubtitle->show();
buttonSubtitle->setOpacity(0.6); buttonSubtitle->setOpacity(0.6);
if (textFg) { if (textFg) {
buttonTitle->setTextColorOverride((*textFg)->c); buttonTitle->setTextColorOverride((*textFg)->c);