From 0ea358ab8157d743dc07f12bde5fb34d03a02522 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Wed, 13 Sep 2023 10:09:04 +1000 Subject: [PATCH] Make the onboarding flow basically work. Much still to do! --- src/drivers/include/display.hpp | 2 +- src/drivers/include/storage.hpp | 7 +++ src/drivers/nvs.cpp | 2 +- src/system_fsm/include/service_locator.hpp | 13 +++- src/system_fsm/running.cpp | 10 +++ src/system_fsm/service_locator.cpp | 6 +- src/ui/include/screen_onboarding.hpp | 10 +++ src/ui/include/ui_events.hpp | 3 + src/ui/include/ui_fsm.hpp | 4 ++ src/ui/screen_onboarding.cpp | 57 ++++++++++++++--- src/ui/ui_fsm.cpp | 73 ++++++++++++++++++++-- 11 files changed, 166 insertions(+), 21 deletions(-) diff --git a/src/drivers/include/display.hpp b/src/drivers/include/display.hpp index a3c0e5ae..e50927d7 100644 --- a/src/drivers/include/display.hpp +++ b/src/drivers/include/display.hpp @@ -51,7 +51,7 @@ class Display { private: IGpios& gpio_; spi_device_handle_t handle_; - spi_transaction_t *transaction_; + spi_transaction_t* transaction_; bool display_on_; uint_fast8_t brightness_; diff --git a/src/drivers/include/storage.hpp b/src/drivers/include/storage.hpp index 0b0cb494..836bbbdc 100644 --- a/src/drivers/include/storage.hpp +++ b/src/drivers/include/storage.hpp @@ -21,6 +21,13 @@ namespace drivers { extern const char* kStoragePath; +enum class SdState { + kNotPresent, + kNotFormatted, + kNotMounted, + kMounted, +}; + class SdStorage { public: enum Error { diff --git a/src/drivers/nvs.cpp b/src/drivers/nvs.cpp index 0a466b16..11dde08c 100644 --- a/src/drivers/nvs.cpp +++ b/src/drivers/nvs.cpp @@ -190,7 +190,7 @@ auto NvsStorage::AmpCurrentVolume(uint16_t val) -> std::future { auto NvsStorage::HasShownOnboarding() -> std::future { return writer_->Dispatch([&]() -> bool { - uint8_t out = true; + uint8_t out = false; nvs_get_u8(handle_, kKeyOnboarded, &out); return out; }); diff --git a/src/system_fsm/include/service_locator.hpp b/src/system_fsm/include/service_locator.hpp index 00285ed5..1dcf0f5e 100644 --- a/src/system_fsm/include/service_locator.hpp +++ b/src/system_fsm/include/service_locator.hpp @@ -14,6 +14,7 @@ #include "gpios.hpp" #include "nvs.hpp" #include "samd.hpp" +#include "storage.hpp" #include "tag_parser.hpp" #include "touchwheel.hpp" #include "track_queue.hpp" @@ -22,7 +23,7 @@ namespace system_fsm { class ServiceLocator { public: - static auto instance() -> ServiceLocator&; + ServiceLocator(); auto gpios() -> drivers::Gpios& { assert(gpios_ != nullptr); @@ -45,6 +46,10 @@ class ServiceLocator { auto nvs(std::unique_ptr i) { nvs_ = std::move(i); } + auto sd() -> drivers::SdState& { return sd_; } + + auto sd(drivers::SdState s) { sd_ = s; } + auto bluetooth() -> drivers::Bluetooth& { assert(bluetooth_ != nullptr); return *bluetooth_; @@ -96,6 +101,10 @@ class ServiceLocator { queue_ = std::move(i); } + // Not copyable or movable. + ServiceLocator(const ServiceLocator&) = delete; + ServiceLocator& operator=(const ServiceLocator&) = delete; + private: std::unique_ptr gpios_; std::unique_ptr samd_; @@ -108,6 +117,8 @@ class ServiceLocator { std::shared_ptr database_; std::unique_ptr tag_parser_; + + drivers::SdState sd_; }; } // namespace system_fsm diff --git a/src/system_fsm/running.cpp b/src/system_fsm/running.cpp index e42429e7..70b4e99c 100644 --- a/src/system_fsm/running.cpp +++ b/src/system_fsm/running.cpp @@ -35,6 +35,15 @@ void Running::entry() { auto storage_res = drivers::SdStorage::Create(sServices->gpios()); if (storage_res.has_error()) { ESP_LOGW(kTag, "failed to mount!"); + switch (storage_res.error()) { + case drivers::SdStorage::FAILED_TO_MOUNT: + sServices->sd(drivers::SdState::kNotFormatted); + break; + case drivers::SdStorage::FAILED_TO_READ: + default: + sServices->sd(drivers::SdState::kNotPresent); + break; + } events::System().Dispatch(StorageError{}); events::Audio().Dispatch(StorageError{}); @@ -42,6 +51,7 @@ void Running::entry() { return; } sStorage.reset(storage_res.value()); + sServices->sd(drivers::SdState::kMounted); ESP_LOGI(kTag, "opening database"); sFileGatherer = new database::FileGathererImpl(); diff --git a/src/system_fsm/service_locator.cpp b/src/system_fsm/service_locator.cpp index 1d4d8a65..d8dcf44a 100644 --- a/src/system_fsm/service_locator.cpp +++ b/src/system_fsm/service_locator.cpp @@ -9,13 +9,11 @@ #include #include "nvs.hpp" +#include "storage.hpp" #include "touchwheel.hpp" namespace system_fsm { -auto ServiceLocator::instance() -> ServiceLocator& { - static ServiceLocator sInstance{}; - return sInstance; -} +ServiceLocator::ServiceLocator() : sd_(drivers::SdState::kNotPresent) {} } // namespace system_fsm diff --git a/src/ui/include/screen_onboarding.hpp b/src/ui/include/screen_onboarding.hpp index 73f2333d..81ce6d3a 100644 --- a/src/ui/include/screen_onboarding.hpp +++ b/src/ui/include/screen_onboarding.hpp @@ -42,11 +42,21 @@ class Controls : public Onboarding { Controls(); }; +class MissingSdCard : public Onboarding { + public: + MissingSdCard(); +}; + class FormatSdCard : public Onboarding { public: FormatSdCard(); }; +class InitDatabase : public Onboarding { + public: + InitDatabase(); +}; + } // namespace onboarding } // namespace screens diff --git a/src/ui/include/ui_events.hpp b/src/ui/include/ui_events.hpp index 297370db..fb3bb2d4 100644 --- a/src/ui/include/ui_events.hpp +++ b/src/ui/include/ui_events.hpp @@ -50,6 +50,9 @@ struct ShowSettingsPage : tinyfsm::Event { 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 de97354e..5363e1a4 100644 --- a/src/ui/include/ui_fsm.hpp +++ b/src/ui/include/ui_fsm.hpp @@ -67,6 +67,7 @@ class UiState : public tinyfsm::Fsm { virtual void react(const internal::ModalConfirmPressed&) { sCurrentModal.reset(); } + virtual void react(const internal::OnboardingNavigate&) {} virtual void react(const system_fsm::DisplayReady&) {} virtual void react(const system_fsm::BootComplete&) {} @@ -101,10 +102,13 @@ class Onboarding : public UiState { public: void entry() override; + void react(const internal::OnboardingNavigate&) override; + using UiState::react; private: uint8_t progress_; + bool has_formatted_; }; class Browse : public UiState { diff --git a/src/ui/screen_onboarding.cpp b/src/ui/screen_onboarding.cpp index e908b744..15f610a7 100644 --- a/src/ui/screen_onboarding.cpp +++ b/src/ui/screen_onboarding.cpp @@ -6,12 +6,15 @@ #include "screen_onboarding.hpp" +#include "core/lv_event.h" #include "core/lv_obj_pos.h" #include "draw/lv_draw_rect.h" +#include "event_queue.hpp" #include "extra/libs/qrcode/lv_qrcode.h" #include "extra/widgets/win/lv_win.h" #include "font/lv_symbol_def.h" #include "misc/lv_color.h" +#include "ui_events.hpp" #include "widgets/lv_btn.h" #include "widgets/lv_label.h" #include "widgets/lv_switch.h" @@ -21,17 +24,27 @@ static const char kManualUrl[] = "https://tangara.gay/onboarding"; namespace ui { namespace screens { +static void next_btn_cb(lv_event_t* ev) { + events::Ui().Dispatch(internal::OnboardingNavigate{.forwards = true}); +} + +static void prev_btn_cb(lv_event_t* ev) { + events::Ui().Dispatch(internal::OnboardingNavigate{.forwards = false}); +} + Onboarding::Onboarding(const std::string& title, bool show_prev, bool show_next) { window_ = lv_win_create(root_, 18); if (show_prev) { prev_button_ = lv_win_add_btn(window_, LV_SYMBOL_LEFT, 20); + lv_obj_add_event_cb(prev_button_, prev_btn_cb, LV_EVENT_CLICKED, NULL); lv_group_add_obj(group_, prev_button_); } title_ = lv_win_add_title(window_, title.c_str()); if (show_next) { next_button_ = lv_win_add_btn(window_, LV_SYMBOL_RIGHT, 20); + lv_obj_add_event_cb(next_button_, next_btn_cb, LV_EVENT_CLICKED, NULL); lv_group_add_obj(group_, next_button_); } @@ -79,23 +92,51 @@ Controls::Controls() : Onboarding("Controls", true, true) { create_radio_button(content_, "Scroll"); } +MissingSdCard::MissingSdCard() : Onboarding("SD Card", true, false) { + lv_obj_t* label = lv_label_create(content_); + lv_label_set_text(label, + "It looks like there isn't an SD card present. Please " + "insert one to continue."); + lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP); + lv_obj_set_size(label, lv_pct(100), LV_SIZE_CONTENT); +} + FormatSdCard::FormatSdCard() : Onboarding("SD Card", true, false) { lv_obj_t* label = lv_label_create(content_); - lv_label_set_text( - label, "this screen is optional. it offers to format your sd card."); + lv_label_set_text(label, + "It looks like there is an SD card present, but it has not " + "been formatted. Would you like to format it?"); + lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP); + lv_obj_set_size(label, lv_pct(100), LV_SIZE_CONTENT); lv_obj_t* button = lv_btn_create(content_); label = lv_label_create(button); lv_label_set_text(label, "Format"); - label = lv_label_create(content_); - lv_label_set_text(label, "Advanced"); + lv_obj_t* exfat_con = lv_obj_create(content_); + lv_obj_set_layout(exfat_con, LV_LAYOUT_FLEX); + lv_obj_set_size(exfat_con, lv_pct(100), LV_SIZE_CONTENT); + lv_obj_set_flex_flow(exfat_con, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(exfat_con, LV_FLEX_ALIGN_SPACE_EVENLY, + LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_START); - lv_obj_t* advanced_container = lv_obj_create(content_); - - label = lv_label_create(advanced_container); + label = lv_label_create(exfat_con); lv_label_set_text(label, "Use exFAT"); - lv_switch_create(advanced_container); + lv_switch_create(exfat_con); +} + +InitDatabase::InitDatabase() : Onboarding("Database", true, true) { + lv_obj_t* label = lv_label_create(content_); + lv_label_set_text(label, + "Many of Tangara's browsing features rely building an " + "index of your music. Would you like to do this now? It " + "will take some time if you have a large collection."); + lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP); + lv_obj_set_size(label, lv_pct(100), LV_SIZE_CONTENT); + + lv_obj_t* button = lv_btn_create(content_); + label = lv_label_create(button); + lv_label_set_text(label, "Index"); } } // namespace onboarding diff --git a/src/ui/ui_fsm.cpp b/src/ui/ui_fsm.cpp index 104f4f1f..c0c06bb0 100644 --- a/src/ui/ui_fsm.cpp +++ b/src/ui/ui_fsm.cpp @@ -29,6 +29,7 @@ #include "screen_splash.hpp" #include "screen_track_browser.hpp" #include "source.hpp" +#include "storage.hpp" #include "system_events.hpp" #include "touchwheel.hpp" #include "track_queue.hpp" @@ -160,18 +161,78 @@ void Splash::react(const system_fsm::BootComplete& ev) { } void Onboarding::entry() { + progress_ = 0; + has_formatted_ = false; sCurrentScreen.reset(new screens::onboarding::LinkToManual()); } -void Browse::entry() {} +void Onboarding::react(const internal::OnboardingNavigate& ev) { + int dir = ev.forwards ? 1 : -1; + progress_ += dir; + + for (;;) { + if (progress_ == 0) { + sCurrentScreen.reset(new screens::onboarding::LinkToManual()); + return; + } else if (progress_ == 1) { + sCurrentScreen.reset(new screens::onboarding::Controls()); + return; + } else if (progress_ == 2) { + if (sServices->sd() == drivers::SdState::kNotPresent) { + sCurrentScreen.reset(new screens::onboarding::MissingSdCard()); + return; + } else { + progress_ += dir; + } + } else if (progress_ == 3) { + if (sServices->sd() == drivers::SdState::kNotFormatted) { + has_formatted_ = true; + sCurrentScreen.reset(new screens::onboarding::FormatSdCard()); + return; + } else { + progress_ += dir; + } + } else if (progress_ == 4) { + if (has_formatted_) { + // If we formatted the SD card during this onboarding flow, then there + // is no music that needs indexing. + progress_ += dir; + } else { + sCurrentScreen.reset(new screens::onboarding::InitDatabase()); + return; + } + } else { + // We finished onboarding! Ensure this flow doesn't appear again. + sServices->nvs().HasShownOnboarding(true); + + transit(); + return; + } + } +} + +static bool sBrowseFirstEntry = true; + +void Browse::entry() { + if (sBrowseFirstEntry) { + auto db = sServices->database().lock(); + if (!db) { + return; + } + sCurrentScreen = std::make_shared(db->GetIndexes()); + sBrowseFirstEntry = false; + } +} void Browse::react(const system_fsm::StorageMounted& ev) { - auto db = sServices->database().lock(); - if (!db) { - // TODO(jacqueline): Hmm. - return; + if (sBrowseFirstEntry) { + auto db = sServices->database().lock(); + if (!db) { + return; + } + sCurrentScreen = std::make_shared(db->GetIndexes()); + sBrowseFirstEntry = false; } - PushScreen(std::make_shared(db->GetIndexes())); } void Browse::react(const internal::ShowNowPlaying& ev) {