refactor: implement ActivityManager (#1016)
## Summary Ref comment: https://github.com/crosspoint-reader/crosspoint-reader/pull/1010#pullrequestreview-3828854640 This PR introduces `ActivityManager`, which mirrors the same concept of Activity in Android, where an activity represents a single screen of the UI. The manager is responsible for launching activities, and ensuring that only one activity is active at a time. Main differences from Android's ActivityManager: - No concept of Bundle or Intent extras - No onPause/onResume, since we don't have a concept of background activities - onActivityResult is implemented via a callback instead of a separate method, for simplicity ## Key changes - Single `renderTask` shared across all activities - No more sub-activity, we manage them using a stack; Results can be passed via `startActivityForResult` and `setResult` - Activity can call `finish()` to destroy themself, but the actual deletion will be handled by `ActivityManager` to avoid `delete this` pattern As a bonus: the manager will automatically call `requestUpdate()` when returning from another activity ## Example usage **BEFORE**: ```cpp // caller enterNewActivity(new WifiSelectionActivity(renderer, mappedInput, [this](const bool connected) { onWifiSelectionComplete(connected); })); // subactivity onComplete(true); // will eventually call exitActivity(), which deletes the caller instance (dangerous behavior) ``` **AFTER**: (mirrors the `startActivityForResult` and `setResult` from android) ```cpp // caller startActivityForResult(new NetworkModeSelectionActivity(renderer, mappedInput), [this](const ActivityResult& result) { onNetworkModeSelected(result.selectedNetworkMode); }); // subactivity ActivityResult result; result.isCancelled = false; result.selectedNetworkMode = mode; setResult(result); finish(); // signals to ActivityManager to go back to last activity AFTER this function returns ``` TODO: - [x] Reconsider if the `Intent` is really necessary or it should be removed (note: it's inspired by [Intent](https://developer.android.com/guide/components/intents-common) from Android API) ==> I decided to keep this pattern fr clarity - [x] Verify if behavior is still correct (i.e. back from sub-activity) - [x] Refactor the `ActivityWithSubactivity` to just simple `Activity` --> We are using a stack for keeping track of sub-activity now - [x] Use single task for rendering --> avoid allocating 8KB stack per activity - [x] Implement the idea of [Activity result](https://developer.android.com/training/basics/intents/result) --> Allow sub-activity like Wifi to report back the status (connected, failed, etc) --- ### 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**, some repetitive migrations are done by Claude, but I'm the one how ultimately approve it --------- Co-authored-by: Zach Nelson <zach@zdnelson.com>
This commit is contained in:
@@ -52,20 +52,20 @@ void ButtonRemapActivity::loop() {
|
||||
SETTINGS.frontButtonLeft = CrossPointSettings::FRONT_HW_LEFT;
|
||||
SETTINGS.frontButtonRight = CrossPointSettings::FRONT_HW_RIGHT;
|
||||
SETTINGS.saveToFile();
|
||||
onBack();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Down)) {
|
||||
// Exit without changing settings.
|
||||
onBack();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
// Wait for the UI to refresh before accepting another assignment.
|
||||
// Make sure UI done rendering before accepting another assignment.
|
||||
// This avoids rapid double-presses that can advance the step without a visible redraw.
|
||||
requestUpdateAndWait();
|
||||
RenderLock lock(*this);
|
||||
|
||||
// Wait for a front button press to assign to the current role.
|
||||
const int pressedButton = mappedInput.getPressedFrontButton();
|
||||
@@ -86,7 +86,7 @@ void ButtonRemapActivity::loop() {
|
||||
// All roles assigned; save to settings and exit.
|
||||
applyTempMapping();
|
||||
SETTINGS.saveToFile();
|
||||
onBack();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ void ButtonRemapActivity::loop() {
|
||||
}
|
||||
}
|
||||
|
||||
void ButtonRemapActivity::render(Activity::RenderLock&&) {
|
||||
void ButtonRemapActivity::render(RenderLock&&) {
|
||||
const auto labelForHardware = [&](uint8_t hardwareIndex) -> const char* {
|
||||
for (uint8_t i = 0; i < kRoleCount; i++) {
|
||||
if (tempMapping[i] == hardwareIndex) {
|
||||
|
||||
@@ -7,20 +7,17 @@
|
||||
|
||||
class ButtonRemapActivity final : public Activity {
|
||||
public:
|
||||
explicit ButtonRemapActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
const std::function<void()>& onBack)
|
||||
: Activity("ButtonRemap", renderer, mappedInput), onBack(onBack) {}
|
||||
explicit ButtonRemapActivity(GfxRenderer& renderer, MappedInputManager& mappedInput)
|
||||
: Activity("ButtonRemap", renderer, mappedInput) {}
|
||||
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
void render(Activity::RenderLock&&) override;
|
||||
void render(RenderLock&&) override;
|
||||
|
||||
private:
|
||||
// Rendering task state.
|
||||
|
||||
// Callback used to exit the remap flow back to the settings list.
|
||||
const std::function<void()> onBack;
|
||||
// Index of the logical role currently awaiting input.
|
||||
uint8_t currentStep = 0;
|
||||
// Temporary mapping from logical role -> hardware button index.
|
||||
|
||||
@@ -17,22 +17,17 @@ const StrId menuNames[MENU_ITEMS] = {StrId::STR_CALIBRE_WEB_URL, StrId::STR_USER
|
||||
} // namespace
|
||||
|
||||
void CalibreSettingsActivity::onEnter() {
|
||||
ActivityWithSubactivity::onEnter();
|
||||
Activity::onEnter();
|
||||
|
||||
selectedIndex = 0;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
void CalibreSettingsActivity::onExit() { ActivityWithSubactivity::onExit(); }
|
||||
void CalibreSettingsActivity::onExit() { Activity::onExit(); }
|
||||
|
||||
void CalibreSettingsActivity::loop() {
|
||||
if (subActivity) {
|
||||
subActivity->loop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||
onBack();
|
||||
activityManager.popActivity();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -56,62 +51,44 @@ void CalibreSettingsActivity::loop() {
|
||||
void CalibreSettingsActivity::handleSelection() {
|
||||
if (selectedIndex == 0) {
|
||||
// OPDS Server URL
|
||||
exitActivity();
|
||||
enterNewActivity(new KeyboardEntryActivity(
|
||||
renderer, mappedInput, tr(STR_CALIBRE_WEB_URL), SETTINGS.opdsServerUrl,
|
||||
127, // maxLength
|
||||
false, // not password
|
||||
[this](const std::string& url) {
|
||||
strncpy(SETTINGS.opdsServerUrl, url.c_str(), sizeof(SETTINGS.opdsServerUrl) - 1);
|
||||
SETTINGS.opdsServerUrl[sizeof(SETTINGS.opdsServerUrl) - 1] = '\0';
|
||||
SETTINGS.saveToFile();
|
||||
exitActivity();
|
||||
requestUpdate();
|
||||
},
|
||||
[this]() {
|
||||
exitActivity();
|
||||
requestUpdate();
|
||||
}));
|
||||
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_CALIBRE_WEB_URL),
|
||||
SETTINGS.opdsServerUrl, 127, false),
|
||||
[this](const ActivityResult& result) {
|
||||
if (!result.isCancelled) {
|
||||
const auto& kb = std::get<KeyboardResult>(result.data);
|
||||
strncpy(SETTINGS.opdsServerUrl, kb.text.c_str(), sizeof(SETTINGS.opdsServerUrl) - 1);
|
||||
SETTINGS.opdsServerUrl[sizeof(SETTINGS.opdsServerUrl) - 1] = '\0';
|
||||
SETTINGS.saveToFile();
|
||||
}
|
||||
});
|
||||
} else if (selectedIndex == 1) {
|
||||
// Username
|
||||
exitActivity();
|
||||
enterNewActivity(new KeyboardEntryActivity(
|
||||
renderer, mappedInput, tr(STR_USERNAME), SETTINGS.opdsUsername,
|
||||
63, // maxLength
|
||||
false, // not password
|
||||
[this](const std::string& username) {
|
||||
strncpy(SETTINGS.opdsUsername, username.c_str(), sizeof(SETTINGS.opdsUsername) - 1);
|
||||
SETTINGS.opdsUsername[sizeof(SETTINGS.opdsUsername) - 1] = '\0';
|
||||
SETTINGS.saveToFile();
|
||||
exitActivity();
|
||||
requestUpdate();
|
||||
},
|
||||
[this]() {
|
||||
exitActivity();
|
||||
requestUpdate();
|
||||
}));
|
||||
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_USERNAME),
|
||||
SETTINGS.opdsUsername, 63, false),
|
||||
[this](const ActivityResult& result) {
|
||||
if (!result.isCancelled) {
|
||||
const auto& kb = std::get<KeyboardResult>(result.data);
|
||||
strncpy(SETTINGS.opdsUsername, kb.text.c_str(), sizeof(SETTINGS.opdsUsername) - 1);
|
||||
SETTINGS.opdsUsername[sizeof(SETTINGS.opdsUsername) - 1] = '\0';
|
||||
SETTINGS.saveToFile();
|
||||
}
|
||||
});
|
||||
} else if (selectedIndex == 2) {
|
||||
// Password
|
||||
exitActivity();
|
||||
enterNewActivity(new KeyboardEntryActivity(
|
||||
renderer, mappedInput, tr(STR_PASSWORD), SETTINGS.opdsPassword,
|
||||
63, // maxLength
|
||||
false, // not password mode
|
||||
[this](const std::string& password) {
|
||||
strncpy(SETTINGS.opdsPassword, password.c_str(), sizeof(SETTINGS.opdsPassword) - 1);
|
||||
SETTINGS.opdsPassword[sizeof(SETTINGS.opdsPassword) - 1] = '\0';
|
||||
SETTINGS.saveToFile();
|
||||
exitActivity();
|
||||
requestUpdate();
|
||||
},
|
||||
[this]() {
|
||||
exitActivity();
|
||||
requestUpdate();
|
||||
}));
|
||||
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_PASSWORD),
|
||||
SETTINGS.opdsPassword, 63, false),
|
||||
[this](const ActivityResult& result) {
|
||||
if (!result.isCancelled) {
|
||||
const auto& kb = std::get<KeyboardResult>(result.data);
|
||||
strncpy(SETTINGS.opdsPassword, kb.text.c_str(), sizeof(SETTINGS.opdsPassword) - 1);
|
||||
SETTINGS.opdsPassword[sizeof(SETTINGS.opdsPassword) - 1] = '\0';
|
||||
SETTINGS.saveToFile();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void CalibreSettingsActivity::render(Activity::RenderLock&&) {
|
||||
void CalibreSettingsActivity::render(RenderLock&&) {
|
||||
renderer.clearScreen();
|
||||
|
||||
const auto& metrics = UITheme::getInstance().getMetrics();
|
||||
|
||||
@@ -1,29 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "activities/ActivityWithSubactivity.h"
|
||||
#include "activities/Activity.h"
|
||||
#include "util/ButtonNavigator.h"
|
||||
|
||||
/**
|
||||
* Submenu for OPDS Browser settings.
|
||||
* Shows OPDS Server URL and HTTP authentication options.
|
||||
*/
|
||||
class CalibreSettingsActivity final : public ActivityWithSubactivity {
|
||||
class CalibreSettingsActivity final : public Activity {
|
||||
public:
|
||||
explicit CalibreSettingsActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
const std::function<void()>& onBack)
|
||||
: ActivityWithSubactivity("CalibreSettings", renderer, mappedInput), onBack(onBack) {}
|
||||
explicit CalibreSettingsActivity(GfxRenderer& renderer, MappedInputManager& mappedInput)
|
||||
: Activity("CalibreSettings", renderer, mappedInput) {}
|
||||
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
void render(Activity::RenderLock&&) override;
|
||||
void render(RenderLock&&) override;
|
||||
|
||||
private:
|
||||
ButtonNavigator buttonNavigator;
|
||||
|
||||
size_t selectedIndex = 0;
|
||||
const std::function<void()> onBack;
|
||||
void handleSelection();
|
||||
};
|
||||
|
||||
@@ -10,15 +10,15 @@
|
||||
#include "fontIds.h"
|
||||
|
||||
void ClearCacheActivity::onEnter() {
|
||||
ActivityWithSubactivity::onEnter();
|
||||
Activity::onEnter();
|
||||
|
||||
state = WARNING;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
void ClearCacheActivity::onExit() { ActivityWithSubactivity::onExit(); }
|
||||
void ClearCacheActivity::onExit() { Activity::onExit(); }
|
||||
|
||||
void ClearCacheActivity::render(Activity::RenderLock&&) {
|
||||
void ClearCacheActivity::render(RenderLock&&) {
|
||||
const auto& metrics = UITheme::getInstance().getMetrics();
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
const auto pageHeight = renderer.getScreenHeight();
|
||||
|
||||
@@ -2,26 +2,25 @@
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "activities/ActivityWithSubactivity.h"
|
||||
#include "activities/Activity.h"
|
||||
|
||||
class ClearCacheActivity final : public ActivityWithSubactivity {
|
||||
class ClearCacheActivity final : public Activity {
|
||||
public:
|
||||
explicit ClearCacheActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
const std::function<void()>& goBack)
|
||||
: ActivityWithSubactivity("ClearCache", renderer, mappedInput), goBack(goBack) {}
|
||||
explicit ClearCacheActivity(GfxRenderer& renderer, MappedInputManager& mappedInput)
|
||||
: Activity("ClearCache", renderer, mappedInput) {}
|
||||
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
bool skipLoopDelay() override { return true; } // Prevent power-saving mode
|
||||
void render(Activity::RenderLock&&) override;
|
||||
void render(RenderLock&&) override;
|
||||
|
||||
private:
|
||||
enum State { WARNING, CLEARING, SUCCESS, FAILED };
|
||||
|
||||
State state = WARNING;
|
||||
|
||||
const std::function<void()> goBack;
|
||||
void goBack() { finish(); }
|
||||
|
||||
int clearedCount = 0;
|
||||
int failedCount = 0;
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
#include "fontIds.h"
|
||||
|
||||
void KOReaderAuthActivity::onWifiSelectionComplete(const bool success) {
|
||||
exitActivity();
|
||||
|
||||
if (!success) {
|
||||
{
|
||||
RenderLock lock(*this);
|
||||
@@ -51,7 +49,7 @@ void KOReaderAuthActivity::performAuthentication() {
|
||||
}
|
||||
|
||||
void KOReaderAuthActivity::onEnter() {
|
||||
ActivityWithSubactivity::onEnter();
|
||||
Activity::onEnter();
|
||||
|
||||
// Turn on WiFi
|
||||
WiFi.mode(WIFI_STA);
|
||||
@@ -74,12 +72,12 @@ void KOReaderAuthActivity::onEnter() {
|
||||
}
|
||||
|
||||
// Launch WiFi selection
|
||||
enterNewActivity(new WifiSelectionActivity(renderer, mappedInput,
|
||||
[this](const bool connected) { onWifiSelectionComplete(connected); }));
|
||||
startActivityForResult(std::make_unique<WifiSelectionActivity>(renderer, mappedInput),
|
||||
[this](const ActivityResult& result) { onWifiSelectionComplete(!result.isCancelled); });
|
||||
}
|
||||
|
||||
void KOReaderAuthActivity::onExit() {
|
||||
ActivityWithSubactivity::onExit();
|
||||
Activity::onExit();
|
||||
|
||||
// Turn off wifi
|
||||
WiFi.disconnect(false);
|
||||
@@ -88,7 +86,7 @@ void KOReaderAuthActivity::onExit() {
|
||||
delay(100);
|
||||
}
|
||||
|
||||
void KOReaderAuthActivity::render(Activity::RenderLock&&) {
|
||||
void KOReaderAuthActivity::render(RenderLock&&) {
|
||||
renderer.clearScreen();
|
||||
|
||||
const auto& metrics = UITheme::getInstance().getMetrics();
|
||||
@@ -115,15 +113,10 @@ void KOReaderAuthActivity::render(Activity::RenderLock&&) {
|
||||
}
|
||||
|
||||
void KOReaderAuthActivity::loop() {
|
||||
if (subActivity) {
|
||||
subActivity->loop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == SUCCESS || state == FAILED) {
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back) ||
|
||||
mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||
onComplete();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,22 +2,21 @@
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "activities/ActivityWithSubactivity.h"
|
||||
#include "activities/Activity.h"
|
||||
|
||||
/**
|
||||
* Activity for testing KOReader credentials.
|
||||
* Connects to WiFi and authenticates with the KOReader sync server.
|
||||
*/
|
||||
class KOReaderAuthActivity final : public ActivityWithSubactivity {
|
||||
class KOReaderAuthActivity final : public Activity {
|
||||
public:
|
||||
explicit KOReaderAuthActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
const std::function<void()>& onComplete)
|
||||
: ActivityWithSubactivity("KOReaderAuth", renderer, mappedInput), onComplete(onComplete) {}
|
||||
explicit KOReaderAuthActivity(GfxRenderer& renderer, MappedInputManager& mappedInput)
|
||||
: Activity("KOReaderAuth", renderer, mappedInput) {}
|
||||
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
void render(Activity::RenderLock&&) override;
|
||||
void render(RenderLock&&) override;
|
||||
bool preventAutoSleep() override { return state == CONNECTING || state == AUTHENTICATING; }
|
||||
|
||||
private:
|
||||
@@ -27,8 +26,6 @@ class KOReaderAuthActivity final : public ActivityWithSubactivity {
|
||||
std::string statusMessage;
|
||||
std::string errorMessage;
|
||||
|
||||
const std::function<void()> onComplete;
|
||||
|
||||
void onWifiSelectionComplete(bool success);
|
||||
void performAuthentication();
|
||||
};
|
||||
|
||||
@@ -19,22 +19,17 @@ const StrId menuNames[MENU_ITEMS] = {StrId::STR_USERNAME, StrId::STR_PASSWORD, S
|
||||
} // namespace
|
||||
|
||||
void KOReaderSettingsActivity::onEnter() {
|
||||
ActivityWithSubactivity::onEnter();
|
||||
Activity::onEnter();
|
||||
|
||||
selectedIndex = 0;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
void KOReaderSettingsActivity::onExit() { ActivityWithSubactivity::onExit(); }
|
||||
void KOReaderSettingsActivity::onExit() { Activity::onExit(); }
|
||||
|
||||
void KOReaderSettingsActivity::loop() {
|
||||
if (subActivity) {
|
||||
subActivity->loop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||
onBack();
|
||||
activityManager.popActivity();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -58,59 +53,46 @@ void KOReaderSettingsActivity::loop() {
|
||||
void KOReaderSettingsActivity::handleSelection() {
|
||||
if (selectedIndex == 0) {
|
||||
// Username
|
||||
exitActivity();
|
||||
enterNewActivity(new KeyboardEntryActivity(
|
||||
renderer, mappedInput, tr(STR_KOREADER_USERNAME), KOREADER_STORE.getUsername(),
|
||||
64, // maxLength
|
||||
false, // not password
|
||||
[this](const std::string& username) {
|
||||
KOREADER_STORE.setCredentials(username, KOREADER_STORE.getPassword());
|
||||
KOREADER_STORE.saveToFile();
|
||||
exitActivity();
|
||||
requestUpdate();
|
||||
},
|
||||
[this]() {
|
||||
exitActivity();
|
||||
requestUpdate();
|
||||
}));
|
||||
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_KOREADER_USERNAME),
|
||||
KOREADER_STORE.getUsername(),
|
||||
64, // maxLength
|
||||
false), // not password
|
||||
[this](const ActivityResult& result) {
|
||||
if (!result.isCancelled) {
|
||||
const auto& kb = std::get<KeyboardResult>(result.data);
|
||||
KOREADER_STORE.setCredentials(kb.text, KOREADER_STORE.getPassword());
|
||||
KOREADER_STORE.saveToFile();
|
||||
}
|
||||
});
|
||||
} else if (selectedIndex == 1) {
|
||||
// Password
|
||||
exitActivity();
|
||||
enterNewActivity(new KeyboardEntryActivity(
|
||||
renderer, mappedInput, tr(STR_KOREADER_PASSWORD), KOREADER_STORE.getPassword(),
|
||||
64, // maxLength
|
||||
false, // show characters
|
||||
[this](const std::string& password) {
|
||||
KOREADER_STORE.setCredentials(KOREADER_STORE.getUsername(), password);
|
||||
KOREADER_STORE.saveToFile();
|
||||
exitActivity();
|
||||
requestUpdate();
|
||||
},
|
||||
[this]() {
|
||||
exitActivity();
|
||||
requestUpdate();
|
||||
}));
|
||||
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_KOREADER_PASSWORD),
|
||||
KOREADER_STORE.getPassword(),
|
||||
64, // maxLength
|
||||
false), // show characters
|
||||
[this](const ActivityResult& result) {
|
||||
if (!result.isCancelled) {
|
||||
const auto& kb = std::get<KeyboardResult>(result.data);
|
||||
KOREADER_STORE.setCredentials(KOREADER_STORE.getUsername(), kb.text);
|
||||
KOREADER_STORE.saveToFile();
|
||||
}
|
||||
});
|
||||
} else if (selectedIndex == 2) {
|
||||
// Sync Server URL - prefill with https:// if empty to save typing
|
||||
const std::string currentUrl = KOREADER_STORE.getServerUrl();
|
||||
const std::string prefillUrl = currentUrl.empty() ? "https://" : currentUrl;
|
||||
exitActivity();
|
||||
enterNewActivity(new KeyboardEntryActivity(
|
||||
renderer, mappedInput, tr(STR_SYNC_SERVER_URL), prefillUrl,
|
||||
128, // maxLength - URLs can be long
|
||||
false, // not password
|
||||
[this](const std::string& url) {
|
||||
// Clear if user just left the prefilled https://
|
||||
const std::string urlToSave = (url == "https://" || url == "http://") ? "" : url;
|
||||
KOREADER_STORE.setServerUrl(urlToSave);
|
||||
KOREADER_STORE.saveToFile();
|
||||
exitActivity();
|
||||
requestUpdate();
|
||||
},
|
||||
[this]() {
|
||||
exitActivity();
|
||||
requestUpdate();
|
||||
}));
|
||||
startActivityForResult(
|
||||
std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_SYNC_SERVER_URL), prefillUrl,
|
||||
128, // maxLength - URLs can be long
|
||||
false), // not password
|
||||
[this](const ActivityResult& result) {
|
||||
if (!result.isCancelled) {
|
||||
const auto& kb = std::get<KeyboardResult>(result.data);
|
||||
const std::string urlToSave = (kb.text == "https://" || kb.text == "http://") ? "" : kb.text;
|
||||
KOREADER_STORE.setServerUrl(urlToSave);
|
||||
KOREADER_STORE.saveToFile();
|
||||
}
|
||||
});
|
||||
} else if (selectedIndex == 3) {
|
||||
// Document Matching - toggle between Filename and Binary
|
||||
const auto current = KOREADER_STORE.getMatchMethod();
|
||||
@@ -125,15 +107,11 @@ void KOReaderSettingsActivity::handleSelection() {
|
||||
// Can't authenticate without credentials - just show message briefly
|
||||
return;
|
||||
}
|
||||
exitActivity();
|
||||
enterNewActivity(new KOReaderAuthActivity(renderer, mappedInput, [this] {
|
||||
exitActivity();
|
||||
requestUpdate();
|
||||
}));
|
||||
startActivityForResult(std::make_unique<KOReaderAuthActivity>(renderer, mappedInput), [](const ActivityResult&) {});
|
||||
}
|
||||
}
|
||||
|
||||
void KOReaderSettingsActivity::render(Activity::RenderLock&&) {
|
||||
void KOReaderSettingsActivity::render(RenderLock&&) {
|
||||
renderer.clearScreen();
|
||||
|
||||
const auto& metrics = UITheme::getInstance().getMetrics();
|
||||
|
||||
@@ -1,30 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "activities/ActivityWithSubactivity.h"
|
||||
#include "activities/Activity.h"
|
||||
#include "util/ButtonNavigator.h"
|
||||
|
||||
/**
|
||||
* Submenu for KOReader Sync settings.
|
||||
* Shows username, password, and authenticate options.
|
||||
*/
|
||||
class KOReaderSettingsActivity final : public ActivityWithSubactivity {
|
||||
class KOReaderSettingsActivity final : public Activity {
|
||||
public:
|
||||
explicit KOReaderSettingsActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
const std::function<void()>& onBack)
|
||||
: ActivityWithSubactivity("KOReaderSettings", renderer, mappedInput), onBack(onBack) {}
|
||||
explicit KOReaderSettingsActivity(GfxRenderer& renderer, MappedInputManager& mappedInput)
|
||||
: Activity("KOReaderSettings", renderer, mappedInput) {}
|
||||
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
void render(Activity::RenderLock&&) override;
|
||||
void render(RenderLock&&) override;
|
||||
|
||||
private:
|
||||
ButtonNavigator buttonNavigator;
|
||||
|
||||
size_t selectedIndex = 0;
|
||||
const std::function<void()> onBack;
|
||||
|
||||
void handleSelection();
|
||||
};
|
||||
|
||||
@@ -58,7 +58,7 @@ void LanguageSelectActivity::handleSelection() {
|
||||
onBack();
|
||||
}
|
||||
|
||||
void LanguageSelectActivity::render(Activity::RenderLock&&) {
|
||||
void LanguageSelectActivity::render(RenderLock&&) {
|
||||
renderer.clearScreen();
|
||||
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "../ActivityWithSubactivity.h"
|
||||
#include "../Activity.h"
|
||||
#include "components/UITheme.h"
|
||||
#include "util/ButtonNavigator.h"
|
||||
|
||||
@@ -16,19 +16,18 @@ class MappedInputManager;
|
||||
*/
|
||||
class LanguageSelectActivity final : public Activity {
|
||||
public:
|
||||
explicit LanguageSelectActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
const std::function<void()>& onBack)
|
||||
: Activity("LanguageSelect", renderer, mappedInput), onBack(onBack) {}
|
||||
explicit LanguageSelectActivity(GfxRenderer& renderer, MappedInputManager& mappedInput)
|
||||
: Activity("LanguageSelect", renderer, mappedInput) {}
|
||||
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
void render(Activity::RenderLock&&) override;
|
||||
void render(RenderLock&&) override;
|
||||
|
||||
private:
|
||||
void handleSelection();
|
||||
|
||||
std::function<void()> onBack;
|
||||
void onBack() { finish(); }
|
||||
ButtonNavigator buttonNavigator;
|
||||
int selectedIndex = 0;
|
||||
constexpr static uint8_t totalItems = getLanguageCount();
|
||||
|
||||
@@ -11,11 +11,9 @@
|
||||
#include "network/OtaUpdater.h"
|
||||
|
||||
void OtaUpdateActivity::onWifiSelectionComplete(const bool success) {
|
||||
exitActivity();
|
||||
|
||||
if (!success) {
|
||||
LOG_ERR("OTA", "WiFi connection failed, exiting");
|
||||
goBack();
|
||||
activityManager.popActivity();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -34,7 +32,6 @@ void OtaUpdateActivity::onWifiSelectionComplete(const bool success) {
|
||||
RenderLock lock(*this);
|
||||
state = FAILED;
|
||||
}
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -44,7 +41,6 @@ void OtaUpdateActivity::onWifiSelectionComplete(const bool success) {
|
||||
RenderLock lock(*this);
|
||||
state = NO_UPDATE;
|
||||
}
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -52,11 +48,10 @@ void OtaUpdateActivity::onWifiSelectionComplete(const bool success) {
|
||||
RenderLock lock(*this);
|
||||
state = WAITING_CONFIRMATION;
|
||||
}
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
void OtaUpdateActivity::onEnter() {
|
||||
ActivityWithSubactivity::onEnter();
|
||||
Activity::onEnter();
|
||||
|
||||
// Turn on WiFi immediately
|
||||
LOG_DBG("OTA", "Turning on WiFi...");
|
||||
@@ -64,12 +59,12 @@ void OtaUpdateActivity::onEnter() {
|
||||
|
||||
// Launch WiFi selection subactivity
|
||||
LOG_DBG("OTA", "Launching WifiSelectionActivity...");
|
||||
enterNewActivity(new WifiSelectionActivity(renderer, mappedInput,
|
||||
[this](const bool connected) { onWifiSelectionComplete(connected); }));
|
||||
startActivityForResult(std::make_unique<WifiSelectionActivity>(renderer, mappedInput),
|
||||
[this](const ActivityResult& result) { onWifiSelectionComplete(!result.isCancelled); });
|
||||
}
|
||||
|
||||
void OtaUpdateActivity::onExit() {
|
||||
ActivityWithSubactivity::onExit();
|
||||
Activity::onExit();
|
||||
|
||||
// Turn off wifi
|
||||
WiFi.disconnect(false); // false = don't erase credentials, send disconnect frame
|
||||
@@ -78,12 +73,7 @@ void OtaUpdateActivity::onExit() {
|
||||
delay(100); // Allow WiFi hardware to fully power down
|
||||
}
|
||||
|
||||
void OtaUpdateActivity::render(Activity::RenderLock&&) {
|
||||
if (subActivity) {
|
||||
// Subactivity handles its own rendering
|
||||
return;
|
||||
}
|
||||
|
||||
void OtaUpdateActivity::render(RenderLock&&) {
|
||||
const auto& metrics = UITheme::getInstance().getMetrics();
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
const auto pageHeight = renderer.getScreenHeight();
|
||||
@@ -154,11 +144,6 @@ void OtaUpdateActivity::loop() {
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
if (subActivity) {
|
||||
subActivity->loop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == WAITING_CONFIRMATION) {
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||
LOG_DBG("OTA", "New update available, starting download...");
|
||||
@@ -166,7 +151,6 @@ void OtaUpdateActivity::loop() {
|
||||
RenderLock lock(*this);
|
||||
state = UPDATE_IN_PROGRESS;
|
||||
}
|
||||
requestUpdate();
|
||||
requestUpdateAndWait();
|
||||
const auto res = updater.installUpdate();
|
||||
|
||||
@@ -188,7 +172,7 @@ void OtaUpdateActivity::loop() {
|
||||
}
|
||||
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||
goBack();
|
||||
activityManager.popActivity();
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -196,14 +180,14 @@ void OtaUpdateActivity::loop() {
|
||||
|
||||
if (state == FAILED) {
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||
goBack();
|
||||
activityManager.popActivity();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == NO_UPDATE) {
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||
goBack();
|
||||
activityManager.popActivity();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "activities/ActivityWithSubactivity.h"
|
||||
#include "activities/Activity.h"
|
||||
#include "network/OtaUpdater.h"
|
||||
|
||||
class OtaUpdateActivity : public ActivityWithSubactivity {
|
||||
class OtaUpdateActivity : public Activity {
|
||||
enum State {
|
||||
WIFI_SELECTION,
|
||||
CHECKING_FOR_UPDATE,
|
||||
@@ -18,7 +18,6 @@ class OtaUpdateActivity : public ActivityWithSubactivity {
|
||||
// Can't initialize this to 0 or the first render doesn't happen
|
||||
static constexpr unsigned int UNINITIALIZED_PERCENTAGE = 111;
|
||||
|
||||
const std::function<void()> goBack;
|
||||
State state = WIFI_SELECTION;
|
||||
unsigned int lastUpdaterPercentage = UNINITIALIZED_PERCENTAGE;
|
||||
OtaUpdater updater;
|
||||
@@ -26,13 +25,12 @@ class OtaUpdateActivity : public ActivityWithSubactivity {
|
||||
void onWifiSelectionComplete(bool success);
|
||||
|
||||
public:
|
||||
explicit OtaUpdateActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
const std::function<void()>& goBack)
|
||||
: ActivityWithSubactivity("OtaUpdate", renderer, mappedInput), goBack(goBack), updater() {}
|
||||
explicit OtaUpdateActivity(GfxRenderer& renderer, MappedInputManager& mappedInput)
|
||||
: Activity("OtaUpdate", renderer, mappedInput), updater() {}
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
void render(Activity::RenderLock&&) override;
|
||||
void render(RenderLock&&) override;
|
||||
bool preventAutoSleep() override { return state == CHECKING_FOR_UPDATE || state == UPDATE_IN_PROGRESS; }
|
||||
bool skipLoopDelay() override { return true; } // Prevent power-saving mode
|
||||
};
|
||||
|
||||
@@ -67,16 +67,12 @@ void SettingsActivity::onEnter() {
|
||||
}
|
||||
|
||||
void SettingsActivity::onExit() {
|
||||
ActivityWithSubactivity::onExit();
|
||||
Activity::onExit();
|
||||
|
||||
UITheme::getInstance().reload(); // Re-apply theme in case it was changed
|
||||
}
|
||||
|
||||
void SettingsActivity::loop() {
|
||||
if (subActivity) {
|
||||
subActivity->loop();
|
||||
return;
|
||||
}
|
||||
bool hasChangedCategory = false;
|
||||
|
||||
// Handle actions with early return
|
||||
@@ -164,50 +160,38 @@ void SettingsActivity::toggleCurrentSetting() {
|
||||
SETTINGS.*(setting.valuePtr) = currentValue + setting.valueRange.step;
|
||||
}
|
||||
} else if (setting.type == SettingType::ACTION) {
|
||||
auto enterSubActivity = [this](Activity* activity) {
|
||||
exitActivity();
|
||||
enterNewActivity(activity);
|
||||
};
|
||||
|
||||
auto onComplete = [this] {
|
||||
exitActivity();
|
||||
requestUpdate();
|
||||
};
|
||||
|
||||
auto onCompleteBool = [this](bool) {
|
||||
exitActivity();
|
||||
requestUpdate();
|
||||
};
|
||||
auto resultHandler = [this](const ActivityResult&) { SETTINGS.saveToFile(); };
|
||||
|
||||
switch (setting.action) {
|
||||
case SettingAction::RemapFrontButtons:
|
||||
enterSubActivity(new ButtonRemapActivity(renderer, mappedInput, onComplete));
|
||||
startActivityForResult(std::make_unique<ButtonRemapActivity>(renderer, mappedInput), resultHandler);
|
||||
break;
|
||||
case SettingAction::CustomiseStatusBar:
|
||||
enterSubActivity(new StatusBarSettingsActivity(renderer, mappedInput, onComplete));
|
||||
startActivityForResult(std::make_unique<StatusBarSettingsActivity>(renderer, mappedInput), resultHandler);
|
||||
break;
|
||||
case SettingAction::KOReaderSync:
|
||||
enterSubActivity(new KOReaderSettingsActivity(renderer, mappedInput, onComplete));
|
||||
startActivityForResult(std::make_unique<KOReaderSettingsActivity>(renderer, mappedInput), resultHandler);
|
||||
break;
|
||||
case SettingAction::OPDSBrowser:
|
||||
enterSubActivity(new CalibreSettingsActivity(renderer, mappedInput, onComplete));
|
||||
startActivityForResult(std::make_unique<CalibreSettingsActivity>(renderer, mappedInput), resultHandler);
|
||||
break;
|
||||
case SettingAction::Network:
|
||||
enterSubActivity(new WifiSelectionActivity(renderer, mappedInput, onCompleteBool, false));
|
||||
startActivityForResult(std::make_unique<WifiSelectionActivity>(renderer, mappedInput, false), resultHandler);
|
||||
break;
|
||||
case SettingAction::ClearCache:
|
||||
enterSubActivity(new ClearCacheActivity(renderer, mappedInput, onComplete));
|
||||
startActivityForResult(std::make_unique<ClearCacheActivity>(renderer, mappedInput), resultHandler);
|
||||
break;
|
||||
case SettingAction::CheckForUpdates:
|
||||
enterSubActivity(new OtaUpdateActivity(renderer, mappedInput, onComplete));
|
||||
startActivityForResult(std::make_unique<OtaUpdateActivity>(renderer, mappedInput), resultHandler);
|
||||
break;
|
||||
case SettingAction::Language:
|
||||
enterSubActivity(new LanguageSelectActivity(renderer, mappedInput, onComplete));
|
||||
startActivityForResult(std::make_unique<LanguageSelectActivity>(renderer, mappedInput), resultHandler);
|
||||
break;
|
||||
case SettingAction::None:
|
||||
// Do nothing
|
||||
break;
|
||||
}
|
||||
return; // Results will be handled in the result handler, so we can return early here
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
@@ -215,7 +199,7 @@ void SettingsActivity::toggleCurrentSetting() {
|
||||
SETTINGS.saveToFile();
|
||||
}
|
||||
|
||||
void SettingsActivity::render(Activity::RenderLock&&) {
|
||||
void SettingsActivity::render(RenderLock&&) {
|
||||
renderer.clearScreen();
|
||||
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "activities/ActivityWithSubactivity.h"
|
||||
#include "activities/Activity.h"
|
||||
#include "util/ButtonNavigator.h"
|
||||
|
||||
class CrossPointSettings;
|
||||
@@ -134,7 +134,7 @@ struct SettingInfo {
|
||||
}
|
||||
};
|
||||
|
||||
class SettingsActivity final : public ActivityWithSubactivity {
|
||||
class SettingsActivity final : public Activity {
|
||||
ButtonNavigator buttonNavigator;
|
||||
|
||||
int selectedCategoryIndex = 0; // Currently selected category
|
||||
@@ -148,8 +148,6 @@ class SettingsActivity final : public ActivityWithSubactivity {
|
||||
std::vector<SettingInfo> systemSettings;
|
||||
const std::vector<SettingInfo>* currentSettings = nullptr;
|
||||
|
||||
const std::function<void()> onGoHome;
|
||||
|
||||
static constexpr int categoryCount = 4;
|
||||
static const StrId categoryNames[categoryCount];
|
||||
|
||||
@@ -157,11 +155,10 @@ class SettingsActivity final : public ActivityWithSubactivity {
|
||||
void toggleCurrentSetting();
|
||||
|
||||
public:
|
||||
explicit SettingsActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
const std::function<void()>& onGoHome)
|
||||
: ActivityWithSubactivity("Settings", renderer, mappedInput), onGoHome(onGoHome) {}
|
||||
explicit SettingsActivity(GfxRenderer& renderer, MappedInputManager& mappedInput)
|
||||
: Activity("Settings", renderer, mappedInput) {}
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
void render(Activity::RenderLock&&) override;
|
||||
void render(RenderLock&&) override;
|
||||
};
|
||||
|
||||
@@ -58,7 +58,7 @@ void StatusBarSettingsActivity::onExit() { Activity::onExit(); }
|
||||
|
||||
void StatusBarSettingsActivity::loop() {
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||
onBack();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ void StatusBarSettingsActivity::handleSelection() {
|
||||
SETTINGS.saveToFile();
|
||||
}
|
||||
|
||||
void StatusBarSettingsActivity::render(Activity::RenderLock&&) {
|
||||
void StatusBarSettingsActivity::render(RenderLock&&) {
|
||||
renderer.clearScreen();
|
||||
|
||||
auto metrics = UITheme::getInstance().getMetrics();
|
||||
|
||||
@@ -9,22 +9,18 @@
|
||||
// Reader status bar configuration activity
|
||||
class StatusBarSettingsActivity final : public Activity {
|
||||
public:
|
||||
explicit StatusBarSettingsActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
const std::function<void()>& onBack)
|
||||
: Activity("StatusBarSettings", renderer, mappedInput), onBack(onBack) {}
|
||||
explicit StatusBarSettingsActivity(GfxRenderer& renderer, MappedInputManager& mappedInput)
|
||||
: Activity("StatusBarSettings", renderer, mappedInput) {}
|
||||
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
void render(Activity::RenderLock&&) override;
|
||||
void render(RenderLock&&) override;
|
||||
|
||||
private:
|
||||
ButtonNavigator buttonNavigator;
|
||||
|
||||
int selectedIndex = 0;
|
||||
|
||||
const std::function<void()> onBack;
|
||||
|
||||
static void taskTrampoline(void* param);
|
||||
void handleSelection();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user