From 28633e857f86a21d874117fd677de5e8ad21d8d3 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Wed, 4 Oct 2023 14:47:30 +1100 Subject: [PATCH] Implement UI for enqueuing instead of replacing the current track --- src/ui/CMakeLists.txt | 2 +- src/ui/include/modal.hpp | 9 ++ src/ui/include/modal_add_to_queue.hpp | 36 ++++++++ src/ui/modal_add_to_queue.cpp | 127 ++++++++++++++++++++++++++ src/ui/screen_playing.cpp | 1 + src/ui/ui_fsm.cpp | 10 +- 6 files changed, 179 insertions(+), 6 deletions(-) create mode 100644 src/ui/include/modal_add_to_queue.hpp create mode 100644 src/ui/modal_add_to_queue.cpp diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 5f2d64d7..8d3640e6 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -7,7 +7,7 @@ idf_component_register( "encoder_input.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" + "event_binding.cpp" "modal_add_to_queue.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" diff --git a/src/ui/include/modal.hpp b/src/ui/include/modal.hpp index a5ac69b8..61e52cdf 100644 --- a/src/ui/include/modal.hpp +++ b/src/ui/include/modal.hpp @@ -30,6 +30,15 @@ class Modal { lv_obj_t* const root_; lv_group_t* const group_; + std::pmr::vector> event_bindings_; + + template + auto lv_bind(lv_obj_t* obj, lv_event_code_t ev, T fn) -> void { + auto binding = std::make_unique(obj, ev); + binding->signal().connect(fn); + event_bindings_.push_back(std::move(binding)); + } + private: Screen* host_; }; diff --git a/src/ui/include/modal_add_to_queue.hpp b/src/ui/include/modal_add_to_queue.hpp new file mode 100644 index 00000000..760a155e --- /dev/null +++ b/src/ui/include/modal_add_to_queue.hpp @@ -0,0 +1,36 @@ +/* + * Copyright 2023 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include +#include + +#include "database.hpp" +#include "index.hpp" +#include "lvgl.h" + +#include "modal.hpp" +#include "source.hpp" +#include "track_queue.hpp" + +namespace ui { +namespace modals { + +class AddToQueue : public Modal { + public: + AddToQueue(Screen*, + audio::TrackQueue&, + std::shared_ptr); + + private: + audio::TrackQueue& queue_; + std::shared_ptr item_; + lv_obj_t* container_; +}; + +} // namespace modals +} // namespace ui diff --git a/src/ui/modal_add_to_queue.cpp b/src/ui/modal_add_to_queue.cpp new file mode 100644 index 00000000..649ba3bd --- /dev/null +++ b/src/ui/modal_add_to_queue.cpp @@ -0,0 +1,127 @@ +/* + * Copyright 2023 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "modal_add_to_queue.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 "source.hpp" +#include "track_queue.hpp" +#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 { + +AddToQueue::AddToQueue(Screen* host, + audio::TrackQueue& queue, + std::shared_ptr item) + : Modal(host), queue_(queue), item_(item) { + 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* label = lv_label_create(root_); + lv_label_set_text(label, "This track"); + + 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_obj_t* btn = lv_btn_create(button_container); + label = lv_label_create(btn); + lv_label_set_text(label, "Play"); + lv_group_add_obj(group_, btn); + + lv_bind(btn, LV_EVENT_CLICKED, [this](lv_obj_t*) { + queue_.Clear(); + auto track = item_->Current(); + if (track) { + queue_.AddNext(*track); + } + events::Ui().Dispatch(internal::ModalCancelPressed{}); + }); + + bool has_queue = queue.GetCurrent().has_value(); + + if (has_queue) { + btn = lv_btn_create(button_container); + label = lv_label_create(btn); + lv_label_set_text(label, "Enqueue"); + lv_group_add_obj(group_, btn); + + lv_bind(btn, LV_EVENT_CLICKED, [this](lv_obj_t*) { + auto track = item_->Current(); + if (track) { + queue_.AddLast(*track); + } + events::Ui().Dispatch(internal::ModalCancelPressed{}); + }); + } + label = lv_label_create(root_); + lv_label_set_text(label, "All tracks"); + + 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); + + btn = lv_btn_create(button_container); + label = lv_label_create(btn); + lv_label_set_text(label, "Play"); + lv_group_add_obj(group_, btn); + + lv_bind(btn, LV_EVENT_CLICKED, [this](lv_obj_t*) { + queue_.Clear(); + queue_.IncludeNext(item_); + events::Ui().Dispatch(internal::ModalCancelPressed{}); + }); + + if (has_queue) { + btn = lv_btn_create(button_container); + label = lv_label_create(btn); + lv_label_set_text(label, "Enqueue"); + lv_group_add_obj(group_, btn); + + lv_bind(btn, LV_EVENT_CLICKED, [this](lv_obj_t*) { + queue_.IncludeLast(item_); + events::Ui().Dispatch(internal::ModalCancelPressed{}); + }); + } + + btn = lv_btn_create(root_); + label = lv_label_create(btn); + lv_label_set_text(label, "Cancel"); + lv_group_add_obj(group_, btn); + + lv_bind(btn, LV_EVENT_CLICKED, [](lv_obj_t*) { + events::Ui().Dispatch(internal::ModalCancelPressed{}); + }); +} + +} // namespace modals +} // namespace ui diff --git a/src/ui/screen_playing.cpp b/src/ui/screen_playing.cpp index 771da679..6b054f7f 100644 --- a/src/ui/screen_playing.cpp +++ b/src/ui/screen_playing.cpp @@ -284,6 +284,7 @@ Playing::Playing(models::TopBar& top_bar_model, 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_, ""); } diff --git a/src/ui/ui_fsm.cpp b/src/ui/ui_fsm.cpp index 03e20882..f8c9d3f3 100644 --- a/src/ui/ui_fsm.cpp +++ b/src/ui/ui_fsm.cpp @@ -19,6 +19,7 @@ #include "event_queue.hpp" #include "gpios.hpp" #include "lvgl_task.hpp" +#include "modal_add_to_queue.hpp" #include "modal_confirm.hpp" #include "model_playback.hpp" #include "nvs.hpp" @@ -287,11 +288,10 @@ void Browse::react(const internal::RecordSelected& ev) { 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( - sServices->database(), ev.initial_page, 0, ev.page, ev.record)); - ESP_LOGI(kTag, "transit to playing"); - transit(); + auto source = std::make_shared( + sServices->database(), ev.initial_page, 0, ev.page, ev.record); + sCurrentModal.reset( + new modals::AddToQueue(sCurrentScreen.get(), queue, source)); } else { ESP_LOGI(kTag, "selected record '%s'", record->text()->c_str()); auto cont = record->Expand(kRecordsPerPage);