mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 14:17:12 +02:00
Initial chat theme changing.
This commit is contained in:
parent
80028e41f3
commit
ab0d2bf9c6
20 changed files with 773 additions and 120 deletions
|
@ -1040,6 +1040,8 @@ PRIVATE
|
|||
ui/chat/attach/attach_item_single_file_preview.h
|
||||
ui/chat/attach/attach_item_single_media_preview.cpp
|
||||
ui/chat/attach/attach_item_single_media_preview.h
|
||||
ui/chat/choose_theme_controller.cpp
|
||||
ui/chat/choose_theme_controller.h
|
||||
ui/effects/fireworks_animation.cpp
|
||||
ui/effects/fireworks_animation.h
|
||||
ui/effects/round_checkbox.cpp
|
||||
|
|
|
@ -2871,6 +2871,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_filters_remove_sure" = "This will remove the folder, your chats will not be deleted.";
|
||||
"lng_filters_remove_yes" = "Remove";
|
||||
|
||||
"lng_chat_theme_change" = "Change colors";
|
||||
"lng_chat_theme_none" = "No\nTheme";
|
||||
"lng_chat_theme_apply" = "Apply Theme";
|
||||
"lng_chat_theme_reset" = "Reset Theme";
|
||||
"lng_chat_theme_dont" = "Do Not Set Theme";
|
||||
"lng_chat_theme_title" = "Select theme";
|
||||
"lng_chat_theme_cant_voice" = "Sorry, you can't change the chat theme while you're having an unsent voice message.";
|
||||
|
||||
"lng_photo_editor_menu_delete" = "Delete";
|
||||
"lng_photo_editor_menu_flip" = "Flip";
|
||||
"lng_photo_editor_menu_duplicate" = "Duplicate";
|
||||
|
|
|
@ -387,26 +387,29 @@ rpl::producer<> CloudThemes::chatThemesUpdated() const {
|
|||
}
|
||||
|
||||
std::optional<ChatTheme> CloudThemes::themeForEmoji(
|
||||
const QString &emoji) const {
|
||||
if (emoji.isEmpty()) {
|
||||
const QString &emoticon) const {
|
||||
const auto emoji = Ui::Emoji::Find(emoticon);
|
||||
if (!emoji) {
|
||||
return {};
|
||||
}
|
||||
const auto i = ranges::find(_chatThemes, emoji, &ChatTheme::emoji);
|
||||
const auto i = ranges::find(_chatThemes, emoji, [](const ChatTheme &v) {
|
||||
return Ui::Emoji::Find(v.emoticon);
|
||||
});
|
||||
return (i != end(_chatThemes)) ? std::make_optional(*i) : std::nullopt;
|
||||
}
|
||||
|
||||
rpl::producer<std::optional<ChatTheme>> CloudThemes::themeForEmojiValue(
|
||||
const QString &emoji) {
|
||||
const QString &emoticon) {
|
||||
const auto testing = TestingColors();
|
||||
if (emoji.isEmpty()) {
|
||||
if (!Ui::Emoji::Find(emoticon)) {
|
||||
return rpl::single<std::optional<ChatTheme>>(std::nullopt);
|
||||
} else if (auto result = themeForEmoji(emoji)) {
|
||||
} else if (auto result = themeForEmoji(emoticon)) {
|
||||
if (testing) {
|
||||
return rpl::single(
|
||||
std::move(result)
|
||||
) | rpl::then(chatThemesUpdated(
|
||||
) | rpl::map([=] {
|
||||
return themeForEmoji(emoji);
|
||||
return themeForEmoji(emoticon);
|
||||
}) | rpl::filter([](const std::optional<ChatTheme> &theme) {
|
||||
return theme.has_value();
|
||||
}));
|
||||
|
@ -419,7 +422,7 @@ rpl::producer<std::optional<ChatTheme>> CloudThemes::themeForEmojiValue(
|
|||
std::nullopt
|
||||
) | rpl::then(chatThemesUpdated(
|
||||
) | rpl::map([=] {
|
||||
return themeForEmoji(emoji);
|
||||
return themeForEmoji(emoticon);
|
||||
}) | rpl::filter([](const std::optional<ChatTheme> &theme) {
|
||||
return theme.has_value();
|
||||
}) | rpl::take(limit));
|
||||
|
@ -482,12 +485,15 @@ QString CloudThemes::prepareTestingLink(const CloudTheme &theme) const {
|
|||
}
|
||||
|
||||
std::optional<CloudTheme> CloudThemes::updateThemeFromLink(
|
||||
const QString &emoji,
|
||||
const QString &emoticon,
|
||||
const QMap<QString, QString> ¶ms) {
|
||||
if (!TestingColors()) {
|
||||
const auto emoji = Ui::Emoji::Find(emoticon);
|
||||
if (!TestingColors() || !emoji) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto i = ranges::find(_chatThemes, emoji, &ChatTheme::emoji);
|
||||
const auto i = ranges::find(_chatThemes, emoji, [](const ChatTheme &v) {
|
||||
return Ui::Emoji::Find(v.emoticon);
|
||||
});
|
||||
if (i == end(_chatThemes)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
@ -552,7 +558,7 @@ void CloudThemes::parseChatThemes(const QVector<MTPChatTheme> &list) {
|
|||
for (const auto &theme : list) {
|
||||
theme.match([&](const MTPDchatTheme &data) {
|
||||
_chatThemes.push_back({
|
||||
.emoji = qs(data.vemoticon()),
|
||||
.emoticon = qs(data.vemoticon()),
|
||||
.light = CloudTheme::Parse(_session, data.vtheme(), true),
|
||||
.dark = CloudTheme::Parse(_session, data.vdark_theme(), true),
|
||||
});
|
||||
|
|
|
@ -50,7 +50,7 @@ struct CloudTheme {
|
|||
};
|
||||
|
||||
struct ChatTheme {
|
||||
QString emoji;
|
||||
QString emoticon;
|
||||
CloudTheme light;
|
||||
CloudTheme dark;
|
||||
};
|
||||
|
@ -71,15 +71,15 @@ public:
|
|||
[[nodiscard]] const std::vector<ChatTheme> &chatThemes() const;
|
||||
[[nodiscard]] rpl::producer<> chatThemesUpdated() const;
|
||||
[[nodiscard]] std::optional<ChatTheme> themeForEmoji(
|
||||
const QString &emoji) const;
|
||||
const QString &emoticon) const;
|
||||
[[nodiscard]] rpl::producer<std::optional<ChatTheme>> themeForEmojiValue(
|
||||
const QString &emoji);
|
||||
const QString &emoticon);
|
||||
|
||||
[[nodiscard]] static bool TestingColors();
|
||||
static void SetTestingColors(bool testing);
|
||||
[[nodiscard]] QString prepareTestingLink(const CloudTheme &theme) const;
|
||||
[[nodiscard]] std::optional<CloudTheme> updateThemeFromLink(
|
||||
const QString &emoji,
|
||||
const QString &emoticon,
|
||||
const QMap<QString, QString> ¶ms);
|
||||
|
||||
void applyUpdate(const MTPTheme &theme);
|
||||
|
|
|
@ -1004,19 +1004,24 @@ PeerId PeerData::groupCallDefaultJoinAs() const {
|
|||
return 0;
|
||||
}
|
||||
|
||||
void PeerData::setThemeEmoji(const QString &emoji) {
|
||||
if (_themeEmoji == emoji) {
|
||||
void PeerData::setThemeEmoji(const QString &emoticon) {
|
||||
if (_themeEmoticon == emoticon) {
|
||||
return;
|
||||
}
|
||||
_themeEmoji = emoji;
|
||||
if (!emoji.isEmpty() && !owner().cloudThemes().themeForEmoji(emoji)) {
|
||||
if (Ui::Emoji::Find(_themeEmoticon) == Ui::Emoji::Find(emoticon)) {
|
||||
_themeEmoticon = emoticon;
|
||||
return;
|
||||
}
|
||||
_themeEmoticon = emoticon;
|
||||
if (!emoticon.isEmpty()
|
||||
&& !owner().cloudThemes().themeForEmoji(emoticon)) {
|
||||
owner().cloudThemes().refreshChatThemes();
|
||||
}
|
||||
session().changes().peerUpdated(this, UpdateFlag::ChatThemeEmoji);
|
||||
}
|
||||
|
||||
const QString &PeerData::themeEmoji() const {
|
||||
return _themeEmoji;
|
||||
return _themeEmoticon;
|
||||
}
|
||||
|
||||
void PeerData::setIsBlocked(bool is) {
|
||||
|
|
|
@ -459,7 +459,7 @@ public:
|
|||
[[nodiscard]] Data::GroupCall *groupCall() const;
|
||||
[[nodiscard]] PeerId groupCallDefaultJoinAs() const;
|
||||
|
||||
void setThemeEmoji(const QString &emoji);
|
||||
void setThemeEmoji(const QString &emoticon);
|
||||
[[nodiscard]] const QString &themeEmoji() const;
|
||||
|
||||
const PeerId id;
|
||||
|
@ -506,7 +506,7 @@ private:
|
|||
LoadedStatus _loadedStatus = LoadedStatus::Not;
|
||||
|
||||
QString _about;
|
||||
QString _themeEmoji;
|
||||
QString _themeEmoticon;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/special_buttons.h"
|
||||
#include "ui/emoji_config.h"
|
||||
#include "ui/chat/attach/attach_prepare.h"
|
||||
#include "ui/chat/choose_theme_controller.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/inner_dropdown.h"
|
||||
#include "ui/widgets/dropdown_menu.h"
|
||||
|
@ -1282,11 +1283,39 @@ void HistoryWidget::insertHashtagOrBotCommand(
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
InlineBotQuery HistoryWidget::parseInlineBotQuery() const {
|
||||
return (isChoosingTheme() || _editMsgId)
|
||||
? InlineBotQuery()
|
||||
: ParseInlineBotQuery(&session(), _field);
|
||||
}
|
||||
|
||||
AutocompleteQuery HistoryWidget::parseMentionHashtagBotCommandQuery() const {
|
||||
const auto result = (isChoosingTheme()
|
||||
|| (_inlineBot && !_inlineLookingUpBot))
|
||||
? AutocompleteQuery()
|
||||
: ParseMentionHashtagBotCommandQuery(_field);
|
||||
if (result.query.isEmpty()) {
|
||||
return result;
|
||||
} else if (result.query[0] == '#'
|
||||
&& cRecentWriteHashtags().isEmpty()
|
||||
&& cRecentSearchHashtags().isEmpty()) {
|
||||
session().local().readRecentHashtagsAndBots();
|
||||
} else if (result.query[0] == '@'
|
||||
&& cRecentInlineBots().isEmpty()) {
|
||||
session().local().readRecentHashtagsAndBots();
|
||||
} else if (result.query[0] == '/'
|
||||
&& ((_peer->isUser() && !_peer->asUser()->isBot()) || _editMsgId)) {
|
||||
return AutocompleteQuery();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void HistoryWidget::updateInlineBotQuery() {
|
||||
if (!_history) {
|
||||
return;
|
||||
}
|
||||
const auto query = ParseInlineBotQuery(&session(), _field);
|
||||
const auto query = parseInlineBotQuery();
|
||||
if (_inlineBotUsername != query.username) {
|
||||
_inlineBotUsername = query.username;
|
||||
if (_inlineBotResolveRequestId) {
|
||||
|
@ -1369,10 +1398,11 @@ void HistoryWidget::orderWidgets() {
|
|||
if (_groupCallBar) {
|
||||
_groupCallBar->raise();
|
||||
}
|
||||
_topShadow->raise();
|
||||
if (_fieldAutocomplete) {
|
||||
_fieldAutocomplete->raise();
|
||||
if (_chooseTheme) {
|
||||
_chooseTheme->raise();
|
||||
}
|
||||
_topShadow->raise();
|
||||
_fieldAutocomplete->raise();
|
||||
if (_membersDropdown) {
|
||||
_membersDropdown->raise();
|
||||
}
|
||||
|
@ -1410,6 +1440,34 @@ bool HistoryWidget::updateStickersByEmoji() {
|
|||
return (emoji != nullptr);
|
||||
}
|
||||
|
||||
void HistoryWidget::toggleChooseChatTheme(not_null<PeerData*> peer) {
|
||||
const auto update = [=] {
|
||||
updateInlineBotQuery();
|
||||
updateControlsGeometry();
|
||||
updateControlsVisibility();
|
||||
};
|
||||
if (peer.get() != _peer) {
|
||||
return;
|
||||
} else if (_chooseTheme) {
|
||||
if (isChoosingTheme()) {
|
||||
_chooseTheme = nullptr;
|
||||
update();
|
||||
}
|
||||
return;
|
||||
} else if (_voiceRecordBar->isActive()) {
|
||||
Ui::ShowMultilineToast({
|
||||
.text = { tr::lng_chat_theme_cant_voice(tr::now) },
|
||||
});
|
||||
return;
|
||||
}
|
||||
_chooseTheme = std::make_unique<Ui::ChooseThemeController>(
|
||||
this,
|
||||
controller(),
|
||||
peer);
|
||||
_chooseTheme->shouldBeShownValue(
|
||||
) | rpl::start_with_next(update, _chooseTheme->lifetime());
|
||||
}
|
||||
|
||||
void HistoryWidget::fieldChanged() {
|
||||
const auto updateTyping = (_textUpdateEvents & TextUpdateEvent::SendTyping);
|
||||
|
||||
|
@ -1925,6 +1983,7 @@ void HistoryWidget::showHistory(
|
|||
_pinnedTracker = nullptr;
|
||||
_groupCallBar = nullptr;
|
||||
_groupCallTracker = nullptr;
|
||||
_chooseTheme = nullptr;
|
||||
_membersDropdown.destroy();
|
||||
_scrollToAnimation.stop();
|
||||
|
||||
|
@ -2322,52 +2381,41 @@ void HistoryWidget::updateControlsVisibility() {
|
|||
if (_contactStatus) {
|
||||
_contactStatus->show();
|
||||
}
|
||||
if (!editingMessage() && (isBlocked() || isJoinChannel() || isMuteUnmute() || isBotStart() || isReportMessages())) {
|
||||
if (isReportMessages()) {
|
||||
_unblock->hide();
|
||||
_joinChannel->hide();
|
||||
_muteUnmute->hide();
|
||||
_botStart->hide();
|
||||
if (_reportMessages->isHidden()) {
|
||||
_reportMessages->clearState();
|
||||
_reportMessages->show();
|
||||
}
|
||||
if (isChoosingTheme()
|
||||
|| (!editingMessage()
|
||||
&& (isBlocked()
|
||||
|| isJoinChannel()
|
||||
|| isMuteUnmute()
|
||||
|| isBotStart()
|
||||
|| isReportMessages()))) {
|
||||
const auto toggle = [&](Ui::FlatButton *shown) {
|
||||
const auto toggleOne = [&](not_null<Ui::FlatButton*> button) {
|
||||
if (button.get() != shown) {
|
||||
button->hide();
|
||||
} else if (button->isHidden()) {
|
||||
button->clearState();
|
||||
button->show();
|
||||
}
|
||||
};
|
||||
toggleOne(_reportMessages);
|
||||
toggleOne(_joinChannel);
|
||||
toggleOne(_muteUnmute);
|
||||
toggleOne(_botStart);
|
||||
toggleOne(_unblock);
|
||||
};
|
||||
if (isChoosingTheme()) {
|
||||
toggle(nullptr);
|
||||
_chooseTheme->show();
|
||||
} else if (isReportMessages()) {
|
||||
toggle(_reportMessages);
|
||||
} else if (isBlocked()) {
|
||||
_reportMessages->hide();
|
||||
_joinChannel->hide();
|
||||
_muteUnmute->hide();
|
||||
_botStart->hide();
|
||||
if (_unblock->isHidden()) {
|
||||
_unblock->clearState();
|
||||
_unblock->show();
|
||||
}
|
||||
toggle(_unblock);
|
||||
} else if (isJoinChannel()) {
|
||||
_reportMessages->hide();
|
||||
_unblock->hide();
|
||||
_muteUnmute->hide();
|
||||
_botStart->hide();
|
||||
if (_joinChannel->isHidden()) {
|
||||
_joinChannel->clearState();
|
||||
_joinChannel->show();
|
||||
}
|
||||
toggle(_joinChannel);
|
||||
} else if (isMuteUnmute()) {
|
||||
_reportMessages->hide();
|
||||
_unblock->hide();
|
||||
_joinChannel->hide();
|
||||
_botStart->hide();
|
||||
if (_muteUnmute->isHidden()) {
|
||||
_muteUnmute->clearState();
|
||||
_muteUnmute->show();
|
||||
}
|
||||
toggle(_muteUnmute);
|
||||
} else if (isBotStart()) {
|
||||
_reportMessages->hide();
|
||||
_unblock->hide();
|
||||
_joinChannel->hide();
|
||||
_muteUnmute->hide();
|
||||
if (_botStart->isHidden()) {
|
||||
_botStart->clearState();
|
||||
_botStart->show();
|
||||
}
|
||||
toggle(_botStart);
|
||||
}
|
||||
_kbShown = false;
|
||||
_fieldAutocomplete->hide();
|
||||
|
@ -3289,6 +3337,9 @@ void HistoryWidget::hideChildWidgets() {
|
|||
if (_voiceRecordBar) {
|
||||
_voiceRecordBar->hideFast();
|
||||
}
|
||||
if (_chooseTheme) {
|
||||
_chooseTheme->hide();
|
||||
}
|
||||
hideChildren();
|
||||
}
|
||||
|
||||
|
@ -3908,7 +3959,7 @@ void HistoryWidget::inlineBotResolveDone(
|
|||
}();
|
||||
session().data().processChats(data.vchats());
|
||||
|
||||
const auto query = ParseInlineBotQuery(&session(), _field);
|
||||
const auto query = parseInlineBotQuery();
|
||||
if (_inlineBotUsername == query.username) {
|
||||
applyInlineBotQuery(
|
||||
query.lookingUpBot ? resolvedBot : query.bot,
|
||||
|
@ -3953,6 +4004,10 @@ bool HistoryWidget::isJoinChannel() const {
|
|||
return _peer && _peer->isChannel() && !_peer->asChannel()->amIn();
|
||||
}
|
||||
|
||||
bool HistoryWidget::isChoosingTheme() const {
|
||||
return _chooseTheme && _chooseTheme->shouldBeShown();
|
||||
}
|
||||
|
||||
bool HistoryWidget::isMuteUnmute() const {
|
||||
return _peer
|
||||
&& ((_peer->isBroadcast() && !_peer->asChannel()->canPublish())
|
||||
|
@ -4337,24 +4392,7 @@ void HistoryWidget::checkFieldAutocomplete() {
|
|||
return;
|
||||
}
|
||||
|
||||
const auto isInlineBot = _inlineBot && !_inlineLookingUpBot;
|
||||
const auto autocomplete = isInlineBot
|
||||
? AutocompleteQuery()
|
||||
: ParseMentionHashtagBotCommandQuery(_field);
|
||||
if (!autocomplete.query.isEmpty()) {
|
||||
if (autocomplete.query[0] == '#'
|
||||
&& cRecentWriteHashtags().isEmpty()
|
||||
&& cRecentSearchHashtags().isEmpty()) {
|
||||
session().local().readRecentHashtagsAndBots();
|
||||
} else if (autocomplete.query[0] == '@'
|
||||
&& cRecentInlineBots().isEmpty()) {
|
||||
session().local().readRecentHashtagsAndBots();
|
||||
} else if (autocomplete.query[0] == '/'
|
||||
&& ((_peer->isUser() && !_peer->asUser()->isBot())
|
||||
|| _editMsgId)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const auto autocomplete = parseMentionHashtagBotCommandQuery();
|
||||
_fieldAutocomplete->showFiltered(
|
||||
_peer,
|
||||
autocomplete.query,
|
||||
|
@ -4922,7 +4960,14 @@ void HistoryWidget::updateHistoryGeometry(
|
|||
if (_contactStatus) {
|
||||
newScrollHeight -= _contactStatus->height();
|
||||
}
|
||||
if (!editingMessage() && (isBlocked() || isBotStart() || isJoinChannel() || isMuteUnmute() || isReportMessages())) {
|
||||
if (isChoosingTheme()) {
|
||||
newScrollHeight -= _chooseTheme->height();
|
||||
} else if (!editingMessage()
|
||||
&& (isBlocked()
|
||||
|| isBotStart()
|
||||
|| isJoinChannel()
|
||||
|| isMuteUnmute()
|
||||
|| isReportMessages())) {
|
||||
newScrollHeight -= _unblock->height();
|
||||
} else {
|
||||
if (editingMessage() || _canSendMessages) {
|
||||
|
@ -6136,16 +6181,17 @@ void HistoryWidget::editMessage(FullMsgId itemId) {
|
|||
}
|
||||
|
||||
void HistoryWidget::editMessage(not_null<HistoryItem*> item) {
|
||||
if (_voiceRecordBar->isActive()) {
|
||||
controller()->show(
|
||||
Box<InformBox>(tr::lng_edit_caption_voice(tr::now)));
|
||||
return;
|
||||
}
|
||||
if (const auto media = item->media()) {
|
||||
if (media->allowsEditCaption()) {
|
||||
controller()->show(Box<EditCaptionBox>(controller(), item));
|
||||
return;
|
||||
}
|
||||
} else if (_chooseTheme) {
|
||||
toggleChooseChatTheme(_peer);
|
||||
} else if (_voiceRecordBar->isActive()) {
|
||||
controller()->show(
|
||||
Box<InformBox>(tr::lng_edit_caption_voice(tr::now)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (isRecording()) {
|
||||
|
|
|
@ -25,6 +25,8 @@ struct FileLoadResult;
|
|||
struct SendingAlbum;
|
||||
enum class SendMediaType;
|
||||
class MessageLinksParser;
|
||||
struct InlineBotQuery;
|
||||
struct AutocompleteQuery;
|
||||
|
||||
namespace MTP {
|
||||
class Error;
|
||||
|
@ -77,6 +79,7 @@ enum class ReportReason;
|
|||
namespace Toast {
|
||||
class Instance;
|
||||
} // namespace Toast
|
||||
class ChooseThemeController;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Window {
|
||||
|
@ -237,6 +240,8 @@ public:
|
|||
void clearDelayedShowAt();
|
||||
void saveFieldToHistoryLocalDraft();
|
||||
|
||||
void toggleChooseChatTheme(not_null<PeerData*> peer);
|
||||
|
||||
void applyCloudDraft(History *history);
|
||||
|
||||
void updateHistoryDownPosition();
|
||||
|
@ -454,6 +459,10 @@ private:
|
|||
std::optional<QString> writeRestriction() const;
|
||||
void orderWidgets();
|
||||
|
||||
[[nodiscard]] InlineBotQuery parseInlineBotQuery() const;
|
||||
[[nodiscard]] auto parseMentionHashtagBotCommandQuery() const
|
||||
-> AutocompleteQuery;
|
||||
|
||||
void clearInlineBot();
|
||||
void inlineBotChanged();
|
||||
|
||||
|
@ -585,19 +594,21 @@ private:
|
|||
void inlineBotResolveDone(const MTPcontacts_ResolvedPeer &result);
|
||||
void inlineBotResolveFail(const MTP::Error &error, const QString &username);
|
||||
|
||||
bool isRecording() const;
|
||||
[[nodiscard]] bool isRecording() const;
|
||||
|
||||
bool isBotStart() const;
|
||||
bool isBlocked() const;
|
||||
bool isJoinChannel() const;
|
||||
bool isMuteUnmute() const;
|
||||
bool isReportMessages() const;
|
||||
[[nodiscard]] bool isBotStart() const;
|
||||
[[nodiscard]] bool isBlocked() const;
|
||||
[[nodiscard]] bool isJoinChannel() const;
|
||||
[[nodiscard]] bool isMuteUnmute() const;
|
||||
[[nodiscard]] bool isReportMessages() const;
|
||||
bool updateCmdStartShown();
|
||||
void updateSendButtonType();
|
||||
bool showRecordButton() const;
|
||||
bool showInlineBotCancel() const;
|
||||
[[nodiscard]] bool showRecordButton() const;
|
||||
[[nodiscard]] bool showInlineBotCancel() const;
|
||||
void refreshSilentToggle();
|
||||
|
||||
[[nodiscard]] bool isChoosingTheme() const;
|
||||
|
||||
void setupScheduledToggle();
|
||||
void refreshScheduledToggle();
|
||||
|
||||
|
@ -689,7 +700,7 @@ private:
|
|||
bool _unreadMentionsIsShown = false;
|
||||
object_ptr<Ui::HistoryDownButton> _unreadMentions;
|
||||
|
||||
object_ptr<FieldAutocomplete> _fieldAutocomplete;
|
||||
const object_ptr<FieldAutocomplete> _fieldAutocomplete;
|
||||
object_ptr<Support::Autocomplete> _supportAutocomplete;
|
||||
std::unique_ptr<MessageLinksParser> _fieldLinksParser;
|
||||
|
||||
|
@ -726,6 +737,8 @@ private:
|
|||
object_ptr<Ui::ScrollArea> _kbScroll;
|
||||
const not_null<BotKeyboard*> _keyboard;
|
||||
|
||||
std::unique_ptr<Ui::ChooseThemeController> _chooseTheme;
|
||||
|
||||
object_ptr<Ui::InnerDropdown> _membersDropdown = { nullptr };
|
||||
base::Timer _membersDropdownShowTimer;
|
||||
|
||||
|
|
|
@ -1353,6 +1353,10 @@ void MainWidget::clearChooseReportMessages() {
|
|||
_history->setChooseReportMessagesDetails({}, nullptr);
|
||||
}
|
||||
|
||||
void MainWidget::toggleChooseChatTheme(not_null<PeerData*> peer) {
|
||||
_history->toggleChooseChatTheme(peer);
|
||||
}
|
||||
|
||||
void MainWidget::ui_showPeerHistory(
|
||||
PeerId peerId,
|
||||
const SectionShow ¶ms,
|
||||
|
|
|
@ -213,6 +213,8 @@ public:
|
|||
Fn<void(MessageIdsList)> done);
|
||||
void clearChooseReportMessages();
|
||||
|
||||
void toggleChooseChatTheme(not_null<PeerData*> peer);
|
||||
|
||||
void ui_showPeerHistory(
|
||||
PeerId peer,
|
||||
const SectionShow ¶ms,
|
||||
|
|
|
@ -511,6 +511,11 @@ const BackgroundState &ChatTheme::backgroundState(QSize area) {
|
|||
return _backgroundState;
|
||||
}
|
||||
|
||||
void ChatTheme::clearBackgroundState() {
|
||||
_backgroundState = BackgroundState();
|
||||
_backgroundFade.stop();
|
||||
}
|
||||
|
||||
bool ChatTheme::readyForBackgroundRotation() const {
|
||||
Expects(_cacheBackgroundTimer.has_value());
|
||||
|
||||
|
|
|
@ -137,6 +137,7 @@ public:
|
|||
QRect viewport,
|
||||
QRect clip);
|
||||
[[nodiscard]] const BackgroundState &backgroundState(QSize area);
|
||||
void clearBackgroundState();
|
||||
[[nodiscard]] rpl::producer<> repaintBackgroundRequests() const;
|
||||
void rotateComplexGradientBackground();
|
||||
|
||||
|
@ -157,7 +158,6 @@ private:
|
|||
void cacheBubblesNow();
|
||||
void cacheBubblesAsync(
|
||||
const CacheBackgroundRequest &request);
|
||||
void setCachedBubbles(CacheBackgroundResult &&cached);
|
||||
[[nodiscard]] CacheBackgroundRequest cacheBubblesRequest(
|
||||
QSize area) const;
|
||||
|
||||
|
|
424
Telegram/SourceFiles/ui/chat/choose_theme_controller.cpp
Normal file
424
Telegram/SourceFiles/ui/chat/choose_theme_controller.cpp
Normal file
|
@ -0,0 +1,424 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "ui/chat/choose_theme_controller.h"
|
||||
|
||||
#include "ui/rp_widget.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/chat/chat_theme.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "main/main_session.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_cloud_themes.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "apiwrap.h"
|
||||
#include "styles/style_widgets.h"
|
||||
#include "styles/style_layers.h" // boxTitle.
|
||||
#include "styles/style_settings.h"
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
constexpr auto kDisableElement = "disable"_cs;
|
||||
|
||||
[[nodiscard]] QImage GeneratePreview(not_null<Ui::ChatTheme*> theme) {
|
||||
const auto &colors = theme->background().colors;
|
||||
auto result = Images::GenerateGradient(
|
||||
st::settingsThemePreviewSize * style::DevicePixelRatio(),
|
||||
colors.empty() ? std::vector{ 1, QColor(0, 0, 0) } : colors
|
||||
).convertToFormat(QImage::Format_ARGB32_Premultiplied);
|
||||
Images::prepareRound(result, ImageRoundRadius::Large);
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] QImage GenerateEmptyPreview() {
|
||||
auto result = QImage(
|
||||
st::settingsThemePreviewSize * style::DevicePixelRatio(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
result.fill(st::settingsThemeNotSupportedBg->c);
|
||||
Images::prepareRound(result, ImageRoundRadius::Large);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct ChooseThemeController::Entry {
|
||||
uint64 id = 0;
|
||||
std::shared_ptr<Ui::ChatTheme> theme;
|
||||
std::shared_ptr<Data::DocumentMedia> media;
|
||||
QImage preview;
|
||||
EmojiPtr emoji = nullptr;
|
||||
QRect geometry;
|
||||
};
|
||||
|
||||
ChooseThemeController::ChooseThemeController(
|
||||
not_null<RpWidget*> parent,
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<PeerData*> peer)
|
||||
: _controller(window)
|
||||
, _peer(peer)
|
||||
, _wrap(std::make_unique<VerticalLayout>(parent))
|
||||
, _topShadow(std::make_unique<PlainShadow>(parent))
|
||||
, _content(_wrap->add(object_ptr<RpWidget>(_wrap.get())))
|
||||
, _inner(CreateChild<RpWidget>(_content.get()))
|
||||
, _dark(Window::Theme::IsThemeDarkValue()) {
|
||||
init(parent->sizeValue());
|
||||
}
|
||||
|
||||
ChooseThemeController::~ChooseThemeController() {
|
||||
_controller->clearPeerThemeOverride(_peer);
|
||||
}
|
||||
|
||||
void ChooseThemeController::init(rpl::producer<QSize> outer) {
|
||||
using namespace rpl::mappers;
|
||||
|
||||
const auto themes = &_controller->session().data().cloudThemes();
|
||||
const auto &list = themes->chatThemes();
|
||||
if (!list.empty()) {
|
||||
fill(list);
|
||||
} else {
|
||||
themes->refreshChatThemes();
|
||||
themes->chatThemesUpdated(
|
||||
) | rpl::take(1) | rpl::start_with_next([=] {
|
||||
fill(themes->chatThemes());
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
const auto skip = st::normalFont->spacew * 2;
|
||||
_wrap->insert(
|
||||
0,
|
||||
object_ptr<FlatLabel>(
|
||||
_wrap.get(),
|
||||
tr::lng_chat_theme_title(),
|
||||
st::boxTitle),
|
||||
style::margins{ skip * 2, skip, skip * 2, skip });
|
||||
_wrap->paintRequest(
|
||||
) | rpl::start_with_next([=](QRect clip) {
|
||||
QPainter(_wrap.get()).fillRect(clip, st::windowBg);
|
||||
}, lifetime());
|
||||
|
||||
initButtons();
|
||||
initList();
|
||||
|
||||
std::move(
|
||||
outer
|
||||
) | rpl::start_with_next([=](QSize outer) {
|
||||
_wrap->resizeToWidth(outer.width());
|
||||
_wrap->move(0, outer.height() - _wrap->height());
|
||||
const auto line = st::lineWidth;
|
||||
_topShadow->setGeometry(0, _wrap->y() - line, outer.width(), line);
|
||||
}, lifetime());
|
||||
|
||||
rpl::combine(
|
||||
_shouldBeShown.value(),
|
||||
_forceHidden.value(),
|
||||
_1 && !_2
|
||||
) | rpl::start_with_next([=](bool shown) {
|
||||
_wrap->setVisible(shown);
|
||||
_topShadow->setVisible(shown);
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void ChooseThemeController::initButtons() {
|
||||
const auto controls = _wrap->add(object_ptr<RpWidget>(_wrap.get()));
|
||||
const auto cancel = CreateChild<RoundButton>(
|
||||
controls,
|
||||
tr::lng_cancel(),
|
||||
st::defaultLightButton);
|
||||
const auto apply = CreateChild<RoundButton>(
|
||||
controls,
|
||||
tr::lng_chat_theme_apply(),
|
||||
st::defaultActiveButton);
|
||||
const auto skip = st::normalFont->spacew * 2;
|
||||
controls->resize(
|
||||
skip + cancel->width() + skip + apply->width() + skip,
|
||||
skip + apply->height() + skip);
|
||||
rpl::combine(
|
||||
controls->widthValue(),
|
||||
cancel->widthValue(),
|
||||
apply->widthValue()
|
||||
) | rpl::start_with_next([=](
|
||||
int outer,
|
||||
int cancelWidth,
|
||||
int applyWidth) {
|
||||
const auto inner = skip + cancelWidth + skip + applyWidth + skip;
|
||||
const auto left = (outer - inner) / 2;
|
||||
cancel->moveToLeft(left, skip);
|
||||
apply->moveToRight(left, skip);
|
||||
}, controls->lifetime());
|
||||
|
||||
const auto findChosen = [=]() -> const Entry* {
|
||||
if (_chosen.isEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
for (const auto &entry : _entries) {
|
||||
if (!entry.id && _chosen == kDisableElement.utf16()) {
|
||||
return &entry;
|
||||
} else if (_chosen == entry.emoji->text()) {
|
||||
return &entry;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
const auto changed = [=] {
|
||||
if (_chosen.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
const auto now = Ui::Emoji::Find(_peer->themeEmoji());
|
||||
if (_chosen == kDisableElement.utf16()) {
|
||||
return !now;
|
||||
}
|
||||
for (const auto &entry : _entries) {
|
||||
if (entry.id && entry.emoji->text() == _chosen) {
|
||||
return (now != entry.emoji);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
cancel->setClickedCallback([=] {
|
||||
if (const auto chosen = findChosen()) {
|
||||
if (Ui::Emoji::Find(_peer->themeEmoji()) != chosen->emoji) {
|
||||
clearCurrentBackgroundState();
|
||||
}
|
||||
}
|
||||
_controller->toggleChooseChatTheme(_peer);
|
||||
});
|
||||
apply->setClickedCallback([=] {
|
||||
if (const auto chosen = findChosen()) {
|
||||
if (Ui::Emoji::Find(_peer->themeEmoji()) != chosen->emoji) {
|
||||
const auto now = chosen->id ? _chosen : QString();
|
||||
_peer->setThemeEmoji(now);
|
||||
if (chosen->theme) {
|
||||
// Remember while changes propagate through event loop.
|
||||
_controller->pushLastUsedChatTheme(chosen->theme);
|
||||
}
|
||||
const auto api = &_peer->session().api();
|
||||
api->request(MTPmessages_SetChatTheme(
|
||||
_peer->input,
|
||||
MTP_string(now)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
api->applyUpdates(result);
|
||||
}).send();
|
||||
}
|
||||
}
|
||||
_controller->toggleChooseChatTheme(_peer);
|
||||
});
|
||||
}
|
||||
|
||||
void ChooseThemeController::paintEntry(QPainter &p, const Entry &entry) {
|
||||
const auto geometry = entry.geometry;
|
||||
p.drawImage(geometry, entry.preview);
|
||||
|
||||
if (entry.theme) {
|
||||
const auto received = QRect(
|
||||
st::settingsThemeBubblePosition,
|
||||
st::settingsThemeBubbleSize);
|
||||
const auto sent = QRect(
|
||||
(geometry.width()
|
||||
- received.width()
|
||||
- st::settingsThemeBubblePosition.x()),
|
||||
received.y() + received.height() + st::settingsThemeBubbleSkip,
|
||||
received.width(),
|
||||
received.height());
|
||||
const auto radius = st::settingsThemeBubbleRadius;
|
||||
|
||||
const auto sentBg = entry.theme->palette()->msgOutBg()->c;
|
||||
const auto receivedBg = entry.theme->palette()->msgInBg()->c;
|
||||
|
||||
PainterHighQualityEnabler hq(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
|
||||
p.setBrush(receivedBg);
|
||||
p.drawRoundedRect(received.translated(geometry.topLeft()), radius, radius);
|
||||
p.setBrush(sentBg);
|
||||
p.drawRoundedRect(sent.translated(geometry.topLeft()), radius, radius);
|
||||
}
|
||||
const auto size = Ui::Emoji::GetSizeLarge();
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto skip = st::normalFont->spacew * 2;
|
||||
Ui::Emoji::Draw(
|
||||
p,
|
||||
entry.emoji,
|
||||
size,
|
||||
(geometry.x()
|
||||
+ (geometry.width() - (size / factor)) / 2),
|
||||
(geometry.y() + geometry.height() - (size / factor) - skip));
|
||||
|
||||
}
|
||||
|
||||
void ChooseThemeController::initList() {
|
||||
_content->resize(
|
||||
_content->width(),
|
||||
st::settingsThemePreviewSize.height());
|
||||
_inner->setMouseTracking(true);
|
||||
|
||||
_inner->paintRequest(
|
||||
) | rpl::start_with_next([=](QRect clip) {
|
||||
auto p = QPainter(_inner.get());
|
||||
for (const auto &entry : _entries) {
|
||||
if (entry.preview.isNull() || !clip.intersects(entry.geometry)) {
|
||||
continue;
|
||||
}
|
||||
paintEntry(p, entry);
|
||||
}
|
||||
}, lifetime());
|
||||
const auto byPoint = [=](QPoint position) -> const Entry* {
|
||||
for (const auto &entry : _entries) {
|
||||
if (entry.geometry.contains(position)) {
|
||||
return &entry;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
const auto chosenText = [=](const Entry *entry) {
|
||||
if (!entry) {
|
||||
return QString();
|
||||
} else if (entry->id) {
|
||||
return entry->emoji->text();
|
||||
} else {
|
||||
return kDisableElement.utf16();
|
||||
}
|
||||
};
|
||||
_inner->events(
|
||||
) | rpl::start_with_next([=](not_null<QEvent*> event) {
|
||||
const auto type = event->type();
|
||||
if (type == QEvent::MouseMove) {
|
||||
const auto mouse = static_cast<QMouseEvent*>(event.get());
|
||||
_inner->setCursor(byPoint(mouse->pos())
|
||||
? style::cur_pointer
|
||||
: style::cur_default);
|
||||
} else if (type == QEvent::MouseButtonPress) {
|
||||
const auto mouse = static_cast<QMouseEvent*>(event.get());
|
||||
_pressed = chosenText(byPoint(mouse->pos()));
|
||||
} else if (type == QEvent::MouseButtonRelease) {
|
||||
const auto mouse = static_cast<QMouseEvent*>(event.get());
|
||||
const auto entry = byPoint(mouse->pos());
|
||||
const auto chosen = chosenText(entry);
|
||||
if (entry && chosen == _pressed && chosen != _chosen) {
|
||||
clearCurrentBackgroundState();
|
||||
_chosen = chosen;
|
||||
if (entry->theme || !entry->id) {
|
||||
_controller->overridePeerTheme(_peer, entry->theme);
|
||||
}
|
||||
}
|
||||
_pressed = QString();
|
||||
}
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void ChooseThemeController::clearCurrentBackgroundState() {
|
||||
for (const auto &entry : _entries) {
|
||||
if (entry.theme && entry.emoji && entry.emoji->text() == _chosen) {
|
||||
entry.theme->clearBackgroundState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChooseThemeController::fill(
|
||||
const std::vector<Data::ChatTheme> &themes) {
|
||||
if (themes.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto count = int(themes.size()) + 1;
|
||||
const auto single = st::settingsThemePreviewSize;
|
||||
const auto skip = st::normalFont->spacew * 2;
|
||||
const auto full = single.width() * count + skip * (count + 1);
|
||||
_inner->resize(full, single.height());
|
||||
|
||||
_dark.value(
|
||||
) | rpl::start_with_next([=](bool dark) {
|
||||
clearCurrentBackgroundState();
|
||||
|
||||
_cachingLifetime.destroy();
|
||||
const auto old = base::take(_entries);
|
||||
auto x = skip;
|
||||
_entries.push_back({
|
||||
.preview = GenerateEmptyPreview(),
|
||||
.emoji = Ui::Emoji::Find(QString::fromUtf8("\xe2\x9d\x8c")),
|
||||
.geometry = QRect(QPoint(x, 0), single),
|
||||
});
|
||||
Assert(_entries.front().emoji != nullptr);
|
||||
style::PaletteChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
_entries.front().preview = GenerateEmptyPreview();
|
||||
}, _cachingLifetime);
|
||||
|
||||
x += single.width() + skip;
|
||||
for (const auto &theme : themes) {
|
||||
const auto emoji = Ui::Emoji::Find(theme.emoticon);
|
||||
if (!emoji) {
|
||||
continue;
|
||||
}
|
||||
const auto &used = dark ? theme.dark : theme.light;
|
||||
const auto id = used.id;
|
||||
_entries.push_back({
|
||||
.id = id,
|
||||
.emoji = emoji,
|
||||
.geometry = QRect(QPoint(x, 0), single),
|
||||
});
|
||||
_controller->cachedChatThemeValue(
|
||||
used
|
||||
) | rpl::filter([=](const std::shared_ptr<ChatTheme> &data) {
|
||||
return data && (data->key() == id);
|
||||
}) | rpl::take(
|
||||
1
|
||||
) | rpl::start_with_next([=](std::shared_ptr<ChatTheme> &&data) {
|
||||
const auto id = data->key();
|
||||
const auto i = ranges::find(_entries, id, &Entry::id);
|
||||
if (i != end(_entries)) {
|
||||
i->theme = std::move(data);
|
||||
i->preview = GeneratePreview(i->theme.get());
|
||||
if (_chosen == i->emoji->text()) {
|
||||
_controller->overridePeerTheme(_peer, i->theme);
|
||||
}
|
||||
_inner->update();
|
||||
}
|
||||
}, _cachingLifetime);
|
||||
_entries.back().preview;
|
||||
x += single.width() + skip;
|
||||
}
|
||||
}, lifetime());
|
||||
_shouldBeShown = true;
|
||||
}
|
||||
|
||||
bool ChooseThemeController::shouldBeShown() const {
|
||||
return _shouldBeShown.current();
|
||||
}
|
||||
|
||||
rpl::producer<bool> ChooseThemeController::shouldBeShownValue() const {
|
||||
return _shouldBeShown.value();
|
||||
}
|
||||
|
||||
int ChooseThemeController::height() const {
|
||||
return shouldBeShown() ? _wrap->height() : 0;
|
||||
}
|
||||
|
||||
void ChooseThemeController::hide() {
|
||||
_forceHidden = true;
|
||||
}
|
||||
|
||||
void ChooseThemeController::show() {
|
||||
_forceHidden = false;
|
||||
}
|
||||
|
||||
void ChooseThemeController::raise() {
|
||||
_wrap->raise();
|
||||
_topShadow->raise();
|
||||
}
|
||||
|
||||
rpl::lifetime &ChooseThemeController::lifetime() {
|
||||
return _wrap->lifetime();
|
||||
}
|
||||
|
||||
} // namespace Ui
|
73
Telegram/SourceFiles/ui/chat/choose_theme_controller.h
Normal file
73
Telegram/SourceFiles/ui/chat/choose_theme_controller.h
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
class PeerData;
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace Data {
|
||||
struct ChatTheme;
|
||||
} // namespace Data
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class RpWidget;
|
||||
class PlainShadow;
|
||||
class VerticalLayout;
|
||||
|
||||
class ChooseThemeController final {
|
||||
public:
|
||||
ChooseThemeController(
|
||||
not_null<RpWidget*> parent,
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<PeerData*> peer);
|
||||
~ChooseThemeController();
|
||||
|
||||
[[nodiscard]] bool shouldBeShown() const;
|
||||
[[nodiscard]] rpl::producer<bool> shouldBeShownValue() const;
|
||||
[[nodiscard]] int height() const;
|
||||
|
||||
void hide();
|
||||
void show();
|
||||
void raise();
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime();
|
||||
|
||||
private:
|
||||
struct Entry;
|
||||
|
||||
void init(rpl::producer<QSize> outer);
|
||||
void initButtons();
|
||||
void initList();
|
||||
void fill(const std::vector<Data::ChatTheme> &themes);
|
||||
|
||||
void clearCurrentBackgroundState();
|
||||
void paintEntry(QPainter &p, const Entry &entry);
|
||||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
const not_null<PeerData*> _peer;
|
||||
const std::unique_ptr<VerticalLayout> _wrap;
|
||||
const std::unique_ptr<PlainShadow> _topShadow;
|
||||
|
||||
const not_null<RpWidget*> _content;
|
||||
const not_null<RpWidget*> _inner;
|
||||
std::vector<Entry> _entries;
|
||||
QString _pressed;
|
||||
QString _chosen;
|
||||
|
||||
rpl::variable<bool> _shouldBeShown = false;
|
||||
rpl::variable<bool> _forceHidden = false;
|
||||
rpl::variable<bool> _dark = false;
|
||||
rpl::lifetime _cachingLifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui
|
|
@ -25,8 +25,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
namespace Window {
|
||||
namespace {
|
||||
|
||||
constexpr auto kDarkValueThreshold = 0.5;
|
||||
|
||||
[[nodiscard]] rpl::producer<QString> PeerThemeEmojiValue(
|
||||
not_null<PeerData*> peer) {
|
||||
return peer->session().changes().peerFlagsValue(
|
||||
|
@ -51,17 +49,9 @@ constexpr auto kDarkValueThreshold = 0.5;
|
|||
[[nodiscard]] auto MaybeCloudThemeValueFromPeer(
|
||||
not_null<PeerData*> peer)
|
||||
-> rpl::producer<std::optional<Data::CloudTheme>> {
|
||||
auto isThemeDarkValue = rpl::single(
|
||||
rpl::empty_value()
|
||||
) | rpl::then(
|
||||
style::PaletteChanged()
|
||||
) | rpl::map([] {
|
||||
return (st::dialogsBg->c.valueF() < kDarkValueThreshold);
|
||||
}) | rpl::distinct_until_changed();
|
||||
|
||||
return rpl::combine(
|
||||
MaybeChatThemeDataValueFromPeer(peer),
|
||||
std::move(isThemeDarkValue)
|
||||
Theme::IsThemeDarkValue() | rpl::distinct_until_changed()
|
||||
) | rpl::map([](std::optional<Data::ChatTheme> theme, bool night) {
|
||||
return !theme
|
||||
? std::nullopt
|
||||
|
@ -304,7 +294,7 @@ auto ChatThemeValueFromPeer(
|
|||
not_null<SessionController*> controller,
|
||||
not_null<PeerData*> peer)
|
||||
-> rpl::producer<std::shared_ptr<Ui::ChatTheme>> {
|
||||
return MaybeCloudThemeValueFromPeer(
|
||||
auto cloud = MaybeCloudThemeValueFromPeer(
|
||||
peer
|
||||
) | rpl::map([=](std::optional<Data::CloudTheme> theme)
|
||||
-> rpl::producer<std::shared_ptr<Ui::ChatTheme>> {
|
||||
|
@ -314,6 +304,17 @@ auto ChatThemeValueFromPeer(
|
|||
return controller->cachedChatThemeValue(*theme);
|
||||
}) | rpl::flatten_latest(
|
||||
) | rpl::distinct_until_changed();
|
||||
|
||||
return rpl::combine(
|
||||
std::move(cloud),
|
||||
controller->peerThemeOverrideValue()
|
||||
) | rpl::map([=](
|
||||
std::shared_ptr<Ui::ChatTheme> &&cloud,
|
||||
PeerThemeOverride &&overriden) {
|
||||
return (overriden.peer == peer.get())
|
||||
? std::move(overriden.theme)
|
||||
: std::move(cloud);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Window
|
||||
|
|
|
@ -46,6 +46,7 @@ namespace {
|
|||
constexpr auto kThemeFileSizeLimit = 5 * 1024 * 1024;
|
||||
constexpr auto kBackgroundSizeLimit = 25 * 1024 * 1024;
|
||||
constexpr auto kNightThemeFile = ":/gui/night.tdesktop-theme"_cs;
|
||||
constexpr auto kDarkValueThreshold = 0.5;
|
||||
|
||||
struct Applying {
|
||||
Saved data;
|
||||
|
@ -1450,6 +1451,16 @@ bool LoadFromContent(
|
|||
out);
|
||||
}
|
||||
|
||||
rpl::producer<bool> IsThemeDarkValue() {
|
||||
return rpl::single(
|
||||
rpl::empty_value()
|
||||
) | rpl::then(
|
||||
style::PaletteChanged()
|
||||
) | rpl::map([] {
|
||||
return (st::dialogsBg->c.valueF() < kDarkValueThreshold);
|
||||
});
|
||||
}
|
||||
|
||||
QString EditingPalettePath() {
|
||||
return cWorkingDir() + "tdata/editing-theme.tdesktop-palette";
|
||||
}
|
||||
|
|
|
@ -97,6 +97,8 @@ void ResetToSomeDefault();
|
|||
[[nodiscard]] bool IsNonDefaultBackground();
|
||||
void Revert();
|
||||
|
||||
[[nodiscard]] rpl::producer<bool> IsThemeDarkValue();
|
||||
|
||||
[[nodiscard]] QString EditingPalettePath();
|
||||
|
||||
// NB! This method looks to Core::App().settings() to get colorizer by 'file'.
|
||||
|
|
|
@ -487,6 +487,11 @@ void Filler::addUserActions(not_null<UserData*> user) {
|
|||
[=] { AddBotToGroup::Start(user); });
|
||||
}
|
||||
addPollAction(user);
|
||||
if (!user->isBot()) {
|
||||
_addAction(
|
||||
tr::lng_chat_theme_change(tr::now),
|
||||
[=] { controller->toggleChooseChatTheme(user); });
|
||||
}
|
||||
if (user->canExportChatHistory()) {
|
||||
_addAction(
|
||||
tr::lng_profile_export_chat(tr::now),
|
||||
|
|
|
@ -125,6 +125,14 @@ void ActivateWindow(not_null<SessionController*> controller) {
|
|||
Ui::ActivateWindowDelayed(window);
|
||||
}
|
||||
|
||||
bool operator==(const PeerThemeOverride &a, const PeerThemeOverride &b) {
|
||||
return (a.peer == b.peer) && (a.theme == b.theme);
|
||||
}
|
||||
|
||||
bool operator!=(const PeerThemeOverride &a, const PeerThemeOverride &b) {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
DateClickHandler::DateClickHandler(Dialogs::Key chat, QDate date)
|
||||
: _chat(chat)
|
||||
, _date(date) {
|
||||
|
@ -1208,6 +1216,10 @@ void SessionController::clearChooseReportMessages() {
|
|||
content()->clearChooseReportMessages();
|
||||
}
|
||||
|
||||
void SessionController::toggleChooseChatTheme(not_null<PeerData*> peer) {
|
||||
content()->toggleChooseChatTheme(peer);
|
||||
}
|
||||
|
||||
void SessionController::updateColumnLayout() {
|
||||
content()->updateColumnLayout();
|
||||
}
|
||||
|
@ -1397,7 +1409,7 @@ auto SessionController::cachedChatThemeValue(
|
|||
const auto i = _customChatThemes.find(key);
|
||||
if (i != end(_customChatThemes)) {
|
||||
if (auto strong = i->second.theme.lock()) {
|
||||
pushToLastUsed(strong);
|
||||
pushLastUsedChatTheme(strong);
|
||||
return rpl::single(std::move(strong));
|
||||
}
|
||||
}
|
||||
|
@ -1413,12 +1425,12 @@ auto SessionController::cachedChatThemeValue(
|
|||
if (theme->key() != key) {
|
||||
return false;
|
||||
}
|
||||
pushToLastUsed(theme);
|
||||
pushLastUsedChatTheme(theme);
|
||||
return true;
|
||||
}) | rpl::take(limit));
|
||||
}
|
||||
|
||||
void SessionController::pushToLastUsed(
|
||||
void SessionController::pushLastUsedChatTheme(
|
||||
const std::shared_ptr<Ui::ChatTheme> &theme) {
|
||||
const auto i = ranges::find(_lastUsedCustomChatThemes, theme);
|
||||
if (i == end(_lastUsedCustomChatThemes)) {
|
||||
|
@ -1444,6 +1456,21 @@ void SessionController::clearCachedChatThemes() {
|
|||
_customChatThemes.clear();
|
||||
}
|
||||
|
||||
void SessionController::overridePeerTheme(
|
||||
not_null<PeerData*> peer,
|
||||
std::shared_ptr<Ui::ChatTheme> theme) {
|
||||
_peerThemeOverride = PeerThemeOverride{
|
||||
peer,
|
||||
theme ? theme : _defaultChatTheme,
|
||||
};
|
||||
}
|
||||
|
||||
void SessionController::clearPeerThemeOverride(not_null<PeerData*> peer) {
|
||||
if (_peerThemeOverride.current().peer == peer.get()) {
|
||||
_peerThemeOverride = PeerThemeOverride();
|
||||
}
|
||||
}
|
||||
|
||||
void SessionController::pushDefaultChatBackground() {
|
||||
const auto background = Theme::Background();
|
||||
const auto &paper = background->paper();
|
||||
|
|
|
@ -75,6 +75,13 @@ enum class GifPauseReason {
|
|||
using GifPauseReasons = base::flags<GifPauseReason>;
|
||||
inline constexpr bool is_flag_type(GifPauseReason) { return true; };
|
||||
|
||||
struct PeerThemeOverride {
|
||||
PeerData *peer = nullptr;
|
||||
std::shared_ptr<Ui::ChatTheme> theme;
|
||||
};
|
||||
bool operator==(const PeerThemeOverride &a, const PeerThemeOverride &b);
|
||||
bool operator!=(const PeerThemeOverride &a, const PeerThemeOverride &b);
|
||||
|
||||
class DateClickHandler : public ClickHandler {
|
||||
public:
|
||||
DateClickHandler(Dialogs::Key chat, QDate date);
|
||||
|
@ -378,6 +385,8 @@ public:
|
|||
Fn<void(MessageIdsList)> done);
|
||||
void clearChooseReportMessages();
|
||||
|
||||
void toggleChooseChatTheme(not_null<PeerData*> peer);
|
||||
|
||||
base::Variable<bool> &dialogsListFocused() {
|
||||
return _dialogsListFocused;
|
||||
}
|
||||
|
@ -412,6 +421,16 @@ public:
|
|||
-> rpl::producer<std::shared_ptr<Ui::ChatTheme>>;
|
||||
void setChatStyleTheme(const std::shared_ptr<Ui::ChatTheme> &theme);
|
||||
void clearCachedChatThemes();
|
||||
void pushLastUsedChatTheme(const std::shared_ptr<Ui::ChatTheme> &theme);
|
||||
|
||||
void overridePeerTheme(
|
||||
not_null<PeerData*> peer,
|
||||
std::shared_ptr<Ui::ChatTheme> theme);
|
||||
void clearPeerThemeOverride(not_null<PeerData*> peer);
|
||||
[[nodiscard]] auto peerThemeOverrideValue() const
|
||||
-> rpl::producer<PeerThemeOverride> {
|
||||
return _peerThemeOverride.value();
|
||||
}
|
||||
|
||||
struct PaintContextArgs {
|
||||
not_null<Ui::ChatTheme*> theme;
|
||||
|
@ -464,7 +483,6 @@ private:
|
|||
[[nodiscard]] Ui::ChatThemeBackgroundData backgroundData(
|
||||
CachedTheme &theme,
|
||||
bool generateGradient = true) const;
|
||||
void pushToLastUsed(const std::shared_ptr<Ui::ChatTheme> &theme);
|
||||
|
||||
const not_null<Controller*> _window;
|
||||
const std::unique_ptr<ChatHelpers::EmojiInteractions> _emojiInteractions;
|
||||
|
@ -500,6 +518,7 @@ private:
|
|||
const std::unique_ptr<Ui::ChatStyle> _chatStyle;
|
||||
std::weak_ptr<Ui::ChatTheme> _chatStyleTheme;
|
||||
std::deque<std::shared_ptr<Ui::ChatTheme>> _lastUsedCustomChatThemes;
|
||||
rpl::variable<PeerThemeOverride> _peerThemeOverride;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue