From 782e8dc8c25402171fc4724075b998eae4fa2c76 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Wed, 18 Oct 2023 14:35:28 +1100 Subject: [PATCH] Add better controls for queue manipulation --- src/playlist/include/source.hpp | 33 +++++ src/playlist/source.cpp | 163 ++++++++++++++++++++++++ src/ui/encoder_input.cpp | 15 ++- src/ui/include/modal_add_to_queue.hpp | 5 +- src/ui/include/screen_track_browser.hpp | 11 +- src/ui/include/ui_events.hpp | 2 + src/ui/modal_add_to_queue.cpp | 75 ++++++----- src/ui/screen_track_browser.cpp | 134 +++++++++++++++++-- src/ui/ui_fsm.cpp | 35 ++++- 9 files changed, 413 insertions(+), 60 deletions(-) diff --git a/src/playlist/include/source.hpp b/src/playlist/include/source.hpp index 069c1e93..aa15e7df 100644 --- a/src/playlist/include/source.hpp +++ b/src/playlist/include/source.hpp @@ -73,6 +73,11 @@ class IResetableSource : public ISource { virtual auto Reset() -> void = 0; }; +auto CreateSourceFromResults( + std::weak_ptr, + std::shared_ptr>) + -> std::shared_ptr; + class IndexRecordSource : public IResetableSource { public: IndexRecordSource(std::weak_ptr db, @@ -102,4 +107,32 @@ class IndexRecordSource : public IResetableSource { ssize_t current_item_; }; +class NestedSource : public IResetableSource { + public: + NestedSource(std::weak_ptr db, + std::shared_ptr>); + + auto Current() -> std::optional override; + auto Advance() -> std::optional override; + auto Peek(std::size_t n, std::vector*) + -> std::size_t override; + + auto Previous() -> std::optional override; + auto Reset() -> void override; + + private: + auto CreateChild(std::shared_ptr page) + -> std::shared_ptr; + + std::weak_ptr db_; + + std::shared_ptr> initial_page_; + ssize_t initial_item_; + + std::shared_ptr> current_page_; + ssize_t current_item_; + + std::shared_ptr current_child_; +}; + } // namespace playlist diff --git a/src/playlist/source.cpp b/src/playlist/source.cpp index 18a7887b..d51d97ab 100644 --- a/src/playlist/source.cpp +++ b/src/playlist/source.cpp @@ -22,6 +22,17 @@ namespace playlist { +auto CreateSourceFromResults( + std::weak_ptr db, + std::shared_ptr> results) + -> std::shared_ptr { + if (results->values()[0]->track()) { + return std::make_shared(db, results); + } else { + return std::make_shared(db, results); + } +} + IndexRecordSource::IndexRecordSource( std::weak_ptr db, std::shared_ptr> initial) @@ -142,4 +153,156 @@ auto IndexRecordSource::Reset() -> void { current_item_ = initial_item_; } +NestedSource::NestedSource( + std::weak_ptr db, + std::shared_ptr> initial) + : db_(db), + initial_page_(initial), + initial_item_(0), + current_page_(initial_page_), + current_item_(initial_item_), + current_child_(CreateChild(initial->values()[0])) {} + +auto NestedSource::Current() -> std::optional { + if (current_child_) { + return current_child_->Current(); + } + return {}; +} + +auto NestedSource::Advance() -> std::optional { + if (!current_child_) { + return {}; + } + + auto child_next = current_child_->Advance(); + if (child_next) { + return child_next; + } + // Our current child has run out of tracks. Move on to the next child. + current_item_++; + current_child_.reset(); + + if (current_item_ >= current_page_->values().size()) { + // We're even out of items in this page! + auto next_page = current_page_->next_page(); + if (!next_page) { + current_item_--; + return {}; + } + + auto db = db_.lock(); + if (!db) { + return {}; + } + + current_page_.reset(db->GetPage(&*next_page).get()); + current_item_ = 0; + } + current_child_ = CreateChild(current_page_->values()[current_item_]); + + return Current(); +} + +auto NestedSource::Previous() -> std::optional { + if (current_page_ == initial_page_ && current_item_ <= initial_item_) { + return {}; + } + + current_item_--; + current_child_.reset(); + + if (current_item_ < 0) { + auto prev_page = current_page_->prev_page(); + if (!prev_page) { + return {}; + } + + auto db = db_.lock(); + if (!db) { + return {}; + } + + current_page_.reset(db->GetPage(&*prev_page).get()); + current_item_ = current_page_->values().size() - 1; + } + current_child_ = CreateChild(current_page_->values()[current_item_]); + + return Current(); +} + +auto NestedSource::Peek(std::size_t n, std::vector* out) + -> std::size_t { + if (current_page_->values().size() <= current_item_) { + return {}; + } + + auto db = db_.lock(); + if (!db) { + return 0; + } + + std::size_t items_added = 0; + + std::shared_ptr> working_page = + current_page_; + std::size_t working_item = current_item_; + std::shared_ptr working_child = current_child_; + + while (working_child) { + auto res = working_child->Peek(n, out); + n -= res; + items_added += res; + + if (n == 0) { + break; + } else { + working_item++; + if (working_item < working_page->values().size()) { + working_child = CreateChild(working_page->values()[working_item]); + } else { + auto next_page = current_page_->next_page(); + if (!next_page) { + break; + } + working_page.reset( + db->GetPage(&*next_page).get()); + working_item = 0; + working_child = CreateChild(working_page->values()[0]); + } + } + } + + return items_added; +} + +auto NestedSource::Reset() -> void { + current_page_ = initial_page_; + current_item_ = initial_item_; + current_child_ = CreateChild(initial_page_->values()[initial_item_]); +} + +auto NestedSource::CreateChild(std::shared_ptr record) + -> std::shared_ptr { + auto cont = record->Expand(10); + if (!cont) { + return {}; + } + auto db = db_.lock(); + if (!db) { + return {}; + } + std::shared_ptr> next_level{ + db->GetPage(&*cont).get()}; + if (!next_level) { + return {}; + } + auto next_level_record = next_level->values()[0]; + if (next_level_record->track()) { + return std::make_shared(db_, next_level); + } else { + return std::make_shared(db_, next_level); + } +} + } // namespace playlist diff --git a/src/ui/encoder_input.cpp b/src/ui/encoder_input.cpp index 0345665b..f6a981a7 100644 --- a/src/ui/encoder_input.cpp +++ b/src/ui/encoder_input.cpp @@ -9,7 +9,10 @@ #include #include +#include "lvgl.h" + #include "audio_events.hpp" +#include "core/lv_event.h" #include "core/lv_group.h" #include "esp_timer.h" #include "event_queue.hpp" @@ -20,6 +23,8 @@ #include "touchwheel.hpp" #include "ui_events.hpp" +static constexpr char kTag[] = "input"; + constexpr int kDPadAngleThreshold = 20; constexpr int kLongPressDelayMs = 500; constexpr int kRepeatDelayMs = 250; @@ -58,6 +63,11 @@ auto EncoderInput::Read(lv_indev_data_t* data) -> void { return; } + lv_obj_t* active_object = nullptr; + if (registration_ && registration_->group) { + active_object = lv_group_get_focused(registration_->group); + } + raw_wheel_.Update(); relative_wheel_->Update(); // GPIOs updating is handled by system_fsm. @@ -226,8 +236,9 @@ auto EncoderInput::Read(lv_indev_data_t* data) -> void { data->state = LV_INDEV_STATE_PRESSED; break; case Trigger::kLongPress: - // TODO: ??? - data->state = LV_INDEV_STATE_PRESSED; + if (active_object) { + lv_event_send(active_object, LV_EVENT_LONG_PRESSED, NULL); + } break; } diff --git a/src/ui/include/modal_add_to_queue.hpp b/src/ui/include/modal_add_to_queue.hpp index 79f804a4..e6417cd4 100644 --- a/src/ui/include/modal_add_to_queue.hpp +++ b/src/ui/include/modal_add_to_queue.hpp @@ -24,11 +24,12 @@ class AddToQueue : public Modal { public: AddToQueue(Screen*, audio::TrackQueue&, - std::shared_ptr); + std::shared_ptr, + bool all_tracks_only = false); private: audio::TrackQueue& queue_; - std::shared_ptr item_; + std::shared_ptr item_; lv_obj_t* container_; lv_obj_t* selected_track_btn_; diff --git a/src/ui/include/screen_track_browser.hpp b/src/ui/include/screen_track_browser.hpp index 719306f0..0b2d6fc3 100644 --- a/src/ui/include/screen_track_browser.hpp +++ b/src/ui/include/screen_track_browser.hpp @@ -16,6 +16,7 @@ #include "database.hpp" #include "model_top_bar.hpp" #include "screen.hpp" +#include "track_queue.hpp" namespace ui { namespace screens { @@ -23,9 +24,10 @@ namespace screens { class TrackBrowser : public Screen { public: TrackBrowser( - models::TopBar&, + models::TopBar& top_bar, + audio::TrackQueue& queue, std::weak_ptr db, - const std::pmr::string& title, + const std::pmr::vector& breadcrumbs, std::future*>&& initial_page); ~TrackBrowser() {} @@ -49,11 +51,16 @@ class TrackBrowser : public Screen { auto GetNumRecords() -> std::size_t; auto GetItemIndex(lv_obj_t* obj) -> std::optional; + audio::TrackQueue& queue_; std::weak_ptr db_; lv_obj_t* back_button_; + lv_obj_t* play_button_; + lv_obj_t* enqueue_button_; lv_obj_t* list_; lv_obj_t* loading_indicator_; + std::pmr::vector breadcrumbs_; + std::optional loading_pos_; std::optional*>> loading_page_; diff --git a/src/ui/include/ui_events.hpp b/src/ui/include/ui_events.hpp index 4549a51d..2bee6222 100644 --- a/src/ui/include/ui_events.hpp +++ b/src/ui/include/ui_events.hpp @@ -27,6 +27,8 @@ struct OnSystemError : tinyfsm::Event {}; namespace internal { struct RecordSelected : tinyfsm::Event { + bool show_menu; + std::pmr::vector new_crumbs; std::shared_ptr> initial_page; std::shared_ptr> page; std::size_t record; diff --git a/src/ui/modal_add_to_queue.cpp b/src/ui/modal_add_to_queue.cpp index cc3a8d51..e102fae8 100644 --- a/src/ui/modal_add_to_queue.cpp +++ b/src/ui/modal_add_to_queue.cpp @@ -37,50 +37,55 @@ namespace modals { AddToQueue::AddToQueue(Screen* host, audio::TrackQueue& queue, - std::shared_ptr item) + std::shared_ptr item, + bool all_tracks_only) : Modal(host), queue_(queue), item_(item), all_tracks_(0) { 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_START, LV_FLEX_ALIGN_CENTER); - 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); - - selected_track_btn_ = lv_btn_create(button_container); - lv_obj_t* label = lv_label_create(selected_track_btn_); - lv_label_set_text(label, "Selected"); - lv_group_add_obj(group_, selected_track_btn_); - lv_obj_add_state(selected_track_btn_, LV_STATE_CHECKED); - themes::Theme::instance()->ApplyStyle(selected_track_btn_, - themes::Style::kTab); + if (all_tracks_only) { + all_tracks_ = true; + } else { + 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); - lv_bind(selected_track_btn_, LV_EVENT_CLICKED, [this](lv_obj_t*) { + selected_track_btn_ = lv_btn_create(button_container); + lv_obj_t* label = lv_label_create(selected_track_btn_); + lv_label_set_text(label, "Selected"); + lv_group_add_obj(group_, selected_track_btn_); lv_obj_add_state(selected_track_btn_, LV_STATE_CHECKED); - lv_obj_clear_state(all_tracks_btn_, LV_STATE_CHECKED); - all_tracks_ = false; - }); + themes::Theme::instance()->ApplyStyle(selected_track_btn_, + themes::Style::kTab); - all_tracks_btn_ = lv_btn_create(button_container); - label = lv_label_create(all_tracks_btn_); - lv_label_set_text(label, "All tracks"); - lv_group_add_obj(group_, all_tracks_btn_); - themes::Theme::instance()->ApplyStyle(all_tracks_btn_, themes::Style::kTab); + lv_bind(selected_track_btn_, LV_EVENT_CLICKED, [this](lv_obj_t*) { + lv_obj_add_state(selected_track_btn_, LV_STATE_CHECKED); + lv_obj_clear_state(all_tracks_btn_, LV_STATE_CHECKED); + all_tracks_ = false; + }); - lv_bind(all_tracks_btn_, LV_EVENT_CLICKED, [this](lv_obj_t*) { - lv_obj_clear_state(selected_track_btn_, LV_STATE_CHECKED); - lv_obj_add_state(all_tracks_btn_, LV_STATE_CHECKED); - all_tracks_ = true; - }); + all_tracks_btn_ = lv_btn_create(button_container); + label = lv_label_create(all_tracks_btn_); + lv_label_set_text(label, "From here"); + lv_group_add_obj(group_, all_tracks_btn_); + themes::Theme::instance()->ApplyStyle(all_tracks_btn_, themes::Style::kTab); - lv_obj_t* spacer = lv_obj_create(root_); - lv_obj_set_size(spacer, 1, 4); + lv_bind(all_tracks_btn_, LV_EVENT_CLICKED, [this](lv_obj_t*) { + lv_obj_clear_state(selected_track_btn_, LV_STATE_CHECKED); + lv_obj_add_state(all_tracks_btn_, LV_STATE_CHECKED); + all_tracks_ = true; + }); - button_container = lv_obj_create(root_); + lv_obj_t* spacer = lv_obj_create(root_); + lv_obj_set_size(spacer, 1, 4); + } + + 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); @@ -88,7 +93,7 @@ AddToQueue::AddToQueue(Screen* host, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lv_obj_t* btn = lv_btn_create(button_container); - label = lv_label_create(btn); + lv_obj_t* label = lv_label_create(btn); lv_label_set_text(label, "Play now"); lv_group_add_obj(group_, btn); @@ -112,7 +117,7 @@ AddToQueue::AddToQueue(Screen* host, label = lv_label_create(root_); lv_label_set_text(label, "Enqueue"); - spacer = lv_obj_create(root_); + lv_obj_t* spacer = lv_obj_create(root_); lv_obj_set_size(spacer, 1, 4); button_container = lv_obj_create(root_); @@ -151,7 +156,7 @@ AddToQueue::AddToQueue(Screen* host, }); } - spacer = lv_obj_create(root_); + lv_obj_t* spacer = lv_obj_create(root_); lv_obj_set_size(spacer, 1, 4); button_container = lv_obj_create(root_); diff --git a/src/ui/screen_track_browser.cpp b/src/ui/screen_track_browser.cpp index 58cd2946..d0bb59e9 100644 --- a/src/ui/screen_track_browser.cpp +++ b/src/ui/screen_track_browser.cpp @@ -16,6 +16,7 @@ #include "font/lv_symbol_def.h" #include "lvgl.h" #include "misc/lv_anim.h" +#include "misc/lv_color.h" #include "model_top_bar.hpp" #include "screen_menu.hpp" @@ -30,6 +31,9 @@ #include "hal/lv_hal_disp.h" #include "misc/lv_area.h" #include "screen_track_browser.hpp" +#include "source.hpp" +#include "themes.hpp" +#include "track_queue.hpp" #include "ui_events.hpp" #include "ui_fsm.hpp" #include "widget_top_bar.hpp" @@ -61,12 +65,17 @@ static void item_select_cb(lv_event_t* ev) { TrackBrowser::TrackBrowser( models::TopBar& top_bar_model, + audio::TrackQueue& queue, std::weak_ptr db, - const std::pmr::string& title, + const std::pmr::vector& crumbs, std::future*>&& initial_page) - : db_(db), + : queue_(queue), + db_(db), + play_button_(nullptr), + enqueue_button_(nullptr), list_(nullptr), loading_indicator_(nullptr), + breadcrumbs_(crumbs), loading_pos_(END), loading_page_(move(initial_page)), initial_page_(), @@ -76,22 +85,111 @@ TrackBrowser::TrackBrowser( lv_obj_set_flex_align(content_, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); - // The default scrollbar is deceptive because we load in items progressively. - lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF); - // Wrapping behaves in surprising ways, again due to progressing loading. - lv_group_set_wrap(group_, false); - widgets::TopBar::Configuration config{ .show_back_button = true, - .title = title, + .title = breadcrumbs_[0], }; auto top_bar = CreateTopBar(content_, config, top_bar_model); back_button_ = top_bar->button(); - list_ = lv_list_create(content_); - lv_obj_set_width(list_, lv_pct(100)); - lv_obj_set_flex_grow(list_, 1); - lv_obj_center(list_); + lv_obj_t* scrollable = lv_obj_create(content_); + lv_obj_set_width(scrollable, lv_pct(100)); + lv_obj_set_flex_grow(scrollable, 1); + lv_obj_set_layout(scrollable, LV_LAYOUT_FLEX); + lv_obj_set_flex_flow(scrollable, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(scrollable, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, + LV_FLEX_ALIGN_START); + + if (crumbs.size() > 1) { + lv_obj_t* header = lv_obj_create(scrollable); + lv_obj_set_size(header, lv_pct(100), LV_SIZE_CONTENT); + lv_obj_set_layout(header, LV_LAYOUT_FLEX); + lv_obj_set_flex_flow(header, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(header, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, + LV_FLEX_ALIGN_START); + + lv_obj_set_style_pad_left(header, 4, LV_PART_MAIN); + lv_obj_set_style_pad_right(header, 4, LV_PART_MAIN); + + lv_obj_t* spacer = lv_obj_create(header); + lv_obj_set_size(spacer, 1, 2); + + for (size_t i = 1; i < crumbs.size(); i++) { + lv_obj_t* crumb = lv_label_create(header); + lv_label_set_text(crumb, crumbs[i].c_str()); + + spacer = lv_obj_create(header); + lv_obj_set_size(spacer, 1, 2); + } + + spacer = lv_obj_create(header); + lv_obj_set_size(spacer, 1, 2); + + lv_obj_t* buttons_container = lv_obj_create(header); + lv_obj_set_width(buttons_container, lv_pct(100)); + lv_obj_set_height(buttons_container, LV_SIZE_CONTENT); + lv_obj_set_layout(buttons_container, LV_LAYOUT_FLEX); + lv_obj_set_flex_flow(buttons_container, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(buttons_container, LV_FLEX_ALIGN_END, + LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + + lv_obj_t* label; + + play_button_ = lv_btn_create(buttons_container); + label = lv_label_create(play_button_); + lv_label_set_text(label, "Play all"); + lv_group_add_obj(group_, play_button_); + themes::Theme::instance()->ApplyStyle(play_button_, + themes::Style::kButtonPrimary); + + lv_bind(play_button_, LV_EVENT_CLICKED, [&](lv_obj_t*) { + if (!initial_page_) { + return; + } + queue_.Clear(); + queue_.IncludeNext(playlist::CreateSourceFromResults(db_, initial_page_)); + events::Ui().Dispatch(internal::ShowNowPlaying{}); + }); + + if (queue_.GetCurrent()) { + spacer = lv_obj_create(buttons_container); + lv_obj_set_size(spacer, 4, 1); + + enqueue_button_ = lv_btn_create(buttons_container); + label = lv_label_create(enqueue_button_); + lv_label_set_text(label, "Enqueue"); + lv_group_add_obj(group_, enqueue_button_); + themes::Theme::instance()->ApplyStyle(enqueue_button_, + themes::Style::kButtonPrimary); + + lv_bind(enqueue_button_, LV_EVENT_CLICKED, [&](lv_obj_t*) { + if (!initial_page_) { + return; + } + queue_.IncludeNext( + playlist::CreateSourceFromResults(db_, initial_page_)); + }); + } + + lv_obj_set_style_border_width(header, 1, LV_PART_MAIN); + lv_obj_set_style_border_color(header, lv_color_black(), LV_PART_MAIN); + lv_obj_set_style_border_side(header, LV_BORDER_SIDE_BOTTOM, LV_PART_MAIN); + + spacer = lv_obj_create(header); + lv_obj_set_size(spacer, 1, 4); + + lv_obj_set_style_border_width(header, 1, LV_PART_MAIN); + lv_obj_set_style_border_color( + header, lv_palette_lighten(LV_PALETTE_GREY, 3), LV_PART_MAIN); + } + + list_ = lv_list_create(scrollable); + lv_obj_set_size(list_, lv_pct(100), LV_SIZE_CONTENT); + + // The default scrollbar is deceptive because we load in items progressively. + lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF); + // Wrapping behaves in surprising ways, again due to progressing loading. + lv_group_set_wrap(group_, false); } auto TrackBrowser::Tick() -> void { @@ -138,7 +236,12 @@ auto TrackBrowser::OnItemClicked(lv_event_t* ev) -> void { for (const auto& page : current_pages_) { for (std::size_t i = 0; i < page->values().size(); i++) { if (index == 0) { + auto text = page->values()[i]->text(); + auto crumbs = breadcrumbs_; + crumbs.push_back(text.value()); events::Ui().Dispatch(internal::RecordSelected{ + .show_menu = ev->code == LV_EVENT_LONG_PRESSED, + .new_crumbs = crumbs, .initial_page = initial_page_, .page = page, .record = i, @@ -181,6 +284,7 @@ auto TrackBrowser::AddResults( lv_obj_t* item = lv_list_add_btn(list_, NULL, text->c_str()); lv_label_set_long_mode(lv_obj_get_child(item, -1), LV_LABEL_LONG_DOT); lv_obj_add_event_cb(item, item_click_cb, LV_EVENT_CLICKED, this); + lv_obj_add_event_cb(item, item_click_cb, LV_EVENT_LONG_PRESSED, this); lv_obj_add_event_cb(item, item_select_cb, LV_EVENT_FOCUSED, this); if (pos == START) { @@ -217,6 +321,12 @@ auto TrackBrowser::AddResults( lv_group_remove_all_objs(group_); lv_group_add_obj(group_, back_button_); + if (play_button_) { + lv_group_add_obj(group_, play_button_); + } + if (enqueue_button_) { + lv_group_add_obj(group_, enqueue_button_); + } int num_children = lv_obj_get_child_cnt(list_); for (int i = 0; i < num_children; i++) { lv_group_add_obj(group_, lv_obj_get_child(list_, i)); diff --git a/src/ui/ui_fsm.cpp b/src/ui/ui_fsm.cpp index 145bcbcc..a33dd38e 100644 --- a/src/ui/ui_fsm.cpp +++ b/src/ui/ui_fsm.cpp @@ -11,6 +11,7 @@ #include "audio_fsm.hpp" #include "battery.hpp" #include "core/lv_obj.h" +#include "database.hpp" #include "misc/lv_gc.h" #include "audio_events.hpp" @@ -112,8 +113,12 @@ void UiState::react(const audio::PlaybackUpdate& ev) { void UiState::react(const audio::QueueUpdate&) { auto& queue = sServices->track_queue(); + bool had_queue = sPlaybackModel.current_track.get().has_value(); sPlaybackModel.current_track.set(queue.GetCurrent()); sPlaybackModel.upcoming_tracks.set(queue.GetUpcoming(10)); + if (!had_queue) { + transit(); + } } void UiState::react(const internal::ControlSchemeChanged&) { @@ -285,14 +290,20 @@ void Browse::react(const internal::RecordSelected& ev) { return; } + auto& queue = sServices->track_queue(); auto record = ev.page->values().at(ev.record); if (record->track()) { ESP_LOGI(kTag, "selected track '%s'", record->text()->c_str()); - auto& queue = sServices->track_queue(); auto source = std::make_shared( sServices->database(), ev.initial_page, 0, ev.page, ev.record); - sCurrentModal.reset( - new modals::AddToQueue(sCurrentScreen.get(), queue, source)); + if (ev.show_menu) { + sCurrentModal.reset( + new modals::AddToQueue(sCurrentScreen.get(), queue, source)); + } else { + queue.Clear(); + queue.AddNext(source); + transit(); + } } else { ESP_LOGI(kTag, "selected record '%s'", record->text()->c_str()); auto cont = record->Expand(kRecordsPerPage); @@ -300,9 +311,17 @@ void Browse::react(const internal::RecordSelected& ev) { return; } auto query = db->GetPage(&cont.value()); - std::pmr::string title = record->text().value_or("TODO"); - PushScreen(std::make_shared( - sTopBarModel, sServices->database(), title, std::move(query))); + if (ev.show_menu) { + std::shared_ptr> res{query.get()}; + auto source = playlist::CreateSourceFromResults(db, res); + sCurrentModal.reset( + new modals::AddToQueue(sCurrentScreen.get(), queue, source, true)); + } else { + std::pmr::string title = record->text().value_or(""); + PushScreen(std::make_shared( + sTopBarModel, sServices->track_queue(), sServices->database(), + ev.new_crumbs, std::move(query))); + } } } @@ -314,8 +333,10 @@ void Browse::react(const internal::IndexSelected& ev) { ESP_LOGI(kTag, "selected index %s", ev.index.name.c_str()); auto query = db->GetTracksByIndex(ev.index, kRecordsPerPage); + std::pmr::vector crumbs = {ev.index.name}; PushScreen(std::make_shared( - sTopBarModel, sServices->database(), ev.index.name, std::move(query))); + sTopBarModel, sServices->track_queue(), sServices->database(), crumbs, + std::move(query))); } void Browse::react(const internal::BackPressed& ev) {