Refactor app loading and window management (#609)
This commit is contained in:
committed by
GitHub
parent
dc3f6104b8
commit
37c507544b
@@ -1,98 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <Tactility/app/AppContext.h>
|
||||
#include <Tactility/app/AppManifest.h>
|
||||
#include <Tactility/app/ElfApp.h>
|
||||
|
||||
#include <Tactility/Bundle.h>
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/log.h>
|
||||
#include <Tactility/Mutex.h>
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
namespace tt::app {
|
||||
|
||||
enum class State {
|
||||
Initial, // AppInstance was created, but the state hasn't advanced yet
|
||||
Created, // App was placed into memory
|
||||
Showing, // App view was created
|
||||
Hiding, // App view was destroyed
|
||||
Destroyed // App was removed from memory
|
||||
};
|
||||
|
||||
/**
|
||||
* Thread-safe app instance.
|
||||
*/
|
||||
class AppInstance : public AppContext {
|
||||
|
||||
Mutex mutex;
|
||||
const std::shared_ptr<AppManifest> manifest;
|
||||
State state = State::Initial;
|
||||
LaunchId launchId;
|
||||
Flags flags = { .hideStatusbar = true };
|
||||
/** @brief Optional parameters to start the app with
|
||||
* When these are stored in the app struct, the struct takes ownership.
|
||||
* Do not mutate after app creation.
|
||||
*/
|
||||
std::shared_ptr<const Bundle> parameters;
|
||||
|
||||
std::shared_ptr<App> app;
|
||||
|
||||
static std::shared_ptr<App> createApp(
|
||||
const std::shared_ptr<AppManifest>& manifest
|
||||
) {
|
||||
if (manifest->appLocation.isInternal()) {
|
||||
assert(manifest->createApp != nullptr);
|
||||
return manifest->createApp();
|
||||
} else if (manifest->appLocation.isExternal()) {
|
||||
if (manifest->createApp != nullptr) {
|
||||
LOG_W("AppInstance", "Manifest specifies createApp, but this is not used with external apps");
|
||||
}
|
||||
#ifdef ESP_PLATFORM
|
||||
return createElfApp(manifest);
|
||||
#else
|
||||
check(false, "not supported");
|
||||
#endif
|
||||
} else {
|
||||
check(false, "not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
explicit AppInstance(const std::shared_ptr<AppManifest>& manifest, LaunchId launchId) :
|
||||
manifest(manifest),
|
||||
launchId(launchId),
|
||||
app(createApp(manifest))
|
||||
{}
|
||||
|
||||
AppInstance(const std::shared_ptr<AppManifest>& manifest, LaunchId launchId, std::shared_ptr<const Bundle> parameters) :
|
||||
manifest(manifest),
|
||||
launchId(launchId),
|
||||
parameters(std::move(parameters)),
|
||||
app(createApp(manifest))
|
||||
{}
|
||||
|
||||
~AppInstance() override = default;
|
||||
|
||||
LaunchId getLaunchId() const { return launchId; }
|
||||
|
||||
void setState(State state);
|
||||
State getState() const;
|
||||
|
||||
const AppManifest& getManifest() const override;
|
||||
|
||||
Flags getFlags() const;
|
||||
void setFlags(Flags flags);
|
||||
Flags& mutableFlags() { return flags; } // TODO: locking mechanism
|
||||
|
||||
std::shared_ptr<const Bundle> getParameters() const override;
|
||||
|
||||
std::unique_ptr<AppPaths> getPaths() const override;
|
||||
|
||||
std::shared_ptr<App> getApp() const override { return app; }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
@@ -1,14 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <Tactility/app/AppManifest.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace tt::app {
|
||||
|
||||
bool isValidId(const std::string& id);
|
||||
|
||||
/** Parses a manifest.properties file, auto-detecting the V1 (sectioned) or V2 (flat) format from its first line. */
|
||||
bool parseManifest(const std::string& filePath, AppManifest& manifest);
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <Tactility/app/AppManifest.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace tt::app {
|
||||
|
||||
bool getValueFromManifest(const std::map<std::string, std::string>& map, const std::string& key, std::string& output);
|
||||
|
||||
bool isValidManifestVersion(const std::string& version);
|
||||
bool isValidAppVersionName(const std::string& version);
|
||||
bool isValidAppVersionCode(const std::string& version);
|
||||
bool isValidName(const std::string& name);
|
||||
|
||||
/** Parses a V1 (sectioned INI, e.g. "[app]versionName=...") manifest map. */
|
||||
bool parseManifestV1(const std::map<std::string, std::string>& map, AppManifest& manifest);
|
||||
|
||||
/** Parses a V2 (flat dot-notation, e.g. "app.version.name=...") manifest map. */
|
||||
bool parseManifestV2(const std::map<std::string, std::string>& map, AppManifest& manifest);
|
||||
|
||||
}
|
||||
@@ -5,11 +5,13 @@
|
||||
|
||||
namespace tt::app::btmanage {
|
||||
|
||||
typedef void (*OnBtToggled)(bool enable);
|
||||
typedef void (*OnScanToggled)(bool enable);
|
||||
// `context` is this app instance's Context* (see BtManagePrivate.h) - the new app-module has no
|
||||
// global "current app" accessor, so callbacks need it threaded through explicitly.
|
||||
typedef void (*OnBtToggled)(void* context, bool enable);
|
||||
typedef void (*OnScanToggled)(void* context, bool enable);
|
||||
typedef void (*OnConnectPeer)(const std::array<uint8_t, 6>& addr, int profileId);
|
||||
typedef void (*OnDisconnectPeer)(const std::array<uint8_t, 6>& addr, int profileId);
|
||||
typedef void (*OnPairPeer)(const std::array<uint8_t, 6>& addr);
|
||||
typedef void (*OnPairPeer)(void* context, const std::array<uint8_t, 6>& addr);
|
||||
typedef void (*OnForgetPeer)(const std::array<uint8_t, 6>& addr);
|
||||
|
||||
struct Bindings {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "./View.h"
|
||||
#include "./State.h"
|
||||
|
||||
#include <Tactility/app/App.h>
|
||||
#include <Tactility/Mutex.h>
|
||||
#include <Tactility/bluetooth/Bluetooth.h>
|
||||
#include <tactility/drivers/bluetooth.h>
|
||||
@@ -13,54 +12,37 @@
|
||||
|
||||
namespace tt::app::btmanage {
|
||||
|
||||
class BtManage final : public App {
|
||||
|
||||
struct Context {
|
||||
uint32_t appInstanceId;
|
||||
Mutex mutex;
|
||||
Bindings bindings = { };
|
||||
Bindings bindings {};
|
||||
State state;
|
||||
View view = View(&bindings, &state);
|
||||
bool isViewEnabled = false;
|
||||
Device* btDevice = nullptr;
|
||||
bool callbackRegistered = false;
|
||||
|
||||
// Bumped by onHide() to invalidate any BT event already dispatched to the main
|
||||
// task for this show/hide session (BtManage is reused across hide/show cycles -
|
||||
// e.g. launching BtPeerSettings pushes it on top and hides this instance without
|
||||
// destroying it). Kept in its own heap allocation, independent of BtManage's
|
||||
// lifetime, so a dispatched callback can check it without touching a possibly
|
||||
// already-destroyed `this`.
|
||||
// Bumped right before the BT event callback is unregistered at the end of appMain(), to
|
||||
// invalidate any BT event already dispatched to the main task for this instance. Kept in
|
||||
// its own heap allocation, independent of Context's (stack-local) lifetime, so a dispatched
|
||||
// callback can check it without touching a possibly already-destroyed Context.
|
||||
std::shared_ptr<std::atomic<int>> generation = std::make_shared<std::atomic<int>>(0);
|
||||
|
||||
public:
|
||||
|
||||
void onBtEvent(const struct BtEvent& event);
|
||||
|
||||
BtManage();
|
||||
|
||||
void lock();
|
||||
void unlock();
|
||||
|
||||
void onShow(AppContext& app, lv_obj_t* parent) override;
|
||||
void onHide(AppContext& app) override;
|
||||
|
||||
Bindings& getBindings() { return bindings; }
|
||||
State& getState() { return state; }
|
||||
|
||||
void requestViewUpdate();
|
||||
|
||||
std::shared_ptr<std::atomic<int>> getGeneration() const { return generation; }
|
||||
|
||||
// Re-attempts registering the device event callback. Needed because the BLE driver
|
||||
// only allocates its callback list while the device is started/on: a registration
|
||||
// attempted while the radio is off silently no-ops, so this must be called again
|
||||
// right after a successful bluetooth::start(). Idempotent: no-ops if already
|
||||
// registered for this device, so it's safe to call from both onShow() and here.
|
||||
void registerDeviceCallback(Device* dev);
|
||||
|
||||
// Call after bluetooth::stop(): the driver frees its callback list on stop, so the
|
||||
// registration state must be cleared here too, without touching the (now-dangling)
|
||||
// driver-side list.
|
||||
void forgetCallbackRegistration();
|
||||
void lock() { mutex.lock(); }
|
||||
void unlock() { mutex.unlock(); }
|
||||
};
|
||||
|
||||
void onBtEvent(Context* ctx, const struct BtEvent& event);
|
||||
void requestViewUpdate(Context* ctx);
|
||||
|
||||
// Re-attempts registering the device event callback. Needed because the BLE driver only
|
||||
// allocates its callback list while the device is started/on: a registration attempted while
|
||||
// the radio is off silently no-ops, so this must be called again right after a successful
|
||||
// bluetooth::start(). Idempotent: no-ops if already registered for this device.
|
||||
void registerDeviceCallback(Context* ctx, Device* dev);
|
||||
|
||||
// Call after bluetooth::stop(): the driver frees its callback list on stop, so the
|
||||
// registration state must be cleared here too, without touching the (now-dangling) driver-side
|
||||
// list.
|
||||
void forgetCallbackRegistration(Context* ctx);
|
||||
|
||||
} // namespace tt::app::btmanage
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
#include "./Bindings.h"
|
||||
#include "./State.h"
|
||||
|
||||
#include <Tactility/app/AppContext.h>
|
||||
#include <Tactility/app/AppPaths.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <lvgl.h>
|
||||
|
||||
namespace tt::app::btmanage {
|
||||
@@ -14,7 +12,9 @@ class View final {
|
||||
|
||||
Bindings* bindings;
|
||||
State* state;
|
||||
std::unique_ptr<AppPaths> paths;
|
||||
// Passed through to onBtToggled/onScanToggled/onPairPeer via lv_obj user_data - see
|
||||
// Bindings.h. Set in init(), before any callback can fire.
|
||||
void* context = nullptr;
|
||||
lv_obj_t* root = nullptr;
|
||||
lv_obj_t* enable_switch = nullptr;
|
||||
lv_obj_t* enable_on_boot_switch = nullptr;
|
||||
@@ -34,7 +34,7 @@ public:
|
||||
|
||||
View(Bindings* bindings, State* state) : bindings(bindings), state(state) {}
|
||||
|
||||
void init(const AppContext& app, lv_obj_t* parent);
|
||||
void init(void* context, lv_obj_t* parent);
|
||||
void update();
|
||||
};
|
||||
|
||||
|
||||
@@ -10,37 +10,32 @@
|
||||
#include "ChatView.h"
|
||||
#include "ChatSettings.h"
|
||||
|
||||
#include <Tactility/app/App.h>
|
||||
#include <Tactility/service/espnow/EspNow.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace tt::app::chat {
|
||||
|
||||
class ChatApp final : public App {
|
||||
|
||||
// Replaces the old ChatApp (tt::app::App subclass) under the thread-per-app model. Declared
|
||||
// here (rather than local to ChatApp.cpp's anonymous namespace, as most converted apps do)
|
||||
// because ChatView - a separate translation unit - also needs to reference it.
|
||||
struct Context {
|
||||
uint32_t appInstanceId;
|
||||
ChatState state;
|
||||
ChatView view = ChatView(this, &state);
|
||||
service::espnow::ReceiverSubscription receiveSubscription = -1;
|
||||
ChatSettingsData settings;
|
||||
bool isFirstLaunch = false;
|
||||
|
||||
void onReceive(const esp_now_recv_info_t* receiveInfo, const uint8_t* data, int length);
|
||||
void enableEspNow();
|
||||
void disableEspNow();
|
||||
|
||||
public:
|
||||
void onCreate(AppContext& appContext) override;
|
||||
void onDestroy(AppContext& appContext) override;
|
||||
void onShow(AppContext& context, lv_obj_t* parent) override;
|
||||
|
||||
void sendMessage(const std::string& text);
|
||||
void applySettings(const std::string& nickname, const std::string& keyHex);
|
||||
void switchChannel(const std::string& chatChannel);
|
||||
|
||||
const ChatSettingsData& getSettings() const { return settings; }
|
||||
|
||||
~ChatApp() override = default;
|
||||
};
|
||||
|
||||
void enableEspNow(Context* ctx);
|
||||
void disableEspNow(Context* ctx);
|
||||
|
||||
void sendMessage(Context* ctx, const std::string& text);
|
||||
void applySettings(Context* ctx, const std::string& nickname, const std::string& keyHex);
|
||||
void switchChannel(Context* ctx, const std::string& chatChannel);
|
||||
|
||||
} // namespace tt::app::chat
|
||||
|
||||
#endif // CONFIG_SOC_WIFI_SUPPORTED || CONFIG_SLAVE_SOC_WIFI_SUPPORTED
|
||||
|
||||
@@ -9,18 +9,16 @@
|
||||
#include "ChatState.h"
|
||||
#include "ChatSettings.h"
|
||||
|
||||
#include <Tactility/app/AppContext.h>
|
||||
|
||||
#include <esp_now.h>
|
||||
#include <lvgl.h>
|
||||
|
||||
namespace tt::app::chat {
|
||||
|
||||
class ChatApp;
|
||||
struct Context;
|
||||
|
||||
class ChatView {
|
||||
|
||||
ChatApp* app;
|
||||
Context* app;
|
||||
ChatState* state;
|
||||
|
||||
lv_obj_t* toolbar = nullptr;
|
||||
@@ -45,6 +43,7 @@ class ChatView {
|
||||
|
||||
static void addMessageToList(lv_obj_t* msgList, const StoredMessage& msg);
|
||||
|
||||
static void onBackPressed(lv_event_t* e);
|
||||
static void onSendClicked(lv_event_t* e);
|
||||
static void onSettingsClicked(lv_event_t* e);
|
||||
static void onSettingsSave(lv_event_t* e);
|
||||
@@ -54,7 +53,7 @@ class ChatView {
|
||||
static void onChannelCancel(lv_event_t* e);
|
||||
|
||||
public:
|
||||
ChatView(ChatApp* app, ChatState* state) : app(app), state(state) {}
|
||||
ChatView(Context* app, ChatState* state) : app(app), state(state) {}
|
||||
~ChatView() = default;
|
||||
|
||||
ChatView(const ChatView&) = delete;
|
||||
@@ -62,7 +61,7 @@ public:
|
||||
ChatView(ChatView&&) = delete;
|
||||
ChatView& operator=(ChatView&&) = delete;
|
||||
|
||||
void init(AppContext& appContext, lv_obj_t* parent);
|
||||
void init(lv_obj_t* parent);
|
||||
|
||||
void displayMessage(const StoredMessage& msg);
|
||||
void refreshMessageList();
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
|
||||
namespace tt::app::development {
|
||||
|
||||
void start();
|
||||
|
||||
}
|
||||
|
||||
#endif // ESP_PLATFORM
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
#include "./State.h"
|
||||
|
||||
#include <Tactility/app/AppManifest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <lvgl.h>
|
||||
#include <memory>
|
||||
|
||||
@@ -11,7 +10,8 @@ namespace tt::app::files {
|
||||
|
||||
class View final {
|
||||
std::shared_ptr<State> state;
|
||||
|
||||
uint32_t appInstanceId = 0;
|
||||
|
||||
size_t current_start_index = 0;
|
||||
size_t last_loaded_index = 0;
|
||||
const size_t MAX_BATCH = 50;
|
||||
@@ -24,7 +24,7 @@ class View final {
|
||||
lv_obj_t* paste_button = nullptr;
|
||||
|
||||
std::string installAppPath = { 0 };
|
||||
LaunchId installAppLaunchId = 0;
|
||||
uint32_t installDialogId = 0;
|
||||
|
||||
void showActions();
|
||||
void showActionsForDirectory();
|
||||
@@ -39,9 +39,10 @@ public:
|
||||
|
||||
explicit View(const std::shared_ptr<State>& state) : state(state) {}
|
||||
|
||||
void init(const AppContext& appContext, lv_obj_t* parent);
|
||||
void init(uint32_t appInstanceId, lv_obj_t* parent);
|
||||
void update(size_t start_index = 0);
|
||||
|
||||
void onBackPressed();
|
||||
void onNavigateUpPressed();
|
||||
void onDirEntryPressed(uint32_t index);
|
||||
void onDirEntryLongPressed(int32_t index);
|
||||
@@ -54,8 +55,8 @@ public:
|
||||
void onPastePressed();
|
||||
void onEjectPressed();
|
||||
void onDirEntryListScrollBegin();
|
||||
void onResult(LaunchId launchId, Result result, std::unique_ptr<Bundle> bundle);
|
||||
void deinit(const AppContext& appContext);
|
||||
void onResult(uint32_t launchId, int32_t result);
|
||||
void deinit();
|
||||
|
||||
private:
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <Tactility/Bundle.h>
|
||||
|
||||
namespace tt::app::fileselection {
|
||||
|
||||
enum class Mode {
|
||||
@@ -9,6 +7,4 @@ enum class Mode {
|
||||
ExistingOrNew = 1
|
||||
};
|
||||
|
||||
Mode getMode(const Bundle& bundle);
|
||||
|
||||
}
|
||||
|
||||
@@ -3,14 +3,13 @@
|
||||
#include "./State.h"
|
||||
#include "./FileSelectionPrivate.h"
|
||||
|
||||
#include <Tactility/app/AppManifest.h>
|
||||
|
||||
#include <lvgl.h>
|
||||
#include <memory>
|
||||
|
||||
namespace tt::app::fileselection {
|
||||
|
||||
class View final {
|
||||
uint32_t appInstanceId;
|
||||
std::shared_ptr<State> state;
|
||||
|
||||
lv_obj_t* dir_entry_list = nullptr;
|
||||
@@ -22,11 +21,15 @@ class View final {
|
||||
void onTapFile(const std::string&path, const std::string&filename);
|
||||
static void onSelectButtonPressed(lv_event_t* event);
|
||||
static void onPathTextChanged(lv_event_t* event);
|
||||
/** Emits an async APP_EVENT_CLOSE for appInstanceId - see FileSelection.cpp's appMain() for
|
||||
* why this indirection (rather than calling app_manager_stop() here) is required. */
|
||||
static void onBackPressedCallback(lv_event_t* event);
|
||||
void createDirEntryWidget(lv_obj_t* parent, dirent& dir_entry);
|
||||
|
||||
public:
|
||||
|
||||
explicit View(const std::shared_ptr<State>& state, std::function<void(const std::string& path)> onFileSelected) :
|
||||
explicit View(uint32_t appInstanceId, const std::shared_ptr<State>& state, std::function<void(const std::string& path)> onFileSelected) :
|
||||
appInstanceId(appInstanceId),
|
||||
state(state),
|
||||
on_file_selected(std::move(onFileSelected))
|
||||
{}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <Tactility/app/App.h>
|
||||
#include <cstdint>
|
||||
|
||||
namespace tt::app::i2cscanner {
|
||||
|
||||
LaunchId start();
|
||||
uint32_t start();
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <Tactility/app/App.h>
|
||||
|
||||
namespace tt::app::launcher {
|
||||
|
||||
LaunchId start();
|
||||
uint32_t start();
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <Tactility/app/App.h>
|
||||
|
||||
namespace tt::app::localesettings {
|
||||
|
||||
LaunchId start();
|
||||
|
||||
}
|
||||
// Intentionally empty: LocaleSettings.cpp has no external callers (verified via repo-wide
|
||||
// grep during its thread-per-app conversion), so it no longer exposes a start() wrapper. This
|
||||
// header is kept as a placeholder in case that changes; nothing currently includes it.
|
||||
@@ -1,10 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <Tactility/app/App.h>
|
||||
|
||||
namespace tt::app::setup {
|
||||
|
||||
LaunchId start();
|
||||
void start();
|
||||
|
||||
/** @return true if the setup wizard has already run to completion */
|
||||
bool isCompleted();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <Tactility/app/App.h>
|
||||
#include <cstdint>
|
||||
|
||||
namespace tt::app::timedatesettings {
|
||||
|
||||
LaunchId start();
|
||||
uint32_t start();
|
||||
|
||||
}
|
||||
@@ -1,13 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <Tactility/app/App.h>
|
||||
#include <Tactility/Bundle.h>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace tt::app::timezone {
|
||||
|
||||
LaunchId start(bool saveTimeZone = false);
|
||||
/**
|
||||
* @return the started dialog's instance id; result (0 = Ok, 1 = Cancelled) is delivered to
|
||||
* @a callerAppInstanceId as APP_EVENT_RESULT once the user picks a time zone - call
|
||||
* getLastName()/getLastCode() right after receiving it, on result == 0.
|
||||
*/
|
||||
uint32_t start(uint32_t callerAppInstanceId, bool saveTimeZone = false);
|
||||
|
||||
std::string getResultName(const Bundle& bundle);
|
||||
std::string getResultCode(const Bundle& bundle);
|
||||
/** @return the name/code from the last time zone picked by any TimeZone dialog instance. Only
|
||||
* one dialog is expected to be open at a time. */
|
||||
std::string getLastName();
|
||||
std::string getLastCode();
|
||||
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <Tactility/service/wifi/WifiApSettings.h>
|
||||
|
||||
namespace tt::app::wificonnect {
|
||||
|
||||
typedef void (*OnConnectSsid)(const service::wifi::settings::WifiApSettings& settings, bool store, void* context);
|
||||
|
||||
typedef struct {
|
||||
OnConnectSsid onConnectSsid;
|
||||
void* onConnectSsidContext;
|
||||
} Bindings;
|
||||
|
||||
} // namespace
|
||||
@@ -1,24 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <Tactility/Mutex.h>
|
||||
#include <Tactility/service/wifi/WifiApSettings.h>
|
||||
|
||||
namespace tt::app::wificonnect {
|
||||
|
||||
class State final {
|
||||
Mutex lock;
|
||||
service::wifi::settings::WifiApSettings apSettings;
|
||||
bool connectionError = false;
|
||||
bool connecting = false;
|
||||
public:
|
||||
|
||||
void setConnectionError(bool error);
|
||||
bool hasConnectionError() const;
|
||||
|
||||
void setApSettings(const service::wifi::settings::WifiApSettings& newSettings);
|
||||
|
||||
void setConnecting(bool isConnecting);
|
||||
bool isConnecting() const;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
@@ -1,45 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "./Bindings.h"
|
||||
#include "./State.h"
|
||||
|
||||
#include <Tactility/app/AppContext.h>
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
namespace tt::app::wificonnect {
|
||||
|
||||
class WifiConnect;
|
||||
|
||||
class View final {
|
||||
|
||||
Bindings* bindings;
|
||||
State* state;
|
||||
|
||||
public:
|
||||
|
||||
lv_obj_t* ssid_textarea = nullptr;
|
||||
lv_obj_t* ssid_error = nullptr;
|
||||
lv_obj_t* password_textarea = nullptr;
|
||||
lv_obj_t* password_error = nullptr;
|
||||
lv_obj_t* connect_button = nullptr;
|
||||
lv_obj_t* remember_switch = nullptr;
|
||||
lv_obj_t* connecting_spinner = nullptr;
|
||||
lv_obj_t* connection_error = nullptr;
|
||||
lv_group_t* group = nullptr;
|
||||
|
||||
View(Bindings* bindings, State* state) :
|
||||
bindings(bindings),
|
||||
state(state)
|
||||
{}
|
||||
|
||||
void init(AppContext& app, lv_obj_t* parent);
|
||||
void update();
|
||||
|
||||
void createBottomButtons(lv_obj_t* parent);
|
||||
void setLoading(bool loading);
|
||||
void resetErrors();
|
||||
};
|
||||
|
||||
|
||||
} // namespace
|
||||
@@ -1,54 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <Tactility/app/App.h>
|
||||
#include <Tactility/app/wificonnect/Bindings.h>
|
||||
#include <Tactility/app/wificonnect/State.h>
|
||||
#include <Tactility/app/wificonnect/View.h>
|
||||
|
||||
#include <Tactility/Mutex.h>
|
||||
#include <Tactility/service/wifi/Wifi.h>
|
||||
#include <string>
|
||||
|
||||
namespace tt::app::wificonnect {
|
||||
|
||||
class WifiConnect final : public App {
|
||||
|
||||
Mutex mutex;
|
||||
State state;
|
||||
Bindings bindings = {
|
||||
.onConnectSsid = nullptr,
|
||||
.onConnectSsidContext = nullptr
|
||||
};
|
||||
View view = View(&bindings, &state);
|
||||
PubSub<service::wifi::WifiEvent>::SubscriptionHandle wifiSubscription;
|
||||
bool viewEnabled = false;
|
||||
|
||||
void onWifiEvent(service::wifi::WifiEvent event);
|
||||
|
||||
public:
|
||||
|
||||
WifiConnect();
|
||||
~WifiConnect() override;
|
||||
|
||||
void lock();
|
||||
void unlock();
|
||||
|
||||
void onShow(AppContext& app, lv_obj_t* parent) override;
|
||||
void onHide(AppContext& app) override;
|
||||
|
||||
State& getState() { return state; }
|
||||
Bindings& getBindings() { return bindings; }
|
||||
View& getView() { return view; }
|
||||
|
||||
void requestViewUpdate();
|
||||
};
|
||||
|
||||
/**
|
||||
* Start the app with optional pre-filled fields.
|
||||
*/
|
||||
LaunchId start(const std::string& ssid = "", const std::string& password = "");
|
||||
|
||||
bool optSsidParameter(const std::shared_ptr<const Bundle>& bundle, std::string& ssid);
|
||||
|
||||
bool optPasswordParameter(const std::shared_ptr<const Bundle>& bundle, std::string& password);
|
||||
void start(const std::string& ssid = "", const std::string& password = "");
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
#include "./Bindings.h"
|
||||
#include "./State.h"
|
||||
|
||||
#include <Tactility/app/AppContext.h>
|
||||
#include <Tactility/app/AppPaths.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <lvgl.h>
|
||||
|
||||
namespace tt::app::wifimanage {
|
||||
@@ -14,7 +12,7 @@ class View final {
|
||||
|
||||
Bindings* bindings;
|
||||
State* state;
|
||||
std::unique_ptr<AppPaths> paths;
|
||||
uint32_t appInstanceId = 0;
|
||||
lv_obj_t* root = nullptr;
|
||||
lv_obj_t* enable_switch = nullptr;
|
||||
lv_obj_t* enable_on_boot_switch = nullptr;
|
||||
@@ -36,7 +34,7 @@ public:
|
||||
|
||||
View(Bindings* bindings, State* state) : bindings(bindings), state(state) {}
|
||||
|
||||
void init(const AppContext& app, lv_obj_t* parent);
|
||||
void init(uint32_t appInstanceId, lv_obj_t* parent);
|
||||
void update();
|
||||
};
|
||||
|
||||
|
||||
@@ -3,39 +3,11 @@
|
||||
#include "./View.h"
|
||||
#include "./State.h"
|
||||
|
||||
#include <Tactility/app/App.h>
|
||||
|
||||
#include <Tactility/PubSub.h>
|
||||
#include <Tactility/Mutex.h>
|
||||
#include <Tactility/service/wifi/Wifi.h>
|
||||
|
||||
namespace tt::app::wifimanage {
|
||||
|
||||
class WifiManage final : public App {
|
||||
|
||||
PubSub<service::wifi::WifiEvent>::SubscriptionHandle wifiSubscription = nullptr;
|
||||
Mutex mutex;
|
||||
Bindings bindings = { };
|
||||
State state;
|
||||
View view = View(&bindings, &state);
|
||||
bool isViewEnabled = false;
|
||||
|
||||
void onWifiEvent(service::wifi::WifiEvent event);
|
||||
|
||||
public:
|
||||
|
||||
WifiManage();
|
||||
|
||||
void lock();
|
||||
void unlock();
|
||||
|
||||
void onShow(AppContext& app, lv_obj_t* parent) override;
|
||||
void onHide(AppContext& app) override;
|
||||
|
||||
Bindings& getBindings() { return bindings; }
|
||||
State& getState() { return state; }
|
||||
|
||||
void requestViewUpdate();
|
||||
};
|
||||
|
||||
} // namespace
|
||||
// Context (the app's actual runtime state) is defined inside WifiManage.cpp's own anonymous
|
||||
// namespace - View.cpp doesn't need it (Bindings*/State* pointers and a raw appInstanceId are
|
||||
// threaded through explicitly instead, see View.h/View.cpp). This header now only bundles
|
||||
// View.h/State.h for convenience, same as before.
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <Tactility/MessageQueue.h>
|
||||
#include <Tactility/PubSub.h>
|
||||
#include <Tactility/RecursiveMutex.h>
|
||||
#include <Tactility/service/Service.h>
|
||||
#include <Tactility/service/loader/Loader.h>
|
||||
|
||||
#include <Tactility/Semaphore.h>
|
||||
|
||||
#include <tactility/concurrent/dispatcher.h>
|
||||
|
||||
#include <lvgl/devices/keyboard.h>
|
||||
|
||||
namespace tt::service::gui {
|
||||
|
||||
/**
|
||||
* Output a log warning if the current task is the GUI task.
|
||||
* This is meant for code that should either create their own task or use a different task to execute on.
|
||||
* @param[in] context a descriptive name or label that refers to the caller of this function
|
||||
*/
|
||||
void warnIfRunningOnGuiTask(const char* context);
|
||||
|
||||
class GuiService final : public Service {
|
||||
|
||||
// Thread and lock
|
||||
Thread* thread = nullptr;
|
||||
DispatcherHandle_t dispatcher = nullptr;
|
||||
bool exitRequested = false;
|
||||
RecursiveMutex mutex;
|
||||
PubSub<loader::LoaderService::Event>::SubscriptionHandle loader_pubsub_subscription = nullptr;
|
||||
|
||||
// Signaled by hideApp() once App::onHide() has actually finished running on the GUI
|
||||
// task. onLoaderEvent() blocks on this (still on the Loader thread, inside the
|
||||
// synchronous pubsub publish() call) before returning from the ApplicationHiding
|
||||
// branch, so LoaderService::transitionAppToState(Hiding) can't return - and therefore
|
||||
// the immediately-following Destroyed transition (which unloads an ELF app's code via
|
||||
// esp_elf_deinit) can't run - until onHide() has fully completed. Without this, the
|
||||
// ELF's code/data can be unmapped while onHide() (and anything it spawned, like a
|
||||
// camera capture task) is still executing it.
|
||||
Semaphore hideDoneSem { 1, 0 };
|
||||
|
||||
// Layers and Canvas
|
||||
lv_obj_t* appRootWidget = nullptr;
|
||||
lv_obj_t* statusbarWidget = nullptr;
|
||||
|
||||
// App-specific
|
||||
std::shared_ptr<app::AppInstance> appToRender = nullptr;
|
||||
|
||||
LvglSoftwareKeyboard software_keyboard = {};
|
||||
|
||||
bool isStarted = false;
|
||||
|
||||
static int32_t guiMain();
|
||||
|
||||
static void onGuiDispatch(void* context);
|
||||
|
||||
void onLoaderEvent(loader::LoaderService::Event event);
|
||||
|
||||
lv_obj_t* createAppViews(lv_obj_t* parent);
|
||||
|
||||
void redraw();
|
||||
|
||||
void lock() const {
|
||||
check(mutex.lock(pdMS_TO_TICKS(1000)));
|
||||
}
|
||||
|
||||
void unlock() const {
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
void showApp(std::shared_ptr<app::AppInstance> app);
|
||||
|
||||
void hideApp();
|
||||
|
||||
public:
|
||||
|
||||
bool onStart(ServiceContext& service) override;
|
||||
|
||||
void onStop(ServiceContext& service) override;
|
||||
|
||||
/**
|
||||
* Show the on-screen keyboard.
|
||||
* @param[in] textarea the textarea to focus the input for
|
||||
*/
|
||||
void softwareKeyboardShow(lv_obj_t* textarea);
|
||||
|
||||
/**
|
||||
* Hide the on-screen keyboard.
|
||||
* Has no effect when the keyboard is not visible.
|
||||
*/
|
||||
void softwareKeyboardHide();
|
||||
|
||||
void keyboardAddTextArea(lv_obj_t* textarea);
|
||||
|
||||
/**
|
||||
* The on-screen keyboard is only shown when both of these conditions are true:
|
||||
* - there is no hardware keyboard
|
||||
* - TT_CONFIG_FORCE_ONSCREEN_KEYBOARD is set to true in tactility_config.h
|
||||
* @return if we should show a on-screen keyboard for text input inside our apps
|
||||
*/
|
||||
bool softwareKeyboardIsEnabled();
|
||||
};
|
||||
|
||||
std::shared_ptr<GuiService> findService();
|
||||
|
||||
} // namespace
|
||||
Reference in New Issue
Block a user