feat: add font family deletion functionality (#1919)

## Summary

* **What is the goal of this PR?**

1. Adds font family deletion support to the font download activity so
users can remove installed font families directly from the download
list.
2. Address an issue where a font family manually deleted in the file
browser by user will show as "update" in the `FontDownloadActivity`
 

* **What changes are included?**
* Updated button hint label to show `Delete` when deletion is available.
  * Added confirmation before deleting a selected installed font family.
* Refreshed font registry in `fetchAndParseManifest` (for the second
goal above)

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**PARTIALLY**_
This commit is contained in:
WuTofu
2026-05-11 15:45:38 +02:00
committed by GitHub
parent 20fee843c7
commit a0037ced2b
7 changed files with 63 additions and 13 deletions
@@ -11,6 +11,7 @@
#include "MappedInputManager.h"
#include "SdCardFontGlobals.h"
#include "activities/network/WifiSelectionActivity.h"
#include "activities/util/ConfirmationActivity.h"
#include "components/UITheme.h"
#include "fontIds.h"
#include "network/HttpDownloader.h"
@@ -106,6 +107,7 @@ bool FontDownloadActivity::fetchAndParseManifest() {
baseUrl_ = doc["baseUrl"] | "";
families_.clear();
fontInstaller_.refreshRegistry();
JsonArray familiesArr = doc["families"].as<JsonArray>();
families_.reserve(familiesArr.size());
@@ -304,6 +306,46 @@ void FontDownloadActivity::downloadFamily(ManifestFamily& family) {
}
}
void FontDownloadActivity::promptDeleteSelectedFamily() {
const int pendingDeleteFamilyIndex = familyIndexFromList(selectedIndex_);
if (pendingDeleteFamilyIndex < 0 || pendingDeleteFamilyIndex >= static_cast<int>(families_.size())) {
return;
}
std::string heading = tr(STR_DELETE);
const auto& family = families_[pendingDeleteFamilyIndex];
std::string body = family.name;
startActivityForResult(std::make_unique<ConfirmationActivity>(renderer, mappedInput, heading, body),
[this](const ActivityResult& result) { onDeleteConfirmationResult(result); });
}
void FontDownloadActivity::onDeleteConfirmationResult(const ActivityResult& result) {
if (result.isCancelled) {
requestUpdate();
return;
}
auto& family = families_[familyIndexFromList(selectedIndex_)];
if (fontInstaller_.deleteFamily(family.name.c_str()) != FontInstaller::Error::OK) {
RenderLock lock(*this);
state_ = ERROR;
errorMessage_ = "Failed to delete font";
} else {
fontInstaller_.refreshRegistry();
family.installed = false;
family.hasUpdate = false;
}
requestUpdate();
}
bool FontDownloadActivity::isSelectedFamilyDeletable() const {
if (selectedIndex_ <= 0 || selectedIndex_ >= listItemCount()) return false;
const auto& family = families_[familyIndexFromList(selectedIndex_)];
return family.installed && !family.hasUpdate;
}
// --- Input handling ---
void FontDownloadActivity::loop() {
@@ -341,9 +383,12 @@ void FontDownloadActivity::loop() {
if (isDownloadAllSelected()) {
downloadAll();
} else {
const auto& family = families_[familyIndexFromList(selectedIndex_)];
auto& family = families_[familyIndexFromList(selectedIndex_)];
if (!family.installed || family.hasUpdate) {
downloadFamily(families_[familyIndexFromList(selectedIndex_)]);
downloadFamily(family);
} else {
promptDeleteSelectedFamily();
return;
}
}
requestUpdateAndWait();
@@ -403,7 +448,7 @@ void FontDownloadActivity::render(RenderLock&&) {
renderer.clearScreen();
GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, tr(STR_FONT_DOWNLOAD));
GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, tr(STR_FONT_BROWSER));
const auto lineHeight = renderer.getLineHeight(UI_10_FONT_ID);
const auto contentTop = metrics.topPadding + metrics.headerHeight + metrics.verticalSpacing;
@@ -446,7 +491,9 @@ void FontDownloadActivity::render(RenderLock&&) {
return f.installed && !f.hasUpdate;
});
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_DOWNLOAD), tr(STR_DIR_UP), tr(STR_DIR_DOWN));
const auto labels =
mappedInput.mapLabels(tr(STR_BACK), isSelectedFamilyDeletable() ? tr(STR_DELETE) : tr(STR_DOWNLOAD),
tr(STR_DIR_UP), tr(STR_DIR_DOWN));
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
}
} else if (state_ == DOWNLOADING) {
@@ -86,6 +86,9 @@ class FontDownloadActivity : public Activity {
void downloadAll();
static bool computeFileCrc32(const char* path, uint32_t& outCrc);
bool isDownloadAllSelected() const { return selectedIndex_ == 0 && !families_.empty(); }
bool isSelectedFamilyDeletable() const;
void promptDeleteSelectedFamily();
void onDeleteConfirmationResult(const ActivityResult& result);
int familyIndexFromList(int listIndex) const { return listIndex - 1; }
int listItemCount() const { return families_.empty() ? 0 : static_cast<int>(families_.size()) + 1; }
size_t totalUninstalledSize() const;
+2 -2
View File
@@ -57,9 +57,9 @@ void SettingsActivity::rebuildSettingsLists() {
systemSettings.push_back(SettingInfo::Action(StrId::STR_CHECK_UPDATES, SettingAction::CheckForUpdates));
systemSettings.push_back(SettingInfo::Action(StrId::STR_SD_FIRMWARE_UPDATE, SettingAction::SdFirmwareUpdate));
systemSettings.push_back(SettingInfo::Action(StrId::STR_LANGUAGE, SettingAction::Language));
// Insert "Download Fonts" right after the font family setting so users discover it naturally
// Insert "Manage Fonts" right after the font family setting so users discover it naturally
readerSettings.insert(readerSettings.begin() + 1,
SettingInfo::Action(StrId::STR_DOWNLOAD_FONTS, SettingAction::DownloadFonts));
SettingInfo::Action(StrId::STR_MANAGE_FONTS, SettingAction::DownloadFonts));
readerSettings.push_back(SettingInfo::Action(StrId::STR_CUSTOMISE_STATUS_BAR, SettingAction::CustomiseStatusBar));
// Update currentSettings pointer and count for the active category