diff options
Diffstat (limited to 'src')
33 files changed, 301 insertions, 1537 deletions
diff --git a/src/audio/audio_fsm.cpp b/src/audio/audio_fsm.cpp index a2f467cb..b903a171 100644 --- a/src/audio/audio_fsm.cpp +++ b/src/audio/audio_fsm.cpp @@ -82,11 +82,32 @@ void AudioState::react(const system_fsm::HasPhonesChanged& ev) { } } -void AudioState::react(const ChangeMaxVolume& ev) { +void AudioState::react(const SetVolume& ev) { + // TODO. +} + +void AudioState::react(const SetVolumeLimit& ev) { ESP_LOGI(kTag, "new max volume %i db", - (ev.new_max - drivers::wm8523::kLineLevelReferenceVolume) / 4); - sI2SOutput->SetMaxVolume(ev.new_max); - sServices->nvs().AmpMaxVolume(ev.new_max); + (ev.new_limit - drivers::wm8523::kLineLevelReferenceVolume) / 4); + sI2SOutput->SetMaxVolume(ev.new_limit); + sServices->nvs().AmpMaxVolume(ev.new_limit); + + events::Ui().Dispatch(VolumeLimitChanged{ + .new_limit = ev.new_limit, + }); + events::Ui().Dispatch(VolumeChanged{ + .percent = sOutput->GetVolumePct(), + .db = sOutput->GetVolumeDb(), + }); +} + +void AudioState::react(const SetVolumeBalance& ev) { + sOutput->SetVolumeImbalance(ev.left_bias); + sServices->nvs().AmpLeftBias(ev.left_bias); + + events::Ui().Dispatch(VolumeBalanceChanged{ + .left_bias = ev.left_bias, + }); } void AudioState::react(const OutputModeChanged& ev) { @@ -136,6 +157,7 @@ void Uninitialised::react(const system_fsm::BootComplete& ev) { auto& nvs = sServices->nvs(); sI2SOutput->SetMaxVolume(nvs.AmpMaxVolume()); sI2SOutput->SetVolume(nvs.AmpCurrentVolume()); + sI2SOutput->SetVolumeImbalance(nvs.AmpLeftBias()); if (sServices->nvs().OutputMode() == drivers::NvsStorage::Output::kHeadphones) { diff --git a/src/audio/include/audio_events.hpp b/src/audio/include/audio_events.hpp index 3c5ab723..0e4d0bc9 100644 --- a/src/audio/include/audio_events.hpp +++ b/src/audio/include/audio_events.hpp @@ -47,12 +47,27 @@ struct PlayFile : tinyfsm::Event { struct StepUpVolume : tinyfsm::Event {}; struct StepDownVolume : tinyfsm::Event {}; +struct SetVolume : tinyfsm::Event { + std::optional<uint_fast8_t> percent; + std::optional<int32_t> db; +}; +struct SetVolumeBalance : tinyfsm::Event { + int left_bias; +}; + struct VolumeChanged : tinyfsm::Event { uint_fast8_t percent; int db; }; -struct ChangeMaxVolume : tinyfsm::Event { - uint16_t new_max; +struct VolumeBalanceChanged : tinyfsm::Event { + int left_bias; +}; +struct VolumeLimitChanged : tinyfsm::Event { + uint16_t new_limit; +}; + +struct SetVolumeLimit : tinyfsm::Event { + uint16_t new_limit; }; struct TogglePlayPause : tinyfsm::Event {}; diff --git a/src/audio/include/audio_fsm.hpp b/src/audio/include/audio_fsm.hpp index 590c6463..b8c505b0 100644 --- a/src/audio/include/audio_fsm.hpp +++ b/src/audio/include/audio_fsm.hpp @@ -44,7 +44,11 @@ class AudioState : public tinyfsm::Fsm<AudioState> { void react(const StepUpVolume&); void react(const StepDownVolume&); virtual void react(const system_fsm::HasPhonesChanged&); - void react(const ChangeMaxVolume&); + + void react(const SetVolume&); + void react(const SetVolumeLimit&); + void react(const SetVolumeBalance&); + void react(const OutputModeChanged&); virtual void react(const system_fsm::BootComplete&) {} diff --git a/src/database/database.cpp b/src/database/database.cpp index 27b5c24c..4bd9d2db 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -17,6 +17,7 @@ #include <memory> #include <optional> #include <sstream> +#include <string> #include <variant> #include "collation.hpp" @@ -198,6 +199,33 @@ Database::~Database() { sIsDbOpen.store(false); } +auto Database::schemaVersion() -> std::string { + // If the database is open, then it must have the current schema. + return std::to_string(kCurrentDbVersion); +} + +auto Database::sizeOnDiskBytes() -> size_t { + auto lock = drivers::acquire_spi(); + + FF_DIR dir; + FRESULT res = f_opendir(&dir, kDbPath); + if (res != FR_OK) { + return 0; + } + + size_t total_size = 0; + for (;;) { + FILINFO info; + res = f_readdir(&dir, &info); + if (res != FR_OK || info.fname[0] == 0) { + break; + } + total_size += info.fsize; + } + + return total_size; +} + auto Database::put(const std::string& key, const std::string& val) -> void { db_->Put(leveldb::WriteOptions{}, kKeyCustom + key, val); } diff --git a/src/database/include/database.hpp b/src/database/include/database.hpp index 88a18e17..cb4064fb 100644 --- a/src/database/include/database.hpp +++ b/src/database/include/database.hpp @@ -63,6 +63,10 @@ class Database { ~Database(); + auto schemaVersion() -> std::string; + + auto sizeOnDiskBytes() -> size_t; + /* Adds an arbitrary record to the database. */ auto put(const std::string& key, const std::string& val) -> void; diff --git a/src/drivers/include/gpios.hpp b/src/drivers/include/gpios.hpp index 55486be7..fffd1f75 100644 --- a/src/drivers/include/gpios.hpp +++ b/src/drivers/include/gpios.hpp @@ -79,7 +79,7 @@ class IGpios { */ virtual auto Get(Pin) const -> bool = 0; - virtual auto IsLocked() const -> bool { return Get(Pin::kKeyLock); } + virtual auto IsLocked() const -> bool { return !Get(Pin::kKeyLock); } }; class Gpios : public IGpios { diff --git a/src/drivers/include/nvs.hpp b/src/drivers/include/nvs.hpp index 46a3d154..b82013b5 100644 --- a/src/drivers/include/nvs.hpp +++ b/src/drivers/include/nvs.hpp @@ -41,6 +41,9 @@ class NvsStorage { auto AmpCurrentVolume() -> uint16_t; auto AmpCurrentVolume(uint16_t) -> bool; + auto AmpLeftBias() -> int_fast8_t; + auto AmpLeftBias(int_fast8_t) -> bool; + auto HasShownOnboarding() -> bool; auto HasShownOnboarding(bool) -> bool; diff --git a/src/drivers/nvs.cpp b/src/drivers/nvs.cpp index c8a9a7c7..c8d4d636 100644 --- a/src/drivers/nvs.cpp +++ b/src/drivers/nvs.cpp @@ -30,6 +30,7 @@ static constexpr char kKeyOutput[] = "out"; static constexpr char kKeyBrightness[] = "bright"; static constexpr char kKeyAmpMaxVolume[] = "hp_vol_max"; static constexpr char kKeyAmpCurrentVolume[] = "hp_vol"; +static constexpr char kKeyAmpLeftBias[] = "hp_bias"; static constexpr char kKeyOnboarded[] = "intro"; static constexpr char kKeyPrimaryInput[] = "in_pri"; @@ -156,6 +157,17 @@ auto NvsStorage::AmpCurrentVolume(uint16_t val) -> bool { return nvs_commit(handle_) == ESP_OK; } +auto NvsStorage::AmpLeftBias() -> int_fast8_t { + int8_t out = 0; + nvs_get_i8(handle_, kKeyAmpLeftBias, &out); + return out; +} + +auto NvsStorage::AmpLeftBias(int_fast8_t val) -> bool { + nvs_set_i8(handle_, kKeyAmpLeftBias, val); + return nvs_commit(handle_) == ESP_OK; +} + auto NvsStorage::HasShownOnboarding() -> bool { uint8_t out = false; nvs_get_u8(handle_, kKeyOnboarded, &out); diff --git a/src/lua/bridge.cpp b/src/lua/bridge.cpp index e7344e0e..82721d4e 100644 --- a/src/lua/bridge.cpp +++ b/src/lua/bridge.cpp @@ -37,21 +37,6 @@ namespace lua { static constexpr char kBridgeKey[] = "bridge"; -static auto open_settings_fn(lua_State* state) -> int { - events::Ui().Dispatch(ui::internal::ShowSettingsPage{ - .page = ui::internal::ShowSettingsPage::Page::kRoot}); - return 0; -} - -static const struct luaL_Reg kLegacyUiFuncs[] = { - {"open_settings", open_settings_fn}, - {NULL, NULL}}; - -static auto lua_legacy_ui(lua_State* state) -> int { - luaL_newlib(state, kLegacyUiFuncs); - return 1; -} - auto Bridge::Get(lua_State* state) -> Bridge* { lua_pushstring(state, kBridgeKey); lua_gettable(state, LUA_REGISTRYINDEX); @@ -64,9 +49,6 @@ Bridge::Bridge(system_fsm::ServiceLocator& services, lua_State& s) lua_pushlightuserdata(&s, this); lua_settable(&s, LUA_REGISTRYINDEX); - luaL_requiref(&s, "legacy_ui", lua_legacy_ui, true); - lua_pop(&s, 1); - luaL_requiref(&s, "linenoise", luaopen_linenoise, true); lua_pop(&s, 1); @@ -94,8 +76,7 @@ inline constexpr bool always_false_v = false; auto Bridge::AddPropertyModule( const std::string& name, - std::vector<std::pair<std::string, - std::variant<LuaFunction, std::shared_ptr<Property>>>> + std::vector<std::pair<std::string, std::variant<LuaFunction, Property*>>> props) -> void { // Create the module (or retrieve it if one with this name already exists) luaL_requiref(&state_, name.c_str(), new_property_module, true); @@ -107,8 +88,8 @@ auto Bridge::AddPropertyModule( using T = std::decay_t<decltype(arg)>; if constexpr (std::is_same_v<T, LuaFunction>) { bindings_.Register(&state_, arg); - } else if constexpr (std::is_same_v<T, std::shared_ptr<Property>>) { - bindings_.Register(&state_, arg.get()); + } else if constexpr (std::is_same_v<T, Property*>) { + bindings_.Register(&state_, arg); } else { static_assert(always_false_v<T>, "missing case"); } diff --git a/src/lua/include/bridge.hpp b/src/lua/include/bridge.hpp index 91153d67..62fbc340 100644 --- a/src/lua/include/bridge.hpp +++ b/src/lua/include/bridge.hpp @@ -26,7 +26,7 @@ class Bridge { const std::string&, std::vector< std::pair<std::string, - std::variant<LuaFunction, std::shared_ptr<Property>>>>) + std::variant<LuaFunction, Property*>>>) -> void; system_fsm::ServiceLocator& services() { return services_; } diff --git a/src/lua/lua_database.cpp b/src/lua/lua_database.cpp index 82b22343..ac7d711b 100644 --- a/src/lua/lua_database.cpp +++ b/src/lua/lua_database.cpp @@ -73,8 +73,38 @@ static auto indexes(lua_State* state) -> int { return 1; } -static const struct luaL_Reg kDatabaseFuncs[] = {{"indexes", indexes}, - {NULL, NULL}}; +static auto version(lua_State* L) -> int { + Bridge* instance = Bridge::Get(L); + auto db = instance->services().database().lock(); + if (!db) { + return 0; + } + auto res = db->schemaVersion(); + lua_pushlstring(L, res.data(), res.size()); + return 1; +} + +static auto size(lua_State* L) -> int { + Bridge* instance = Bridge::Get(L); + auto db = instance->services().database().lock(); + if (!db) { + return 0; + } + lua_pushinteger(L, db->sizeOnDiskBytes()); + return 1; +} + +static auto recreate(lua_State* L) -> int { + return 0; +} + +static auto update(lua_State* L) -> int { + return 0; +} + +static const struct luaL_Reg kDatabaseFuncs[] = { + {"indexes", indexes}, {"version", version}, {"size", size}, + {"recreate", recreate}, {"update", update}, {NULL, NULL}}; /* * Struct to be used as userdata for the Lua representation of database records. diff --git a/src/lua/lua_version.cpp b/src/lua/lua_version.cpp index ac72d3ae..c1098a1b 100644 --- a/src/lua/lua_version.cpp +++ b/src/lua/lua_version.cpp @@ -34,8 +34,17 @@ static auto samd(lua_State* L) -> int { return 1; } +static auto collator(lua_State* L) -> int { + Bridge* instance = Bridge::Get(L); + auto& collator = instance->services().collator(); + auto version = collator.Describe().value_or("None"); + lua_pushlstring(L, version.data(), version.size()); + return 1; +} + static const struct luaL_Reg kVersionFuncs[] = {{"esp", esp}, {"samd", samd}, + {"collator", collator}, {NULL, NULL}}; static auto lua_version(lua_State* L) -> int { diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 0d9de5a4..6d45fc9f 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -4,10 +4,8 @@ idf_component_register( SRCS "lvgl_task.cpp" "ui_fsm.cpp" "screen_splash.cpp" "encoder_input.cpp" - "themes.cpp" "widget_top_bar.cpp" "screen.cpp" "modal_progress.cpp" - "modal.cpp" "modal_confirm.cpp" "screen_settings.cpp" "event_binding.cpp" - "screen_lua.cpp" + "themes.cpp" "screen.cpp" "modal.cpp" "screen_lua.cpp" "splash.c" "font_fusion_12.c" "font_fusion_10.c" INCLUDE_DIRS "include" - REQUIRES "drivers" "lvgl" "tinyfsm" "events" "system_fsm" "database" "esp_timer" "battery" "bindey" "lua" "luavgl" "esp_app_format") + REQUIRES "drivers" "lvgl" "tinyfsm" "events" "system_fsm" "database" "esp_timer" "battery" "lua" "luavgl" "esp_app_format") target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) diff --git a/src/ui/event_binding.cpp b/src/ui/event_binding.cpp deleted file mode 100644 index ed15ccfb..00000000 --- a/src/ui/event_binding.cpp +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#include "event_binding.hpp" - -#include "core/lv_event.h" - -namespace ui { - -static auto event_cb(lv_event_t* ev) -> void { - EventBinding* binding = - static_cast<EventBinding*>(lv_event_get_user_data(ev)); - binding->signal()(lv_event_get_target(ev)); -} - -EventBinding::EventBinding(lv_obj_t* obj, lv_event_code_t ev) { - lv_obj_add_event_cb(obj, event_cb, ev, this); -} - -} // namespace ui diff --git a/src/ui/include/event_binding.hpp b/src/ui/include/event_binding.hpp deleted file mode 100644 index 19514db4..00000000 --- a/src/ui/include/event_binding.hpp +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include <cstdint> - -#include "lvgl.h" - -#include "core/lv_event.h" -#include "core/lv_obj.h" -#include "nod/nod.hpp" - -namespace ui { - -class EventBinding { - public: - EventBinding(lv_obj_t* obj, lv_event_code_t ev); - - auto signal() -> nod::signal<void(lv_obj_t*)>& { return signal_; } - - private: - lv_obj_t* obj_; - nod::signal<void(lv_obj_t*)> signal_; -}; - -} // namespace ui diff --git a/src/ui/include/modal.hpp b/src/ui/include/modal.hpp index 61e52cdf..6b7e792e 100644 --- a/src/ui/include/modal.hpp +++ b/src/ui/include/modal.hpp @@ -12,7 +12,6 @@ #include "core/lv_obj.h" #include "core/lv_obj_tree.h" #include "lvgl.h" -#include "widget_top_bar.hpp" #include "screen.hpp" @@ -30,15 +29,6 @@ class Modal { lv_obj_t* const root_; lv_group_t* const group_; - std::pmr::vector<std::unique_ptr<EventBinding>> event_bindings_; - - template <typename T> - auto lv_bind(lv_obj_t* obj, lv_event_code_t ev, T fn) -> void { - auto binding = std::make_unique<EventBinding>(obj, ev); - binding->signal().connect(fn); - event_bindings_.push_back(std::move(binding)); - } - private: Screen* host_; }; diff --git a/src/ui/include/modal_confirm.hpp b/src/ui/include/modal_confirm.hpp deleted file mode 100644 index 29d80041..00000000 --- a/src/ui/include/modal_confirm.hpp +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include <memory> -#include <vector> - -#include "index.hpp" -#include "lvgl.h" - -#include "modal.hpp" - -namespace ui { -namespace modals { - -class Confirm : public Modal { - public: - Confirm(Screen*, const std::pmr::string& title, bool has_cancel); - - private: - lv_obj_t* container_; -}; - -} // namespace modals -} // namespace ui diff --git a/src/ui/include/modal_progress.hpp b/src/ui/include/modal_progress.hpp deleted file mode 100644 index 2ccb671a..00000000 --- a/src/ui/include/modal_progress.hpp +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include <memory> -#include <vector> - -#include "index.hpp" -#include "lvgl.h" - -#include "modal.hpp" - -namespace ui { -namespace modals { - -class Progress : public Modal { - public: - Progress(Screen*, std::pmr::string title, std::pmr::string subtitle = ""); - - void title(const std::pmr::string&); - void subtitle(const std::pmr::string&); - - private: - lv_obj_t* container_; - lv_obj_t* title_; - lv_obj_t* subtitle_; -}; - -} // namespace modals -} // namespace ui diff --git a/src/ui/include/model_playback.hpp b/src/ui/include/model_playback.hpp deleted file mode 100644 index f932dcfd..00000000 --- a/src/ui/include/model_playback.hpp +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include "bindey/property.h" - -#include "track.hpp" - -namespace ui { -namespace models { - -struct Playback { - bindey::property<bool> is_playing; - bindey::property<std::optional<database::TrackId>> current_track; - bindey::property<std::vector<database::TrackId>> upcoming_tracks; - - bindey::property<uint32_t> current_track_position; - bindey::property<uint32_t> current_track_duration; -}; - -} // namespace models -} // namespace ui
\ No newline at end of file diff --git a/src/ui/include/model_top_bar.hpp b/src/ui/include/model_top_bar.hpp deleted file mode 100644 index c0f148f3..00000000 --- a/src/ui/include/model_top_bar.hpp +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include "battery.hpp" -#include "bindey/property.h" - -#include "track.hpp" - -namespace ui { -namespace models { - -struct TopBar { - bindey::property<battery::Battery::BatteryState> battery_state; - - // Shared with the Playback model - bindey::property<bool>& is_playing; - bindey::property<std::optional<database::TrackId>>& current_track; -}; - -} // namespace models -} // namespace ui diff --git a/src/ui/include/screen.hpp b/src/ui/include/screen.hpp index 4fe0a3b7..60939660 100644 --- a/src/ui/include/screen.hpp +++ b/src/ui/include/screen.hpp @@ -10,15 +10,10 @@ #include <optional> #include <vector> -#include "bindey/binding.h" #include "core/lv_group.h" #include "core/lv_obj.h" #include "core/lv_obj_tree.h" -#include "event_binding.hpp" #include "lvgl.h" -#include "model_top_bar.hpp" -#include "nod/nod.hpp" -#include "widget_top_bar.hpp" namespace ui { @@ -32,12 +27,6 @@ class Screen { Screen(); virtual ~Screen(); - /* - * Called periodically to allow the screen to update itself, e.g. to handle - * std::futures that are still loading in. - */ - virtual auto Tick() -> void {} - auto root() -> lv_obj_t* { return root_; } auto content() -> lv_obj_t* { return content_; } auto alert() -> lv_obj_t* { return alert_; } @@ -52,20 +41,6 @@ class Screen { } protected: - auto CreateTopBar(lv_obj_t* parent, - const widgets::TopBar::Configuration&, - models::TopBar& model) -> widgets::TopBar*; - - std::pmr::vector<bindey::scoped_binding> data_bindings_; - std::pmr::vector<std::unique_ptr<EventBinding>> event_bindings_; - - template <typename T> - auto lv_bind(lv_obj_t* obj, lv_event_code_t ev, T fn) -> void { - auto binding = std::make_unique<EventBinding>(obj, ev); - binding->signal().connect(fn); - event_bindings_.push_back(std::move(binding)); - } - lv_obj_t* const root_; lv_obj_t* content_; lv_obj_t* modal_content_; @@ -73,16 +48,6 @@ class Screen { lv_group_t* const group_; lv_group_t* modal_group_; - - private: - std::unique_ptr<widgets::TopBar> top_bar_; -}; - -class MenuScreen : public Screen { - public: - MenuScreen(models::TopBar&, - const std::pmr::string& title, - bool show_back_button = true); }; } // namespace ui diff --git a/src/ui/include/screen_settings.hpp b/src/ui/include/screen_settings.hpp deleted file mode 100644 index 7402f9f9..00000000 --- a/src/ui/include/screen_settings.hpp +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include <stdint.h> -#include <cstdint> -#include <list> -#include <memory> -#include <vector> - -#include "bluetooth.hpp" -#include "bluetooth_types.hpp" -#include "display.hpp" -#include "index.hpp" -#include "lvgl.h" - -#include "model_top_bar.hpp" -#include "nvs.hpp" -#include "samd.hpp" -#include "screen.hpp" - -namespace ui { -namespace screens { - -class Settings : public MenuScreen { - public: - Settings(models::TopBar&); -}; - -class Bluetooth : public MenuScreen { - public: - Bluetooth(models::TopBar&, drivers::Bluetooth& bt, drivers::NvsStorage& nvs); - ~Bluetooth(); - - auto ChangeEnabledState(bool enabled) -> void; - auto RefreshDevicesList() -> void; - auto OnDeviceSelected(ssize_t index) -> void; - - private: - auto RemoveAllDevices() -> void; - auto AddPreferredDevice(const drivers::bluetooth::Device&) -> void; - auto AddDevice(const drivers::bluetooth::Device&) -> void; - - drivers::Bluetooth& bt_; - drivers::NvsStorage& nvs_; - - lv_obj_t* devices_list_; - lv_obj_t* preferred_device_; - - std::list<drivers::bluetooth::mac_addr_t> macs_in_list_; -}; - -class Headphones : public MenuScreen { - public: - Headphones(models::TopBar&, drivers::NvsStorage& nvs); - - auto ChangeMaxVolume(uint8_t index) -> void; - auto ChangeCustomVolume(int8_t diff) -> void; - - private: - auto UpdateCustomVol(uint16_t) -> void; - - drivers::NvsStorage& nvs_; - lv_obj_t* custom_vol_container_; - lv_obj_t* custom_vol_label_; - - std::vector<uint16_t> index_to_level_; - uint16_t custom_limit_; -}; - -class Appearance : public MenuScreen { - public: - Appearance(models::TopBar&, - drivers::NvsStorage& nvs, - drivers::Display& display); - - auto ChangeBrightness(uint_fast8_t) -> void; - auto CommitBrightness() -> void; - - private: - drivers::NvsStorage& nvs_; - drivers::Display& display_; - - lv_obj_t* current_brightness_label_; - uint_fast8_t current_brightness_; -}; - -class InputMethod : public MenuScreen { - public: - InputMethod(models::TopBar&, drivers::NvsStorage& nvs); - - private: - drivers::NvsStorage& nvs_; -}; - -class Storage : public MenuScreen { - public: - Storage(models::TopBar&); -}; - -class FirmwareUpdate : public MenuScreen { - public: - FirmwareUpdate(models::TopBar&, drivers::Samd&); -}; - -class About : public MenuScreen { - public: - About(models::TopBar&); -}; - -} // namespace screens -} // namespace ui diff --git a/src/ui/include/ui_events.hpp b/src/ui/include/ui_events.hpp index 59be7606..07b18dea 100644 --- a/src/ui/include/ui_events.hpp +++ b/src/ui/include/ui_events.hpp @@ -35,21 +35,6 @@ struct ReindexDatabase : tinyfsm::Event {}; struct BackPressed : tinyfsm::Event {}; struct ShowNowPlaying : tinyfsm::Event {}; -struct ShowSettingsPage : tinyfsm::Event { - enum class Page { - kRoot, - kBluetooth, - kHeadphones, - kAppearance, - kInput, - kStorage, - kFirmwareUpdate, - kAbout, - } page; -}; -struct OnboardingNavigate : tinyfsm::Event { - bool forwards; -}; struct ModalConfirmPressed : tinyfsm::Event {}; struct ModalCancelPressed : tinyfsm::Event {}; diff --git a/src/ui/include/ui_fsm.hpp b/src/ui/include/ui_fsm.hpp index 33f9eac4..0b883ee0 100644 --- a/src/ui/include/ui_fsm.hpp +++ b/src/ui/include/ui_fsm.hpp @@ -12,26 +12,21 @@ #include "audio_events.hpp" #include "battery.hpp" -#include "bindey/property.h" #include "db_events.hpp" +#include "display.hpp" +#include "encoder_input.hpp" #include "gpios.hpp" #include "lua_thread.hpp" #include "lvgl_task.hpp" -#include "model_playback.hpp" -#include "model_top_bar.hpp" +#include "modal.hpp" #include "nvs.hpp" #include "property.hpp" #include "relative_wheel.hpp" -#include "screen_settings.hpp" -#include "service_locator.hpp" -#include "tinyfsm.hpp" - -#include "display.hpp" -#include "encoder_input.hpp" -#include "modal.hpp" #include "screen.hpp" +#include "service_locator.hpp" #include "storage.hpp" #include "system_events.hpp" +#include "tinyfsm.hpp" #include "touchwheel.hpp" #include "track.hpp" #include "track_queue.hpp" @@ -60,20 +55,21 @@ class UiState : public tinyfsm::Fsm<UiState> { virtual void react(const audio::PlaybackFinished&); virtual void react(const audio::PlaybackUpdate&); virtual void react(const audio::QueueUpdate&); - virtual void react(const audio::VolumeChanged&){}; + + virtual void react(const audio::VolumeChanged&); + virtual void react(const audio::VolumeBalanceChanged&); + virtual void react(const audio::VolumeLimitChanged&); virtual void react(const system_fsm::KeyLockChanged&); virtual void react(const OnLuaError&) {} virtual void react(const internal::BackPressed&) {} - virtual void react(const internal::ShowSettingsPage&){}; virtual void react(const internal::ModalCancelPressed&) { sCurrentModal.reset(); } virtual void react(const internal::ModalConfirmPressed&) { sCurrentModal.reset(); } - virtual void react(const internal::OnboardingNavigate&) {} void react(const internal::ControlSchemeChanged&); virtual void react(const internal::ReindexDatabase&){}; @@ -102,10 +98,29 @@ class UiState : public tinyfsm::Fsm<UiState> { static std::shared_ptr<Modal> sCurrentModal; static std::shared_ptr<lua::LuaThread> sLua; - static std::weak_ptr<screens::Bluetooth> bluetooth_screen_; + static lua::Property sBatteryPct; + static lua::Property sBatteryMv; + static lua::Property sBatteryCharging; - static models::Playback sPlaybackModel; - static models::TopBar sTopBarModel; + static lua::Property sBluetoothEnabled; + static lua::Property sBluetoothConnected; + + static lua::Property sPlaybackPlaying; + + static lua::Property sPlaybackTrack; + static lua::Property sPlaybackPosition; + + static lua::Property sQueuePosition; + static lua::Property sQueueSize; + static lua::Property sQueueRepeat; + static lua::Property sQueueRandom; + + static lua::Property sVolumeCurrentPct; + static lua::Property sVolumeCurrentDb; + static lua::Property sVolumeLeftBias; + static lua::Property sVolumeLimit; + + static lua::Property sDisplayBrightness; }; namespace states { @@ -113,8 +128,10 @@ namespace states { class Splash : public UiState { public: void exit() override; + void react(const system_fsm::BootComplete&) override; void react(const system_fsm::StorageMounted&) override; + using UiState::react; }; @@ -124,15 +141,6 @@ class Lua : public UiState { void exit() override; void react(const OnLuaError&) override; - - void react(const internal::ShowSettingsPage&) override; - - void react(const system_fsm::BatteryStateChanged&) override; - void react(const audio::QueueUpdate&) override; - void react(const audio::PlaybackStarted&) override; - void react(const audio::PlaybackUpdate&) override; - void react(const audio::PlaybackFinished&) override; - void react(const audio::VolumeChanged&) override; void react(const internal::BackPressed&) override; using UiState::react; @@ -147,54 +155,8 @@ class Lua : public UiState { auto SetPlaying(const lua::LuaValue&) -> bool; auto SetRandom(const lua::LuaValue&) -> bool; auto SetRepeat(const lua::LuaValue&) -> bool; - - std::shared_ptr<lua::Property> battery_pct_; - std::shared_ptr<lua::Property> battery_mv_; - std::shared_ptr<lua::Property> battery_charging_; - - std::shared_ptr<lua::Property> bluetooth_en_; - - std::shared_ptr<lua::Property> playback_playing_; - std::shared_ptr<lua::Property> playback_track_; - std::shared_ptr<lua::Property> playback_position_; - - std::shared_ptr<lua::Property> queue_position_; - std::shared_ptr<lua::Property> queue_size_; - std::shared_ptr<lua::Property> queue_repeat_; - std::shared_ptr<lua::Property> queue_random_; - - std::shared_ptr<lua::Property> volume_current_pct_; - std::shared_ptr<lua::Property> volume_current_db_; -}; - -class Browse : public UiState { - public: - void entry() override; - - void react(const internal::BackPressed&) override; - - void react(const internal::ShowSettingsPage&) override; - void react(const internal::ReindexDatabase&) override; - - void react(const system_fsm::BluetoothDevicesChanged&) override; - - using UiState::react; }; -class Indexing : public UiState { - public: - void entry() override; - void exit() override; - - void react(const database::event::UpdateStarted&) override; - void react(const database::event::UpdateProgress&) override; - void react(const database::event::UpdateFinished&) override; - - using UiState::react; -}; - -class FatalError : public UiState {}; - } // namespace states } // namespace ui diff --git a/src/ui/include/widget_top_bar.hpp b/src/ui/include/widget_top_bar.hpp deleted file mode 100644 index b240188c..00000000 --- a/src/ui/include/widget_top_bar.hpp +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include <cstdint> -#include <string> - -#include "bindey/binding.h" -#include "lvgl.h" - -#include "memory_resource.hpp" -#include "model_top_bar.hpp" - -namespace ui { - -namespace widgets { - -class TopBar { - public: - struct Configuration { - bool show_back_button; - std::pmr::string title; - }; - - explicit TopBar(lv_obj_t* parent, - const Configuration& config, - models::TopBar& model); - - auto root() -> lv_obj_t* { return container_; } - auto button() -> lv_obj_t* { return back_button_; } - - private: - std::vector<bindey::scoped_binding> bindings_; - - lv_obj_t* container_; - lv_obj_t* back_button_; -}; - -} // namespace widgets - -} // namespace ui diff --git a/src/ui/lvgl_task.cpp b/src/ui/lvgl_task.cpp index 96f9bdf5..f0184766 100644 --- a/src/ui/lvgl_task.cpp +++ b/src/ui/lvgl_task.cpp @@ -81,10 +81,6 @@ auto UiTask::Main() -> void { lv_group_set_focus_cb(current_group, &group_focus_cb); } - if (current_screen_) { - current_screen_->Tick(); - } - TickType_t delay = lv_timer_handler(); vTaskDelay(pdMS_TO_TICKS(std::clamp<TickType_t>(delay, 0, 100))); } diff --git a/src/ui/modal.cpp b/src/ui/modal.cpp index 3cab85f5..88f6d3ef 100644 --- a/src/ui/modal.cpp +++ b/src/ui/modal.cpp @@ -5,8 +5,9 @@ * SPDX-License-Identifier: GPL-3.0-only */ +#include "modal.hpp" + #include "misc/lv_color.h" -#include "modal_progress.hpp" #include "core/lv_event.h" #include "esp_log.h" @@ -24,7 +25,6 @@ #include "themes.hpp" #include "ui_events.hpp" #include "ui_fsm.hpp" -#include "widget_top_bar.hpp" #include "widgets/lv_label.h" namespace ui { diff --git a/src/ui/modal_confirm.cpp b/src/ui/modal_confirm.cpp deleted file mode 100644 index dfb1b1eb..00000000 --- a/src/ui/modal_confirm.cpp +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#include "modal_confirm.hpp" - -#include "core/lv_event.h" -#include "core/lv_obj.h" -#include "core/lv_obj_tree.h" -#include "esp_log.h" - -#include "core/lv_group.h" -#include "core/lv_obj_pos.h" -#include "event_queue.hpp" -#include "extra/widgets/list/lv_list.h" -#include "extra/widgets/menu/lv_menu.h" -#include "extra/widgets/spinner/lv_spinner.h" -#include "hal/lv_hal_disp.h" -#include "index.hpp" -#include "misc/lv_area.h" -#include "ui_events.hpp" -#include "ui_fsm.hpp" -#include "widget_top_bar.hpp" -#include "widgets/lv_btn.h" -#include "widgets/lv_label.h" - -namespace ui { -namespace modals { - -static void button_cancel_cb(lv_event_t* e) { - events::Ui().Dispatch(internal::ModalCancelPressed{}); -} - -static void button_confirm_cb(lv_event_t* e) { - events::Ui().Dispatch(internal::ModalConfirmPressed{}); -} - -Confirm::Confirm(Screen* host, - const std::pmr::string& title_text, - bool has_cancel) - : Modal(host) { - lv_obj_set_layout(root_, LV_LAYOUT_FLEX); - lv_obj_set_flex_flow(root_, LV_FLEX_FLOW_COLUMN); - lv_obj_set_flex_align(root_, LV_FLEX_ALIGN_SPACE_EVENLY, LV_FLEX_ALIGN_CENTER, - LV_FLEX_ALIGN_CENTER); - - lv_obj_t* title = lv_label_create(root_); - lv_label_set_text(title, title_text.c_str()); - lv_obj_set_size(title, LV_SIZE_CONTENT, LV_SIZE_CONTENT); - - lv_obj_t* button_container = lv_obj_create(root_); - lv_obj_set_size(button_container, lv_pct(100), LV_SIZE_CONTENT); - lv_obj_set_layout(button_container, LV_LAYOUT_FLEX); - lv_obj_set_flex_flow(button_container, LV_FLEX_FLOW_ROW); - lv_obj_set_flex_align(button_container, LV_FLEX_ALIGN_SPACE_EVENLY, - LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); - - if (has_cancel) { - lv_obj_t* cancel_btn = lv_btn_create(button_container); - lv_obj_t* cancel_label = lv_label_create(cancel_btn); - lv_label_set_text(cancel_label, "Cancel"); - lv_group_add_obj(group_, cancel_btn); - lv_obj_add_event_cb(cancel_btn, button_cancel_cb, LV_EVENT_CLICKED, NULL); - } - - lv_obj_t* ok_btn = lv_btn_create(button_container); - lv_obj_t* ok_label = lv_label_create(ok_btn); - lv_label_set_text(ok_label, "Okay"); - lv_group_add_obj(group_, ok_btn); - lv_obj_add_event_cb(ok_btn, button_confirm_cb, LV_EVENT_CLICKED, NULL); -} - -} // namespace modals -} // namespace ui diff --git a/src/ui/modal_progress.cpp b/src/ui/modal_progress.cpp deleted file mode 100644 index d3d3e9b9..00000000 --- a/src/ui/modal_progress.cpp +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#include "modal_progress.hpp" - -#include "core/lv_event.h" -#include "core/lv_obj.h" -#include "core/lv_obj_tree.h" -#include "esp_log.h" - -#include "core/lv_group.h" -#include "core/lv_obj_pos.h" -#include "event_queue.hpp" -#include "extra/widgets/list/lv_list.h" -#include "extra/widgets/menu/lv_menu.h" -#include "extra/widgets/spinner/lv_spinner.h" -#include "hal/lv_hal_disp.h" -#include "index.hpp" -#include "misc/lv_area.h" -#include "ui_events.hpp" -#include "ui_fsm.hpp" -#include "widget_top_bar.hpp" -#include "widgets/lv_label.h" - -namespace ui { -namespace modals { - -Progress::Progress(Screen* host, - std::pmr::string title_text, - std::pmr::string subtitle_text) - : Modal(host) { - lv_obj_set_layout(root_, LV_LAYOUT_FLEX); - lv_obj_set_flex_flow(root_, LV_FLEX_FLOW_COLUMN); - lv_obj_set_flex_align(root_, LV_FLEX_ALIGN_SPACE_EVENLY, LV_FLEX_ALIGN_CENTER, - LV_FLEX_ALIGN_CENTER); - - title_ = lv_label_create(root_); - lv_obj_set_size(title_, LV_SIZE_CONTENT, LV_SIZE_CONTENT); - - subtitle_ = lv_label_create(root_); - lv_obj_set_size(subtitle_, LV_SIZE_CONTENT, LV_SIZE_CONTENT); - - lv_obj_t* spinner = lv_spinner_create(root_, 3000, 45); - lv_obj_set_size(spinner, 16, 16); - - title(title_text); - subtitle(subtitle_text); -} - -void Progress::title(const std::pmr::string& s) { - lv_label_set_text(title_, s.c_str()); -} - -void Progress::subtitle(const std::pmr::string& s) { - if (s.empty()) { - lv_obj_add_flag(subtitle_, LV_OBJ_FLAG_HIDDEN); - } else { - lv_obj_clear_flag(subtitle_, LV_OBJ_FLAG_HIDDEN); - lv_label_set_text(subtitle_, s.c_str()); - } -} - -} // namespace modals -} // namespace ui diff --git a/src/ui/screen.cpp b/src/ui/screen.cpp index 9ac5ec0e..3e4f8e42 100644 --- a/src/ui/screen.cpp +++ b/src/ui/screen.cpp @@ -13,8 +13,6 @@ #include "hal/lv_hal_disp.h" #include "misc/lv_area.h" #include "misc/lv_color.h" -#include "model_top_bar.hpp" -#include "widget_top_bar.hpp" namespace ui { @@ -49,43 +47,4 @@ Screen::~Screen() { lv_obj_del(root_); } -auto Screen::CreateTopBar(lv_obj_t* parent, - const widgets::TopBar::Configuration& config, - models::TopBar& model) -> widgets::TopBar* { - assert(top_bar_ == nullptr); - top_bar_ = std::make_unique<widgets::TopBar>(parent, config, model); - if (top_bar_->button()) { - lv_group_add_obj(group_, top_bar_->button()); - } - return top_bar_.get(); -} - -MenuScreen::MenuScreen(models::TopBar& top_bar_model, - const std::pmr::string& title, - bool show_back_button) - : Screen() { - lv_group_set_wrap(group_, false); - - lv_obj_set_layout(content_, LV_LAYOUT_FLEX); - lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_COLUMN); - lv_obj_set_flex_align(content_, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, - LV_FLEX_ALIGN_CENTER); - - widgets::TopBar::Configuration config{ - .show_back_button = show_back_button, - .title = title.c_str(), - }; - CreateTopBar(content_, config, top_bar_model); - - content_ = lv_obj_create(content_); - lv_obj_set_flex_grow(content_, 1); - lv_obj_set_width(content_, lv_pct(100)); - lv_obj_set_layout(content_, LV_LAYOUT_FLEX); - lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_COLUMN); - lv_obj_set_flex_align(content_, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, - LV_FLEX_ALIGN_START); - - lv_obj_set_style_pad_all(content_, 4, LV_PART_MAIN); -} - } // namespace ui diff --git a/src/ui/screen_settings.cpp b/src/ui/screen_settings.cpp deleted file mode 100644 index 3f4c2c46..00000000 --- a/src/ui/screen_settings.cpp +++ /dev/null @@ -1,575 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#include "screen_settings.hpp" -#include <stdint.h> -#include <string> - -#include "audio_events.hpp" -#include "bluetooth.hpp" -#include "bluetooth_types.hpp" -#include "core/lv_event.h" -#include "core/lv_obj.h" -#include "core/lv_obj_tree.h" -#include "display.hpp" -#include "esp_app_desc.h" -#include "esp_log.h" - -#include "core/lv_group.h" -#include "core/lv_obj_pos.h" -#include "event_queue.hpp" -#include "extra/layouts/flex/lv_flex.h" -#include "extra/widgets/list/lv_list.h" -#include "extra/widgets/menu/lv_menu.h" -#include "extra/widgets/spinbox/lv_spinbox.h" -#include "extra/widgets/spinner/lv_spinner.h" -#include "hal/lv_hal_disp.h" -#include "index.hpp" -#include "lv_api_map.h" -#include "misc/lv_anim.h" -#include "misc/lv_area.h" -#include "model_top_bar.hpp" -#include "nvs.hpp" -#include "samd.hpp" -#include "screen.hpp" -#include "themes.hpp" -#include "ui_events.hpp" -#include "ui_fsm.hpp" -#include "widget_top_bar.hpp" -#include "widgets/lv_bar.h" -#include "widgets/lv_btn.h" -#include "widgets/lv_dropdown.h" -#include "widgets/lv_label.h" -#include "widgets/lv_slider.h" -#include "widgets/lv_switch.h" -#include "wm8523.hpp" - -namespace ui { -namespace screens { - -using Page = internal::ShowSettingsPage::Page; - -static void open_sub_menu_cb(lv_event_t* e) { - Page next_page = static_cast<Page>(reinterpret_cast<uintptr_t>(e->user_data)); - events::Ui().Dispatch(internal::ShowSettingsPage{ - .page = next_page, - }); -} - -static void sub_menu(lv_obj_t* list, - lv_group_t* group, - const std::pmr::string& text, - Page page) { - lv_obj_t* item = lv_list_add_btn(list, NULL, text.c_str()); - lv_group_add_obj(group, item); - lv_obj_add_event_cb(item, open_sub_menu_cb, LV_EVENT_CLICKED, - reinterpret_cast<void*>(static_cast<uintptr_t>(page))); -} - -Settings::Settings(models::TopBar& bar) : MenuScreen(bar, "Settings") { - lv_obj_t* list = lv_list_create(content_); - lv_obj_set_size(list, lv_pct(100), lv_pct(100)); - - themes::Theme::instance()->ApplyStyle(lv_list_add_text(list, "Audio"), - themes::Style::kMenuSubheadFirst); - sub_menu(list, group_, "Bluetooth", Page::kBluetooth); - sub_menu(list, group_, "Headphones", Page::kHeadphones); - - themes::Theme::instance()->ApplyStyle(lv_list_add_text(list, "Interface"), - themes::Style::kMenuSubhead); - sub_menu(list, group_, "Appearance", Page::kAppearance); - sub_menu(list, group_, "Input Method", Page::kInput); - - themes::Theme::instance()->ApplyStyle(lv_list_add_text(list, "System"), - themes::Style::kMenuSubhead); - sub_menu(list, group_, "Storage", Page::kStorage); - sub_menu(list, group_, "Firmware Update", Page::kFirmwareUpdate); - sub_menu(list, group_, "About", Page::kAbout); -} - -static auto settings_container(lv_obj_t* parent) -> lv_obj_t* { - lv_obj_t* res = lv_obj_create(parent); - lv_obj_set_layout(res, LV_LAYOUT_FLEX); - lv_obj_set_size(res, lv_pct(100), LV_SIZE_CONTENT); - lv_obj_set_flex_flow(res, LV_FLEX_FLOW_ROW); - lv_obj_set_flex_align(res, LV_FLEX_ALIGN_SPACE_EVENLY, LV_FLEX_ALIGN_CENTER, - LV_FLEX_ALIGN_START); - return res; -} - -static auto label_pair(lv_obj_t* parent, - const std::pmr::string& left, - const std::pmr::string& right) -> lv_obj_t* { - lv_obj_t* container = settings_container(parent); - lv_obj_t* left_label = lv_label_create(container); - lv_label_set_text(left_label, left.c_str()); - lv_obj_t* right_label = lv_label_create(container); - lv_label_set_text(right_label, right.c_str()); - return right_label; -} - -static auto toggle_bt_cb(lv_event_t* ev) { - Bluetooth* instance = reinterpret_cast<Bluetooth*>(ev->user_data); - instance->ChangeEnabledState(lv_obj_has_state(ev->target, LV_STATE_CHECKED)); -} - -static auto select_device_cb(lv_event_t* ev) { - Bluetooth* instance = reinterpret_cast<Bluetooth*>(ev->user_data); - instance->OnDeviceSelected(lv_obj_get_index(ev->target)); -} - -static auto remove_preferred_cb(lv_event_t* ev) { - Bluetooth* instance = reinterpret_cast<Bluetooth*>(ev->user_data); - instance->OnDeviceSelected(-1); -} - -Bluetooth::Bluetooth(models::TopBar& bar, - drivers::Bluetooth& bt, - drivers::NvsStorage& nvs) - : MenuScreen(bar, "Bluetooth"), bt_(bt), nvs_(nvs) { - lv_obj_t* toggle_container = settings_container(content_); - lv_obj_t* toggle_label = lv_label_create(toggle_container); - lv_label_set_text(toggle_label, "Enable"); - lv_obj_set_flex_grow(toggle_label, 1); - lv_obj_t* toggle = lv_switch_create(toggle_container); - lv_group_add_obj(group_, toggle); - - if (bt.IsEnabled()) { - lv_obj_add_state(toggle, LV_STATE_CHECKED); - } - - lv_obj_add_event_cb(toggle, toggle_bt_cb, LV_EVENT_VALUE_CHANGED, this); - - lv_obj_t* devices_label = lv_label_create(content_); - lv_label_set_text(devices_label, "Devices"); - - devices_list_ = lv_list_create(content_); - RefreshDevicesList(); - bt_.SetDeviceDiscovery(true); -} - -Bluetooth::~Bluetooth() { - bt_.SetDeviceDiscovery(false); -} - -auto Bluetooth::ChangeEnabledState(bool enabled) -> void { - if (enabled) { - events::System().RunOnTask([&]() { bt_.Enable(); }); - nvs_.OutputMode(drivers::NvsStorage::Output::kBluetooth); - } else { - events::System().RunOnTask([&]() { bt_.Disable(); }); - nvs_.OutputMode(drivers::NvsStorage::Output::kHeadphones); - } - events::Audio().Dispatch(audio::OutputModeChanged{}); - RefreshDevicesList(); -} - -auto Bluetooth::RefreshDevicesList() -> void { - if (!bt_.IsEnabled()) { - // Bluetooth is disabled, so we just clear the list. - RemoveAllDevices(); - return; - } - - auto devices = bt_.KnownDevices(); - std::optional<drivers::bluetooth::mac_addr_t> preferred_device = - nvs_.PreferredBluetoothDevice(); - - // If the user's current selection is within the devices list, then we need - // to be careful not to rearrange the list items underneath them. - lv_obj_t* current_selection = lv_group_get_focused(group_); - bool is_in_list = current_selection != NULL && - lv_obj_get_parent(current_selection) == devices_list_; - - if (!is_in_list) { - // The user isn't in the list! We can blow everything away and recreate it - // without issues. - RemoveAllDevices(); - - // First look to see if the user's preferred device is in the list. If it - // is, we hoist it up to the top of the list. - if (preferred_device) { - for (size_t i = 0; i < devices.size(); i++) { - if (devices[i].address == *preferred_device) { - AddPreferredDevice(devices[i]); - devices.erase(devices.begin() + i); - break; - } - } - } - - // The rest of the list is already sorted by signal strength. - for (const auto& device : devices) { - AddDevice(device); - } - } else { - // The user's selection is within the device list. We need to work out - // which devices are new, then add them to the end. - for (const auto& mac : macs_in_list_) { - auto pos = std::find_if( - devices.begin(), devices.end(), - [&mac](const auto& device) { return device.address == mac; }); - - if (pos != devices.end()) { - devices.erase(pos); - } - } - - // The remaining list is now just the new devices. - for (const auto& device : devices) { - if (preferred_device && device.address == *preferred_device) { - AddPreferredDevice(device); - } else { - AddDevice(device); - } - } - } -} - -auto Bluetooth::RemoveAllDevices() -> void { - while (lv_obj_get_child_cnt(devices_list_) > 0) { - lv_obj_del(lv_obj_get_child(devices_list_, 0)); - } - macs_in_list_.clear(); - preferred_device_ = nullptr; -} - -auto Bluetooth::AddPreferredDevice(const drivers::bluetooth::Device& dev) - -> void { - preferred_device_ = lv_list_add_btn(devices_list_, NULL, dev.name.c_str()); - lv_obj_t* remove = lv_btn_create(preferred_device_); - lv_obj_t* remove_icon = lv_label_create(remove); - lv_label_set_text(remove_icon, "x"); - lv_group_add_obj(group_, remove); - - macs_in_list_.push_back(dev.address); -} - -auto Bluetooth::AddDevice(const drivers::bluetooth::Device& dev) -> void { - lv_obj_t* item = lv_list_add_btn(devices_list_, NULL, dev.name.c_str()); - lv_group_add_obj(group_, item); - lv_obj_add_event_cb(item, select_device_cb, LV_EVENT_CLICKED, this); - macs_in_list_.push_back(dev.address); -} - -auto Bluetooth::OnDeviceSelected(ssize_t index) -> void { - if (index == -1) { - events::System().RunOnTask([=]() { - nvs_.PreferredBluetoothDevice({}); - bt_.SetPreferredDevice({}); - }); - RefreshDevicesList(); - return; - } - - // Tell the bluetooth driver that our preference changed. - auto it = macs_in_list_.begin(); - std::advance(it, index); - events::System().RunOnTask([=]() { bt_.SetPreferredDevice(*it); }); - - // Update which devices are selectable. - if (preferred_device_) { - lv_group_add_obj(group_, preferred_device_); - // Bubble the newly added object up to its visible position in the list. - size_t pos = lv_obj_get_index(preferred_device_); - while (pos > 0) { - lv_group_swap_obj(preferred_device_, - lv_obj_get_child(devices_list_, pos - 1)); - pos--; - } - } - - preferred_device_ = lv_obj_get_child(devices_list_, index); - lv_group_remove_obj(preferred_device_); -} - -static void change_vol_limit_cb(lv_event_t* ev) { - int selected_index = lv_dropdown_get_selected(ev->target); - Headphones* instance = reinterpret_cast<Headphones*>(ev->user_data); - instance->ChangeMaxVolume(selected_index); -} - -static void increase_vol_limit_cb(lv_event_t* ev) { - Headphones* instance = reinterpret_cast<Headphones*>(ev->user_data); - instance->ChangeCustomVolume(2); -} - -static void decrease_vol_limit_cb(lv_event_t* ev) { - Headphones* instance = reinterpret_cast<Headphones*>(ev->user_data); - instance->ChangeCustomVolume(-2); -} - -Headphones::Headphones(models::TopBar& bar, drivers::NvsStorage& nvs) - : MenuScreen(bar, "Headphones"), nvs_(nvs), custom_limit_(0) { - uint16_t reference = drivers::wm8523::kLineLevelReferenceVolume; - index_to_level_.push_back(reference - (10 * 4)); - index_to_level_.push_back(reference + (6 * 4)); - index_to_level_.push_back(reference + (9.5 * 4)); - - lv_obj_t* vol_label = lv_label_create(content_); - lv_label_set_text(vol_label, "Volume Limit"); - lv_obj_t* vol_dropdown = lv_dropdown_create(content_); - lv_obj_set_width(vol_dropdown, lv_pct(100)); - lv_dropdown_set_options( - vol_dropdown, - "Line Level (-10 dB)\nCD Level (+6 dB)\nMaximum (+10dB)\nCustom"); - lv_group_add_obj(group_, vol_dropdown); - themes::Theme::instance()->ApplyStyle(lv_dropdown_get_list(vol_dropdown), - themes::Style::kPopup); - - uint16_t level = nvs.AmpMaxVolume(); - for (int i = 0; i < index_to_level_.size() + 1; i++) { - if (i == index_to_level_.size() || index_to_level_[i] == level) { - lv_dropdown_set_selected(vol_dropdown, i); - break; - } - } - - lv_obj_add_event_cb(vol_dropdown, change_vol_limit_cb, LV_EVENT_VALUE_CHANGED, - this); - - custom_vol_container_ = settings_container(content_); - - lv_obj_t* decrease_btn = lv_btn_create(custom_vol_container_); - lv_obj_t* btn_label = lv_label_create(decrease_btn); - lv_label_set_text(btn_label, "-"); - lv_obj_add_event_cb(decrease_btn, decrease_vol_limit_cb, LV_EVENT_CLICKED, - this); - - custom_vol_label_ = lv_label_create(custom_vol_container_); - UpdateCustomVol(level); - - lv_obj_t* increase_btn = lv_btn_create(custom_vol_container_); - btn_label = lv_label_create(increase_btn); - lv_label_set_text(btn_label, "+"); - lv_obj_add_event_cb(increase_btn, increase_vol_limit_cb, LV_EVENT_CLICKED, - this); - - if (lv_dropdown_get_selected(vol_dropdown) != index_to_level_.size()) { - lv_obj_add_flag(custom_vol_container_, LV_OBJ_FLAG_HIDDEN); - } - - lv_obj_t* spacer = lv_obj_create(content_); - lv_obj_set_size(spacer, 1, 4); - - lv_obj_t* balance_label = lv_label_create(content_); - lv_label_set_text(balance_label, "Left/Right Balance"); - - spacer = lv_obj_create(content_); - lv_obj_set_size(spacer, 1, 4); - - lv_obj_t* balance = lv_slider_create(content_); - lv_obj_set_size(balance, lv_pct(100), 5); - lv_slider_set_range(balance, -10, 10); - lv_slider_set_value(balance, 0, LV_ANIM_OFF); - lv_slider_set_mode(balance, LV_SLIDER_MODE_SYMMETRICAL); - lv_group_add_obj(group_, balance); - lv_obj_t* current_balance_label = lv_label_create(content_); - lv_label_set_text(current_balance_label, "0dB"); - lv_obj_set_size(current_balance_label, lv_pct(100), LV_SIZE_CONTENT); - - lv_obj_move_foreground(lv_dropdown_get_list(vol_dropdown)); -} - -auto Headphones::ChangeMaxVolume(uint8_t index) -> void { - if (index >= index_to_level_.size()) { - lv_obj_clear_flag(custom_vol_container_, LV_OBJ_FLAG_HIDDEN); - return; - } - auto vol = index_to_level_[index]; - lv_obj_add_flag(custom_vol_container_, LV_OBJ_FLAG_HIDDEN); - UpdateCustomVol(vol); - events::Audio().Dispatch(audio::ChangeMaxVolume{.new_max = vol}); -} - -auto Headphones::ChangeCustomVolume(int8_t diff) -> void { - UpdateCustomVol(custom_limit_ + diff); -} - -auto Headphones::UpdateCustomVol(uint16_t level) -> void { - custom_limit_ = level; - int16_t db = (static_cast<int32_t>(level) - - drivers::wm8523::kLineLevelReferenceVolume) / - 4; - int16_t db_parts = (static_cast<int32_t>(level) - - drivers::wm8523::kLineLevelReferenceVolume) % - 4; - - std::ostringstream builder; - if (db >= 0) { - builder << "+"; - } - builder << db << "."; - builder << (db_parts * 100 / 4); - builder << " dBV"; - - lv_label_set_text(custom_vol_label_, builder.str().c_str()); -} - -static void change_brightness_cb(lv_event_t* ev) { - Appearance* instance = reinterpret_cast<Appearance*>(ev->user_data); - instance->ChangeBrightness(lv_slider_get_value(ev->target)); -} - -static void release_brightness_cb(lv_event_t* ev) { - Appearance* instance = reinterpret_cast<Appearance*>(ev->user_data); - instance->CommitBrightness(); -} - -static auto brightness_str(uint_fast8_t percent) -> std::string { - return std::to_string(percent) + "%"; -} - -Appearance::Appearance(models::TopBar& bar, - drivers::NvsStorage& nvs, - drivers::Display& display) - : MenuScreen(bar, "Appearance"), nvs_(nvs), display_(display) { - lv_obj_t* toggle_container = settings_container(content_); - lv_obj_t* toggle_label = lv_label_create(toggle_container); - lv_obj_set_flex_grow(toggle_label, 1); - lv_label_set_text(toggle_label, "Dark Mode"); - lv_obj_t* toggle = lv_switch_create(toggle_container); - lv_group_add_obj(group_, toggle); - - uint_fast8_t initial_brightness = nvs_.ScreenBrightness(); - - lv_obj_t* brightness_label = lv_label_create(content_); - lv_label_set_text(brightness_label, "Brightness"); - lv_obj_t* brightness = lv_slider_create(content_); - lv_obj_set_size(brightness, lv_pct(100), 5); - lv_slider_set_range(brightness, 10, 100); - lv_slider_set_value(brightness, initial_brightness, LV_ANIM_OFF); - lv_group_add_obj(group_, brightness); - current_brightness_label_ = lv_label_create(content_); - lv_label_set_text(current_brightness_label_, - brightness_str(initial_brightness).c_str()); - lv_obj_set_size(current_brightness_label_, lv_pct(100), LV_SIZE_CONTENT); - - lv_obj_add_event_cb(brightness, change_brightness_cb, LV_EVENT_VALUE_CHANGED, - this); - lv_obj_add_event_cb(brightness, release_brightness_cb, LV_EVENT_RELEASED, - this); -} - -auto Appearance::ChangeBrightness(uint_fast8_t new_level) -> void { - current_brightness_ = new_level; - display_.SetBrightness(new_level); - lv_label_set_text(current_brightness_label_, - brightness_str(new_level).c_str()); -} - -auto Appearance::CommitBrightness() -> void { - nvs_.ScreenBrightness(current_brightness_); -} - -InputMethod::InputMethod(models::TopBar& bar, drivers::NvsStorage& nvs) - : MenuScreen(bar, "Input Method"), nvs_(nvs) { - lv_obj_t* primary_label = lv_label_create(content_); - lv_label_set_text(primary_label, "Control scheme"); - lv_obj_t* primary_dropdown = lv_dropdown_create(content_); - lv_dropdown_set_options( - primary_dropdown, - "Side buttons only\nButtons and touch\nD-Pad\nClickwheel"); - lv_group_add_obj(group_, primary_dropdown); - - lv_dropdown_set_selected(primary_dropdown, - static_cast<uint16_t>(nvs.PrimaryInput())); - themes::Theme::instance()->ApplyStyle(lv_dropdown_get_list(primary_dropdown), - themes::Style::kPopup); - - lv_bind(primary_dropdown, LV_EVENT_VALUE_CHANGED, [this](lv_obj_t* obj) { - drivers::NvsStorage::InputModes mode; - switch (lv_dropdown_get_selected(obj)) { - case 0: - mode = drivers::NvsStorage::InputModes::kButtonsOnly; - break; - case 1: - mode = drivers::NvsStorage::InputModes::kButtonsWithWheel; - break; - case 2: - mode = drivers::NvsStorage::InputModes::kDirectionalWheel; - break; - case 3: - mode = drivers::NvsStorage::InputModes::kRotatingWheel; - break; - default: - return; - } - nvs_.PrimaryInput(mode); - events::Ui().Dispatch(internal::ControlSchemeChanged{}); - }); -} - -Storage::Storage(models::TopBar& bar) : MenuScreen(bar, "Storage") { - label_pair(content_, "Storage Capacity:", "32 GiB"); - label_pair(content_, "Currently Used:", "6 GiB"); - label_pair(content_, "DB Size:", "1.2 MiB"); - - lv_obj_t* usage_bar = lv_bar_create(content_); - lv_bar_set_range(usage_bar, 0, 32); - lv_bar_set_value(usage_bar, 6, LV_ANIM_OFF); - - lv_obj_t* container = lv_obj_create(content_); - lv_obj_set_size(container, lv_pct(100), 30); - lv_obj_set_layout(container, LV_LAYOUT_FLEX); - lv_obj_set_flex_flow(container, LV_FLEX_FLOW_ROW); - lv_obj_set_flex_align(container, LV_FLEX_ALIGN_SPACE_EVENLY, - LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); - - lv_obj_t* reset_btn = lv_btn_create(container); - lv_obj_t* reset_label = lv_label_create(reset_btn); - lv_label_set_text(reset_label, "Update Database"); - lv_group_add_obj(group_, reset_btn); - themes::Theme::instance()->ApplyStyle(reset_btn, - themes::Style::kButtonPrimary); - - lv_bind(reset_btn, LV_EVENT_CLICKED, [&](lv_obj_t*) { - events::Ui().Dispatch(internal::ReindexDatabase{}); - }); -} - -FirmwareUpdate::FirmwareUpdate(models::TopBar& bar, drivers::Samd& samd) - : MenuScreen(bar, "Firmware Update") { - lv_obj_set_flex_align(content_, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, - LV_FLEX_ALIGN_CENTER); - - auto samd_ver = samd.Version(); - label_pair(content_, "SAMD21 FW:", {samd_ver.data(), samd_ver.size()}); - - lv_obj_t* spacer = lv_obj_create(content_); - lv_obj_set_size(spacer, 1, 4); - - lv_obj_t* flash_esp_btn = lv_btn_create(content_); - lv_obj_t* flash_esp_label = lv_label_create(flash_esp_btn); - lv_label_set_text(flash_esp_label, "Update"); - lv_group_add_obj(group_, flash_esp_btn); - themes::Theme::instance()->ApplyStyle(flash_esp_btn, - themes::Style::kButtonPrimary); - - spacer = lv_obj_create(content_); - lv_obj_set_size(spacer, 1, 8); - - auto desc = esp_app_get_description(); - label_pair(content_, "ESP32 FW:", desc->version); - - spacer = lv_obj_create(content_); - lv_obj_set_size(spacer, 1, 4); - - lv_obj_t* flash_samd_btn = lv_btn_create(content_); - lv_obj_t* flash_samd_label = lv_label_create(flash_samd_btn); - lv_label_set_text(flash_samd_label, "Update"); - lv_group_add_obj(group_, flash_samd_btn); - themes::Theme::instance()->ApplyStyle(flash_samd_btn, - themes::Style::kButtonPrimary); -} - -About::About(models::TopBar& bar) : MenuScreen(bar, "About") { - lv_obj_t* label = lv_label_create(content_); - lv_label_set_text(label, "Some licenses or whatever"); -} - -} // namespace screens -} // namespace ui diff --git a/src/ui/ui_fsm.cpp b/src/ui/ui_fsm.cpp index 75327a58..adda1c18 100644 --- a/src/ui/ui_fsm.cpp +++ b/src/ui/ui_fsm.cpp @@ -33,15 +33,11 @@ #include "event_queue.hpp" #include "gpios.hpp" #include "lvgl_task.hpp" -#include "modal_confirm.hpp" -#include "modal_progress.hpp" -#include "model_playback.hpp" #include "nvs.hpp" #include "property.hpp" #include "relative_wheel.hpp" #include "screen.hpp" #include "screen_lua.hpp" -#include "screen_settings.hpp" #include "screen_splash.hpp" #include "spiffs.hpp" #include "storage.hpp" @@ -66,13 +62,6 @@ std::shared_ptr<Screen> UiState::sCurrentScreen; std::shared_ptr<Modal> UiState::sCurrentModal; std::shared_ptr<lua::LuaThread> UiState::sLua; -std::weak_ptr<screens::Bluetooth> UiState::bluetooth_screen_; - -models::Playback UiState::sPlaybackModel; -models::TopBar UiState::sTopBarModel{{}, - UiState::sPlaybackModel.is_playing, - UiState::sPlaybackModel.current_track}; - static TimerHandle_t sAlertTimer; static lv_obj_t* sAlertContainer; @@ -80,6 +69,70 @@ static void alert_timer_callback(TimerHandle_t timer) { events::Ui().Dispatch(internal::DismissAlerts{}); } +lua::Property UiState::sBatteryPct{0}; +lua::Property UiState::sBatteryMv{0}; +lua::Property UiState::sBatteryCharging{false}; + +lua::Property UiState::sBluetoothEnabled{false}; +lua::Property UiState::sBluetoothConnected{false}; + +lua::Property UiState::sPlaybackPlaying{ + false, [](const lua::LuaValue& val) { + bool current_val = std::get<bool>(sPlaybackPlaying.Get()); + if (!std::holds_alternative<bool>(val)) { + return false; + } + bool new_val = std::get<bool>(val); + if (current_val != new_val) { + events::Audio().Dispatch(audio::TogglePlayPause{}); + } + return true; + }}; + +lua::Property UiState::sPlaybackTrack{0}; +lua::Property UiState::sPlaybackPosition{0}; + +lua::Property UiState::sQueuePosition{0}; +lua::Property UiState::sQueueSize{0}; +lua::Property UiState::sQueueRepeat{false}; +lua::Property UiState::sQueueRandom{false}; + +lua::Property UiState::sVolumeCurrentPct{ + 0, [](const lua::LuaValue& val) { + events::Audio().Dispatch(audio::SetVolume{ + .percent = {}, + .db = 0, + }); + return false; + }}; +lua::Property UiState::sVolumeCurrentDb{ + 0, [](const lua::LuaValue& val) { + events::Audio().Dispatch(audio::SetVolume{ + .percent = {}, + .db = 0, + }); + return false; + }}; +lua::Property UiState::sVolumeLeftBias{ + 0, [](const lua::LuaValue& val) { + events::Audio().Dispatch(audio::SetVolumeBalance{ + .left_bias = 0, + }); + return false; + }}; +lua::Property UiState::sVolumeLimit{ + 0, [](const lua::LuaValue& val) { + events::Audio().Dispatch(audio::SetVolumeLimit{ + .new_limit = 0, + }); + return false; + }}; + +lua::Property UiState::sDisplayBrightness{0, [](const lua::LuaValue& val) { + sDisplay->SetBrightness(0); + return false; + }}; + auto UiState::InitBootSplash(drivers::IGpios& gpios) -> bool { // Init LVGL first, since the display driver registers itself with LVGL. lv_init(); @@ -118,30 +171,59 @@ void UiState::react(const system_fsm::KeyLockChanged& ev) { sInput->lock(ev.locking); } -void UiState::react(const system_fsm::BatteryStateChanged& ev) { - sTopBarModel.battery_state.set(ev.new_state); +void UiState::react(const internal::ControlSchemeChanged&) { + if (!sInput) { + return; + } + sInput->mode(sServices->nvs().PrimaryInput()); } -void UiState::react(const audio::PlaybackStarted&) {} - -void UiState::react(const audio::PlaybackFinished&) {} +void UiState::react(const internal::DismissAlerts&) { + lv_obj_clean(sAlertContainer); +} -void UiState::react(const audio::PlaybackUpdate& ev) {} +void UiState::react(const system_fsm::BatteryStateChanged& ev) { + sBatteryPct.Update(static_cast<int>(ev.new_state.percent)); + sBatteryMv.Update(static_cast<int>(ev.new_state.millivolts)); +} void UiState::react(const audio::QueueUpdate&) { auto& queue = sServices->track_queue(); - sPlaybackModel.current_track.set(queue.current()); -} + sQueueSize.Update(static_cast<int>(queue.totalSize())); -void UiState::react(const internal::ControlSchemeChanged&) { - if (!sInput) { - return; + int current_pos = queue.currentPosition(); + if (queue.current()) { + current_pos++; } - sInput->mode(sServices->nvs().PrimaryInput()); + sQueuePosition.Update(current_pos); + sQueueRandom.Update(queue.random()); + sQueueRepeat.Update(queue.repeat()); } -void UiState::react(const internal::DismissAlerts&) { - lv_obj_clean(sAlertContainer); +void UiState::react(const audio::PlaybackStarted& ev) { + sPlaybackPlaying.Update(true); +} + +void UiState::react(const audio::PlaybackUpdate& ev) { + sPlaybackTrack.Update(*ev.track); + sPlaybackPosition.Update(static_cast<int>(ev.seconds_elapsed)); +} + +void UiState::react(const audio::PlaybackFinished&) { + sPlaybackPlaying.Update(false); +} + +void UiState::react(const audio::VolumeChanged& ev) { + sVolumeCurrentPct.Update(static_cast<int>(ev.percent)); + sVolumeCurrentDb.Update(static_cast<int>(ev.db)); +} + +void UiState::react(const audio::VolumeBalanceChanged& ev) { + sVolumeLeftBias.Update(ev.left_bias); +} + +void UiState::react(const audio::VolumeLimitChanged& ev) { + sVolumeLeftBias.Update(ev.new_limit); } namespace states { @@ -185,57 +267,36 @@ void Lua::entry() { alert_timer_callback); sAlertContainer = lv_obj_create(sCurrentScreen->alert()); - auto bat = - sServices->battery().State().value_or(battery::Battery::BatteryState{}); - battery_pct_ = - std::make_shared<lua::Property>(static_cast<int>(bat.percent)); - battery_mv_ = - std::make_shared<lua::Property>(static_cast<int>(bat.millivolts)); - battery_charging_ = std::make_shared<lua::Property>(bat.is_charging); - - bluetooth_en_ = std::make_shared<lua::Property>(false); - - queue_position_ = std::make_shared<lua::Property>(0); - queue_size_ = std::make_shared<lua::Property>(0); - queue_repeat_ = std::make_shared<lua::Property>(false); - queue_random_ = std::make_shared<lua::Property>(false); - - playback_playing_ = std::make_shared<lua::Property>( - false, [&](const lua::LuaValue& val) { return SetPlaying(val); }); - playback_track_ = std::make_shared<lua::Property>(); - playback_position_ = std::make_shared<lua::Property>(); - - volume_current_pct_ = std::make_shared<lua::Property>(0); - volume_current_db_ = std::make_shared<lua::Property>(0); - sLua.reset(lua::LuaThread::Start(*sServices, sCurrentScreen->content())); sLua->bridge().AddPropertyModule("power", { - {"battery_pct", battery_pct_}, - {"battery_millivolts", battery_mv_}, - {"plugged_in", battery_charging_}, + {"battery_pct", &sBatteryPct}, + {"battery_millivolts", &sBatteryMv}, + {"plugged_in", &sBatteryCharging}, }); sLua->bridge().AddPropertyModule("bluetooth", { - {"enabled", bluetooth_en_}, - {"connected", bluetooth_en_}, + {"enabled", &sBluetoothEnabled}, + {"connected", &sBluetoothConnected}, }); sLua->bridge().AddPropertyModule("playback", { - {"playing", playback_playing_}, - {"track", playback_track_}, - {"position", playback_position_}, + {"playing", &sPlaybackPlaying}, + {"track", &sPlaybackTrack}, + {"position", &sPlaybackPosition}, }); sLua->bridge().AddPropertyModule("queue", { - {"position", queue_position_}, - {"size", queue_size_}, - {"replay", queue_repeat_}, - {"random", queue_random_}, + {"position", &sQueuePosition}, + {"size", &sQueueSize}, + {"replay", &sQueueRepeat}, + {"random", &sQueueRandom}, }); sLua->bridge().AddPropertyModule("volume", { - {"current_pct", volume_current_pct_}, - {"current_db", volume_current_db_}, + {"current_pct", &sVolumeCurrentPct}, + {"current_db", &sVolumeCurrentDb}, + {"left_bias", &sVolumeLeftBias}, + {"limit_db", &sVolumeLimit}, }); sLua->bridge().AddPropertyModule( @@ -288,18 +349,6 @@ auto Lua::PopLuaScreen(lua_State* s) -> int { return 0; } -auto Lua::SetPlaying(const lua::LuaValue& val) -> bool { - bool current_val = std::get<bool>(playback_playing_->Get()); - if (!std::holds_alternative<bool>(val)) { - return false; - } - bool new_val = std::get<bool>(val); - if (current_val != new_val) { - events::Audio().Dispatch(audio::TogglePlayPause{}); - } - return true; -} - auto Lua::ShowAlert(lua_State* s) -> int { if (!sCurrentScreen) { return 0; @@ -358,150 +407,10 @@ void Lua::react(const OnLuaError& err) { ESP_LOGE("lua", "%s", err.message.c_str()); } -void Lua::react(const internal::ShowSettingsPage& ev) { - PushScreen(std::shared_ptr<Screen>(new screens::Settings(sTopBarModel))); - transit<Browse>(); -} - -void Lua::react(const system_fsm::BatteryStateChanged& ev) { - battery_pct_->Update(static_cast<int>(ev.new_state.percent)); - battery_mv_->Update(static_cast<int>(ev.new_state.millivolts)); -} - -void Lua::react(const audio::QueueUpdate&) { - auto& queue = sServices->track_queue(); - queue_size_->Update(static_cast<int>(queue.totalSize())); - - int current_pos = queue.currentPosition(); - if (queue.current()) { - current_pos++; - } - queue_position_->Update(current_pos); - queue_random_->Update(queue.random()); - queue_repeat_->Update(queue.repeat()); -} - -void Lua::react(const audio::PlaybackStarted& ev) { - playback_playing_->Update(true); -} - -void Lua::react(const audio::PlaybackUpdate& ev) { - playback_track_->Update(*ev.track); - playback_position_->Update(static_cast<int>(ev.seconds_elapsed)); -} - -void Lua::react(const audio::PlaybackFinished&) { - playback_playing_->Update(false); -} - -void Lua::react(const audio::VolumeChanged& ev) { - volume_current_pct_->Update(static_cast<int>(ev.percent)); - volume_current_db_->Update(static_cast<int>(ev.db)); -} - void Lua::react(const internal::BackPressed& ev) { PopLuaScreen(sLua->state()); } -void Browse::entry() {} - -void Browse::react(const internal::ShowSettingsPage& ev) { - std::shared_ptr<Screen> screen; - std::shared_ptr<screens::Bluetooth> bt_screen; - switch (ev.page) { - case internal::ShowSettingsPage::Page::kRoot: - screen.reset(new screens::Settings(sTopBarModel)); - break; - case internal::ShowSettingsPage::Page::kBluetooth: - bt_screen = std::make_shared<screens::Bluetooth>( - sTopBarModel, sServices->bluetooth(), sServices->nvs()); - screen = bt_screen; - bluetooth_screen_ = bt_screen; - break; - case internal::ShowSettingsPage::Page::kHeadphones: - screen.reset(new screens::Headphones(sTopBarModel, sServices->nvs())); - break; - case internal::ShowSettingsPage::Page::kAppearance: - screen.reset( - new screens::Appearance(sTopBarModel, sServices->nvs(), *sDisplay)); - break; - case internal::ShowSettingsPage::Page::kInput: - screen.reset(new screens::InputMethod(sTopBarModel, sServices->nvs())); - break; - case internal::ShowSettingsPage::Page::kStorage: - screen.reset(new screens::Storage(sTopBarModel)); - break; - case internal::ShowSettingsPage::Page::kFirmwareUpdate: - screen.reset( - new screens::FirmwareUpdate(sTopBarModel, sServices->samd())); - break; - case internal::ShowSettingsPage::Page::kAbout: - screen.reset(new screens::About(sTopBarModel)); - break; - } - if (screen) { - PushScreen(screen); - } -} - -void Browse::react(const internal::BackPressed& ev) { - if (PopScreen() == 0) { - transit<Lua>(); - } -} - -void Browse::react(const system_fsm::BluetoothDevicesChanged&) { - auto bt = bluetooth_screen_.lock(); - if (bt) { - bt->RefreshDevicesList(); - } -} - -void Browse::react(const internal::ReindexDatabase& ev) { - transit<Indexing>(); -} - -static std::shared_ptr<modals::Progress> sIndexProgress; - -void Indexing::entry() { - sIndexProgress.reset(new modals::Progress(sCurrentScreen.get(), "Indexing", - "Preparing database")); - sCurrentModal = sIndexProgress; - auto db = sServices->database().lock(); - if (!db) { - // TODO: Hmm. - return; - } - sServices->bg_worker().Dispatch<void>([=]() { db->updateIndexes(); }); -} - -void Indexing::exit() { - sCurrentModal.reset(); - sIndexProgress.reset(); -} - -void Indexing::react(const database::event::UpdateStarted&) {} - -void Indexing::react(const database::event::UpdateProgress& ev) { - std::ostringstream str; - switch (ev.stage) { - case database::event::UpdateProgress::Stage::kVerifyingExistingTracks: - sIndexProgress->title("Verifying"); - str << "Tracks checked: " << ev.val; - sIndexProgress->subtitle(str.str().c_str()); - break; - case database::event::UpdateProgress::Stage::kScanningForNewTracks: - sIndexProgress->title("Scanning"); - str << "Files checked: " << ev.val; - sIndexProgress->subtitle(str.str().c_str()); - break; - } -} - -void Indexing::react(const database::event::UpdateFinished&) { - transit<Browse>(); -} - } // namespace states } // namespace ui diff --git a/src/ui/widget_top_bar.cpp b/src/ui/widget_top_bar.cpp deleted file mode 100644 index fbad5548..00000000 --- a/src/ui/widget_top_bar.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#include "widget_top_bar.hpp" -#include "battery.hpp" -#include "core/lv_group.h" -#include "core/lv_obj.h" -#include "event_queue.hpp" -#include "extra/layouts/flex/lv_flex.h" -#include "font/lv_symbol_def.h" -#include "model_top_bar.hpp" -#include "themes.hpp" -#include "track.hpp" -#include "ui_events.hpp" -#include "ui_fsm.hpp" -#include "widgets/lv_img.h" -#include "widgets/lv_label.h" - -namespace ui { -namespace widgets { - -static void back_click_cb(lv_event_t* ev) { - events::Ui().Dispatch(internal::BackPressed{}); -} - -TopBar::TopBar(lv_obj_t* parent, - const Configuration& config, - models::TopBar& model) { - container_ = lv_obj_create(parent); - lv_obj_set_size(container_, lv_pct(100), 20); - lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_ROW); - lv_obj_set_flex_align(container_, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, - LV_FLEX_ALIGN_END); - lv_obj_set_style_pad_column(container_, 5, LV_PART_MAIN); - - if (config.show_back_button) { - back_button_ = lv_btn_create(container_); - lv_obj_set_size(back_button_, LV_SIZE_CONTENT, 12); - lv_obj_t* button_icon = lv_label_create(back_button_); - lv_label_set_text(button_icon, "<"); - lv_obj_add_event_cb(back_button_, back_click_cb, LV_EVENT_CLICKED, NULL); - lv_obj_center(button_icon); - } else { - back_button_ = nullptr; - } - - lv_obj_t* title_ = lv_label_create(container_); - lv_obj_set_height(title_, 17); - lv_obj_set_flex_grow(title_, 1); - - lv_label_set_text(title_, config.title.c_str()); - lv_label_set_long_mode(title_, LV_LABEL_LONG_DOT); - - themes::Theme::instance()->ApplyStyle(container_, themes::Style::kTopBar); -} - -} // namespace widgets -} // namespace ui |
