Check premium for todo lists actions.

This commit is contained in:
John Preston 2025-06-10 20:25:24 +04:00
parent e5de8e22b7
commit bf217bf7aa
12 changed files with 155 additions and 8 deletions

View file

@ -2636,6 +2636,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_premium_summary_about_effects" = "Add over 500 animated effects to private messages.";
"lng_premium_summary_subtitle_filter_tags" = "Tag Your Chats";
"lng_premium_summary_about_filter_tags" = "Display folder names for each chat in the chat list.";
"lng_premium_summary_subtitle_todo_lists" = "To-Do Lists";
"lng_premium_summary_about_todo_lists" = "Create To-Do Lists, I guess..";
"lng_premium_summary_bottom_subtitle" = "About Telegram Premium";
"lng_premium_summary_bottom_about" = "While the free version of Telegram already gives its users more than any other messaging application, **Telegram Premium** pushes its capabilities even further.\n\n**Telegram Premium** is a paid option, because most Premium Features require additional expenses from Telegram to third parties such as data center providers and server manufacturers. Contributions from **Telegram Premium** users allow us to cover such costs and also help Telegram stay free for everyone.";
"lng_premium_summary_button" = "Subscribe for {cost} per month";
@ -5860,6 +5862,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_todo_completed#one" = "{count} of {total} completed";
"lng_todo_completed#other" = "{count} of {total} completed";
"lng_todo_completed_none" = "None of {total} completed";
"lng_todo_menu_item" = "To-Do List";
"lng_todo_create" = "Create To-Do List";
"lng_todo_create_title" = "New To-Do List";
"lng_todo_create_title_placeholder" = "Title";
@ -5875,6 +5878,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_todo_choose_title" = "Please enter a title.";
"lng_todo_choose_tasks" = "Please enter at least one task.";
"lng_todo_add_title" = "Add Tasks";
"lng_todo_create_premium" = "Only subscribers of {link} can create To-Do Lists.";
"lng_todo_add_premium" = "Only subscribers of {link} can add tasks.";
"lng_todo_mark_premium" = "Only subscribers of {link} can mark tasks as done.";
"lng_todo_premium_link" = "Telegram Premium";
"lng_todo_mark_restricted" = "{user} has restricted others from marking tasks as done.";
"lng_outdated_title" = "PLEASE UPDATE YOUR OPERATING SYSTEM.";
"lng_outdated_title_bits" = "PLEASE SWITCH TO A 64-BIT OPERATING SYSTEM.";
"lng_outdated_soon" = "Otherwise, Telegram Desktop will stop updating on {date}.";

View file

