From fe6a0d1a1007b78f979ae66548eb664e10feb83b Mon Sep 17 00:00:00 2001
From: adgohar <114059470+adgohar@users.noreply.github.com>
Date: Wed, 9 Apr 2025 00:33:32 +0200
Subject: [PATCH 1/5] Add MiniApps to main code
---
.../app/src/main/assets/web/eventemitter.js | 48 ++
.../app/src/main/assets/web/img/miniapps.svg | 39 ++
.../main/assets/web/miniapps/miniAppHelper.js | 35 ++
.../src/main/assets/web/miniapps/mini_apps.js | 179 ++++++++
.../app/src/main/assets/web/tremola.css | 50 ++-
.../app/src/main/assets/web/tremola.html | 24 +-
.../app/src/main/assets/web/tremola.js | 18 +-
.../app/src/main/assets/web/tremola_ui.js | 104 ++++-
.../tremolavossbol/MainActivity.kt | 53 ++-
.../tremolavossbol/WebAppInterface.kt | 146 +++++-
.../tremolavossbol/miniapps/KanbanPlugin.kt | 117 +++++
.../tremolavossbol/miniapps/MiniAppPlugin.kt | 422 ++++++++++++++++++
.../tremolavossbol/miniapps/PluginLoader.kt | 66 +++
.../tremolavossbol/miniapps/SketchPlugin.kt | 22 +
.../tremolavossbol/utils/Constants.kt | 1 +
15 files changed, 1285 insertions(+), 39 deletions(-)
create mode 100644 android/tinySSB/app/src/main/assets/web/eventemitter.js
create mode 100644 android/tinySSB/app/src/main/assets/web/img/miniapps.svg
create mode 100644 android/tinySSB/app/src/main/assets/web/miniapps/miniAppHelper.js
create mode 100644 android/tinySSB/app/src/main/assets/web/miniapps/mini_apps.js
create mode 100644 android/tinySSB/app/src/main/java/nz/scuttlebutt/tremolavossbol/miniapps/KanbanPlugin.kt
create mode 100644 android/tinySSB/app/src/main/java/nz/scuttlebutt/tremolavossbol/miniapps/MiniAppPlugin.kt
create mode 100644 android/tinySSB/app/src/main/java/nz/scuttlebutt/tremolavossbol/miniapps/PluginLoader.kt
create mode 100644 android/tinySSB/app/src/main/java/nz/scuttlebutt/tremolavossbol/miniapps/SketchPlugin.kt
diff --git a/android/tinySSB/app/src/main/assets/web/eventemitter.js b/android/tinySSB/app/src/main/assets/web/eventemitter.js
new file mode 100644
index 00000000..c4c19275
--- /dev/null
+++ b/android/tinySSB/app/src/main/assets/web/eventemitter.js
@@ -0,0 +1,48 @@
+// eventEmitter.js
+
+/**
+ * Class representing an EventEmitter.
+ * This class allows different parts of an application to communicate with each other
+ * by emitting and listening to events.
+ */
+class EventEmitter {
+
+ /**
+ * Create an EventEmitter.
+ * Initializes an empty events object to store event listeners.
+ */
+ constructor() {
+ this.events = {};
+ }
+
+ /**
+ * Register an event listener for a specific event.
+ * If the event does not exist, it is created.
+ *
+ * @param {string} event - The name of the event.
+ * @param {function} listener - The callback function to execute when the event is emitted.
+ */
+ on(event, listener) {
+ if (!this.events[event]) {
+ this.events[event] = [];
+ }
+ this.events[event].push(listener);
+ }
+
+ /**
+ * Emit a specific event, executing all registered listeners for that event.
+ * Additional arguments are passed to the listener functions.
+ *
+ * @param {string} event - The name of the event to emit.
+ * @param {...*} args - The arguments to pass to the listener functions.
+ * @returns {Array} - The results of the listener functions.
+ */
+ emit(event, ...args) {
+ if (!this.events[event]) return [];
+
+ return this.events[event].map(listener => listener(...args));
+ }
+}
+
+// Create a global instance of EventEmitter and assign it to window object
+window.eventEmitter = new EventEmitter();
diff --git a/android/tinySSB/app/src/main/assets/web/img/miniapps.svg b/android/tinySSB/app/src/main/assets/web/img/miniapps.svg
new file mode 100644
index 00000000..892646bd
--- /dev/null
+++ b/android/tinySSB/app/src/main/assets/web/img/miniapps.svg
@@ -0,0 +1,39 @@
+
+
+
+
diff --git a/android/tinySSB/app/src/main/assets/web/miniapps/miniAppHelper.js b/android/tinySSB/app/src/main/assets/web/miniapps/miniAppHelper.js
new file mode 100644
index 00000000..be21bfbe
--- /dev/null
+++ b/android/tinySSB/app/src/main/assets/web/miniapps/miniAppHelper.js
@@ -0,0 +1,35 @@
+function getContactsList() {
+ // Get the contacts list from the native code
+ backend("getContactsList");
+}
+
+function writeLogEntry(entry) {
+ // Write a log entry to the native code, use currentMiniAppID to identify the app
+ // entry is a JSONString
+ backend("customApp:writeEntry " + currentMiniAppID + " " + entry);
+}
+
+function readLogEntries(numberOfEntries) {
+ // Read the log entries from the native code associated with the currentMiniAppID
+ backend("customApp:readEntries " + currentMiniAppID + " " + numberOfEntries);
+}
+
+function quitApp() {
+ // Quit the app
+ setScenario('miniapps');
+}
+
+function launchContactsMenu(heading, subheading) {
+ closeOverlay()
+ fill_members(true);
+ prev_scenario = 'customApp';
+ setScenario("members");
+
+ document.getElementById("div:textarea").style.display = 'none';
+ document.getElementById("div:confirm-members").style.display = 'flex';
+ document.getElementById("tremolaTitle").style.display = 'none';
+ var c = document.getElementById("conversationTitle");
+ c.style.display = null;
+ c.innerHTML = "" + heading + "
" + subheading;
+ document.getElementById('plus').style.display = 'none';
+}
\ No newline at end of file
diff --git a/android/tinySSB/app/src/main/assets/web/miniapps/mini_apps.js b/android/tinySSB/app/src/main/assets/web/miniapps/mini_apps.js
new file mode 100644
index 00000000..806894e4
--- /dev/null
+++ b/android/tinySSB/app/src/main/assets/web/miniapps/mini_apps.js
@@ -0,0 +1,179 @@
+/**
+ * mini_apps.js
+ *
+ * This file handles the dynamic loading and initialization of mini applications and chat extensions
+ * within the main application. It retrieves the manifest files for each mini app, processes the
+ * manifest data, and creates corresponding UI elements such as buttons. These buttons allow users
+ * to launch the mini apps and chat extensions from the mini app menu.
+ *
+ */
+
+"use strict";
+
+/**
+ * Handles the paths to manifest files.
+ *
+ * This function gets the paths to the manifest files of every app from the backend as input and
+ * forwards each path to the backend to retrieve the data of each manifest file.
+ *
+ * @param {string} manifestPathsJson - JSON string containing an array of paths to manifest files.
+ */
+function handleManifestPaths(manifestPathsJson) {
+
+ const manifestPaths = JSON.parse(manifestPathsJson);
+ const listElement = document.getElementById('lst:miniapps');
+
+ manifestPaths.forEach(path => {
+ backend("getManifestData " + path);
+ //fetchManifestFile(path);
+ });
+
+}
+
+/**
+ * Handles the content of a manifest file.
+ *
+ * This function gets the data of a manifest file, creates a button containing that data and appends
+ * that button to the list that contains all the buttons that initiate each mini app.
+ *
+ * @param {string} content - JSON string containing the manifest data.
+ */
+function handleManifestContent(content) {
+
+ setTimeout(() => {
+ const manifest = JSON.parse(content);
+ // Process the manifest data (e.g., create buttons)
+ const listElement = document.getElementById('lst:miniapps');
+ const miniAppButton = createMiniAppButton(manifest);
+ listElement.appendChild(miniAppButton);
+ console.log(`Added button after delay: ${miniAppButton.id}`);
+ }, 100);
+
+}
+
+/**
+ * Creates a button to initiate a Mini App.
+ *
+ * This function gets the manifest data as input and uses it to create the button that initiates
+ * the mini app in the mini app menu. If the mini app is also a chat extension, a button will
+ * be added in the attach-menu.
+ *
+ * @param {Object} manifest - The manifest data of the mini app.
+ * @param {string} manifest.id - The ID of the mini app.
+ * @param {string} manifest.icon - The path to the icon of the mini app.
+ * @param {string} manifest.name - The name of the mini app.
+ * @param {string} manifest.description - The description of the mini app.
+ * @param {string} manifest.init - The initialization function as a string.
+ * @param {string} [manifest.extension] - Indicates if the app is also a chat extension.
+ * @returns {HTMLElement} The created button element.
+ */
+function createMiniAppButton(manifest) {
+ const item = document.createElement('div');
+ item.className = 'miniapp_item_div';
+
+ const button = document.createElement('button');
+ button.id = 'btn:' + manifest.id;
+ button.className = 'miniapp_item_button w100';
+ button.style.display = 'flex';
+ button.style.alignItems = 'center';
+ button.style.padding = '10px';
+ button.style.border = '1px solid #ccc';
+ button.style.borderRadius = '5px';
+ button.style.backgroundColor = '#f9f9f9';
+
+ console.log("Init function: " + manifest.init);
+
+ //button.onclick = manifest.init(); // This didn't work as intended
+ button.addEventListener('click', () => {
+ try {
+ // Dynamically evaluate the init function
+ console.log("Init function: " + manifest.init);
+ //Set currentMiniAppID to the manifest.id
+ currentMiniAppID = manifest.id
+ setScenario("customApp:" + manifest.id);
+ console.log(curr_scenario);
+ eval(manifest.init);
+ } catch (error) {
+ console.error(`Error executing init function: ${manifest.init}`, error + " " + error.stack);
+ }
+ });
+
+ const icon = document.createElement('img');
+ console.log("App Icon: " + manifest.icon);
+ let iconSrc = manifest.icon;
+ // Remove the incorrect asset prefix if present
+ if (iconSrc.startsWith("file:///android_asset/")) {
+ iconSrc = iconSrc.replace("file:///android_asset/", "file://");
+ }
+ icon.src = iconSrc;
+ console.log("Icon source: " + icon.src);
+ icon.alt = `${manifest.name} icon`;
+ icon.className = 'miniapp_icon';
+ icon.style.width = '50px';
+ icon.style.height = '50px';
+ icon.style.marginRight = '10px';
+
+
+ const textContainer = document.createElement('div');
+ textContainer.className = 'miniapp_text_container';
+
+ const nameElement = document.createElement('div');
+ nameElement.className = 'miniapp_name';
+ nameElement.textContent = manifest.name;
+
+ const descriptionElement = document.createElement('div');
+ descriptionElement.className = 'miniapp_description';
+ descriptionElement.textContent = manifest.description;
+
+ textContainer.appendChild(nameElement);
+ textContainer.appendChild(descriptionElement);
+
+ button.appendChild(icon);
+ button.appendChild(textContainer);
+ item.appendChild(button);
+
+ if (manifest.extension === "True") {
+ createExtensionButton(manifest);
+ }
+
+ return item;
+}
+
+/**
+ * Creates a button for a chat extension.
+ *
+ * This function gets the manifest data and uses it to create a button for the chat extension,
+ * which is then appended to the attach-menu.
+ *
+ * @param {Object} manifest - The manifest data of the chat extension.
+ * @param {string} manifest.extensionText - The text to display on the extension button.
+ * @param {string} manifest.extensionInit - The initialization function for the extension as a string.
+ */
+function createExtensionButton(manifest) {
+ //console.log("Extension entered")
+ const attachMenu = document.getElementById('attach-menu');
+
+ // Create a new button element
+ const newButton = document.createElement('button');
+
+ // Set the button's class
+ newButton.className = 'attach-menu-item-button';
+
+ // Set the button's text content
+ newButton.textContent = manifest.extensionText;
+
+ // Set the button's onclick event
+ newButton.addEventListener('click', () => {
+ try {
+ // Dynamically evaluate the init function
+ eval(manifest.extensionInit);
+ } catch (error) {
+ console.error(`Error executing extensionInit function: ${manifest.extensionInit}`, error);
+ }
+ });
+
+ // Append the new button to the target div
+ attachMenu.appendChild(newButton);
+}
+
+
diff --git a/android/tinySSB/app/src/main/assets/web/tremola.css b/android/tinySSB/app/src/main/assets/web/tremola.css
index 607e8270..c3a9434c 100644
--- a/android/tinySSB/app/src/main/assets/web/tremola.css
+++ b/android/tinySSB/app/src/main/assets/web/tremola.css
@@ -115,9 +115,13 @@
width: 100%;
}
.buttontext {
- height: 45pt;
- padding: 4px 10px 10px 10px;
- }
+ font-size: small;
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center;
+ height: 50px;
+ padding: 3px;
+ }
.item {
border: none;
text-align: left;
@@ -646,6 +650,46 @@ input:checked + .slider:before {
background-color: red;
}
+/* --------------------------------------------------------------------------- */
+/* Mini App Menu */
+/* --------------------------------------------------------------------------- */
+
+.miniapp_item_div {
+ padding: 0px 5px 10px 5px;
+ margin: 3px 3px 6px 3px;
+}
+
+.miniapp_item_button {
+ overflow: hidden;
+ position: relative;
+ border: 1px solid #ccc;
+ border-radius: 5px;
+ background-color: #f9f9f9;
+ padding: 10px;
+ width: 100%;
+ text-align: left;
+ display: flex;
+ align-items: center;
+}
+
+.miniapp_icon {
+ width: 50px;
+ height: 50px;
+ margin-right: 10px;
+}
+
+.miniapp_text_container {
+ display: flex;
+ flex-direction: column;
+}
+
+.miniapp_name {
+ font-weight: bold;
+}
+
+.miniapp_description {
+ font-size: small;
+}
/* eof */
diff --git a/android/tinySSB/app/src/main/assets/web/tremola.html b/android/tinySSB/app/src/main/assets/web/tremola.html
index f81aa2fe..c78aedff 100644
--- a/android/tinySSB/app/src/main/assets/web/tremola.html
+++ b/android/tinySSB/app/src/main/assets/web/tremola.html
@@ -12,6 +12,8 @@
+
+
@@ -62,7 +64,7 @@