Implement "Save Power on Low Battery" function.

This commit is contained in:
John Preston 2023-02-27 18:09:12 +04:00
parent 388541a3fb
commit ce0e07d332
16 changed files with 218 additions and 50 deletions

View File

@ -625,6 +625,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_power_ui" = "Interface animations";
"lng_settings_power_auto" = "Save Power on Low Battery";
"lng_settings_power_auto_about" = "Automatically disable all animations when your laptop is in a battery saving mode.";
"lng_settings_power_turn_off" = "Please turn off Save Power on Low Battery to change these settings.";
"lng_settings_cloud_password_on" = "On";
"lng_settings_cloud_password_off" = "Off";

View File

@ -42,6 +42,7 @@ namespace {
constexpr auto kSlowmodeValues = 7;
constexpr auto kSuggestGigagroupThreshold = 199000;
constexpr auto kForceDisableTooltipDuration = 3 * crl::time(1000);
[[nodiscard]] auto Dependencies(PowerSaving::Flags)
-> std::vector<std::pair<PowerSaving::Flag, PowerSaving::Flag>> {
@ -453,8 +454,38 @@ template <typename Flags>
struct State final {
std::map<Flags, not_null<Ui::AbstractCheckView*>> checkViews;
rpl::event_stream<> anyChanges;
rpl::variable<QString> forceDisabledMessage;
rpl::variable<bool> forceDisabled;
base::flat_map<Flags, bool> realCheckedValues;
base::weak_ptr<Ui::Toast::Instance> toast;
};
const auto state = container->lifetime().make_state<State>();
if (descriptor.forceDisabledMessage) {
state->forceDisabledMessage = std::move(
descriptor.forceDisabledMessage);
state->forceDisabled = state->forceDisabledMessage.value(
) | rpl::map([=](const QString &message) {
return !message.isEmpty();
});
state->forceDisabled.value(
) | rpl::start_with_next([=](bool disabled) {
if (disabled) {
for (const auto &[flags, checkView] : state->checkViews) {
checkView->setChecked(false, anim::type::normal);
}
} else {
for (const auto &[flags, checkView] : state->checkViews) {
if (const auto i = state->realCheckedValues.find(flags)
; i != state->realCheckedValues.end()) {
checkView->setChecked(
i->second,
anim::type::normal);
}
}
}
}, container->lifetime());
}
const auto &st = descriptor.st ? *descriptor.st : st::rightsButton;
const auto value = [=] {
@ -492,7 +523,9 @@ template <typename Flags>
const auto locked = (lockedIt != end(descriptor.disabledMessages))
? std::make_optional(lockedIt->second)
: std::nullopt;
const auto toggled = ((checked & flags) != 0);
const auto realChecked = (checked & flags) != 0;
state->realCheckedValues.emplace(flags, realChecked);
const auto toggled = realChecked && !state->forceDisabled.current();
const auto checkView = [&]() -> not_null<Ui::AbstractCheckView*> {
if (isInner) {
@ -559,15 +592,30 @@ template <typename Flags>
state->checkViews.emplace(flags, checkView);
checkView->checkedChanges(
) | rpl::start_with_next([=](bool checked) {
if (locked.has_value()) {
if (checked != toggled) {
Ui::ShowMultilineToast({
if (checked && state->forceDisabled.current()) {
if (!state->toast) {
state->toast = Ui::ShowMultilineToast({
.parentOverride = container,
.text = { *locked },
.text = { state->forceDisabledMessage.current() },
.duration = kForceDisableTooltipDuration,
});
}
checkView->setChecked(false, anim::type::instant);
} else if (locked.has_value()) {
if (checked != toggled) {
if (!state->toast) {
state->toast = Ui::ShowMultilineToast({
.parentOverride = container,
.text = { *locked },
.duration = kForceDisableTooltipDuration,
});
}
checkView->setChecked(toggled, anim::type::instant);
}
} else {
if (!state->forceDisabled.current()) {
state->realCheckedValues[flags] = checked;
}
InvokeQueued(container, [=] {
applyDependencies(checkView);
state->anyChanges.fire({});
@ -1141,12 +1189,15 @@ ChatAdminRights AdminRightsForOwnershipTransfer(
EditFlagsControl<PowerSaving::Flags> CreateEditPowerSaving(
QWidget *parent,
PowerSaving::Flags flags) {
PowerSaving::Flags flags,
rpl::producer<QString> forceDisabledMessage) {
auto widget = object_ptr<Ui::VerticalLayout>(parent);
auto descriptor = Settings::PowerSavingLabels();
descriptor.forceDisabledMessage = std::move(forceDisabledMessage);
auto result = CreateEditFlags(
widget.data(),
flags,
Settings::PowerSavingLabels());
std::move(descriptor));
result.widget = std::move(widget);
return result;

View File

@ -74,6 +74,7 @@ struct EditFlagsDescriptor {
std::vector<NestedEditFlagsLabels<Flags>> labels;
base::flat_map<Flags, QString> disabledMessages;
const style::SettingsButton *st = nullptr;
rpl::producer<QString> forceDisabledMessage;
};
using RestrictionLabel = EditFlagsLabel<ChatRestrictions>;
@ -109,5 +110,6 @@ using AdminRightLabel = EditFlagsLabel<ChatAdminRights>;
[[nodiscard]] auto CreateEditPowerSaving(
QWidget *parent,
PowerSaving::Flags flags
PowerSaving::Flags flags,
rpl::producer<QString> forceDisabledMessage
) -> EditFlagsControl<PowerSaving::Flags>;

View File

@ -499,7 +499,7 @@ void TopBar::initBlobsUnder(
using namespace rpl::mappers;
auto hideBlobs = rpl::combine(
PowerSaving::Value(PowerSaving::kCalls),
PowerSaving::OnValue(PowerSaving::kCalls),
Core::App().appDeactivatedValue(),
group->instanceStateValue()
) | rpl::map(_1 || _2 || _3 == GroupCall::InstanceState::Disconnected);

View File

@ -235,7 +235,7 @@ Members::Controller::Controller(
}, _lifetime);
rpl::combine(
PowerSaving::Value(PowerSaving::kCalls),
PowerSaving::OnValue(PowerSaving::kCalls),
Core::App().appDeactivatedValue()
) | rpl::start_with_next([=](bool disabled, bool deactivated) {
const auto hide = disabled || deactivated;

View File

@ -14,10 +14,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h"
#include "data/data_channel.h"
#include "data/data_download_manager.h"
#include "base/timer.h"
#include "base/battery_saving.h"
#include "base/event_filter.h"
#include "base/concurrent_timer.h"
#include "base/qt_signal_producer.h"
#include "base/timer.h"
#include "base/unixtime.h"
#include "core/core_settings.h"
#include "core/update_checker.h"
@ -78,6 +79,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/animations.h"
#include "ui/effects/spoiler_mess.h"
#include "ui/cached_round_corners.h"
#include "ui/power_saving.h"
#include "storage/serialize_common.h"
#include "storage/storage_domain.h"
#include "storage/storage_databases.h"
@ -143,6 +145,7 @@ Application::Application(not_null<Launcher*> launcher)
, _launcher(launcher)
, _private(std::make_unique<Private>())
, _platformIntegration(Platform::Integration::Create())
, _batterySaving(std::make_unique<base::BatterySaving>())
, _databases(std::make_unique<Storage::Databases>())
, _animationsManager(std::make_unique<Ui::Animations::Manager>())
, _clearEmojiImageLoaderTimer([=] { clearEmojiSourceImages(); })
@ -279,6 +282,13 @@ void Application::run() {
_mediaControlsManager = std::make_unique<MediaControlsManager>();
}
rpl::combine(
_batterySaving->value(),
settings().ignoreBatterySavingValue()
) | rpl::start_with_next([=](bool saving, bool ignore) {
PowerSaving::SetForceAll(saving && !ignore);
}, _lifetime);
style::ShortAnimationPlaying(
) | rpl::start_with_next([=](bool playing) {
if (playing) {
@ -409,14 +419,14 @@ void Application::showOpenGLCrashNotification() {
const auto enable = [=] {
Ui::GL::ForceDisable(false);
Ui::GL::CrashCheckFinish();
Core::App().settings().setDisableOpenGL(false);
settings().setDisableOpenGL(false);
Local::writeSettings();
Restart();
};
const auto keepDisabled = [=] {
Ui::GL::ForceDisable(true);
Ui::GL::CrashCheckFinish();
Core::App().settings().setDisableOpenGL(true);
settings().setDisableOpenGL(true);
Local::writeSettings();
};
_lastActivePrimaryWindow->show(Ui::MakeConfirmBox({
@ -658,6 +668,10 @@ Settings &Application::settings() {
return _private->settings;
}
const Settings &Application::settings() const {
return _private->settings;
}
void Application::saveSettingsDelayed(crl::time delay) {
if (_saveSettingsTimer) {
_saveSettingsTimer->callOnce(delay);
@ -670,7 +684,7 @@ void Application::saveSettings() {
bool Application::canReadDefaultDownloadPath(bool always) const {
if (KSandbox::isInside()
&& (always || Core::App().settings().downloadPath().isEmpty())) {
&& (always || settings().downloadPath().isEmpty())) {
const auto path = QStandardPaths::writableLocation(
QStandardPaths::DownloadLocation);
return base::CanReadDirectory(path);
@ -679,7 +693,7 @@ bool Application::canReadDefaultDownloadPath(bool always) const {
}
bool Application::canSaveFileWithoutAskingForPath() const {
return !Core::App().settings().askDownloadPath();
return !settings().askDownloadPath();
}
MTP::Config &Application::fallbackProductionConfig() const {

View File

@ -13,6 +13,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class History;
namespace base {
class BatterySaving;
} // namespace base
namespace Platform {
class Integration;
} // namespace Platform
@ -127,15 +131,14 @@ public:
Application &operator=(const Application &other) = delete;
~Application();
void run();
[[nodiscard]] Launcher &launcher() const {
return *_launcher;
}
[[nodiscard]] Platform::Integration &platformIntegration() const {
return *_platformIntegration;
}
void run();
[[nodiscard]] Ui::Animations::Manager &animationManager() const {
return *_animationsManager;
}
@ -150,6 +153,9 @@ public:
[[nodiscard]] Tray &tray() const {
return *_tray;
}
[[nodiscard]] base::BatterySaving &batterySaving() const {
return *_batterySaving;
}
// Windows interface.
bool hasActiveWindow(not_null<Main::Session*> session) const;
@ -191,6 +197,7 @@ public:
void startSettingsAndBackground();
[[nodiscard]] Settings &settings();
[[nodiscard]] const Settings &settings() const;
void saveSettingsDelayed(crl::time delay = kDefaultSaveDelay);
void saveSettings();
@ -373,6 +380,7 @@ private:
struct Private;
const std::unique_ptr<Private> _private;
const std::unique_ptr<Platform::Integration> _platformIntegration;
const std::unique_ptr<base::BatterySaving> _batterySaving;
const std::unique_ptr<Storage::Databases> _databases;
const std::unique_ptr<Ui::Animations::Manager> _animationsManager;

View File

@ -197,7 +197,8 @@ QByteArray Settings::serialize() const {
+ sizeof(qint32)
+ sizeof(quint64)
+ sizeof(qint32) * 3
+ Serialize::bytearraySize(mediaViewPosition);
+ Serialize::bytearraySize(mediaViewPosition)
+ sizeof(qint32);
auto result = QByteArray();
result.reserve(size);
@ -329,7 +330,8 @@ QByteArray Settings::serialize() const {
<< qint32(_windowTitleContent.current().hideChatName ? 1 : 0)
<< qint32(_windowTitleContent.current().hideAccountName ? 1 : 0)
<< qint32(_windowTitleContent.current().hideTotalUnread ? 1 : 0)
<< mediaViewPosition;
<< mediaViewPosition
<< qint32(_ignoreBatterySaving.current() ? 1 : 0);
}
return result;
}
@ -433,6 +435,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
qint32 hideAccountName = _windowTitleContent.current().hideAccountName ? 1 : 0;
qint32 hideTotalUnread = _windowTitleContent.current().hideTotalUnread ? 1 : 0;
QByteArray mediaViewPosition;
qint32 ignoreBatterySaving = _ignoreBatterySaving.current() ? 1 : 0;
stream >> themesAccentColors;
if (!stream.atEnd()) {
@ -661,6 +664,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
if (!stream.atEnd()) {
stream >> mediaViewPosition;
}
if (!stream.atEnd()) {
stream >> ignoreBatterySaving;
}
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for Core::Settings::constructFromSerialized()"));
@ -857,6 +863,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
_mediaViewPosition = { .maximized = 2 };
}
}
_ignoreBatterySaving = (ignoreBatterySaving == 1);
}
QString Settings::getSoundPath(const QString &key) const {

View File

@ -777,6 +777,15 @@ public:
void setMediaViewPosition(const WindowPosition &position) {
_mediaViewPosition = position;
}
[[nodiscard]] bool ignoreBatterySaving() const {
return _ignoreBatterySaving.current();
}
[[nodiscard]] rpl::producer<bool> ignoreBatterySavingValue() const {
return _ignoreBatterySaving.value();
}
void setIgnoreBatterySavingValue(bool value) {
_ignoreBatterySaving = value;
}
[[nodiscard]] static bool ThirdColumnByDefault();
[[nodiscard]] static float64 DefaultDialogsWidthRatio();
@ -900,6 +909,7 @@ private:
rpl::event_stream<> _skipTranslationLanguagesChanges;
bool _rememberedDeleteMessageOnlyForYou = false;
WindowPosition _mediaViewPosition = { .maximized = 2 };
rpl::variable<bool> _ignoreBatterySaving = false;
bool _tabbedReplacedWithInfo = false; // per-window
rpl::event_stream<bool> _tabbedReplacedWithInfoValue; // per-window

View File

@ -7,11 +7,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "settings/settings_power_saving.h"
#include "base/battery_saving.h"
#include "boxes/peers/edit_peer_permissions_box.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "lang/lang_keys.h"
#include "settings/settings_common.h"
#include "ui/layers/generic_box.h"
#include "ui/toasts/common_toasts.h"
#include "ui/widgets/buttons.h"
#include "ui/power_saving.h"
#include "styles/style_menu_icons.h"
@ -19,6 +22,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_settings.h"
namespace Settings {
namespace {
constexpr auto kForceDisableTooltipDuration = 3 * crl::time(1000);
} // namespace
void PowerSavingBox(not_null<Ui::GenericBox*> box) {
box->setStyle(st::layerBox);
@ -26,40 +34,90 @@ void PowerSavingBox(not_null<Ui::GenericBox*> box) {
box->setWidth(st::boxWideWidth);
const auto container = box->verticalLayout();
const auto ignore = Core::App().settings().ignoreBatterySaving();
const auto batterySaving = Core::App().batterySaving().enabled();
// Force top shadow visibility.
box->setPinnedToTopContent(
object_ptr<Ui::FixedHeightWidget>(box, st::lineWidth));
AddSubsectionTitle(
const auto subtitle = AddSubsectionTitle(
container,
tr::lng_settings_power_subtitle(),
st::powerSavingSubtitlePadding);
struct State {
rpl::variable<QString> forceDisabledMessage;
};
const auto state = container->lifetime().make_state<State>();
state->forceDisabledMessage = (batterySaving.value_or(false) && !ignore)
? tr::lng_settings_power_turn_off(tr::now)
: QString();
auto [checkboxes, getResult, changes] = CreateEditPowerSaving(
box,
PowerSaving::kAll & ~PowerSaving::Current());
PowerSaving::kAll & ~PowerSaving::Current(),
state->forceDisabledMessage.value());
const auto controlsRaw = checkboxes.data();
box->addRow(std::move(checkboxes), {});
auto automatic = (Ui::SettingsButton*)nullptr;
const auto hasBattery = true;
const auto automaticEnabled = true;
if (hasBattery) {
if (batterySaving.has_value()) {
AddSkip(container);
AddDivider(container);
AddSkip(container);
AddButton(
automatic = AddButton(
container,
tr::lng_settings_power_auto(),
st::powerSavingButtonNoIcon
)->toggleOn(rpl::single(automaticEnabled));
)->toggleOn(rpl::single(!ignore));
AddSkip(container);
AddDividerText(container, tr::lng_settings_power_auto_about());
state->forceDisabledMessage = rpl::combine(
automatic->toggledValue(),
Core::App().batterySaving().value()
) | rpl::map([=](bool dontIgnore, bool saving) {
return (saving && dontIgnore)
? tr::lng_settings_power_turn_off()
: rpl::single(QString());
}) | rpl::flatten_latest();
const auto disabler = Ui::CreateChild<Ui::AbstractButton>(container.get());
disabler->setClickedCallback([=] {
Ui::ShowMultilineToast({
.parentOverride = container,
.text = tr::lng_settings_power_turn_off(tr::now),
.duration = kForceDisableTooltipDuration,
});
});
disabler->paintRequest() | rpl::start_with_next([=](QRect clip) {
auto color = st::boxBg->c;
color.setAlpha(96);
QPainter(disabler).fillRect(clip, color);
}, disabler->lifetime());
rpl::combine(
subtitle->geometryValue(),
controlsRaw->geometryValue()
) | rpl::start_with_next([=](QRect subtitle, QRect controls) {
disabler->setGeometry(subtitle.united(controls));
}, disabler->lifetime());
disabler->showOn(state->forceDisabledMessage.value(
) | rpl::map([=](const QString &value) {
return !value.isEmpty();
}));
}
box->addButton(tr::lng_settings_save(), [=, collect = getResult] {
Set(PowerSaving::kAll & ~collect());
const auto ignore = automatic
? !automatic->toggled()
: Core::App().settings().ignoreBatterySaving();
const auto batterySaving = Core::App().batterySaving().enabled();
if (ignore || !batterySaving.value_or(false)) {
Set(PowerSaving::kAll & ~collect());
}
Core::App().settings().setIgnoreBatterySavingValue(ignore);
Core::App().saveSettingsDelayed();
box->closeBox();
});

View File

@ -117,7 +117,7 @@ GroupCallUserpics::GroupCallUserpics(
});
rpl::combine(
PowerSaving::Value(PowerSaving::kCalls),
PowerSaving::OnValue(PowerSaving::kCalls),
std::move(hideBlobs)
) | rpl::start_with_next([=](bool disabled, bool deactivated) {
const auto hide = disabled || deactivated;

View File

@ -517,7 +517,7 @@ CallMuteButton::CallMuteButton(
parent,
_st->active.bgSize,
rpl::combine(
PowerSaving::Value(PowerSaving::kCalls),
PowerSaving::OnValue(PowerSaving::kCalls),
std::move(hideBlobs),
_state.value(
) | rpl::map([](const CallMuteButtonState &state) {

View File

@ -12,16 +12,32 @@ namespace {
Flags Data/* = {}*/;
rpl::event_stream<> Events;
bool AllForced/* = false*/;
} // namespace
void Set(Flags flags) {
if (const auto diff = Data ^ flags) {
Data = flags;
if (!AllForced) {
if (diff & kAnimations) {
anim::SetDisabled(On(kAnimations));
}
Events.fire({});
}
}
}
Flags Current() {
return Data;
}
void Set(Flags flags) {
if (const auto diff = Data ^ flags) {
Data = flags;
void SetForceAll(bool force) {
if (AllForced == force) {
return;
}
AllForced = force;
if (const auto diff = Data ^ kAll) {
if (diff & kAnimations) {
anim::SetDisabled(On(kAnimations));
}
@ -29,18 +45,12 @@ void Set(Flags flags) {
}
}
rpl::producer<Flags> Changes() {
return Events.events() | rpl::map(Current);
bool ForceAll() {
return AllForced;
}
rpl::producer<Flags> Value() {
return rpl::single(Current()) | rpl::then(Changes());
}
rpl::producer<bool> Value(Flag flag) {
return Value() | rpl::map([=](Flags flags) {
return (flags & flag) != 0;
}) | rpl::distinct_until_changed();
rpl::producer<> Changes() {
return Events.events();
}
} // namespace PowerSaving

View File

@ -25,14 +25,21 @@ enum Flag : uint32 {
inline constexpr bool is_flag_type(Flag) { return true; }
using Flags = base::flags<Flag>;
[[nodiscard]] Flags Current();
[[nodiscard]] rpl::producer<Flags> Changes();
[[nodiscard]] rpl::producer<Flags> Value();
[[nodiscard]] rpl::producer<bool> Value(Flag flag);
void Set(Flags flags);
[[nodiscard]] Flags Current();
void SetForceAll(bool force);
[[nodiscard]] bool ForceAll();
[[nodiscard]] rpl::producer<> Changes();
[[nodiscard]] inline bool On(Flag flag) {
return Current() & flag;
return ForceAll() || (Current() & flag);
}
[[nodiscard]] inline rpl::producer<bool> OnValue(Flag flag) {
return rpl::single(On(flag)) | rpl::then(Changes() | rpl::map([=] {
return On(flag);
})) | rpl::distinct_until_changed();
}
} // namespace PowerSaving

@ -1 +1 @@
Subproject commit 2e306a7245de70b6e6943b0bc33892bf0d327320
Subproject commit 17ac5644d1f5cdaeb79b42cdf931b819cf0dbcf3

@ -1 +1 @@
Subproject commit bc7b4cae2ea69c67a7b501289ffba7c8eddc5d19
Subproject commit 8b1015d1bd57ef03fcd07a3eeddd3f5a9b688ade