@ -133,6 +133,8 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
return tr::lng_premium_summary_subtitle_business();
case PremiumFeature::Effects:
return tr::lng_premium_summary_subtitle_effects();
case PremiumFeature::TodoLists:
return tr::lng_premium_summary_subtitle_todo_lists();
case PremiumFeature::BusinessLocation:
return tr::lng_business_subtitle_location();
@ -198,6 +200,8 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
return tr::lng_premium_summary_about_business();
case PremiumFeature::Effects:
return tr::lng_premium_summary_about_effects();
case PremiumFeature::TodoLists:
return tr::lng_premium_summary_about_todo_lists();
case PremiumFeature::BusinessLocation:
return tr::lng_business_about_location();
@ -538,6 +542,7 @@ struct VideoPreviewDocument {
case PremiumFeature::LastSeen: return "last_seen";
case PremiumFeature::MessagePrivacy: return "message_privacy";
case PremiumFeature::Effects: return "effects";
case PremiumFeature::TodoLists: return "todo_lists"; AssertIsDebug()
case PremiumFeature::BusinessLocation: return "business_location";
case PremiumFeature::BusinessHours: return "business_hours";

View file

@ -72,6 +72,7 @@ enum class PremiumFeature {
Business,
Effects,
FilterTags,
TodoLists,
// Business features.
BusinessLocation,

View file

@ -2332,7 +2332,10 @@ MediaTodoList::~MediaTodoList() {
}
std::unique_ptr<Media> MediaTodoList::clone(not_null<HistoryItem*> parent) {
return std::make_unique<MediaTodoList>(parent, _todolist);
const auto id = parent->fullId();
return std::make_unique<MediaTodoList>(
parent,
parent->history()->owner().duplicateTodoList(id, _todolist));
}
TodoListData *MediaTodoList::todolist() const {

View file

@ -4142,6 +4142,19 @@ not_null<TodoListData*> Session::processTodoList(
return result;
}
not_null<TodoListData*> Session::duplicateTodoList(
TodoListId id,
not_null<TodoListData*> existing) {
const auto result = todoList(id);
result->title = existing->title;
result->items = existing->items;
for (auto &item : result->items) {
item.completedBy = nullptr;
item.completionDate = TimeId();
}
return result;
}
void Session::checkPollsClosings() {
const auto now = base::unixtime::now();
auto closest = 0;

View file

@ -698,6 +698,9 @@ public:
not_null<TodoListData*> processTodoList(
TodoListId id,
const MTPDmessageMediaToDo &data);
[[nodiscard]] not_null<TodoListData*> duplicateTodoList(
TodoListId id,
not_null<TodoListData*> existing);
[[nodiscard]] not_null<CloudImage*> location(
const LocationPoint &point);

View file

@ -8,6 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/media/history_view_todo_list.h"
#include "base/unixtime.h"
#include "core/application.h"
#include "core/click_handler_types.h"
#include "core/ui_integration.h" // TextContext
#include "lang/lang_keys.h"
#include "history/history.h"
@ -35,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h"
#include "apiwrap.h"
#include "api/api_todo_lists.h"
#include "window/window_peer_menu.h"
#include "styles/style_chat.h"
#include "styles/style_widgets.h"
#include "styles/style_window.h"
@ -324,6 +327,18 @@ void TodoList::startToggleAnimation(Task &task) {
}
void TodoList::toggleCompletion(int id) {
if (!canComplete()) {
_parent->delegate()->elementShowTooltip(
tr::lng_todo_mark_restricted(
tr::now,
lt_user,
Ui::Text::Bold(_parent->data()->from()->shortName()),
Ui::Text::RichLangValue), [] {});
return;
} else if (!_parent->history()->session().premium()) {
Window::PeerMenuTodoWantsPremium(Window::TodoWantsPremium::Mark);
return;
}
const auto i = ranges::find(
_tasks,
id,
@ -477,7 +492,11 @@ int TodoList::paintTask(
p.setOpacity(1.);
}
paintRadio(p, task, left, top, context);
if (canComplete()) {
paintRadio(p, task, left, top, context);
} else {
paintStatus(p, task, left, top, context);
}
top += st::historyPollAnswerPadding.top();
p.setPen(stm->historyTextFg);
@ -584,6 +603,39 @@ void TodoList::paintRadio(
p.setOpacity(o);
}
void TodoList::paintStatus(
Painter &p,
const Task &task,
int left,
int top,
const PaintContext &context) const {
top += st::historyPollAnswerPadding.top();
const auto stm = context.messageStyle();
const auto &radio = st::historyPollRadio;
const auto completed = (task.completionDate != 0);
const auto rect = QRect(left, top, radio.diameter, radio.diameter);
if (completed) {
const auto &icon = stm->historyPollChosen;
icon.paint(
p,
left + (radio.diameter - icon.width()) / 2,
top + (radio.diameter - icon.height()) / 2,
width(),
stm->msgFileBg->c);
} else {
p.setPen(Qt::NoPen);
p.setBrush(stm->msgFileBg);
PainterHighQualityEnabler hq(p);
p.drawEllipse(style::centerrect(
rect,
QRect(0, 0, st::mediaUnreadSize, st::mediaUnreadSize)));
}
}
TextSelection TodoList::adjustSelection(
TextSelection selection,
TextSelectType type) const {
@ -600,7 +652,6 @@ TextForMimeData TodoList::selectedText(TextSelection selection) const {
TextState TodoList::textState(QPoint point, StateRequest request) const {
auto result = TextState(_parent);
const auto can = canComplete();
const auto padding = st::msgPadding;
auto paintw = width();
auto tshift = st::historyPollQuestionTop;
@ -622,10 +673,9 @@ TextState TodoList::textState(QPoint point, StateRequest request) const {
for (const auto &task : _tasks) {
const auto height = countTaskHeight(task, paintw);
if (point.y() >= tshift && point.y() < tshift + height) {
if (can) {
_lastLinkPoint = point;
result.link = task.handler;
} else if (task.completionDate) {
_lastLinkPoint = point;
result.link = task.handler;
if (task.completionDate) {
result.customTooltip = true;
using Flag = Ui::Text::StateRequest::Flag;
if (request.flags & Flag::LookupCustomTooltip) {

View file

@ -103,6 +103,12 @@ private:
int left,
int top,
const PaintContext &context) const;
void paintStatus(
Painter &p,
const Task &task,
int left,
int top,
const PaintContext &context) const;
void paintBottom(
Painter &p,
int left,

View file

@ -2628,7 +2628,7 @@ std::unique_ptr<Ui::DropdownMenu> MakeAttachBotsMenu(
}
if (peer->canCreateTodoLists()) {
++minimal;
raw->addAction(tr::lng_todo_create(tr::now), [=] {
raw->addAction(tr::lng_todo_menu_item(tr::now), [=] {
const auto action = actionFactory();
const auto source = action.options.scheduled
? Api::SendType::Scheduled

View file

@ -386,6 +386,15 @@ using Order = std::vector<QString>;
PremiumFeature::Effects,
},
},
{
u"todo_lists"_q,AssertIsDebug()
Entry{
&st::settingsPremiumIconTranslations,
tr::lng_premium_summary_subtitle_todo_lists(),
tr::lng_premium_summary_about_todo_lists(),
PremiumFeature::TodoLists,
},
},
};
}
@ -1608,6 +1617,8 @@ std::vector<PremiumFeature> PremiumFeaturesOrder(
return PremiumFeature::Wallpapers;
} else if (s == u"effects"_q) {
return PremiumFeature::Effects;
} else if (s == u"todo_lists"_q) {AssertIsDebug()
return PremiumFeature::TodoLists;
}
return PremiumFeature::kCount;
}) | ranges::views::filter([](PremiumFeature type) {

View file

@ -98,6 +98,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/application.h"
#include "export/export_manager.h"
#include "boxes/peers/edit_peer_info_box.h"
#include "boxes/premium_preview_box.h"
#include "styles/style_chat.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
@ -1920,12 +1921,50 @@ void PeerMenuCreatePoll(
controller->show(std::move(box), Ui::LayerOption::CloseOther);
}
void PeerMenuTodoWantsPremium(TodoWantsPremium type) {
const auto window = Core::App().activeWindow();
if (!window) {
return;
}
const auto filter = [=](const auto &...) {
if (const auto controller = window->sessionController()) {
ShowPremiumPreviewBox(controller, PremiumFeature::TodoLists);
window->activate();
}
return false;
};
const auto link = Ui::Text::Link(
Ui::Text::Semibold(tr::lng_todo_premium_link(tr::now)));
const auto text = [&] {
switch (type) {
case TodoWantsPremium::Create: return tr::lng_todo_create_premium;
case TodoWantsPremium::Add: return tr::lng_todo_add_premium;
case TodoWantsPremium::Mark: return tr::lng_todo_mark_premium;
}
Unexpected("Type in PeerMenuTodoWantsPremium.");
}();
constexpr auto kToastDuration = crl::time(4000);
window->uiShow()->showToast(Ui::Toast::Config{
.text = text(
tr::now,
lt_link,
link,
Ui::Text::WithEntities),
.filter = filter,
.duration = kToastDuration,
});
}
void PeerMenuCreateTodoList(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer,
FullReplyTo replyTo,
Api::SendType sendType,
SendMenu::Details sendMenuDetails) {
if (!peer->session().premium()) {
PeerMenuTodoWantsPremium(TodoWantsPremium::Create);
return;
}
auto starsRequired = peer->session().changes().peerFlagsValue(
peer,
Data::PeerUpdate::Flag::FullInfo

View file

@ -111,6 +111,12 @@ void PeerMenuCreatePoll(
PollData::Flags disabled = PollData::Flags(),
Api::SendType sendType = Api::SendType::Normal,
SendMenu::Details sendMenuDetails = SendMenu::Details());
enum class TodoWantsPremium {
Create,
Add,
Mark,
};
void PeerMenuTodoWantsPremium(TodoWantsPremium type);
void PeerMenuCreateTodoList(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer,