Implemented AlertDialog app (#117)

This commit is contained in:
Ken Van Hoeylandt 2024-12-10 00:22:29 +01:00 committed by GitHub
parent 103588948f
commit 77280def1d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 160 additions and 22 deletions

View File

@ -1,6 +1,5 @@
# TODOs
- AppContext's onResult should pass the app id (or launch request id!) that was started, so we can differentiate between multiple types of apps being launched
- Create AlertDialog app like on Android with title, message and optional buttons. Update WifiApSettings with it.
- Loader: Use Timer instead of Thread, and move API to `tt::app::`
- Gpio: Use Timer instead of Thread
- I2cScannerThread: Use Timer instead of Thread
@ -36,7 +35,6 @@
# App Ideas
- System logger
- Add FreeRTOS task manager functionality to System Info app
- BlueTooth keyboard app
- Chip 8 emulator
- BadUSB (in December 2024, TinyUSB has a bug where uninstalling and re-installing the driver fails)

View File

@ -1,4 +1,3 @@
#include <Dispatcher.h>
#include "Tactility.h"
#include "app/ManifestRegistry.h"
@ -36,6 +35,7 @@ static const std::vector<const service::ServiceManifest*> system_services = {
// region Default apps
namespace app {
namespace alertdialog { extern const AppManifest manifest; }
namespace boot { extern const AppManifest manifest; }
namespace desktop { extern const AppManifest manifest; }
namespace files { extern const AppManifest manifest; }
@ -63,6 +63,7 @@ extern const app::AppManifest screenshot_app;
#endif
static const std::vector<const app::AppManifest*> system_apps = {
&app::alertdialog::manifest,
&app::boot::manifest,
&app::desktop::manifest,
&app::display::manifest,

View File

@ -0,0 +1,125 @@
#include "AlertDialog.h"
#include "lvgl.h"
#include "lvgl/Toolbar.h"
#include "service/loader/Loader.h"
#include <StringUtils.h>
#include <TactilityCore.h>
namespace tt::app::alertdialog {
#define PARAMETER_BUNDLE_KEY_TITLE "title"
#define PARAMETER_BUNDLE_KEY_MESSAGE "message"
#define PARAMETER_BUNDLE_KEY_BUTTON_LABELS "buttonLabels"
#define RESULT_BUNDLE_KEY_INDEX "index"
#define PARAMETER_ITEM_CONCATENATION_TOKEN ";;"
#define DEFAULT_TITLE "Select..."
#define TAG "selection_dialog"
extern const AppManifest manifest;
void start(std::string title, std::string message, const std::vector<std::string>& buttonLabels) {
std::string items_joined = string::join(buttonLabels, PARAMETER_ITEM_CONCATENATION_TOKEN);
auto bundle = std::make_shared<Bundle>();
bundle->putString(PARAMETER_BUNDLE_KEY_TITLE, title);
bundle->putString(PARAMETER_BUNDLE_KEY_MESSAGE, message);
bundle->putString(PARAMETER_BUNDLE_KEY_BUTTON_LABELS, items_joined);
service::loader::startApp(manifest.id, false, bundle);
}
int32_t getResultIndex(const Bundle& bundle) {
int32_t index = -1;
bundle.optInt32(RESULT_BUNDLE_KEY_INDEX, index);
return index;
}
void setResultIndex(std::shared_ptr<Bundle> bundle, int32_t index) {
bundle->putInt32(RESULT_BUNDLE_KEY_INDEX, index);
}
static std::string getTitleParameter(std::shared_ptr<const Bundle> bundle) {
std::string result;
if (bundle->optString(PARAMETER_BUNDLE_KEY_TITLE, result)) {
return result;
} else {
return DEFAULT_TITLE;
}
}
static void onButtonClicked(lv_event_t* e) {
lv_event_code_t code = lv_event_get_code(e);
if (code == LV_EVENT_CLICKED) {
size_t index = (size_t)(e->user_data);
TT_LOG_I(TAG, "Selected item at index %d", index);
tt::app::AppContext* app = service::loader::getCurrentApp();
auto bundle = std::make_shared<Bundle>();
setResultIndex(bundle, (int32_t)index);
app->setResult(app::ResultOk, bundle);
service::loader::stopApp();
}
}
static void createButton(lv_obj_t* parent, const std::string& text, size_t index) {
lv_obj_t* button = lv_button_create(parent);
lv_obj_t* button_label = lv_label_create(button);
lv_obj_align(button_label, LV_ALIGN_CENTER, 0, 0);
lv_label_set_text(button_label, text.c_str());
lv_obj_add_event_cb(button, &onButtonClicked, LV_EVENT_CLICKED, (void*)index);
}
static void onShow(AppContext& app, lv_obj_t* parent) {
auto parameters = app.getParameters();
tt_check(parameters != nullptr, "Parameters missing");
std::string title = getTitleParameter(app.getParameters());
lv_obj_t* toolbar = lvgl::toolbar_create(parent, title);
lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0);
lv_obj_t* message_label = lv_label_create(parent);
lv_obj_align(message_label, LV_ALIGN_CENTER, 0, 0);
std::string message;
if (parameters->optString(PARAMETER_BUNDLE_KEY_MESSAGE, message)) {
lv_label_set_text(message_label, message.c_str());
lv_label_set_long_mode(message_label, LV_LABEL_LONG_WRAP);
}
lv_obj_t* button_wrapper = lv_obj_create(parent);
lv_obj_set_flex_flow(button_wrapper, LV_FLEX_FLOW_ROW);
lv_obj_set_size(button_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_pad_all(button_wrapper, 0, 0);
lv_obj_set_flex_align(button_wrapper, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
lv_obj_set_style_border_width(button_wrapper, 0, 0);
lv_obj_align(button_wrapper, LV_ALIGN_BOTTOM_MID, 0, -4);
std::string items_concatenated;
if (parameters->optString(PARAMETER_BUNDLE_KEY_BUTTON_LABELS, items_concatenated)) {
std::vector<std::string> labels = string::split(items_concatenated, PARAMETER_ITEM_CONCATENATION_TOKEN);
if (labels.empty() || labels.front().empty()) {
TT_LOG_E(TAG, "No items provided");
app.setResult(ResultError);
service::loader::stopApp();
} else if (labels.size() == 1) {
auto result_bundle = std::make_shared<Bundle>();
setResultIndex(result_bundle, 0);
app.setResult(ResultOk, result_bundle);
service::loader::stopApp();
TT_LOG_W(TAG, "Auto-selecting single item");
} else {
size_t index = 0;
for (const auto& label: labels) {
createButton(button_wrapper, label, index++);
}
}
}
}
extern const AppManifest manifest = {
.id = "AlertDialog",
.name = "Alert Dialog",
.type = TypeHidden,
.onShow = onShow
};
}

View File

@ -0,0 +1,23 @@
#pragma once
#include <string>
#include <vector>
#include "Bundle.h"
/**
* Start the app by its ID and provide:
* - a title
* - a text
* - 0, 1 or more buttons
*/
namespace tt::app::alertdialog {
void start(std::string title, std::string message, const std::vector<std::string>& buttonLabels);
/**
* Get the index of the button that the user selected.
*
* @return a value greater than 0 when a selection was done, or -1 when the app was closed clicking one of the selection buttons.
*/
int32_t getResultIndex(const Bundle& bundle);
}

View File

@ -109,15 +109,3 @@ extern const AppManifest manifest = {
};
}
extern "C" {
extern void tt_app_selectiondialog_start2(const char* title, int argc, const char* argv[]) {
std::vector<std::string> list;
for (int i = 0; i < argc; i++) {
const char* item = argv[i];
list.push_back(item);
}
tt::app::selectiondialog::start(title, list);
}
}

View File

@ -16,7 +16,10 @@ namespace tt::app::selectiondialog {
void start(std::string title, const std::vector<std::string>& items);
/** App result data */
/**
* Get the index of the item that the user selected.
*
* @return a value greater than 0 when a selection was done, or -1 when the app was closed without selecting an item.
*/
int32_t getResultIndex(const Bundle& bundle);
}

View File

@ -1,7 +1,7 @@
#include "WifiApSettings.h"
#include "TactilityCore.h"
#include "app/AppContext.h"
#include "app/selectiondialog/SelectionDialog.h"
#include "app/alertdialog/AlertDialog.h"
#include "lvgl.h"
#include "lvgl/Style.h"
#include "lvgl/Toolbar.h"
@ -30,12 +30,12 @@ void start(const std::string& ssid) {
service::loader::startApp(manifest.id, false, bundle);
}
static void onPressForget(lv_event_t* event) {
static void onPressForget(TT_UNUSED lv_event_t* event) {
std::vector<std::string> choices = {
"Yes",
"No"
};
selectiondialog::start("Are you sure?", choices);
alertdialog::start("Confirmation", "Forget the Wi-Fi access point?", choices);
}
static void onToggleAutoConnect(lv_event_t* event) {
@ -118,8 +118,8 @@ static void onShow(AppContext& app, lv_obj_t* parent) {
}
}
void onResult(AppContext& app, Result result, const Bundle& bundle) {
auto index = selectiondialog::getResultIndex(bundle);
void onResult(TT_UNUSED AppContext& app, TT_UNUSED Result result, const Bundle& bundle) {
auto index = alertdialog::getResultIndex(bundle);
if (index == 0) {// Yes
auto* app = optWifiApSettingsApp();
if (app == nullptr) {