2026-10-16 00:54:13 +02:00

131 lines
4.0 KiB
C++

#include <Tactility/app/launcher/Favourites.h>
#include <Tactility/app/launcher/LauncherMode.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/package_manifest.h>
#include <lvgl/icons/shared.h>
#include <tactility/log.h>
#include <algorithm>
#include <cstring>
#include <string>
namespace tt::app::launcher {
namespace {
constexpr auto* TAG = "Launcher";
const Favourites favourites;
struct IconEntry {
const char* id;
const char* icon;
};
constexpr IconEntry ICONS[] = {
{"tactility.apphub", LVGL_ICON_SHARED_DOWNLOAD},
{"tactility.camera", LVGL_ICON_SHARED_CAMERA},
{"tactility.chat", LVGL_ICON_SHARED_FORUM},
{"tactility.files", LVGL_ICON_SHARED_FOLDER},
{"tactility.i2cscanner", LVGL_ICON_SHARED_CABLE},
{"tactility.meshinspect", LVGL_ICON_SHARED_LISTS},
{"tactility.notes", LVGL_ICON_SHARED_EDIT_NOTE},
{"tactility.systeminfo", LVGL_ICON_SHARED_DEVICES},
{"tactility.terminal", LVGL_ICON_SHARED_TERMINAL},
{"tactility.webserversettings", LVGL_ICON_SHARED_CLOUD},
};
const char* appIcon(const ::AppManifest* manifest) {
for (const auto& entry : ICONS) {
if (!strcmp(manifest->id, entry.id)) return entry.icon;
}
return LVGL_ICON_SHARED_DEPLOYED_CODE;
}
bool isShown(const ::AppManifest& manifest) {
if (manifest.category != APP_CATEGORY_USER && manifest.category != APP_CATEGORY_SYSTEM) {
return false;
}
if ((manifest.flags & APP_MANIFEST_FLAG_HEADLESS) != 0) {
return false;
}
if ((manifest.flags & APP_MANIFEST_FLAG_HIDDEN) != 0) {
return false;
}
return true;
}
void collectManifest(const ::AppManifest* manifest, void* context) {
auto* manifests = static_cast<std::vector<::AppManifest>*>(context);
manifests->push_back(*manifest);
}
// Apps from installed packages this device can't run (wrong device, too little RAM)
void collectIncompatibleAppIds(const AppPackage* package, void* context) {
if (app_package_manifest_is_compatible(&package->package)) {
return;
}
auto* ids = static_cast<std::vector<std::string>*>(context);
for (size_t i = 0; i < package->app_id_count; i++) {
ids->emplace_back(package->app_ids[i]);
}
}
std::vector<TileGridItem> collectItems() {
const std::vector<std::string> favouriteIds = favourites.load();
std::vector<::AppManifest> collected;
app_manager_for_each_manifest(collectManifest, &collected);
std::vector<std::string> incompatibleIds;
app_manager_for_each_package(collectIncompatibleAppIds, &incompatibleIds);
std::erase_if(collected, [&](const ::AppManifest& manifest) {
return !isShown(manifest) || std::ranges::find(incompatibleIds, std::string(manifest.id)) != incompatibleIds.end();
});
std::vector<TileGridItem> items;
items.reserve(collected.size());
for (const auto& manifest : collected) {
items.push_back({ manifest.id, manifest.name, appIcon(&manifest), Favourites::contains(favouriteIds, manifest.id) });
}
std::ranges::sort(items, [](const TileGridItem& a, const TileGridItem& b) {
if (a.highlighted != b.highlighted) {
return a.highlighted;
}
return a.name < b.name;
});
return items;
}
void toggleFavourite(TileGrid& grid, const char* appId, bool keepSelection) {
if (!favourites.toggle(appId)) {
LOG_E(TAG, "Failed to save favourites");
}
grid.requestRepopulate(keepSelection);
}
void onLongPressed(TileGrid& grid, const TileGridItem& item) {
toggleFavourite(grid, item.id.c_str(), false);
}
void onKey(TileGrid& grid, const TileGridItem& item, uint32_t key) {
if (key == 'f' || key == 'F') {
toggleFavourite(grid, item.id.c_str(), true);
}
}
} // namespace
const LauncherMode APPS_MODE = {
.buttonIcon = LVGL_ICON_SHARED_APPS,
.iconColor = TileGrid::IconColor::Primary,
.collect = collectItems,
.onLongPressed = onLongPressed,
.onKey = onKey
};
}