summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/include/screen_settings.hpp22
-rw-r--r--src/ui/include/ui_fsm.hpp9
-rw-r--r--src/ui/screen_settings.cpp152
-rw-r--r--src/ui/ui_fsm.cpp13
4 files changed, 182 insertions, 14 deletions
diff --git a/src/ui/include/screen_settings.hpp b/src/ui/include/screen_settings.hpp
index caa23fd4..4e1936a2 100644
--- a/src/ui/include/screen_settings.hpp
+++ b/src/ui/include/screen_settings.hpp
@@ -8,9 +8,12 @@
#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"
@@ -28,7 +31,24 @@ class Settings : public MenuScreen {
class Bluetooth : public MenuScreen {
public:
- Bluetooth();
+ Bluetooth(drivers::Bluetooth& bt, drivers::NvsStorage& nvs);
+
+ auto ChangeEnabledState(bool enabled) -> void;
+ auto RefreshDevicesList() -> void;
+ auto OnDeviceSelected(size_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 {
diff --git a/src/ui/include/ui_fsm.hpp b/src/ui/include/ui_fsm.hpp
index 5363e1a4..9980dac6 100644
--- a/src/ui/include/ui_fsm.hpp
+++ b/src/ui/include/ui_fsm.hpp
@@ -17,6 +17,7 @@
#include "nvs.hpp"
#include "relative_wheel.hpp"
#include "screen_playing.hpp"
+#include "screen_settings.hpp"
#include "service_locator.hpp"
#include "tinyfsm.hpp"
@@ -72,6 +73,7 @@ class UiState : public tinyfsm::Fsm<UiState> {
virtual void react(const system_fsm::DisplayReady&) {}
virtual void react(const system_fsm::BootComplete&) {}
virtual void react(const system_fsm::StorageMounted&) {}
+ virtual void react(const system_fsm::BluetoothDevicesChanged&) {}
protected:
void PushScreen(std::shared_ptr<Screen>);
@@ -112,6 +114,7 @@ class Onboarding : public UiState {
};
class Browse : public UiState {
+ public:
void entry() override;
void react(const internal::RecordSelected&) override;
@@ -122,10 +125,16 @@ class Browse : public UiState {
void react(const internal::ShowSettingsPage&) override;
void react(const system_fsm::StorageMounted&) override;
+ void react(const system_fsm::BluetoothDevicesChanged&) override;
+
using UiState::react;
+
+ private:
+ std::weak_ptr<screens::Bluetooth> bluetooth_screen_;
};
class Playing : public UiState {
+ public:
void entry() override;
void exit() override;
diff --git a/src/ui/screen_settings.cpp b/src/ui/screen_settings.cpp
index d8c867dc..faeac865 100644
--- a/src/ui/screen_settings.cpp
+++ b/src/ui/screen_settings.cpp
@@ -9,8 +9,11 @@
#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_log.h"
@@ -100,7 +103,18 @@ static auto label_pair(lv_obj_t* parent,
return right_label;
}
-Bluetooth::Bluetooth() : MenuScreen("Bluetooth") {
+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));
+}
+
+Bluetooth::Bluetooth(drivers::Bluetooth& bt, drivers::NvsStorage& nvs)
+ : MenuScreen("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");
@@ -108,20 +122,134 @@ Bluetooth::Bluetooth() : MenuScreen("Bluetooth") {
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");
- lv_obj_t* devices_list = lv_list_create(content_);
- lv_list_add_text(devices_list, "My Headphones");
- lv_group_add_obj(group_,
- lv_list_add_btn(devices_list, NULL, "Something else"));
- lv_group_add_obj(group_, lv_list_add_btn(devices_list, NULL, "A car??"));
- lv_group_add_obj(group_,
- lv_list_add_btn(devices_list, NULL, "OLED TV ANDROID"));
- lv_group_add_obj(
- group_, lv_list_add_btn(devices_list, NULL, "there could be another"));
- lv_group_add_obj(group_, lv_list_add_btn(devices_list, NULL,
- "this one has a really long name"));
+ devices_list_ = lv_list_create(content_);
+ RefreshDevicesList();
+}
+
+auto Bluetooth::ChangeEnabledState(bool enabled) -> void {
+ if (enabled) {
+ events::System().RunOnTask([&]() { bt_.Enable(); });
+ nvs_.OutputMode(drivers::NvsStorage::Output::kBluetooth).get();
+ } else {
+ events::System().RunOnTask([&]() { bt_.Disable(); });
+ nvs_.OutputMode(drivers::NvsStorage::Output::kHeadphones).get();
+ }
+ 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().get();
+
+ // 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());
+ 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(size_t index) -> void {
+ // 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) {
diff --git a/src/ui/ui_fsm.cpp b/src/ui/ui_fsm.cpp
index c0c06bb0..18e9caf4 100644
--- a/src/ui/ui_fsm.cpp
+++ b/src/ui/ui_fsm.cpp
@@ -241,12 +241,16 @@ void Browse::react(const internal::ShowNowPlaying& ev) {
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());
break;
case internal::ShowSettingsPage::Page::kBluetooth:
- screen.reset(new screens::Bluetooth());
+ bt_screen = std::make_shared<screens::Bluetooth>(sServices->bluetooth(),
+ sServices->nvs());
+ screen = bt_screen;
+ bluetooth_screen_ = bt_screen;
break;
case internal::ShowSettingsPage::Page::kHeadphones:
screen.reset(new screens::Headphones(sServices->nvs()));
@@ -315,6 +319,13 @@ void Browse::react(const internal::BackPressed& ev) {
PopScreen();
}
+void Browse::react(const system_fsm::BluetoothDevicesChanged&) {
+ auto bt = bluetooth_screen_.lock();
+ if (bt) {
+ bt->RefreshDevicesList();
+ }
+}
+
static std::shared_ptr<screens::Playing> sPlayingScreen;
void Playing::entry() {