diff --git a/Tactility/Private/Tactility/app/appdetails/AppDetails.h b/Tactility/Private/Tactility/app/appdetails/AppDetails.h new file mode 100644 index 00000000..8ddb22f9 --- /dev/null +++ b/Tactility/Private/Tactility/app/appdetails/AppDetails.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +namespace tt::app::appdetails { + +void start(const std::string& appId); + +} // namespace diff --git a/Tactility/Source/Tactility.cpp b/Tactility/Source/Tactility.cpp index 7e8ffa29..35e06ae1 100644 --- a/Tactility/Source/Tactility.cpp +++ b/Tactility/Source/Tactility.cpp @@ -57,7 +57,9 @@ namespace service { namespace app { namespace addgps { extern const AppManifest manifest; } namespace alertdialog { extern const AppManifest manifest; } + namespace appdetails { extern const AppManifest manifest; } namespace applist { extern const AppManifest manifest; } + namespace appsettings { extern const AppManifest manifest; } namespace boot { extern const AppManifest manifest; } namespace chat { extern const AppManifest manifest; } namespace development { extern const AppManifest manifest; } @@ -99,7 +101,9 @@ namespace app { // List of all apps excluding Boot app (as Boot app calls this function indirectly) static void registerInternalApps() { addApp(app::alertdialog::manifest); + addApp(app::appdetails::manifest); addApp(app::applist::manifest); + addApp(app::appsettings::manifest); addApp(app::display::manifest); addApp(app::files::manifest); addApp(app::fileselection::manifest); diff --git a/Tactility/Source/app/appdetails/AppDetails.cpp b/Tactility/Source/app/appdetails/AppDetails.cpp new file mode 100644 index 00000000..8d40ac12 --- /dev/null +++ b/Tactility/Source/app/appdetails/AppDetails.cpp @@ -0,0 +1,114 @@ +#include "Tactility/lvgl/LvglSync.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace tt::app::appdetails { + +extern const AppManifest manifest; + +void start(const std::string& appId) { + auto bundle = std::make_shared(); + bundle->putString("appId", appId); + app::start(manifest.appId, bundle); +} + +class AppDetailsApp : public App { + + std::shared_ptr manifest; + + static void onPressUninstall(TT_UNUSED lv_event_t* event) { + auto* self = static_cast(lv_event_get_user_data(event)); + std::vector choices = { + "Yes", + "No" + }; + alertdialog::start("Confirmation", std::format("Uninstall {}?", self->manifest->appName), choices); + } + +public: + + void onCreate(AppContext& app) override { + const auto parameters = app.getParameters(); + tt_check(parameters != nullptr, "Parameters missing"); + auto app_id = parameters->getString("appId"); + manifest = findAppById(app_id); + assert(manifest != nullptr); + } + + void onShow(AppContext& app, lv_obj_t* parent) override { + lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); + lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT); + + auto title = std::format("{} details", manifest->appName); + lvgl::toolbar_create(parent, title); + + auto* wrapper = lv_obj_create(parent); + lv_obj_set_width(wrapper, LV_PCT(100)); + lv_obj_set_flex_grow(wrapper, 1); + lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_COLUMN); + lv_obj_set_style_border_width(wrapper, 0, LV_STATE_DEFAULT); + lvgl::obj_set_style_bg_invisible(wrapper); + + auto identifier = std::format("Identifier: {}", manifest->appId); + auto* identifier_label = lv_label_create(wrapper); + lv_label_set_text(identifier_label, identifier.c_str()); + + auto* location_label = lv_label_create(wrapper); + std::string location; + if (manifest->appLocation.isInternal()) { + location = "internal"; + } else { + if (!string::getPathParent(manifest->appLocation.getPath(), location)) { + location = "external"; + } + } + std::string location_label_text = std::format("Location: {}", location); + lv_label_set_text(location_label, location_label_text.c_str()); + + if (manifest->appLocation.isExternal()) { + auto* uninstall_button = lv_button_create(wrapper); + lv_obj_set_width(uninstall_button, LV_PCT(100)); + lv_obj_add_event_cb(uninstall_button, onPressUninstall, LV_EVENT_SHORT_CLICKED, this); + auto* uninstall_label = lv_label_create(uninstall_button); + lv_obj_align(uninstall_label, LV_ALIGN_CENTER, 0, 0); + lv_label_set_text(uninstall_label, "Uninstall"); + } + } + + void onResult(TT_UNUSED AppContext& appContext, TT_UNUSED LaunchId launchId, TT_UNUSED Result result, std::unique_ptr bundle) override { + if (result != Result::Ok || bundle == nullptr) { + return; + } + + if (alertdialog::getResultIndex(*bundle) != 0) { // 0 = Yes + return; + } + + uninstall(manifest->appId); + + // Stop app + stop(); + } +}; + +extern const AppManifest manifest = { + .appId = "AppDetails", + .appName = "App Details", + .appCategory = Category::System, + .appFlags = AppManifest::Flags::Hidden, + .createApp = create +}; + +} // namespace + diff --git a/Tactility/Source/app/appsettings/AppSettings.cpp b/Tactility/Source/app/appsettings/AppSettings.cpp new file mode 100644 index 00000000..ba8e1411 --- /dev/null +++ b/Tactility/Source/app/appsettings/AppSettings.cpp @@ -0,0 +1,66 @@ +#include +#include +#include +#include + +#include + +#include +#include + +namespace tt::app::appsettings { + +class AppSettingsApp final : public App { + + static void onAppPressed(lv_event_t* e) { + const auto* manifest = static_cast(lv_event_get_user_data(e)); + appdetails::start(manifest->appId); + } + + static void createAppWidget(const std::shared_ptr& manifest, lv_obj_t* list) { + const void* icon = !manifest->appIcon.empty() ? manifest->appIcon.c_str() : TT_ASSETS_APP_ICON_FALLBACK; + lv_obj_t* btn = lv_list_add_button(list, icon, manifest->appName.c_str()); + lv_obj_add_event_cb(btn, &onAppPressed, LV_EVENT_SHORT_CLICKED, manifest.get()); + } + +public: + + void onShow(TT_UNUSED AppContext& app, lv_obj_t* parent) override { + auto* toolbar = lvgl::toolbar_create(parent, "External Apps"); + lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0); + + lv_obj_t* list = lv_list_create(parent); + lv_obj_set_width(list, LV_PCT(100)); + lv_obj_align_to(list, toolbar, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); + + auto toolbar_height = lv_obj_get_height(toolbar); + auto parent_content_height = lv_obj_get_content_height(parent); + lv_obj_set_height(list, parent_content_height - toolbar_height); + + auto manifests = getApps(); + std::ranges::sort(manifests, SortAppManifestByName); + + size_t app_count = 0; + for (const auto& manifest: manifests) { + if (manifest->appLocation.isExternal()) { + app_count++; + createAppWidget(manifest, list); + } + } + + if (app_count == 0) { + auto* no_apps_label = lv_label_create(parent); + lv_label_set_text(no_apps_label, "No apps installed"); + lv_obj_align(no_apps_label, LV_ALIGN_CENTER, 0, 0); + } + } +}; + +extern const AppManifest manifest = { + .appId = "AppSettings", + .appName = "Apps", + .appCategory = Category::Settings, + .createApp = create, +}; + +} // namespace