Added quick action select display

This commit is contained in:
CommanderRedYT
2022-01-05 01:21:42 +01:00
parent 280263c145
commit 10501dacf6
17 changed files with 354 additions and 49 deletions

View File

@ -45,6 +45,7 @@ set(headers
buildserver.h
can.h
changevaluedisplay_bluetoothmode.h
changevaluedisplay_bobbyquickactions.h
changevaluedisplay_controlmode.h
changevaluedisplay_controltype.h
changevaluedisplay_handbremsmode.h
@ -123,6 +124,7 @@ set(headers
displays/menus/selectmodemenu.h
displays/menus/selectotabuildmenu.h
displays/menus/settingsmenu.h
displays/menus/setupquickactionsmenu.h
displays/menus/statisticsmenu.h
displays/menus/taskmanagermenu.h
displays/menus/tempomatmodesettingsmenu.h
@ -273,6 +275,7 @@ set(sources
buildserver.cpp
can.cpp
changevaluedisplay_bluetoothmode.cpp
changevaluedisplay_bobbyquickactions.cpp
changevaluedisplay_controlmode.cpp
changevaluedisplay_controltype.cpp
changevaluedisplay_handbremsmode.cpp
@ -349,6 +352,7 @@ set(sources
displays/menus/selectmodemenu.cpp
displays/menus/selectotabuildmenu.cpp
displays/menus/settingsmenu.cpp
displays/menus/setupquickactionsmenu.cpp
displays/menus/statisticsmenu.cpp
displays/menus/taskmanagermenu.cpp
displays/menus/tempomatmodesettingsmenu.cpp

View File

@ -248,3 +248,9 @@ struct ButtonProfile3Accessor : public NewSettingsAccessor<uint8_t> { ConfigWrap
// Can
struct CanResetOnErrorAccessor : public NewSettingsAccessor<bool> { ConfigWrapper<bool> &getConfig() const override { return configs.canResetOnError; } };
// Quick Actions
struct QuickActionLeft2Accessor : public NewSettingsAccessor<BobbyQuickActions> { ConfigWrapper<BobbyQuickActions> &getConfig() const override { return configs.quickActionLeft2; } };
struct QuickActionRight2Accessor : public NewSettingsAccessor<BobbyQuickActions> { ConfigWrapper<BobbyQuickActions> &getConfig() const override { return configs.quickActionRight2; } };
struct QuickActionUp2Accessor : public NewSettingsAccessor<BobbyQuickActions> { ConfigWrapper<BobbyQuickActions> &getConfig() const override { return configs.quickActionUp2; } };
struct QuickActionDown2Accessor : public NewSettingsAccessor<BobbyQuickActions> { ConfigWrapper<BobbyQuickActions> &getConfig() const override { return configs.quickActionDown2; } };

View File

@ -12,6 +12,8 @@
#include "ledstrip.h"
#endif
#include "bobbyquickactions.h"
namespace {
constexpr const char TAG[] = "BUTTONS";
} // namespace
@ -69,51 +71,12 @@ void buttonPressedCommon(espgui::Button button)
case BobbyButton::Profile3:
settingsutils::switchProfile(3);
break;
case BobbyButton::Left2:
#ifdef FEATURE_LEDSTRIP
if (blinkAnimation == LEDSTRIP_OVERWRITE_NONE) //transition from off to left
{
blinkAnimation = LEDSTRIP_OVERWRITE_BLINKLEFT;
}
else if (blinkAnimation == LEDSTRIP_OVERWRITE_BLINKRIGHT) // transition to warning
{
blinkAnimation = LEDSTRIP_OVERWRITE_BLINKBOTH;
}
else // transition to off
{
blinkAnimation = LEDSTRIP_OVERWRITE_NONE;
}
#endif
break;
case BobbyButton::Right2:
#ifdef FEATURE_LEDSTRIP
if (blinkAnimation == LEDSTRIP_OVERWRITE_NONE) //transition from off to right
{
blinkAnimation = LEDSTRIP_OVERWRITE_BLINKRIGHT;
}
else if (blinkAnimation == LEDSTRIP_OVERWRITE_BLINKLEFT) // transition to warning
{
blinkAnimation = LEDSTRIP_OVERWRITE_BLINKBOTH;
}
else // transition to off
{
blinkAnimation = LEDSTRIP_OVERWRITE_NONE;
}
#endif
break;
case BobbyButton::Up2:
if (configs.handbremse.enable.value)
{
using namespace handbremse;
if (stateWish == StateWish::brake || angezogen)
stateWish = StateWish::release;
else
stateWish = StateWish::brake;
wishTimer = espchrono::millis_clock::now();
}
break;
case BobbyButton::Down2:
/* TODO */
quickactions::handle_bobby_quickaction(button);
break;
default:;
}

119
main/bobbyquickactions.cpp Normal file
View File

@ -0,0 +1,119 @@
#include "bobbyquickactions.h"
// local includes
#ifdef FEATURE_ESPNOW
#include "espnowfunctions.h"
#endif
#include "newsettings.h"
namespace quickactions {
void handle_bobby_quickaction(espgui::Button button)
{
espconfig::ConfigWrapper<BobbyQuickActions> *config = nullptr;
switch (BobbyButton(button))
{
case Left2:
config = &configs.quickActionLeft2;
break;
case Right2:
config = &configs.quickActionRight2;
break;
case Up2:
config = &configs.quickActionUp2;
break;
case Down2:
config = &configs.quickActionDown2;
break;
default:
return;
}
if (config)
{
switch(config->value)
{
case BobbyQuickActions::BLINK_LEFT:
blink_left();
break;
case BobbyQuickActions::BLINK_RIGHT:
blink_right();
break;
case BobbyQuickActions::HANDBREMSE:
handle_handbremse();
break;
case BobbyQuickActions::OPEN_GARAGE:
open_garage();
break;
default:
return;
}
}
}
void open_garage()
{
#ifdef FEATURE_ESPNOW
if (!espnow::espnow_init_allowed())
return;
for (const auto &config : configs.wireless_door_configs)
{
if (const auto error = espnow::send_espnow_message(fmt::format("BOBBYOPEN:{}:{}", config.doorId.value, config.doorToken.value)); error != ESP_OK)
{
ESP_LOGE("BOBBY", "send_espnow_message() failed with: %s", esp_err_to_name(error));
continue;
}
}
#endif
}
void blink_left()
{
#ifdef FEATURE_LEDSTRIP
if (blinkAnimation == LEDSTRIP_OVERWRITE_NONE) //transition from off to left
{
blinkAnimation = LEDSTRIP_OVERWRITE_BLINKLEFT;
}
else if (blinkAnimation == LEDSTRIP_OVERWRITE_BLINKRIGHT) // transition to warning
{
blinkAnimation = LEDSTRIP_OVERWRITE_BLINKBOTH;
}
else // transition to off
{
blinkAnimation = LEDSTRIP_OVERWRITE_NONE;
}
#endif
}
void blink_right()
{
#ifdef FEATURE_LEDSTRIP
if (blinkAnimation == LEDSTRIP_OVERWRITE_NONE) //transition from off to right
{
blinkAnimation = LEDSTRIP_OVERWRITE_BLINKRIGHT;
}
else if (blinkAnimation == LEDSTRIP_OVERWRITE_BLINKLEFT) // transition to warning
{
blinkAnimation = LEDSTRIP_OVERWRITE_BLINKBOTH;
}
else // transition to off
{
blinkAnimation = LEDSTRIP_OVERWRITE_NONE;
}
#endif
}
void handle_handbremse()
{
if (configs.handbremse.enable.value)
{
using namespace handbremse;
if (stateWish == StateWish::brake || angezogen)
stateWish = StateWish::release;
else
stateWish = StateWish::brake;
wishTimer = espchrono::millis_clock::now();
}
}
} // namespace quickactions

28
main/bobbyquickactions.h Normal file
View File

@ -0,0 +1,28 @@
#pragma once
// 3rdparty lib includes
#include <cpptypesafeenum.h>
#include <buttonsinterface.h>
// local includes
#include "bobbybuttons.h"
#define BobbyQuickActionsValues(x) \
x(NONE) \
x(BLINK_LEFT) \
x(BLINK_RIGHT) \
x(HANDBREMSE) \
x(OPEN_GARAGE)
DECLARE_TYPESAFE_ENUM(BobbyQuickActions, : uint8_t, BobbyQuickActionsValues)
namespace quickactions {
void handle_bobby_quickaction(espgui::Button button);
// functions
void open_garage();
void blink_left();
void blink_right();
void handle_handbremse();
} // namespace quickactions

View File

@ -0,0 +1,54 @@
#include "changevaluedisplay_bobbyquickactions.h"
// esp-idf includes
#include <esp_log.h>
// 3rdparty lib includes
#include <actions/setvalueaction.h>
#include <actions/backproxyaction.h>
#include <icons/back.h>
#include <futurecpp.h>
// local includes
#include "utils.h"
namespace espgui {
namespace {
constexpr const char * const TAG = "ESPGUI";
constexpr char TEXT_QUICKACTION_NONE[] = "None";
constexpr char TEXT_QUICKACTION_BLINK_LEFT[] = "Blink Left";
constexpr char TEXT_QUICKACTION_BLINK_RIGHT[] = "Blink Right";
constexpr char TEXT_QUICKACTION_HANDBREMSE[] = "Handbremse";
constexpr char TEXT_QUICKACTION_OPEN_GARAGE[] = "Open Garage";
constexpr char TEXT_BACK[] = "Back";
} // namespace
ChangeValueDisplay<BobbyQuickActions>::ChangeValueDisplay()
{
constructMenuItem<makeComponentArgs<MenuItem, SetValueAction<BobbyQuickActions>, StaticText<TEXT_QUICKACTION_NONE>>>(BobbyQuickActions::NONE, *this, *this, *this);
constructMenuItem<makeComponentArgs<MenuItem, SetValueAction<BobbyQuickActions>, StaticText<TEXT_QUICKACTION_BLINK_LEFT>>>(BobbyQuickActions::BLINK_LEFT, *this, *this, *this);
constructMenuItem<makeComponentArgs<MenuItem, SetValueAction<BobbyQuickActions>, StaticText<TEXT_QUICKACTION_BLINK_RIGHT>>>(BobbyQuickActions::BLINK_RIGHT, *this, *this, *this);
constructMenuItem<makeComponentArgs<MenuItem, SetValueAction<BobbyQuickActions>, StaticText<TEXT_QUICKACTION_HANDBREMSE>>>(BobbyQuickActions::HANDBREMSE, *this, *this, *this);
constructMenuItem<makeComponentArgs<MenuItem, SetValueAction<BobbyQuickActions>, StaticText<TEXT_QUICKACTION_OPEN_GARAGE>>>(BobbyQuickActions::OPEN_GARAGE, *this, *this, *this);
constructMenuItem<makeComponentArgs<MenuItem, BackProxyAction, StaticText<TEXT_BACK>, StaticMenuItemIcon<&espgui::icons::back>>>(*this);
}
void ChangeValueDisplay<BobbyQuickActions>::start()
{
Base::start();
switch (const auto value = getValue())
{
case BobbyQuickActions::NONE: setSelectedIndex(0); break;
case BobbyQuickActions::BLINK_LEFT: setSelectedIndex(1); break;
case BobbyQuickActions::BLINK_RIGHT: setSelectedIndex(2); break;
case BobbyQuickActions::HANDBREMSE: setSelectedIndex(3); break;
case BobbyQuickActions::OPEN_GARAGE: setSelectedIndex(4); break;
default:
ESP_LOGW(TAG, "Unknown BobbyQuickActions: %i", std::to_underlying(value));
setSelectedIndex(5);
}
}
} // namespace espgui

View File

@ -0,0 +1,29 @@
#pragma once
// 3rdparty lib includes
#include <changevaluedisplay.h>
#include <menudisplay.h>
#include <confirminterface.h>
#include <errorhandlerinterface.h>
// local includes
#include "bobbyquickactions.h"
namespace espgui {
template<>
class ChangeValueDisplay<BobbyQuickActions> :
public MenuDisplay,
public virtual AccessorInterface<BobbyQuickActions>,
public virtual ConfirmInterface,
public virtual ErrorHandlerInterface
{
using Base = MenuDisplay;
public:
ChangeValueDisplay();
void start() override;
};
} // namespace espgui

View File

@ -7,7 +7,9 @@
// local includes
#include "ledstrip.h"
#include "handbremse.h"
#include "bobbyquickactions.h"
IMPLEMENT_NVS_GET_SET_ENUM(OtaAnimationModes)
IMPLEMENT_NVS_GET_SET_ENUM(HandbremseMode)
IMPLEMENT_NVS_GET_SET_ENUM(LedstripAnimation)
IMPLEMENT_NVS_GET_SET_ENUM(BobbyQuickActions)

View File

@ -7,3 +7,4 @@
INSTANTIATE_CONFIGWRAPPER_TEMPLATES(OtaAnimationModes)
INSTANTIATE_CONFIGWRAPPER_TEMPLATES(HandbremseMode)
INSTANTIATE_CONFIGWRAPPER_TEMPLATES(LedstripAnimation)
INSTANTIATE_CONFIGWRAPPER_TEMPLATES(BobbyQuickActions)

View File

@ -19,11 +19,13 @@
#include "displays/menus/settingsmenu.h"
#include "displays/buttoncalibratedisplay.h"
#include "displays/menus/extrabuttoncalibratemenu.h"
#include "displays/menus/setupquickactionsmenu.h"
namespace {
constexpr char TEXT_BOARDCOMPUTERHARDWARESETTINGS[] = "Boardcomputer H/W settings";
constexpr char TEXT_BUTTONCALIBRATE[] = "Button Calibrate";
constexpr char TEXT_EXTRABUTTONCALIBRATE[] = "Cal other Buttons";
constexpr char TEXT_QUICKACTIONS[] = "Quick Actions";
constexpr char TEXT_LOCKSCREENSETTINGS[] = "Lockscreen Settings";
constexpr char TEXT_POTISCALIBRATE[] = "Potis Calibrate";
constexpr char TEXT_SAMPLECOUNT[] = "sampleCount";
@ -99,6 +101,7 @@ using BremsMaxChangeScreen = espgui::makeComponent<
espgui::BackActionInterface<espgui::SwitchScreenAction<BoardcomputerHardwareSettingsMenu>>
>;
#if defined(FEATURE_DPAD) || defined(FEATURE_DPAD_3WIRESW) || defined(FEATURE_DPAD_5WIRESW) || defined(FEATURE_DPAD_5WIRESW_2OUT) || defined (FEATURE_DPAD_6WIRESW)
using DPadDebounceChangeScreen = espgui::makeComponent<
BobbyChangeValueDisplay<uint8_t>,
@ -175,6 +178,7 @@ BoardcomputerHardwareSettingsMenu::BoardcomputerHardwareSettingsMenu()
constructMenuItem<makeComponent<MenuItem, StaticText<TEXT_LOCKSCREENSETTINGS>, SwitchScreenAction<LockscreenSettingsMenu>, StaticMenuItemIcon<&bobbyicons::lock>>>();
constructMenuItem<makeComponent<MenuItem, StaticText<TEXT_BUTTONCALIBRATE>, SwitchScreenAction<ButtonCalibrateDisplay>>>();
constructMenuItem<makeComponent<MenuItem, StaticText<TEXT_EXTRABUTTONCALIBRATE>, SwitchScreenAction<ExtraButtonCalibrateMenu>>>();
constructMenuItem<makeComponent<MenuItem, StaticText<TEXT_QUICKACTIONS>, SwitchScreenAction<SetupQuickActionsMenu>>>();
constructMenuItem<makeComponent<MenuItem, GasText, DisabledColor, StaticFont<2>, DummyAction>>();
constructMenuItem<makeComponent<MenuItem, BremsText, DisabledColor, StaticFont<2>, DummyAction>>();
constructMenuItem<makeComponent<MenuItem, StaticText<TEXT_POTISCALIBRATE>, SwitchScreenAction<PotisCalibrateDisplay>>>();

View File

@ -57,7 +57,7 @@ void GarageMenu::back()
}
namespace {
void SendEspNowMessageAction:: triggered()
void SendEspNowMessageAction::triggered()
{
if (const auto error = espnow::send_espnow_message(fmt::format("BOBBYOPEN:{}:{}", configs.wireless_door_configs[m_index].doorId.value, configs.wireless_door_configs[m_index].doorToken.value)); error != ESP_OK)
{

View File

@ -0,0 +1,52 @@
#include "setupquickactionsmenu.h"
// 3rdparty lib includes
#include <textwithvaluehelper.h>
// local includes
#include "actions/switchscreenaction.h"
#include "accessors/settingsaccessors.h"
#include "displays/bobbychangevaluedisplay.h"
#include "changevaluedisplay_bobbyquickactions.h"
#include "displays/menus/boardcomputerhardwaresettingsmenu.h"
#include "icons/back.h"
#include "bobbyquickactions.h"
using namespace espgui;
namespace {
constexpr char TEXT_SETUPQUICKACTIONS[] = "Setup QuickActions";
constexpr char TEXT_SETUPLEFT2[] = "&sSetup Left2";
constexpr char TEXT_SETUPRIGHT2[] = "&sSetup Right2";
constexpr char TEXT_SETUPUP2[] = "&sSetup Up2";
constexpr char TEXT_SETUPDOWN2[] = "&sSetup Down2";
constexpr char TEXT_BACK[] = "Back";
template<typename Tvalue, const char* TEXT, typename Accessor, typename Screen>
using QuickActionChangeValueDisplay = espgui::makeComponent<
BobbyChangeValueDisplay<Tvalue>,
espgui::StaticText<TEXT>,
Accessor,
espgui::ConfirmActionInterface<espgui::SwitchScreenAction<Screen>>,
espgui::BackActionInterface<espgui::SwitchScreenAction<Screen>>
>;
} // namespace
SetupQuickActionsMenu::SetupQuickActionsMenu()
{
constructMenuItem<makeComponent<MenuItem, TextWithValueHelper<TEXT_SETUPLEFT2, QuickActionLeft2Accessor>, SwitchScreenAction<QuickActionChangeValueDisplay<BobbyQuickActions, TEXT_SETUPLEFT2, QuickActionLeft2Accessor, SetupQuickActionsMenu>>>>();
constructMenuItem<makeComponent<MenuItem, TextWithValueHelper<TEXT_SETUPRIGHT2, QuickActionRight2Accessor>, SwitchScreenAction<QuickActionChangeValueDisplay<BobbyQuickActions, TEXT_SETUPRIGHT2, QuickActionRight2Accessor, SetupQuickActionsMenu>>>>();
constructMenuItem<makeComponent<MenuItem, TextWithValueHelper<TEXT_SETUPUP2, QuickActionUp2Accessor>, SwitchScreenAction<QuickActionChangeValueDisplay<BobbyQuickActions, TEXT_SETUPUP2, QuickActionUp2Accessor, SetupQuickActionsMenu>>>>();
constructMenuItem<makeComponent<MenuItem, TextWithValueHelper<TEXT_SETUPDOWN2, QuickActionDown2Accessor>, SwitchScreenAction<QuickActionChangeValueDisplay<BobbyQuickActions, TEXT_SETUPDOWN2, QuickActionDown2Accessor, SetupQuickActionsMenu>>>>();
constructMenuItem<makeComponent<MenuItem, StaticText<TEXT_BACK>, SwitchScreenAction<BoardcomputerHardwareSettingsMenu>, StaticMenuItemIcon<&espgui::icons::back>>>();
}
std::string SetupQuickActionsMenu::text() const
{
return TEXT_SETUPQUICKACTIONS;
}
void SetupQuickActionsMenu::back()
{
switchScreen<BoardcomputerHardwareSettingsMenu>();
}

View File

@ -0,0 +1,14 @@
#pragma once
// local includes
#include "displays/bobbymenudisplay.h"
class SetupQuickActionsMenu : public BobbyMenuDisplay
{
public:
SetupQuickActionsMenu();
std::string text() const override;
void back() override;
};

View File

@ -23,8 +23,10 @@ bool receiveTsFromOtherBobbycars{true};
namespace {
constexpr const char * const TAG = "BOBBY_ESP_NOW";
} // namespace
inline bool espnow_init_allowed() {
bool espnow_init_allowed()
{
const auto wifi_mode = wifi_stack::get_wifi_mode();
return
(
@ -46,7 +48,6 @@ inline bool espnow_init_allowed() {
)
);
}
} // namespace
namespace {
extern "C" void onReceive(const uint8_t *mac_addr, const uint8_t *data, int data_len)

View File

@ -19,6 +19,8 @@ struct esp_now_message_t
extern bool receiveTimeStamp;
extern bool receiveTsFromOtherBobbycars;
extern bool espnow_init_allowed();
extern std::deque<esp_now_message_t> message_queue;
extern std::vector<esp_now_peer_info_t> peers;

View File

@ -25,6 +25,7 @@
#include "unifiedmodelmode.h"
#include "displays/lockscreen.h"
#include "handbremse.h"
#include "bobbyquickactions.h"
using namespace espconfig;
@ -147,6 +148,11 @@ public:
ConfigWrapper<uint8_t> dpadMappingUp2 {INPUT_MAPPING_NONE, DoReset, {}, "dpadMapUp2" };
ConfigWrapper<uint8_t> dpadMappingDown2 {INPUT_MAPPING_NONE, DoReset, {}, "dpadMapDown2" };
ConfigWrapper<BobbyQuickActions> quickActionLeft2{ BobbyQuickActions::BLINK_LEFT, DoReset, {}, "quickActleft2" };
ConfigWrapper<BobbyQuickActions> quickActionRight2{ BobbyQuickActions::BLINK_RIGHT, DoReset, {}, "quickActright2" };
ConfigWrapper<BobbyQuickActions> quickActionUp2{ BobbyQuickActions::NONE, DoReset, {}, "quickActup2" };
ConfigWrapper<BobbyQuickActions> quickActionDown2{ BobbyQuickActions::HANDBREMSE, DoReset, {}, "quickActdown2" };
std::array<WirelessDoorsConfig, 5> wireless_door_configs {
WirelessDoorsConfig { "door_id0", "door_token0" },
WirelessDoorsConfig { "door_id1", "door_token1" },
@ -432,6 +438,11 @@ public:
x(dpadMappingUp2) \
x(dpadMappingDown2) \
\
x(quickActionLeft2) \
x(quickActionRight2) \
x(quickActionUp2) \
x(quickActionDown2) \
\
x(wireless_door_configs[0].doorId) \
x(wireless_door_configs[0].doorToken) \
x(wireless_door_configs[1].doorId) \

View File

@ -23,8 +23,6 @@
// local includes
#include "newsettings.h"
#include "webserver_lock.h"
#include "ledstrip.h"
#include "handbremse.h"
#ifdef FEATURE_WEBSERVER
using namespace std::chrono_literals;
@ -56,7 +54,8 @@ typename std::enable_if<
!std::is_same_v<T, espchrono::DayLightSavingMode> &&
!std::is_same_v<T, OtaAnimationModes> &&
!std::is_same_v<T, LedstripAnimation> &&
!std::is_same_v<T, HandbremseMode>
!std::is_same_v<T, HandbremseMode> &&
!std::is_same_v<T, BobbyQuickActions>
, void>::type
showInputForSetting(std::string_view key, T value, std::string &body)
{
@ -245,6 +244,20 @@ showInputForSetting(std::string_view key, T value, std::string &body)
body += esphttpdutils::htmlentities(enumKey);
});
}
template<typename T>
typename std::enable_if<
std::is_same_v<T, BobbyQuickActions>
, void>::type
showInputForSetting(std::string_view key, T value, std::string &body)
{
HtmlTag select{"select", fmt::format("name=\"{}\"", esphttpdutils::htmlentities(key)), body};
iterateBobbyQuickActions([&](T enumVal, std::string_view enumKey){
HtmlTag option{"option", fmt::format("value=\"{}\"{}", std::to_underlying(enumVal), value == enumVal ? " selected" : ""), body};
body += esphttpdutils::htmlentities(enumKey);
});
}
} // namespace
esp_err_t webserver_newSettings_handler(httpd_req_t *req)
@ -393,7 +406,8 @@ typename std::enable_if<
!std::is_same_v<T, espchrono::DayLightSavingMode> &&
!std::is_same_v<T, OtaAnimationModes> &&
!std::is_same_v<T, LedstripAnimation> &&
!std::is_same_v<T, HandbremseMode>
!std::is_same_v<T, HandbremseMode> &&
!std::is_same_v<T, BobbyQuickActions>
, tl::expected<void, std::string>>::type
saveSetting(ConfigWrapper<T> &config, std::string_view newValue)
{
@ -479,7 +493,8 @@ typename std::enable_if<
std::is_same_v<T, espchrono::DayLightSavingMode> ||
std::is_same_v<T, OtaAnimationModes> ||
std::is_same_v<T, LedstripAnimation> ||
std::is_same_v<T, HandbremseMode>
std::is_same_v<T, HandbremseMode> ||
std::is_same_v<T, BobbyQuickActions>
, tl::expected<void, std::string>>::type
saveSetting(ConfigWrapper<T> &config, std::string_view newValue)
{