From 93b52e2599541d33b528e409c30992b31f14614d Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Mon, 20 Jan 2025 22:30:38 +0100 Subject: [PATCH] Update more apps --- .../Private/app/screenshot/ScreenshotUi.h | 42 --- .../Source/app/screenshot/Screenshot.cpp | 274 +++++++++++++++++- .../Source/app/screenshot/ScreenshotUi.cpp | 256 ---------------- .../app/timedatesettings/TimeDateSettings.cpp | 44 +-- Tactility/Source/app/timezone/TimeZone.cpp | 216 +++++++------- 5 files changed, 388 insertions(+), 444 deletions(-) delete mode 100644 Tactility/Private/app/screenshot/ScreenshotUi.h delete mode 100644 Tactility/Source/app/screenshot/ScreenshotUi.cpp diff --git a/Tactility/Private/app/screenshot/ScreenshotUi.h b/Tactility/Private/app/screenshot/ScreenshotUi.h deleted file mode 100644 index 36919d61..00000000 --- a/Tactility/Private/app/screenshot/ScreenshotUi.h +++ /dev/null @@ -1,42 +0,0 @@ -#include "Timer.h" -#include "TactilityConfig.h" - -#if TT_FEATURE_SCREENSHOT_ENABLED - -#pragma once - -#include "app/AppContext.h" -#include "lvgl.h" - -namespace tt::app::screenshot { - -class ScreenshotUi { - - lv_obj_t* modeDropdown = nullptr; - lv_obj_t* pathTextArea = nullptr; - lv_obj_t* startStopButtonLabel = nullptr; - lv_obj_t* timerWrapper = nullptr; - lv_obj_t* delayTextArea = nullptr; - std::unique_ptr updateTimer; - - void createTimerSettingsWidgets(lv_obj_t* parent); - void createModeSettingWidgets(lv_obj_t* parent); - void createFilePathWidgets(lv_obj_t* parent); - - void updateScreenshotMode(); - -public: - - ScreenshotUi(); - ~ScreenshotUi(); - - void createWidgets(const AppContext& app, lv_obj_t* parent); - void onStartPressed(); - void onModeSet(); - void onTimerTick(); -}; - - -} // namespace - -#endif diff --git a/Tactility/Source/app/screenshot/Screenshot.cpp b/Tactility/Source/app/screenshot/Screenshot.cpp index a33289c7..9f6b95c3 100644 --- a/Tactility/Source/app/screenshot/Screenshot.cpp +++ b/Tactility/Source/app/screenshot/Screenshot.cpp @@ -1,26 +1,282 @@ #include "TactilityConfig.h" +#include +#include #if TT_FEATURE_SCREENSHOT_ENABLED +#include "TactilityHeadless.h" #include "app/App.h" #include "app/AppManifest.h" -#include "app/screenshot/ScreenshotUi.h" -#include +#include "lvgl/LvglSync.h" +#include "lvgl/Toolbar.h" +#include "service/gui/Gui.h" +#include "service/loader/Loader.h" +#include "service/screenshot/Screenshot.h" + +#define TAG "screenshot" namespace tt::app::screenshot { +extern const AppManifest manifest; + class ScreenshotApp : public App { - void onShow(AppContext& app, lv_obj_t* parent) override { - auto ui = std::static_pointer_cast(app.getData()); - ui->createWidgets(app, parent); + lv_obj_t* modeDropdown = nullptr; + lv_obj_t* pathTextArea = nullptr; + lv_obj_t* startStopButtonLabel = nullptr; + lv_obj_t* timerWrapper = nullptr; + lv_obj_t* delayTextArea = nullptr; + std::unique_ptr updateTimer; + + void createTimerSettingsWidgets(lv_obj_t* parent); + void createModeSettingWidgets(lv_obj_t* parent); + void createFilePathWidgets(lv_obj_t* parent); + + void updateScreenshotMode(); + +public: + + ScreenshotApp(); + ~ScreenshotApp(); + + void onShow(AppContext& app, lv_obj_t* parent) override; + void onStartPressed(); + void onModeSet(); + void onTimerTick(); +}; + + +/** Returns the app data if the app is active. Note that this could clash if the same app is started twice and a background thread is slow. */ +std::shared_ptr _Nullable optApp() { + auto appContext = service::loader::getCurrentApp(); + if (appContext != nullptr && appContext->getManifest().id == manifest.id) { + return std::static_pointer_cast(appContext->getApp()); + } else { + return nullptr; } +} - void onStart(AppContext& app) override { - auto ui = std::make_shared(); - app.setData(ui); // Ensure data gets deleted when no more in use +static void onStartPressedCallback(TT_UNUSED lv_event_t* event) { + auto app = optApp(); + if (app != nullptr) { + app->onStartPressed(); } -}; +} + +static void onModeSetCallback(TT_UNUSED lv_event_t* event) { + auto app = optApp(); + if (app != nullptr) { + app->onModeSet(); + } +} + +static void onTimerCallback(TT_UNUSED std::shared_ptr context) { + auto app = optApp(); + if (app != nullptr) { + app->onTimerTick(); + } +} + +ScreenshotApp::ScreenshotApp() { + updateTimer = std::make_unique(Timer::Type::Periodic, onTimerCallback, nullptr); +} + +ScreenshotApp::~ScreenshotApp() { + if (updateTimer->isRunning()) { + updateTimer->stop(); + } +} + +void ScreenshotApp::onTimerTick() { + auto lvgl_lock = lvgl::getLvglSyncLockable()->scoped(); + if (lvgl_lock->lock(50 / portTICK_PERIOD_MS)) { + updateScreenshotMode(); + } +} + +void ScreenshotApp::onModeSet() { + updateScreenshotMode(); +} + +void ScreenshotApp::onStartPressed() { + auto service = service::screenshot::optScreenshotService(); + if (service == nullptr) { + TT_LOG_E(TAG, "Service not found/running"); + return; + } + + if (service->isTaskStarted()) { + TT_LOG_I(TAG, "Stop screenshot"); + service->stop(); + } else { + uint32_t selected = lv_dropdown_get_selected(modeDropdown); + const char* path = lv_textarea_get_text(pathTextArea); + if (selected == 0) { + TT_LOG_I(TAG, "Start timed screenshots"); + const char* delay_text = lv_textarea_get_text(delayTextArea); + int delay = atoi(delay_text); + if (delay > 0) { + service->startTimed(path, delay, 1); + } else { + TT_LOG_W(TAG, "Ignored screenshot start because delay was 0"); + } + } else { + TT_LOG_I(TAG, "Start app screenshots"); + service->startApps(path); + } + } + + updateScreenshotMode(); +} + +void ScreenshotApp::updateScreenshotMode() { + auto service = service::screenshot::optScreenshotService(); + if (service == nullptr) { + TT_LOG_E(TAG, "Service not found/running"); + return; + } + + lv_obj_t* label = startStopButtonLabel; + if (service->isTaskStarted()) { + lv_label_set_text(label, "Stop"); + } else { + lv_label_set_text(label, "Start"); + } + + uint32_t selected = lv_dropdown_get_selected(modeDropdown); + if (selected == 0) { // Timer + lv_obj_remove_flag(timerWrapper, LV_OBJ_FLAG_HIDDEN); + } else { + lv_obj_add_flag(timerWrapper, LV_OBJ_FLAG_HIDDEN); + } +} + + +void ScreenshotApp::createModeSettingWidgets(lv_obj_t* parent) { + auto service = service::screenshot::optScreenshotService(); + if (service == nullptr) { + TT_LOG_E(TAG, "Service not found/running"); + return; + } + + auto* mode_wrapper = lv_obj_create(parent); + lv_obj_set_size(mode_wrapper, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(mode_wrapper, 0, 0); + lv_obj_set_style_border_width(mode_wrapper, 0, 0); + + auto* mode_label = lv_label_create(mode_wrapper); + lv_label_set_text(mode_label, "Mode:"); + lv_obj_align(mode_label, LV_ALIGN_LEFT_MID, 0, 0); + + modeDropdown = lv_dropdown_create(mode_wrapper); + lv_dropdown_set_options(modeDropdown, "Timer\nApp start"); + lv_obj_align_to(modeDropdown, mode_label, LV_ALIGN_OUT_RIGHT_MID, 8, 0); + lv_obj_add_event_cb(modeDropdown, onModeSetCallback, LV_EVENT_VALUE_CHANGED, nullptr); + service::screenshot::Mode mode = service->getMode(); + if (mode == service::screenshot::Mode::Apps) { + lv_dropdown_set_selected(modeDropdown, 1); + } + + auto* button = lv_button_create(mode_wrapper); + lv_obj_align(button, LV_ALIGN_RIGHT_MID, 0, 0); + lv_obj_add_event_cb(button, &onStartPressedCallback, LV_EVENT_SHORT_CLICKED, nullptr); + startStopButtonLabel = lv_label_create(button); + lv_obj_align(startStopButtonLabel, LV_ALIGN_CENTER, 0, 0); +} + +void ScreenshotApp::createFilePathWidgets(lv_obj_t* parent) { + auto* path_wrapper = lv_obj_create(parent); + lv_obj_set_size(path_wrapper, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(path_wrapper, 0, 0); + lv_obj_set_style_border_width(path_wrapper, 0, 0); + lv_obj_set_flex_flow(path_wrapper, LV_FLEX_FLOW_ROW); + + auto* label_wrapper = lv_obj_create(path_wrapper); + lv_obj_set_style_border_width(label_wrapper, 0, 0); + lv_obj_set_style_pad_all(label_wrapper, 0, 0); + lv_obj_set_size(label_wrapper, 44, 36); + auto* path_label = lv_label_create(label_wrapper); + lv_label_set_text(path_label, "Path:"); + lv_obj_align(path_label, LV_ALIGN_LEFT_MID, 0, 0); + + pathTextArea = lv_textarea_create(path_wrapper); + lv_textarea_set_one_line(pathTextArea, true); + lv_obj_set_flex_grow(pathTextArea, 1); + if (kernel::getPlatform() == kernel::PlatformEsp) { + auto sdcard = tt::hal::getConfiguration()->sdcard; + if (sdcard != nullptr && sdcard->getState() == hal::SdCard::State::Mounted) { + lv_textarea_set_text(pathTextArea, "A:/sdcard"); + } else { + lv_textarea_set_text(pathTextArea, "Error: no SD card"); + } + } else { // PC + lv_textarea_set_text(pathTextArea, "A:"); + } +} + +void ScreenshotApp::createTimerSettingsWidgets(lv_obj_t* parent) { + timerWrapper = lv_obj_create(parent); + lv_obj_set_size(timerWrapper, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(timerWrapper, 0, 0); + lv_obj_set_style_border_width(timerWrapper, 0, 0); + + auto* delay_wrapper = lv_obj_create(timerWrapper); + lv_obj_set_size(delay_wrapper, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(delay_wrapper, 0, 0); + lv_obj_set_style_border_width(delay_wrapper, 0, 0); + lv_obj_set_flex_flow(delay_wrapper, LV_FLEX_FLOW_ROW); + + auto* delay_label_wrapper = lv_obj_create(delay_wrapper); + lv_obj_set_style_border_width(delay_label_wrapper, 0, 0); + lv_obj_set_style_pad_all(delay_label_wrapper, 0, 0); + lv_obj_set_size(delay_label_wrapper, 44, 36); + auto* delay_label = lv_label_create(delay_label_wrapper); + lv_label_set_text(delay_label, "Delay:"); + lv_obj_align(delay_label, LV_ALIGN_LEFT_MID, 0, 0); + + delayTextArea = lv_textarea_create(delay_wrapper); + lv_textarea_set_one_line(delayTextArea, true); + lv_textarea_set_accepted_chars(delayTextArea, "0123456789"); + lv_textarea_set_text(delayTextArea, "10"); + lv_obj_set_flex_grow(delayTextArea, 1); + + auto* delay_unit_label_wrapper = lv_obj_create(delay_wrapper); + lv_obj_set_style_border_width(delay_unit_label_wrapper, 0, 0); + lv_obj_set_style_pad_all(delay_unit_label_wrapper, 0, 0); + lv_obj_set_size(delay_unit_label_wrapper, LV_SIZE_CONTENT, 36); + auto* delay_unit_label = lv_label_create(delay_unit_label_wrapper); + lv_obj_align(delay_unit_label, LV_ALIGN_LEFT_MID, 0, 0); + lv_label_set_text(delay_unit_label, "seconds"); +} + +void ScreenshotApp::onShow(AppContext& appContext, lv_obj_t* parent) { + if (updateTimer->isRunning()) { + updateTimer->stop(); + } + + lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); + auto* toolbar = lvgl::toolbar_create(parent, appContext); + lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0); + + auto* wrapper = lv_obj_create(parent); + lv_obj_set_width(wrapper, LV_PCT(100)); + lv_obj_set_flex_grow(wrapper, 1); + lv_obj_set_style_border_width(wrapper, 0, 0); + lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_COLUMN); + + createModeSettingWidgets(wrapper); + createFilePathWidgets(wrapper); + createTimerSettingsWidgets(wrapper); + + service::gui::keyboardAddTextArea(delayTextArea); + service::gui::keyboardAddTextArea(pathTextArea); + + updateScreenshotMode(); + + if (!updateTimer->isRunning()) { + updateTimer->start(500 / portTICK_PERIOD_MS); + } +} extern const AppManifest manifest = { .id = "Screenshot", diff --git a/Tactility/Source/app/screenshot/ScreenshotUi.cpp b/Tactility/Source/app/screenshot/ScreenshotUi.cpp deleted file mode 100644 index e0a9bb69..00000000 --- a/Tactility/Source/app/screenshot/ScreenshotUi.cpp +++ /dev/null @@ -1,256 +0,0 @@ -#include "TactilityConfig.h" - -#if TT_FEATURE_SCREENSHOT_ENABLED - -#include "app/screenshot/ScreenshotUi.h" - -#include "TactilityCore.h" -#include "hal/SdCard.h" -#include "service/gui/Gui.h" -#include "service/loader/Loader.h" -#include "service/screenshot/Screenshot.h" -#include "lvgl/Toolbar.h" -#include "TactilityHeadless.h" -#include "lvgl/LvglSync.h" - -namespace tt::app::screenshot { - -#define TAG "screenshot_ui" - -extern AppManifest manifest; - -/** Returns the app data if the app is active. Note that this could clash if the same app is started twice and a background thread is slow. */ -std::shared_ptr _Nullable optScreenshotUi() { - auto app = service::loader::getCurrentApp(); - if (app != nullptr && app->getManifest().id == manifest.id) { - return std::static_pointer_cast(app->getData()); - } else { - return nullptr; - } -} - -static void onStartPressedCallback(TT_UNUSED lv_event_t* event) { - auto ui = optScreenshotUi(); - if (ui != nullptr) { - ui->onStartPressed(); - } -} - -static void onModeSetCallback(TT_UNUSED lv_event_t* event) { - auto ui = optScreenshotUi(); - if (ui != nullptr) { - ui->onModeSet(); - } -} - -static void onTimerCallback(TT_UNUSED std::shared_ptr context) { - auto screenshot_ui = optScreenshotUi(); - if (screenshot_ui != nullptr) { - screenshot_ui->onTimerTick(); - } -} - -ScreenshotUi::ScreenshotUi() { - updateTimer = std::make_unique(Timer::Type::Periodic, onTimerCallback, nullptr); -} - -ScreenshotUi::~ScreenshotUi() { - if (updateTimer->isRunning()) { - updateTimer->stop(); - } -} - -void ScreenshotUi::onTimerTick() { - auto lvgl_lock = lvgl::getLvglSyncLockable()->scoped(); - if (lvgl_lock->lock(50 / portTICK_PERIOD_MS)) { - updateScreenshotMode(); - } -} - -void ScreenshotUi::onModeSet() { - updateScreenshotMode(); -} - -void ScreenshotUi::onStartPressed() { - auto service = service::screenshot::optScreenshotService(); - if (service == nullptr) { - TT_LOG_E(TAG, "Service not found/running"); - return; - } - - if (service->isTaskStarted()) { - TT_LOG_I(TAG, "Stop screenshot"); - service->stop(); - } else { - uint32_t selected = lv_dropdown_get_selected(modeDropdown); - const char* path = lv_textarea_get_text(pathTextArea); - if (selected == 0) { - TT_LOG_I(TAG, "Start timed screenshots"); - const char* delay_text = lv_textarea_get_text(delayTextArea); - int delay = atoi(delay_text); - if (delay > 0) { - service->startTimed(path, delay, 1); - } else { - TT_LOG_W(TAG, "Ignored screenshot start because delay was 0"); - } - } else { - TT_LOG_I(TAG, "Start app screenshots"); - service->startApps(path); - } - } - - updateScreenshotMode(); -} - -void ScreenshotUi::updateScreenshotMode() { - auto service = service::screenshot::optScreenshotService(); - if (service == nullptr) { - TT_LOG_E(TAG, "Service not found/running"); - return; - } - - lv_obj_t* label = startStopButtonLabel; - if (service->isTaskStarted()) { - lv_label_set_text(label, "Stop"); - } else { - lv_label_set_text(label, "Start"); - } - - uint32_t selected = lv_dropdown_get_selected(modeDropdown); - if (selected == 0) { // Timer - lv_obj_remove_flag(timerWrapper, LV_OBJ_FLAG_HIDDEN); - } else { - lv_obj_add_flag(timerWrapper, LV_OBJ_FLAG_HIDDEN); - } -} - - -void ScreenshotUi::createModeSettingWidgets(lv_obj_t* parent) { - auto service = service::screenshot::optScreenshotService(); - if (service == nullptr) { - TT_LOG_E(TAG, "Service not found/running"); - return; - } - - auto* mode_wrapper = lv_obj_create(parent); - lv_obj_set_size(mode_wrapper, LV_PCT(100), LV_SIZE_CONTENT); - lv_obj_set_style_pad_all(mode_wrapper, 0, 0); - lv_obj_set_style_border_width(mode_wrapper, 0, 0); - - auto* mode_label = lv_label_create(mode_wrapper); - lv_label_set_text(mode_label, "Mode:"); - lv_obj_align(mode_label, LV_ALIGN_LEFT_MID, 0, 0); - - modeDropdown = lv_dropdown_create(mode_wrapper); - lv_dropdown_set_options(modeDropdown, "Timer\nApp start"); - lv_obj_align_to(modeDropdown, mode_label, LV_ALIGN_OUT_RIGHT_MID, 8, 0); - lv_obj_add_event_cb(modeDropdown, onModeSetCallback, LV_EVENT_VALUE_CHANGED, nullptr); - service::screenshot::Mode mode = service->getMode(); - if (mode == service::screenshot::Mode::Apps) { - lv_dropdown_set_selected(modeDropdown, 1); - } - - auto* button = lv_button_create(mode_wrapper); - lv_obj_align(button, LV_ALIGN_RIGHT_MID, 0, 0); - lv_obj_add_event_cb(button, &onStartPressedCallback, LV_EVENT_SHORT_CLICKED, nullptr); - startStopButtonLabel = lv_label_create(button); - lv_obj_align(startStopButtonLabel, LV_ALIGN_CENTER, 0, 0); -} - -void ScreenshotUi::createFilePathWidgets(lv_obj_t* parent) { - auto* path_wrapper = lv_obj_create(parent); - lv_obj_set_size(path_wrapper, LV_PCT(100), LV_SIZE_CONTENT); - lv_obj_set_style_pad_all(path_wrapper, 0, 0); - lv_obj_set_style_border_width(path_wrapper, 0, 0); - lv_obj_set_flex_flow(path_wrapper, LV_FLEX_FLOW_ROW); - - auto* label_wrapper = lv_obj_create(path_wrapper); - lv_obj_set_style_border_width(label_wrapper, 0, 0); - lv_obj_set_style_pad_all(label_wrapper, 0, 0); - lv_obj_set_size(label_wrapper, 44, 36); - auto* path_label = lv_label_create(label_wrapper); - lv_label_set_text(path_label, "Path:"); - lv_obj_align(path_label, LV_ALIGN_LEFT_MID, 0, 0); - - pathTextArea = lv_textarea_create(path_wrapper); - lv_textarea_set_one_line(pathTextArea, true); - lv_obj_set_flex_grow(pathTextArea, 1); - if (kernel::getPlatform() == kernel::PlatformEsp) { - auto sdcard = tt::hal::getConfiguration()->sdcard; - if (sdcard != nullptr && sdcard->getState() == hal::SdCard::State::Mounted) { - lv_textarea_set_text(pathTextArea, "A:/sdcard"); - } else { - lv_textarea_set_text(pathTextArea, "Error: no SD card"); - } - } else { // PC - lv_textarea_set_text(pathTextArea, "A:"); - } -} - -void ScreenshotUi::createTimerSettingsWidgets(lv_obj_t* parent) { - timerWrapper = lv_obj_create(parent); - lv_obj_set_size(timerWrapper, LV_PCT(100), LV_SIZE_CONTENT); - lv_obj_set_style_pad_all(timerWrapper, 0, 0); - lv_obj_set_style_border_width(timerWrapper, 0, 0); - - auto* delay_wrapper = lv_obj_create(timerWrapper); - lv_obj_set_size(delay_wrapper, LV_PCT(100), LV_SIZE_CONTENT); - lv_obj_set_style_pad_all(delay_wrapper, 0, 0); - lv_obj_set_style_border_width(delay_wrapper, 0, 0); - lv_obj_set_flex_flow(delay_wrapper, LV_FLEX_FLOW_ROW); - - auto* delay_label_wrapper = lv_obj_create(delay_wrapper); - lv_obj_set_style_border_width(delay_label_wrapper, 0, 0); - lv_obj_set_style_pad_all(delay_label_wrapper, 0, 0); - lv_obj_set_size(delay_label_wrapper, 44, 36); - auto* delay_label = lv_label_create(delay_label_wrapper); - lv_label_set_text(delay_label, "Delay:"); - lv_obj_align(delay_label, LV_ALIGN_LEFT_MID, 0, 0); - - delayTextArea = lv_textarea_create(delay_wrapper); - lv_textarea_set_one_line(delayTextArea, true); - lv_textarea_set_accepted_chars(delayTextArea, "0123456789"); - lv_textarea_set_text(delayTextArea, "10"); - lv_obj_set_flex_grow(delayTextArea, 1); - - auto* delay_unit_label_wrapper = lv_obj_create(delay_wrapper); - lv_obj_set_style_border_width(delay_unit_label_wrapper, 0, 0); - lv_obj_set_style_pad_all(delay_unit_label_wrapper, 0, 0); - lv_obj_set_size(delay_unit_label_wrapper, LV_SIZE_CONTENT, 36); - auto* delay_unit_label = lv_label_create(delay_unit_label_wrapper); - lv_obj_align(delay_unit_label, LV_ALIGN_LEFT_MID, 0, 0); - lv_label_set_text(delay_unit_label, "seconds"); -} - -void ScreenshotUi::createWidgets(const AppContext& app, lv_obj_t* parent) { - if (updateTimer->isRunning()) { - updateTimer->stop(); - } - - lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); - auto* toolbar = lvgl::toolbar_create(parent, app); - lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0); - - auto* wrapper = lv_obj_create(parent); - lv_obj_set_width(wrapper, LV_PCT(100)); - lv_obj_set_flex_grow(wrapper, 1); - lv_obj_set_style_border_width(wrapper, 0, 0); - lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_COLUMN); - - createModeSettingWidgets(wrapper); - createFilePathWidgets(wrapper); - createTimerSettingsWidgets(wrapper); - - service::gui::keyboardAddTextArea(delayTextArea); - service::gui::keyboardAddTextArea(pathTextArea); - - updateScreenshotMode(); - - if (!updateTimer->isRunning()) { - updateTimer->start(500 / portTICK_PERIOD_MS); - } -} - -} // namespace - -#endif \ No newline at end of file diff --git a/Tactility/Source/app/timedatesettings/TimeDateSettings.cpp b/Tactility/Source/app/timedatesettings/TimeDateSettings.cpp index e4619351..c86d9928 100644 --- a/Tactility/Source/app/timedatesettings/TimeDateSettings.cpp +++ b/Tactility/Source/app/timedatesettings/TimeDateSettings.cpp @@ -13,41 +13,26 @@ namespace tt::app::timedatesettings { extern const AppManifest manifest; -struct Data { +class TimeDateSettingsApp : public App { + +private: + Mutex mutex = Mutex(Mutex::Type::Recursive); lv_obj_t* regionLabelWidget = nullptr; -}; -/** Returns the app data if the app is active. Note that this could clash if the same app is started twice and a background thread is slow. */ -std::shared_ptr _Nullable optData() { - auto app = service::loader::getCurrentApp(); - if (app != nullptr && app->getManifest().id == manifest.id) { - return std::static_pointer_cast(app->getData()); - } else { - return nullptr; + static void onConfigureTimeZonePressed(TT_UNUSED lv_event_t* event) { + timezone::start(); } -} - -static void onConfigureTimeZonePressed(TT_UNUSED lv_event_t* event) { - timezone::start(); -} -static void onTimeFormatChanged(lv_event_t* event) { - auto* widget = lv_event_get_target_obj(event); - bool show_24 = lv_obj_has_state(widget, LV_STATE_CHECKED); - time::setTimeFormat24Hour(show_24); -} - -class TimeDateSettingsApp : public App { - - void onStart(AppContext& app) override { - auto data = std::make_shared(); - app.setData(data); + static void onTimeFormatChanged(lv_event_t* event) { + auto* widget = lv_event_get_target_obj(event); + bool show_24 = lv_obj_has_state(widget, LV_STATE_CHECKED); + time::setTimeFormat24Hour(show_24); } - void onShow(AppContext& app, lv_obj_t* parent) override { - auto data = std::static_pointer_cast(app.getData()); +public: + void onShow(AppContext& app, lv_obj_t* parent) override { lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); lvgl::toolbar_create(parent, app); @@ -72,7 +57,7 @@ class TimeDateSettingsApp : public App { if (timeZoneName.empty()) { timeZoneName = "not set"; } - data->regionLabelWidget = region_label; + regionLabelWidget = region_label; lv_label_set_text(region_label, timeZoneName.c_str()); // TODO: Find out why Y offset is needed lv_obj_align_to(region_label, region_prefix_label, LV_ALIGN_OUT_RIGHT_MID, 0, 8); @@ -105,7 +90,6 @@ class TimeDateSettingsApp : public App { void onResult(AppContext& app, Result result, const Bundle& bundle) override { if (result == Result::Ok) { - auto data = std::static_pointer_cast(app.getData()); auto name = timezone::getResultName(bundle); auto code = timezone::getResultCode(bundle); TT_LOG_I(TAG, "Result name=%s code=%s", name.c_str(), code.c_str()); @@ -113,7 +97,7 @@ class TimeDateSettingsApp : public App { if (!name.empty()) { if (lvgl::lock(100 / portTICK_PERIOD_MS)) { - lv_label_set_text(data->regionLabelWidget, name.c_str()); + lv_label_set_text(regionLabelWidget, name.c_str()); lvgl::unlock(); } } diff --git a/Tactility/Source/app/timezone/TimeZone.cpp b/Tactility/Source/app/timezone/TimeZone.cpp index 84d8dd2f..5728da1b 100644 --- a/Tactility/Source/app/timezone/TimeZone.cpp +++ b/Tactility/Source/app/timezone/TimeZone.cpp @@ -26,16 +26,6 @@ struct TimeZoneEntry { std::string code; }; -struct Data { - Mutex mutex; - std::vector entries; - std::unique_ptr updateTimer; - lv_obj_t* listWidget = nullptr; - lv_obj_t* filterTextareaWidget = nullptr; -}; - -static void updateList(std::shared_ptr& data); - static bool parseEntry(const std::string& input, std::string& outName, std::string& outCode) { std::string partial_strip = input.substr(1, input.size() - 3); auto first_end_quote = partial_strip.find('"'); @@ -72,126 +62,140 @@ void setResultCode(std::shared_ptr& bundle, const std::string& code) { // endregion -static void onUpdateTimer(std::shared_ptr context) { - auto data = std::static_pointer_cast(context); - updateList(data); -} -static void onTextareaValueChanged(TT_UNUSED lv_event_t* e) { - auto app = service::loader::getCurrentApp(); - tt_assert(app != nullptr); - auto app_data = app->getData(); - auto data = std::static_pointer_cast(app_data); +class TimeZoneApp : public App { - if (data->mutex.lock(100 / portTICK_PERIOD_MS)) { - if (data->updateTimer->isRunning()) { - data->updateTimer->stop(); - } +private: - data->updateTimer->start(500 / portTICK_PERIOD_MS); + Mutex mutex; + std::vector entries; + std::unique_ptr updateTimer; + lv_obj_t* listWidget = nullptr; + lv_obj_t* filterTextareaWidget = nullptr; - data->mutex.unlock(); + static void onTextareaValueChangedCallback(TT_UNUSED lv_event_t* e) { + auto* app = (TimeZoneApp*)lv_event_get_user_data(e); + app->onTextareaValueChanged(e); } -} -static void onListItemSelected(lv_event_t* e) { - auto index = reinterpret_cast(lv_event_get_user_data(e)); - TT_LOG_I(TAG, "Selected item at index %zu", index); - auto app = service::loader::getCurrentApp(); - tt_assert(app != nullptr); - auto data = std::static_pointer_cast(app->getData()); + void onTextareaValueChanged(TT_UNUSED lv_event_t* e) { + if (mutex.lock(100 / portTICK_PERIOD_MS)) { + if (updateTimer->isRunning()) { + updateTimer->stop(); + } - auto& entry = data->entries[index]; + updateTimer->start(500 / portTICK_PERIOD_MS); - auto bundle = std::make_shared(); - setResultName(bundle, entry.name); - setResultCode(bundle, entry.code); - app->setResult(app::Result::Ok, bundle); + mutex.unlock(); + } + } - service::loader::stopApp(); -} + static void onListItemSelectedCallback(lv_event_t* e) { + auto index = reinterpret_cast(lv_event_get_user_data(e)); + auto appContext = service::loader::getCurrentApp(); + if (appContext != nullptr && appContext->getManifest().id == manifest.id) { + auto app = std::static_pointer_cast(appContext->getApp()); + app->onListItemSelected(index); + } + } -static void createListItem(lv_obj_t* list, const std::string& title, size_t index) { - lv_obj_t* btn = lv_list_add_button(list, nullptr, title.c_str()); - lv_obj_add_event_cb(btn, &onListItemSelected, LV_EVENT_SHORT_CLICKED, (void*)index); -} + void onListItemSelected(std::size_t index) { + TT_LOG_I(TAG, "Selected item at index %zu", index); + + auto& entry = entries[index]; + + auto bundle = std::make_shared(); + setResultName(bundle, entry.name); + setResultCode(bundle, entry.code); -static void readTimeZones(const std::shared_ptr& data, std::string filter) { - auto path = std::string(MOUNT_POINT_SYSTEM) + "/timezones.csv"; - auto* file = fopen(path.c_str(), "rb"); - if (file == nullptr) { - TT_LOG_E(TAG, "Failed to open %s", path.c_str()); - return; + service::loader::getCurrentApp()->setResult(app::Result::Ok, bundle); + service::loader::stopApp(); } - char line[96]; - std::string name; - std::string code; - uint32_t count = 0; - std::vector entries; - while (fgets(line, 96, file)) { - if (parseEntry(line, name, code)) { - if (tt::string::lowercase(name).find(filter) != std::string::npos) { - count++; - entries.push_back({ - .name = name, - .code = code - }); - - // Safety guard - if (count > 50) { - // TODO: Show warning that we're not displaying a complete list - break; + + static void createListItem(lv_obj_t* list, const std::string& title, size_t index) { + lv_obj_t* btn = lv_list_add_button(list, nullptr, title.c_str()); + lv_obj_add_event_cb(btn, &onListItemSelectedCallback, LV_EVENT_SHORT_CLICKED, (void*)index); + } + + static void updateTimerCallback(std::shared_ptr context) { + auto appContext = service::loader::getCurrentApp(); + if (appContext != nullptr && appContext->getManifest().id == manifest.id) { + auto app = std::static_pointer_cast(appContext->getApp()); + app->updateList(); + } + } + + void readTimeZones(std::string filter) { + auto path = std::string(MOUNT_POINT_SYSTEM) + "/timezones.csv"; + auto* file = fopen(path.c_str(), "rb"); + if (file == nullptr) { + TT_LOG_E(TAG, "Failed to open %s", path.c_str()); + return; + } + char line[96]; + std::string name; + std::string code; + uint32_t count = 0; + std::vector new_entries; + while (fgets(line, 96, file)) { + if (parseEntry(line, name, code)) { + if (tt::string::lowercase(name).find(filter) != std::string::npos) { + count++; + new_entries.push_back({.name = name, .code = code}); + + // Safety guard + if (count > 50) { + // TODO: Show warning that we're not displaying a complete list + break; + } } + } else { + TT_LOG_E(TAG, "Parse error at line %lu", count); } - } else { - TT_LOG_E(TAG, "Parse error at line %lu", count); } - } - fclose(file); + fclose(file); - if (data->mutex.lock(100 / portTICK_PERIOD_MS)) { - data->entries = std::move(entries); - data->mutex.unlock(); - } else { - TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED); + if (mutex.lock(100 / portTICK_PERIOD_MS)) { + entries = std::move(new_entries); + mutex.unlock(); + } else { + TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED); + } + + TT_LOG_I(TAG, "Processed %lu entries", count); } - TT_LOG_I(TAG, "Processed %lu entries", count); -} + void updateList() { + if (lvgl::lock(100 / portTICK_PERIOD_MS)) { + std::string filter = tt::string::lowercase(std::string(lv_textarea_get_text(filterTextareaWidget))); + readTimeZones(filter); + lvgl::unlock(); + } else { + TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "LVGL"); + return; + } -static void updateList(std::shared_ptr& data) { - if (lvgl::lock(100 / portTICK_PERIOD_MS)) { - std::string filter = tt::string::lowercase(std::string(lv_textarea_get_text(data->filterTextareaWidget))); - readTimeZones(data, filter); - lvgl::unlock(); - } else { - TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "LVGL"); - return; - } + if (lvgl::lock(100 / portTICK_PERIOD_MS)) { + if (mutex.lock(100 / portTICK_PERIOD_MS)) { + lv_obj_clean(listWidget); - if (lvgl::lock(100 / portTICK_PERIOD_MS)) { - if (data->mutex.lock(100 / portTICK_PERIOD_MS)) { - lv_obj_clean(data->listWidget); + uint32_t index = 0; + for (auto& entry : entries) { + createListItem(listWidget, entry.name, index); + index++; + } - uint32_t index = 0; - for (auto& entry : data->entries) { - createListItem(data->listWidget, entry.name, index); - index++; + mutex.unlock(); } - data->mutex.unlock(); + lvgl::unlock(); } - - lvgl::unlock(); } -} -class TimeZoneApp : public App { +public: void onShow(AppContext& app, lv_obj_t* parent) override { - auto data = std::static_pointer_cast(app.getData()); - lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); lvgl::toolbar_create(parent, app); @@ -214,8 +218,8 @@ class TimeZoneApp : public App { auto* textarea = lv_textarea_create(search_wrapper); lv_textarea_set_placeholder_text(textarea, "e.g. Europe/Amsterdam"); lv_textarea_set_one_line(textarea, true); - lv_obj_add_event_cb(textarea, onTextareaValueChanged, LV_EVENT_VALUE_CHANGED, nullptr); - data->filterTextareaWidget = textarea; + lv_obj_add_event_cb(textarea, onTextareaValueChangedCallback, LV_EVENT_VALUE_CHANGED, this); + filterTextareaWidget = textarea; lv_obj_set_flex_grow(textarea, 1); service::gui::keyboardAddTextArea(textarea); @@ -223,13 +227,11 @@ class TimeZoneApp : public App { lv_obj_set_width(list, LV_PCT(100)); lv_obj_set_flex_grow(list, 1); lv_obj_set_style_border_width(list, 0, 0); - data->listWidget = list; + listWidget = list; } void onStart(AppContext& app) override { - auto data = std::make_shared(); - data->updateTimer = std::make_unique(Timer::Type::Once, onUpdateTimer, data); - app.setData(data); + updateTimer = std::make_unique(Timer::Type::Once, updateTimerCallback, nullptr); } };