summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/CMakeLists.txt3
-rw-r--r--src/ui/event_binding.cpp23
-rw-r--r--src/ui/include/event_binding.hpp30
-rw-r--r--src/ui/include/model_playback.hpp26
-rw-r--r--src/ui/include/screen.hpp14
-rw-r--r--src/ui/include/screen_playing.hpp33
-rw-r--r--src/ui/include/ui_fsm.hpp23
-rw-r--r--src/ui/screen_playing.cpp214
-rw-r--r--src/ui/screen_track_browser.cpp4
-rw-r--r--src/ui/ui_fsm.cpp63
10 files changed, 264 insertions, 169 deletions
diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt
index 906e9e1f..e331d96f 100644
--- a/src/ui/CMakeLists.txt
+++ b/src/ui/CMakeLists.txt
@@ -7,10 +7,11 @@ idf_component_register(
"wheel_encoder.cpp" "screen_track_browser.cpp" "screen_playing.cpp"
"themes.cpp" "widget_top_bar.cpp" "screen.cpp" "screen_onboarding.cpp"
"modal_progress.cpp" "modal.cpp" "modal_confirm.cpp" "screen_settings.cpp"
+ "event_binding.cpp"
"splash.c" "font_fusion.c" "font_symbols.c"
"icons/battery_empty.c" "icons/battery_full.c" "icons/battery_20.c"
"icons/battery_40.c" "icons/battery_60.c" "icons/battery_80.c" "icons/play.c"
"icons/pause.c" "icons/bluetooth.c"
INCLUDE_DIRS "include"
- REQUIRES "drivers" "lvgl" "tinyfsm" "events" "system_fsm" "database" "esp_timer" "battery")
+ REQUIRES "drivers" "lvgl" "tinyfsm" "events" "system_fsm" "database" "esp_timer" "battery" "bindey")
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS})
diff --git a/src/ui/event_binding.cpp b/src/ui/event_binding.cpp
new file mode 100644
index 00000000..ed15ccfb
--- /dev/null
+++ b/src/ui/event_binding.cpp
@@ -0,0 +1,23 @@
+/*
+ * 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
new file mode 100644
index 00000000..19514db4
--- /dev/null
+++ b/src/ui/include/event_binding.hpp
@@ -0,0 +1,30 @@
+/*
+ * 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/model_playback.hpp b/src/ui/include/model_playback.hpp
new file mode 100644
index 00000000..f932dcfd
--- /dev/null
+++ b/src/ui/include/model_playback.hpp
@@ -0,0 +1,26 @@
+/*
+ * 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/screen.hpp b/src/ui/include/screen.hpp
index 76251a72..ac7b19f8 100644
--- a/src/ui/include/screen.hpp
+++ b/src/ui/include/screen.hpp
@@ -8,11 +8,15 @@
#include <memory>
#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 "nod/nod.hpp"
#include "widget_top_bar.hpp"
namespace ui {
@@ -51,6 +55,16 @@ class Screen {
auto CreateTopBar(lv_obj_t* parent, const widgets::TopBar::Configuration&)
-> 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_;
diff --git a/src/ui/include/screen_playing.hpp b/src/ui/include/screen_playing.hpp
index 2e29130c..fff9cc35 100644
--- a/src/ui/include/screen_playing.hpp
+++ b/src/ui/include/screen_playing.hpp
@@ -11,10 +11,13 @@
#include <memory>
#include <vector>
+#include "bindey/property.h"
+#include "esp_log.h"
#include "lvgl.h"
#include "database.hpp"
#include "future_fetcher.hpp"
+#include "model_playback.hpp"
#include "screen.hpp"
#include "track.hpp"
#include "track_queue.hpp"
@@ -28,48 +31,36 @@ namespace screens {
*/
class Playing : public Screen {
public:
- explicit Playing(std::weak_ptr<database::Database> db,
+ explicit Playing(models::Playback& playback_model,
+ std::weak_ptr<database::Database> db,
audio::TrackQueue& queue);
~Playing();
auto Tick() -> void override;
- // Callbacks invoked by the UI state machine in response to audio events.
-
- auto OnTrackUpdate() -> void;
- auto OnPlaybackUpdate(uint32_t, uint32_t) -> void;
- auto OnQueueUpdate() -> void;
-
auto OnFocusAboveFold() -> void;
auto OnFocusBelowFold() -> void;
+ Playing(const Playing&) = delete;
+ Playing& operator=(const Playing&) = delete;
+
private:
auto control_button(lv_obj_t* parent, char* icon) -> lv_obj_t*;
auto next_up_label(lv_obj_t* parent, const std::pmr::string& text)
-> lv_obj_t*;
- auto BindTrack(const database::Track& track) -> void;
- auto ApplyNextUp(const std::vector<database::Track>& tracks) -> void;
-
std::weak_ptr<database::Database> db_;
audio::TrackQueue& queue_;
- std::optional<database::Track> track_;
- std::vector<database::Track> next_tracks_;
+ bindey::property<std::shared_ptr<database::Track>> current_track_;
+ bindey::property<std::vector<std::shared_ptr<database::Track>>> next_tracks_;
- std::unique_ptr<database::FutureFetcher<std::optional<database::Track>>>
+ std::unique_ptr<database::FutureFetcher<std::shared_ptr<database::Track>>>
new_track_;
std::unique_ptr<
- database::FutureFetcher<std::vector<std::optional<database::Track>>>>
+ database::FutureFetcher<std::vector<std::shared_ptr<database::Track>>>>
new_next_tracks_;
- lv_obj_t* artist_label_;
- lv_obj_t* album_label_;
- lv_obj_t* title_label_;
-
- lv_obj_t* scrubber_;
- lv_obj_t* play_pause_control_;
-
lv_obj_t* next_up_header_;
lv_obj_t* next_up_label_;
lv_obj_t* next_up_hint_;
diff --git a/src/ui/include/ui_fsm.hpp b/src/ui/include/ui_fsm.hpp
index 9980dac6..cb3e651c 100644
--- a/src/ui/include/ui_fsm.hpp
+++ b/src/ui/include/ui_fsm.hpp
@@ -7,13 +7,16 @@
#pragma once
#include <stdint.h>
+#include <sys/_stdint.h>
#include <memory>
#include <stack>
#include "audio_events.hpp"
#include "battery.hpp"
+#include "bindey/property.h"
#include "gpios.hpp"
#include "lvgl_task.hpp"
+#include "model_playback.hpp"
#include "nvs.hpp"
#include "relative_wheel.hpp"
#include "screen_playing.hpp"
@@ -27,6 +30,7 @@
#include "storage.hpp"
#include "system_events.hpp"
#include "touchwheel.hpp"
+#include "track.hpp"
#include "track_queue.hpp"
#include "ui_events.hpp"
#include "wheel_encoder.hpp"
@@ -49,11 +53,11 @@ class UiState : public tinyfsm::Fsm<UiState> {
/* Fallback event handler. Does nothing. */
void react(const tinyfsm::Event& ev) {}
- virtual void react(const system_fsm::BatteryStateChanged&);
- virtual void react(const audio::PlaybackStarted&);
- virtual void react(const audio::PlaybackFinished&);
- virtual void react(const audio::PlaybackUpdate&) {}
- virtual void react(const audio::QueueUpdate&) {}
+ void react(const system_fsm::BatteryStateChanged&);
+ void react(const audio::PlaybackStarted&);
+ void react(const audio::PlaybackFinished&);
+ void react(const audio::PlaybackUpdate&);
+ void react(const audio::QueueUpdate&);
virtual void react(const system_fsm::KeyLockChanged&);
@@ -88,6 +92,10 @@ class UiState : public tinyfsm::Fsm<UiState> {
static std::stack<std::shared_ptr<Screen>> sScreens;
static std::shared_ptr<Screen> sCurrentScreen;
static std::shared_ptr<Modal> sCurrentModal;
+
+ static models::Playback sPlaybackModel;
+
+ static bindey::property<battery::Battery::BatteryState> sPropBatteryState;
};
namespace states {
@@ -96,7 +104,6 @@ class Splash : public UiState {
public:
void exit() override;
void react(const system_fsm::BootComplete&) override;
- void react(const system_fsm::BatteryStateChanged&) override{};
using UiState::react;
};
@@ -140,10 +147,6 @@ class Playing : public UiState {
void react(const internal::BackPressed&) override;
- void react(const audio::PlaybackStarted&) override;
- void react(const audio::PlaybackUpdate&) override;
- void react(const audio::PlaybackFinished&) override;
- void react(const audio::QueueUpdate&) override;
using UiState::react;
};
diff --git a/src/ui/screen_playing.cpp b/src/ui/screen_playing.cpp
index bd55924d..547bcf98 100644
--- a/src/ui/screen_playing.cpp
+++ b/src/ui/screen_playing.cpp
@@ -9,6 +9,7 @@
#include <memory>
#include "audio_events.hpp"
+#include "bindey/binding.h"
#include "core/lv_event.h"
#include "core/lv_obj.h"
#include "core/lv_obj_scroll.h"
@@ -35,6 +36,7 @@
#include "misc/lv_area.h"
#include "misc/lv_color.h"
#include "misc/lv_txt.h"
+#include "model_playback.hpp"
#include "track.hpp"
#include "ui_events.hpp"
#include "ui_fsm.hpp"
@@ -46,8 +48,6 @@
namespace ui {
namespace screens {
-static constexpr std::size_t kMaxUpcoming = 10;
-
static void above_fold_focus_cb(lv_event_t* ev) {
if (ev->user_data == NULL) {
return;
@@ -64,10 +64,6 @@ static void below_fold_focus_cb(lv_event_t* ev) {
instance->OnFocusBelowFold();
}
-static void play_pause_cb(lv_event_t* ev) {
- events::Audio().Dispatch(audio::TogglePlayPause{});
-}
-
static lv_style_t scrubber_style;
auto info_label(lv_obj_t* parent) -> lv_obj_t* {
@@ -105,13 +101,42 @@ auto Playing::next_up_label(lv_obj_t* parent, const std::pmr::string& text)
return button;
}
-Playing::Playing(std::weak_ptr<database::Database> db, audio::TrackQueue& queue)
+Playing::Playing(models::Playback& playback_model,
+ std::weak_ptr<database::Database> db,
+ audio::TrackQueue& queue)
: db_(db),
queue_(queue),
- track_(),
+ current_track_(),
next_tracks_(),
new_track_(),
new_next_tracks_() {
+ data_bindings_.emplace_back(playback_model.current_track.onChangedAndNow(
+ [=, this](const std::optional<database::TrackId>& id) {
+ if (!id) {
+ return;
+ }
+ if (current_track_.get() && current_track_.get()->data().id() == *id) {
+ return;
+ }
+ auto db = db_.lock();
+ if (!db) {
+ return;
+ }
+ new_track_.reset(
+ new database::FutureFetcher<std::shared_ptr<database::Track>>(
+ db->GetTrack(*id)));
+ }));
+ data_bindings_.emplace_back(playback_model.upcoming_tracks.onChangedAndNow(
+ [=, this](const std::vector<database::TrackId>& ids) {
+ auto db = db_.lock();
+ if (!db) {
+ return;
+ }
+ new_next_tracks_.reset(new database::FutureFetcher<
+ std::vector<std::shared_ptr<database::Track>>>(
+ db->GetBulkTracks(ids)));
+ }));
+
lv_obj_set_layout(content_, LV_LAYOUT_FLEX);
lv_group_set_wrap(group_, false);
@@ -143,20 +168,40 @@ Playing::Playing(std::weak_ptr<database::Database> db, audio::TrackQueue& queue)
lv_obj_set_flex_align(info_container, LV_FLEX_ALIGN_CENTER,
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
- artist_label_ = info_label(info_container);
- album_label_ = info_label(info_container);
- title_label_ = info_label(info_container);
+ lv_obj_t* artist_label = info_label(info_container);
+ lv_obj_t* album_label = info_label(info_container);
+ lv_obj_t* title_label = info_label(info_container);
- scrubber_ = lv_slider_create(above_fold_container);
- lv_obj_set_size(scrubber_, lv_pct(100), 5);
- lv_slider_set_range(scrubber_, 0, 100);
- lv_slider_set_value(scrubber_, 0, LV_ANIM_OFF);
+ data_bindings_.emplace_back(current_track_.onChangedAndNow(
+ [=](const std::shared_ptr<database::Track>& t) {
+ if (!t) {
+ return;
+ }
+ lv_label_set_text(
+ artist_label,
+ t->tags().at(database::Tag::kArtist).value_or("").c_str());
+ lv_label_set_text(
+ album_label,
+ t->tags().at(database::Tag::kAlbum).value_or("").c_str());
+ lv_label_set_text(title_label, t->TitleOrFilename().c_str());
+ }));
+
+ lv_obj_t* scrubber = lv_slider_create(above_fold_container);
+ lv_obj_set_size(scrubber, lv_pct(100), 5);
lv_style_init(&scrubber_style);
lv_style_set_bg_color(&scrubber_style, lv_color_black());
- lv_obj_add_style(scrubber_, &scrubber_style, LV_PART_INDICATOR);
+ lv_obj_add_style(scrubber, &scrubber_style, LV_PART_INDICATOR);
- lv_group_add_obj(group_, scrubber_);
+ lv_group_add_obj(group_, scrubber);
+
+ data_bindings_.emplace_back(
+ playback_model.current_track_duration.onChangedAndNow([=](uint32_t d) {
+ lv_slider_set_range(scrubber, 0, std::max<uint32_t>(1, d));
+ }));
+ data_bindings_.emplace_back(
+ playback_model.current_track_position.onChangedAndNow(
+ [=](uint32_t p) { lv_slider_set_value(scrubber, p, LV_ANIM_OFF); }));
lv_obj_t* controls_container = lv_obj_create(above_fold_container);
lv_obj_set_size(controls_container, lv_pct(100), 20);
@@ -164,15 +209,25 @@ Playing::Playing(std::weak_ptr<database::Database> db, audio::TrackQueue& queue)
lv_obj_set_flex_align(controls_container, LV_FLEX_ALIGN_SPACE_EVENLY,
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
- play_pause_control_ = control_button(controls_container, LV_SYMBOL_PLAY);
- lv_obj_add_event_cb(play_pause_control_, play_pause_cb, LV_EVENT_CLICKED,
- NULL);
- lv_group_add_obj(group_, play_pause_control_);
+ lv_obj_t* play_pause_control =
+ control_button(controls_container, LV_SYMBOL_PLAY);
+ lv_group_add_obj(group_, play_pause_control);
+ lv_bind(play_pause_control, LV_EVENT_CLICKED, [=](lv_obj_t*) {
+ events::Audio().Dispatch(audio::TogglePlayPause{});
+ });
+
+ lv_obj_t* track_prev = control_button(controls_container, LV_SYMBOL_PREV);
+ lv_group_add_obj(group_, track_prev);
+ lv_bind(track_prev, LV_EVENT_CLICKED, [=](lv_obj_t*) { queue_.Previous(); });
+
+ lv_obj_t* track_next = control_button(controls_container, LV_SYMBOL_NEXT);
+ lv_group_add_obj(group_, track_next);
+ lv_bind(track_next, LV_EVENT_CLICKED, [=](lv_obj_t*) { queue_.Next(); });
+
+ lv_obj_t* shuffle = control_button(controls_container, LV_SYMBOL_SHUFFLE);
+ lv_group_add_obj(group_, shuffle);
+ // lv_bind(shuffle, LV_EVENT_CLICKED, [=](lv_obj_t*) { queue_ });
- lv_group_add_obj(group_, control_button(controls_container, LV_SYMBOL_PREV));
- lv_group_add_obj(group_, control_button(controls_container, LV_SYMBOL_NEXT));
- lv_group_add_obj(group_,
- control_button(controls_container, LV_SYMBOL_SHUFFLE));
lv_group_add_obj(group_, control_button(controls_container, LV_SYMBOL_LOOP));
next_up_header_ = lv_obj_create(above_fold_container);
@@ -198,111 +253,56 @@ Playing::Playing(std::weak_ptr<database::Database> db, audio::TrackQueue& queue)
lv_obj_set_flex_align(next_up_container_, LV_FLEX_ALIGN_START,
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
- OnTrackUpdate();
- OnQueueUpdate();
-}
-
-Playing::~Playing() {}
+ data_bindings_.emplace_back(next_tracks_.onChangedAndNow(
+ [=](const std::vector<std::shared_ptr<database::Track>>& tracks) {
+ // TODO(jacqueline): Do a proper diff to maintain selection.
+ int children = lv_obj_get_child_cnt(next_up_container_);
+ while (children > 0) {
+ lv_obj_del(lv_obj_get_child(next_up_container_, 0));
+ children--;
+ }
-auto Playing::OnTrackUpdate() -> void {
- auto current = queue_.GetCurrent();
- if (!current) {
- return;
- }
- if (track_ && track_->data().id() == *current) {
- return;
- }
- auto db = db_.lock();
- if (!db) {
- return;
- }
- new_track_.reset(new database::FutureFetcher<std::optional<database::Track>>(
- db->GetTrack(*current)));
-}
+ if (tracks.empty()) {
+ lv_label_set_text(next_up_label_, "Nothing queued");
+ lv_label_set_text(next_up_hint_, "");
+ return;
+ } else {
+ lv_label_set_text(next_up_label_, "Next up");
+ lv_label_set_text(next_up_hint_, "");
+ }
-auto Playing::OnPlaybackUpdate(uint32_t pos_seconds, uint32_t new_duration)
- -> void {
- if (!track_) {
- return;
- }
- lv_slider_set_range(scrubber_, 0, new_duration);
- lv_slider_set_value(scrubber_, pos_seconds, LV_ANIM_ON);
+ for (const auto& track : tracks) {
+ lv_group_add_obj(group_, next_up_label(next_up_container_,
+ track->TitleOrFilename()));
+ }
+ }));
}
-auto Playing::OnQueueUpdate() -> void {
- OnTrackUpdate();
- auto current = queue_.GetUpcoming(kMaxUpcoming);
- auto db = db_.lock();
- if (!db) {
- return;
- }
- new_next_tracks_.reset(
- new database::FutureFetcher<std::vector<std::optional<database::Track>>>(
- db->GetBulkTracks(current)));
-}
+Playing::~Playing() {}
auto Playing::Tick() -> void {
if (new_track_ && new_track_->Finished()) {
auto res = new_track_->Result();
new_track_.reset();
- if (res && *res) {
- BindTrack(**res);
+ if (res) {
+ current_track_(*res);
}
}
if (new_next_tracks_ && new_next_tracks_->Finished()) {
auto res = new_next_tracks_->Result();
new_next_tracks_.reset();
if (res) {
- std::vector<database::Track> filtered;
+ std::vector<std::shared_ptr<database::Track>> filtered;
for (const auto& t : *res) {
if (t) {
- filtered.push_back(*t);
+ filtered.push_back(t);
}
}
- ApplyNextUp(filtered);
+ next_tracks_.set(filtered);
}
}
}
-auto Playing::BindTrack(const database::Track& t) -> void {
- track_ = t;
-
- lv_label_set_text(artist_label_,
- t.tags().at(database::Tag::kArtist).value_or("").c_str());
- lv_label_set_text(album_label_,
- t.tags().at(database::Tag::kAlbum).value_or("").c_str());
- lv_label_set_text(title_label_, t.TitleOrFilename().c_str());
-
- std::optional<int> duration = t.tags().duration;
- lv_slider_set_range(scrubber_, 0, duration.value_or(1));
- lv_slider_set_value(scrubber_, 0, LV_ANIM_OFF);
-}
-
-auto Playing::ApplyNextUp(const std::vector<database::Track>& tracks) -> void {
- // TODO(jacqueline): Do a proper diff to maintain selection.
- int children = lv_obj_get_child_cnt(next_up_container_);
- while (children > 0) {
- lv_obj_del(lv_obj_get_child(next_up_container_, 0));
- children--;
- }
-
- next_tracks_ = tracks;
-
- if (next_tracks_.empty()) {
- lv_label_set_text(next_up_label_, "Nothing queued");
- lv_label_set_text(next_up_hint_, "");
- return;
- } else {
- lv_label_set_text(next_up_label_, "Next up");
- lv_label_set_text(next_up_hint_, "");
- }
-
- for (const auto& track : next_tracks_) {
- lv_group_add_obj(
- group_, next_up_label(next_up_container_, track.TitleOrFilename()));
- }
-}
-
auto Playing::OnFocusAboveFold() -> void {
lv_obj_scroll_to_y(content_, 0, LV_ANIM_ON);
}
diff --git a/src/ui/screen_track_browser.cpp b/src/ui/screen_track_browser.cpp
index 6cd92a04..8d1fe653 100644
--- a/src/ui/screen_track_browser.cpp
+++ b/src/ui/screen_track_browser.cpp
@@ -170,8 +170,8 @@ auto TrackBrowser::AddResults(
initial_page_ = results;
}
- auto fn = [&](const database::IndexRecord& record) {
- auto text = record.text();
+ auto fn = [&](const std::shared_ptr<database::IndexRecord>& record) {
+ auto text = record->text();
if (!text) {
// TODO(jacqueline): Display category-specific text.
text = "[ no data ]";
diff --git a/src/ui/ui_fsm.cpp b/src/ui/ui_fsm.cpp
index fa4939f3..d7bb9bb7 100644
--- a/src/ui/ui_fsm.cpp
+++ b/src/ui/ui_fsm.cpp
@@ -19,6 +19,7 @@
#include "gpios.hpp"
#include "lvgl_task.hpp"
#include "modal_confirm.hpp"
+#include "model_playback.hpp"
#include "nvs.hpp"
#include "relative_wheel.hpp"
#include "screen.hpp"
@@ -52,6 +53,10 @@ std::stack<std::shared_ptr<Screen>> UiState::sScreens;
std::shared_ptr<Screen> UiState::sCurrentScreen;
std::shared_ptr<Modal> UiState::sCurrentModal;
+models::Playback UiState::sPlaybackModel;
+
+bindey::property<battery::Battery::BatteryState> UiState::sPropBatteryState;
+
auto UiState::InitBootSplash(drivers::IGpios& gpios) -> bool {
// Init LVGL first, since the display driver registers itself with LVGL.
lv_init();
@@ -89,15 +94,33 @@ void UiState::react(const system_fsm::KeyLockChanged& ev) {
}
void UiState::react(const system_fsm::BatteryStateChanged&) {
- UpdateTopBar();
+ if (!sServices) {
+ return;
+ }
+ auto state = sServices->battery().State();
+ if (state) {
+ sPropBatteryState.set(*state);
+ }
}
void UiState::react(const audio::PlaybackStarted&) {
- UpdateTopBar();
+ sPlaybackModel.is_playing.set(true);
}
void UiState::react(const audio::PlaybackFinished&) {
- UpdateTopBar();
+ sPlaybackModel.is_playing.set(false);
+}
+
+void UiState::react(const audio::PlaybackUpdate& ev) {
+ sPlaybackModel.current_track_duration.set(ev.seconds_total);
+ sPlaybackModel.current_track_position.set(ev.seconds_elapsed);
+}
+
+void UiState::react(const audio::QueueUpdate&) {
+ ESP_LOGI(kTag, "current changed!");
+ auto& queue = sServices->track_queue();
+ sPlaybackModel.current_track.set(queue.GetCurrent());
+ sPlaybackModel.upcoming_tracks.set(queue.GetUpcoming(10));
}
void UiState::UpdateTopBar() {
@@ -283,21 +306,22 @@ void Browse::react(const internal::RecordSelected& ev) {
}
auto record = ev.page->values().at(ev.record);
- if (record.track()) {
- ESP_LOGI(kTag, "selected track '%s'", record.text()->c_str());
+ if (record->track()) {
+ ESP_LOGI(kTag, "selected track '%s'", record->text()->c_str());
auto& queue = sServices->track_queue();
queue.Clear();
queue.IncludeLast(std::make_shared<playlist::IndexRecordSource>(
sServices->database(), ev.initial_page, 0, ev.page, ev.record));
+ ESP_LOGI(kTag, "transit to playing");
transit<Playing>();
} else {
- ESP_LOGI(kTag, "selected record '%s'", record.text()->c_str());
- auto cont = record.Expand(kRecordsPerPage);
+ ESP_LOGI(kTag, "selected record '%s'", record->text()->c_str());
+ auto cont = record->Expand(kRecordsPerPage);
if (!cont) {
return;
}
auto query = db->GetPage(&cont.value());
- std::pmr::string title = record.text().value_or("TODO");
+ std::pmr::string title = record->text().value_or("TODO");
PushScreen(std::make_shared<screens::TrackBrowser>(
sServices->database(), title, std::move(query)));
}
@@ -329,8 +353,9 @@ void Browse::react(const system_fsm::BluetoothDevicesChanged&) {
static std::shared_ptr<screens::Playing> sPlayingScreen;
void Playing::entry() {
- sPlayingScreen.reset(
- new screens::Playing(sServices->database(), sServices->track_queue()));
+ ESP_LOGI(kTag, "push playing screen");
+ sPlayingScreen.reset(new screens::Playing(
+ sPlaybackModel, sServices->database(), sServices->track_queue()));
PushScreen(sPlayingScreen);
}
@@ -339,24 +364,6 @@ void Playing::exit() {
PopScreen();
}
-void Playing::react(const audio::PlaybackStarted& ev) {
- UpdateTopBar();
- sPlayingScreen->OnTrackUpdate();
-}
-
-void Playing::react(const audio::PlaybackFinished& ev) {
- UpdateTopBar();
- sPlayingScreen->OnTrackUpdate();
-}
-
-void Playing::react(const audio::PlaybackUpdate& ev) {
- sPlayingScreen->OnPlaybackUpdate(ev.seconds_elapsed, ev.seconds_total);
-}
-
-void Playing::react(const audio::QueueUpdate& ev) {
- sPlayingScreen->OnQueueUpdate();
-}
-
void Playing::react(const internal::BackPressed& ev) {
transit<Browse>();
}