diff --git a/README.md b/README.md index 03cd302..c4a3fc4 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ # DynamicComponents-AI2 `Extension` +[![forthebadge](https://forthebadge.com/images/badges/its-not-a-lie-if-you-believe-it.svg)](https://forthebadge.com) + [![Maintainability](https://api.codeclimate.com/v1/badges/31e4cd31de1bd0e186c8/maintainability)](https://codeclimate.com/github/ysfchn/DynamicComponents-AI2/maintainability) Fully supported Dynamic Components extension for MIT App Inventor 2. It is based on Java's reflection feature, so it creates the components by searching for a class by just typing its name. So it doesn't have a limited support for specific components, because it supports every component which is ever added to your App Inventor distribution! @@ -10,7 +12,13 @@ So if you use Kodular, you will able to create all Kodular components, if you us > ⚠ The `beta` branch will be reset after every release. So stay on the `main` branch if you don't know what you do. -[![forthebadge](https://forthebadge.com/images/badges/its-not-a-lie-if-you-believe-it.svg)](https://forthebadge.com) +--- + +### Asynchronous support + +This extension can create components asynchronously or synchronously based on your choice. If you don't want to block the main app during creating a bunch of components, go to the Designer (after importing the extension) and select between "UI" (asynchronous) and "Main" (synchronous). + + ## 🧩 Blocks @@ -78,7 +86,10 @@ So if you use Kodular, you will able to create all Kodular components, if you us --> - Changes ID of one of created components to a new one. The old ID must be exist and new ID mustn't exist. + Changes ID of one of created components to a new one. The old ID must be exist and new ID mustn't exist.
+
+ When replacing an ID, it will loop through ALL IDs and replace each one that contains the old ID! Even if the ID you're wanting to replace is "Timmy", but there are IDs like "Timmy-truck". To be clear again, only "Timmy" from "Timmy-truck" will be replaced, the "-truck" part will stay the same! +
@@ -214,23 +225,6 @@ So if you use Kodular, you will able to create all Kodular components, if you us Component --> ID - - - - - - - - Returns the internal/class name of any component or object. The returned value can be also used in Create block. - - @@ -318,21 +312,40 @@ So if you use Kodular, you will able to create all Kodular components, if you us Get a property value of a component by typing its property name. Can be known as a Getter property block. It can be also used to get properties that only exists in Designer. - + - + - - Gives the information of the specified component with all properties, events, methods as JSON text. + Get meta data about the specified component. + + + + + + + + + Get meta data about events for the specified component. + + + + + + + + + Get meta data about properties for the specified component. + + + + + + + + + Get meta data about functions for the specified component. @@ -353,10 +366,10 @@ So if you use Kodular, you will able to create all Kodular components, if you us Returns all used IDs of current components as App Inventor list. - + - + Makes a random unique UUID. Use this block in Create block if component ID is not required for you. @@ -398,16 +411,6 @@ So if you use Kodular, you will able to create all Kodular components, if you us Returns the version name of the extension. - - - -
- - - - Sets whether component creation should work asynchronously or synchronously. - - @@ -417,13 +420,13 @@ So if you use Kodular, you will able to create all Kodular components, if you us Raises after Schema has been created with Schema block. - + - + - Raises after a component has been created using the Create block. It also will be raised for components that created with Schema. + Raises after a component has been created. diff --git a/assets/blocks/event_componentbuilt.png b/assets/blocks/event_componentbuilt.png new file mode 100644 index 0000000..77bffe6 Binary files /dev/null and b/assets/blocks/event_componentbuilt.png differ diff --git a/assets/blocks/event_componentcreated.png b/assets/blocks/event_componentcreated.png deleted file mode 100644 index 1b4baac..0000000 Binary files a/assets/blocks/event_componentcreated.png and /dev/null differ diff --git a/assets/blocks/method_generateid.png b/assets/blocks/method_generateid.png new file mode 100644 index 0000000..afc3ee1 Binary files /dev/null and b/assets/blocks/method_generateid.png differ diff --git a/assets/blocks/method_getcomponentmeta.png b/assets/blocks/method_getcomponentmeta.png new file mode 100644 index 0000000..ec1cf4e Binary files /dev/null and b/assets/blocks/method_getcomponentmeta.png differ diff --git a/assets/blocks/method_geteventmeta.png b/assets/blocks/method_geteventmeta.png new file mode 100644 index 0000000..3ad366e Binary files /dev/null and b/assets/blocks/method_geteventmeta.png differ diff --git a/assets/blocks/method_getfunctionmeta.png b/assets/blocks/method_getfunctionmeta.png new file mode 100644 index 0000000..87f373a Binary files /dev/null and b/assets/blocks/method_getfunctionmeta.png differ diff --git a/assets/blocks/method_getname.png b/assets/blocks/method_getname.png deleted file mode 100644 index 14ff7d7..0000000 Binary files a/assets/blocks/method_getname.png and /dev/null differ diff --git a/assets/blocks/method_getpropertymeta.png b/assets/blocks/method_getpropertymeta.png new file mode 100644 index 0000000..e256e91 Binary files /dev/null and b/assets/blocks/method_getpropertymeta.png differ diff --git a/assets/blocks/method_listdetails.png b/assets/blocks/method_listdetails.png deleted file mode 100644 index 05de1a3..0000000 Binary files a/assets/blocks/method_listdetails.png and /dev/null differ diff --git a/assets/blocks/method_randomuuid.png b/assets/blocks/method_randomuuid.png deleted file mode 100644 index 94df1bb..0000000 Binary files a/assets/blocks/method_randomuuid.png and /dev/null differ diff --git a/assets/blocks/setget_async.png b/assets/blocks/setget_async.png deleted file mode 100644 index f942f09..0000000 Binary files a/assets/blocks/setget_async.png and /dev/null differ diff --git a/assets/blocks/setget_async_2.png b/assets/blocks/setget_async_2.png deleted file mode 100644 index 9542ff7..0000000 Binary files a/assets/blocks/setget_async_2.png and /dev/null differ diff --git a/assets/icon.png b/assets/icon.png index 36c685f..1c688f6 100644 Binary files a/assets/icon.png and b/assets/icon.png differ diff --git a/assets/other/method_getdesignerproperties.png b/assets/other/method_getdesignerproperties.png deleted file mode 100644 index 034884a..0000000 Binary files a/assets/other/method_getdesignerproperties.png and /dev/null differ diff --git a/assets/other/thread_choice.png b/assets/other/thread_choice.png new file mode 100644 index 0000000..d1a026a Binary files /dev/null and b/assets/other/thread_choice.png differ diff --git a/src/com/yusufcihan/DynamicComponents/DynamicComponents.java b/src/com/yusufcihan/DynamicComponents/DynamicComponents.java index 09743bc..db387a9 100644 --- a/src/com/yusufcihan/DynamicComponents/DynamicComponents.java +++ b/src/com/yusufcihan/DynamicComponents/DynamicComponents.java @@ -14,17 +14,21 @@ import com.google.appinventor.components.runtime.Component; import com.google.appinventor.components.runtime.ComponentContainer; import com.google.appinventor.components.runtime.EventDispatcher; +import com.google.appinventor.components.runtime.Form; import com.google.appinventor.components.runtime.errors.YailRuntimeError; -import com.google.appinventor.components.runtime.util.YailList; import com.google.appinventor.components.runtime.util.YailDictionary; +import com.google.appinventor.components.runtime.util.YailList; import org.json.JSONArray; -import org.json.JSONException; import org.json.JSONObject; -import android.app.Activity; +import android.os.Handler; +import android.os.Looper; import android.view.View; import android.view.ViewGroup; +import gnu.lists.FString; +import gnu.math.DFloNum; +import gnu.math.IntNum; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; @@ -32,803 +36,556 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; -import java.util.Objects; +import java.util.List; import java.util.Set; import java.util.UUID; @DesignerComponent( - description = "Dynamic Components is an extension that creates any component in your App Inventor distribution programmatically, instead of having pre-defined components. Made with ❤️ by Yusuf Cihan", + description = "Dynamic Components is an extension that creates any component in your App Inventor distribution programmatically, instead of having pre-defined components. Made with ❤️ by Yusuf Cihan.", category = ComponentCategory.EXTENSION, - helpUrl = "https://github.com/ysfchn/DynamicComponents-AI2", + helpUrl = "https://github.com/ysfchn/DynamicComponents-AI2/blob/main/README.md", iconName = "aiwebres/icon.png", nonVisible = true, - version = 6, - versionName = "2.1.0" + version = 7, + versionName = "2.2.0" ) @SimpleObject(external = true) public class DynamicComponents extends AndroidNonvisibleComponent { - private Activity activity; - private Internal internal = null; - private boolean isAsync = false; - - /** - * Contains the created components. Key is the ID of the components, and their values are the components - * that created with Create block. - */ - private final HashMap COMPONENTS = new HashMap<>(); - - /** - * Specifies the base package for creating the components. - */ - private final String BASE_PACKAGE = "com.google.appinventor.components.runtime"; - - /** - * Stores the component template. Needs to be cleared before rendering Schema operation. - */ - private JSONArray PROPERTIESARRAY = new JSONArray(); + // Base package name for components + private final String BASE = "com.google.appinventor.components.runtime."; + + // Whether component creation should happen on the UI thread + private boolean postOnUiThread = false; + + // Components created with Dynamic Components + private final HashMap COMPONENTS = new HashMap(); + + // IDs of components created with Dynamic Components + private final HashMap COMPONENT_IDS = new HashMap(); + + private Object lastUsedId = ""; + private ArrayList componentListeners = new ArrayList(); + private JSONArray propertiesArray = new JSONArray(); + private final Util UTIL_INSTANCE = new Util(); public DynamicComponents(ComponentContainer container) { super(container.$form()); - this.activity = container.$context(); - this.internal = new Internal(); } - /** - * Raises after a schema has created with the "Schema" block. - * - * @param name Name of the template. - * @param parameters The JSON parameters in the schema. - */ - @SimpleEvent(description = "Raises after a schema has created with the \"Schema\" block.") - public void SchemaCreated(String name, YailList parameters) { - EventDispatcher.dispatchEvent(this, "SchemaCreated", name, parameters); - } - - /** - * Raises after a component has been created using the Create block. - * - * @param id The ID of the created component. - * @param type Type type name of the created component. - */ - @SimpleEvent(description = "Raises after a component has been created using the Create block. It also will be raised for components that created with Schema.") - public void ComponentCreated(String id, String type) { - EventDispatcher.dispatchEvent(this, "ComponentCreated", id, type); + interface ComponentListener { + public void onCreation(Component component, String id); } - @DesignerProperty(defaultValue = "False", editorType = PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN) - @SimpleProperty(description = "Sets whether component creation should rely on the UI thread.") - public void Async(boolean async) { - isAsync = async; - } - - @SimpleProperty(category = PropertyCategory.BEHAVIOR, description = "Gets whether component creation should rely on the UI thread.") - public boolean Async() { - return isAsync; - } - - /** - * Creates a new dynamic component. It supports all component that added to your - * current AI2 distribution. In componentName, you can type the component's name - * like "Button", or you can pass a static component then it can create a new - * instance of it. - * - * @param in Layout/Component where the created component will be placed. - * @param componentName Name/FQCN of the component that will be created. - * @param id A unique identifier for this component. - */ - @SimpleFunction(description = "Creates a new dynamic component. It supports all component that added to your current AI2 distribution. In componentName, you can type the component's name like 'Button', or you can pass a static component then it can create a new instance of it, or just type the full class name of component.") - public void Create(final AndroidViewComponent in, final Object componentName, final String id) { - if (isAsync) { - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - CreateMethod(in, componentName, id); - } - }); - } else { - CreateMethod(in, componentName, id); + class Util { + public boolean exists(Component component) { + return COMPONENTS.containsValue(component); } - } - public void CreateMethod(final AndroidViewComponent in, final Object componentName, final String id) { - String className = internal.checkId(componentName, id); + public boolean exists(String id) { + return COMPONENTS.containsKey(id); + } - try { - if (!"".equals(className)) { - // Create a Class object from class name. - Class classObject = Class.forName(className.trim().replace(" ", "")); + public Method getMethod(Method[] methods, String name, int parameterCount) { + name = name.replaceAll("[^a-zA-Z0-9]", ""); + for (Method method : methods) { + int methodParameterCount = method.getParameterTypes().length; + if (method.getName().equals(name) && methodParameterCount == parameterCount) { + return method; + } + } - // Create constructor object for creating a new instance. - Constructor constructor = classObject.getConstructor(ComponentContainer.class); + return null; + } - // Create a new instance of specified component. - Component component = (Component) constructor.newInstance((ComponentContainer) in); + public void newInstance(Constructor constructor, String id, AndroidViewComponent input) { + Component mComponent = null; - if (className.contains("ImageSprite") || className.contains("Sprite")) { - Invoke(component, "Initialize", new YailList()); + try { + mComponent = (Component) constructor.newInstance((ComponentContainer) input); + } catch(Exception e) { + throw new YailRuntimeError(e.getMessage(), "DynamicComponents"); + } finally { + if (mComponent != null) { + String mComponentClassName = mComponent.getClass().getSimpleName(); + if (mComponentClassName == "ImageSprite" || mComponentClassName == "Sprite") { + Invoke(mComponent, "Initialize", new YailList()); + } + + COMPONENT_IDS.put(mComponent, id); + COMPONENTS.put(id, mComponent); + this.notifyListenersOfCreation(mComponent, id); + ComponentBuilt(mComponent, id, mComponentClassName); } - - // Save the component. - COMPONENTS.put(id, component); - - // Finalize component creation. - ComponentCreated(id, classObject.getSimpleName()); } - } catch (Exception exception) { - throw new YailRuntimeError("DynamicComponents-AI2: " + exception.toString(), "Error"); } - } - - public void SchemaMethod(final AndroidViewComponent in, final YailList parameters, final String template) { - try { - // Remove the contents of the array by creating a new JSONArray. - PROPERTIESARRAY = new JSONArray(); - // Create a JSONObject from template for checking. - JSONObject j = new JSONObject(template); - // Save the template string to a new variable for editing. - String modifiedTemplate = internal.replaceKeys(j, parameters, template); - // Lastly parse the JSONObject. - internal.parseJson("", new JSONObject(modifiedTemplate)); - // Delete the first element, because it contains metadata instead of components. - PROPERTIESARRAY.remove(0); - - // Start creating the extensions (finally). - for (int i = 0; i < PROPERTIESARRAY.length(); i++) { - // Check if component has an ID key. - if (!PROPERTIESARRAY.getJSONObject(i).has("id")) { - throw new YailRuntimeError("One or more of the components has not an ID in template!", "Error"); - } - internal.createBySchema(in, i, PROPERTIESARRAY); + public void parse(String id, JSONObject json) { + JSONObject data = new JSONObject(json.toString()); + data.remove("components"); - // If JSONObject contains a "properties" section, then set its properties with - // Invoke block. - if (PROPERTIESARRAY.getJSONObject(i).has("properties")) { - internal.setPropertiesBySchema(i, PROPERTIESARRAY); - } + if (!"".equals(id)) { + data.put("in", id); } - SchemaCreated(j.optString("name"), parameters); - } catch (Exception e) { - throw new YailRuntimeError(e.getMessage(), "Error"); - } - } + propertiesArray.put(data); - /** - * Imports a JSON string that is a template for creating the dynamic components - * automatically with single block. Templates can also contain parameters that - * will be replaced with the values which defined in the "parameters" list. - * - * @param in Layout/Component where the created component will be placed. - * @param template A JSON string containing information for creating the component. - * @param parameters Data for parameters defined in the above JSON string. - */ - @SimpleFunction(description = "Imports a JSON string that is a template for creating the dynamic components automatically with single block. Templates can also contain parameters that will be replaced with the values which defined in the 'parameters' list.") - public void Schema(final AndroidViewComponent in, final String template, final YailList parameters) { - if (isAsync) { - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - SchemaMethod(in, parameters, template); + if (json.has("components")) { + for (int i = 0; i < json.getJSONArray("components").length(); i++) { + this.parse(data.optString("id", ""), json.getJSONArray("components").getJSONObject(i)); } - }); - } else { - SchemaMethod(in, parameters, template); + } } - } - /** - * Replaces the specified ID with the given new ID. The old ID must be bound to - * a component and the new ID must be unique, i.e., not used already. - * - * @param id Existing ID of the specified component. - * @param newId The ID to replace existing one with. - */ - @SimpleFunction(description = "Replaces the specified ID with the given new ID. The old ID must be bound to a component and the new ID must be unique, i.e., not used already.") - public void ChangeId(String id, String newId) { - if (COMPONENTS.containsKey(id) && !COMPONENTS.containsKey(newId)) { - Component component = COMPONENTS.remove(id); - COMPONENTS.put(newId, component); - } else { - throw new YailRuntimeError("Old ID must exist and new ID mustn't exist.", "Error"); + public void notifyListenersOfCreation(Component component, String id) { + for (ComponentListener listener : componentListeners) { + listener.onCreation(component, id); + } } } - /** - * Moves the specified component to the given arrangement. - * - * @param arrangement Target arrangement in which the specified component should be transferred. - * @param component Component that needs to be transferred. - */ - @SimpleFunction(description = "Moves the specified component to the given arrangement.") - public void Move(AndroidViewComponent arrangement, AndroidViewComponent component) { - View comp = component.getView(); - ViewGroup source = (ViewGroup)comp.getParent(); - source.removeView(comp); - - ViewGroup vg2 = (ViewGroup)arrangement.getView(); - ViewGroup target = (ViewGroup)vg2.getChildAt(0); - - target.addView(comp); - } - - /** - * Returns the position of the component with respect to other sibling components - * in it's parent arrangement. Index starts from 1. - * - * @param component Target component. - * @return Position index of the specified component in the parent arrangement. - */ - @SimpleFunction(description = "Returns the position of the component with respect to other sibling components in it's parent arrangement. Index starts from 1.") - public int GetOrder(AndroidViewComponent component){ - View comp = component.getView(); - View parent = (View) component.getView().getParent(); - - if (comp != null && parent != null) { - View v = component.getView(); - ViewGroup vg = (ViewGroup)v.getParent(); - int index = vg.indexOfChild(v); - return index + 1; - } else { - return 0; - } + @DesignerProperty( + defaultValue = "UI", + editorArgs = {"Main", "UI"}, + editorType = PropertyTypeConstants.PROPERTY_TYPE_CHOICES + ) + @SimpleProperty(userVisible = false) + public void Thread(String thread) { + postOnUiThread = (thread == "UI"); + } + + @Deprecated + @SimpleEvent(description = "Do NOT use this event. Use 'ComponentBuilt' as a replacement.") + public void ComponentCreated(String id, String type) { + EventDispatcher.dispatchEvent(this, "ComponentCreated", id, type); } - /** - * Sets the position of the specified component with respect to other sibling - * components in it's parent arrangement. Index starts from 1. Typing 0 (zero) - * will move the component to the end. - * - * @param component Target component. - * @param index Index at which the component should be placed in it's parent component. - */ - @SimpleFunction(description = "Sets the position of the specified component with respect to other sibling components in it's parent arrangement. Index starts from 1. Typing 0 (zero) will move the component to the end.") - public void SetOrder(AndroidViewComponent component, int index) { - View comp = component.getView(); - ViewGroup source = (ViewGroup)comp.getParent(); - source.removeView(comp); - - int i = index - 1; - int childCount = source.getChildCount(); - - if (i > childCount) { - i = childCount; - } - - source.addView(comp, i); + @SimpleEvent(description = "Is called after a component has been created.") + public void ComponentBuilt(Component component, String id, String type) { + EventDispatcher.dispatchEvent(this, "ComponentBuilt", component, id, type); } - /** - * Removes the component with specified ID from screen/layout and the component - * list. So you will able to use its ID again as it will be deleted. - * - * @param id The ID of the component that is supposed to be removed. - */ - @SimpleFunction(description = "Removes the component with specified ID from screen/layout and the component list so you can use its ID again after it's deleted.") - public void Remove(String id) { - if (internal.isIdTaken(id)) { - Object component = COMPONENTS.get(id); + @SimpleEvent(description = "Is called after a schema has/mostly finished component creation.") + public void SchemaCreated(String name, YailList parameters) { + EventDispatcher.dispatchEvent(this, "SchemaCreated", name, parameters); + } - try { - if (component != null) { - Method method = component.getClass().getMethod("getView"); - View componentView = (View) method.invoke(component); - ViewGroup parent = (ViewGroup) componentView.getParent(); - parent.removeView(componentView); + @SimpleFunction(description = "Assign a new ID to a previously created dynamic component.") + public void ChangeId(String id, String newId) { + if (UTIL_INSTANCE.exists(id) && !UTIL_INSTANCE.exists(newId)) { + for (String mId : UsedIDs().toStringArray()) { + if (mId.contains(id)) { + Component mComponent = (Component) GetComponent(mId); + String mReplacementId = mId.replace(id, newId); + COMPONENT_IDS.remove(mComponent); + COMPONENTS.put(mReplacementId, COMPONENTS.remove(mId)); + COMPONENT_IDS.put(mComponent, mReplacementId); } - } catch (Exception e) { - e.printStackTrace(); } - - COMPONENTS.remove(id); + } else { + throw new YailRuntimeError("The ID you used is either not a dynamic component, or the ID you've used to replace the old ID is already taken.", "DynamicComponents"); } } - /** - * Returns the ID of the last component created using the "Create" block. - * - * @return The last used ID. - */ - @SimpleFunction(description = "Returns the ID of the last component created using the \"Create\" block.") - public String LastUsedID() { - Object[] COMPONENT_IDS = COMPONENTS.keySet().toArray(); - return (COMPONENT_IDS.length > 0 ? COMPONENT_IDS[COMPONENT_IDS.length - 1].toString() : ""); + @SimpleFunction(description = "Creates a new dynamic component.") + public void Create(final AndroidViewComponent in, Object componentName, final String id) throws Exception { + componentName = componentName.toString().replaceAll("[^a-zA-Z0-9]", ""); + if (!COMPONENTS.containsKey(id)) { + lastUsedId = id; + + String mClassName = BASE + componentName; + Class mClass = Class.forName(mClassName); + final Constructor mConstructor = mClass.getConstructor(ComponentContainer.class); + + if (postOnUiThread) { + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + UTIL_INSTANCE.newInstance(mConstructor, id, in); + } + }); + } else { + UTIL_INSTANCE.newInstance(mConstructor, id, in); + } + + ComponentCreated(id.toString(), mClass.getSimpleName()); + } else { + throw new YailRuntimeError("ID must be unique.", "DynamicComponents"); + } } - /** - * Returns a random UUID. - * - * @return A random UUID. - */ - @SimpleFunction(description = "Generates a unique ID that can be used to create a component.") - public String RandomUUID() { - String uuid = ""; + @SimpleFunction(description = "Generates a random ID to create a component with.") + public String GenerateID() { + String id = ""; do { - uuid = UUID.randomUUID().toString(); - } while (internal.isIdTaken(uuid)); + id = UUID.randomUUID().toString(); + } while (UTIL_INSTANCE.exists(id)); - return uuid; + return id; } - /** - * Returns a list of IDs of every created component. - * - * @return A list of IDs. - */ - @SimpleFunction(description = "Returns a list of IDs of every created component.") - public YailList UsedIDs() { - Set keys = COMPONENTS.keySet(); - return YailList.makeList(keys); - } - - /** - * Returns the component itself for setting properties. ID must be a valid ID - * which is added with Create block. ID --> Component - * - * @param id ID of the desired component. - * @return A component bound to the given ID. - */ - @SimpleFunction(description = "Returns the component itself for setting properties. ID must be a valid ID which is added with Create block.") + @SimpleFunction(description = "Returns the component associated with the specified ID.") public Object GetComponent(String id) { return COMPONENTS.get(id); } - /** - * Returns "true" if the specified component is created by Dynamic Components - * extension. Otherwise, "false". - * - * @param component Component that needs to be analysed. - * @return A boolean value indicating whether the specified component is dynamically created or not. - */ - @SimpleFunction(description = "Returns 'true' if component was created by the Dynamic Components extension. Otherwise, 'false'.") - public Object IsDynamic(Component component) { - return internal.isDynamicComponent(component); - } - - /** - * Returns the ID of the specified component. The desired component must created using the "Create" block, else it will return a blank string. Component --> ID - * - * @param component The component that its ID will be returned. - * @return ID of the specified component. - */ - @SimpleFunction(description = "Returns the ID of the specified component. The desired component must created using the \"Create\" block, else it will return a blank string. Component --> ID") - public String GetId(Component component) { - // Getting key from value, - for (String o : COMPONENTS.keySet()) { - if (COMPONENTS.get(o).equals(component)) { - return o; - } + @SimpleFunction(description = "Get meta data about the specified component.") + public YailDictionary GetComponentMeta(Component component) { + Class mClass = component.getClass(); + DesignerComponent mDesignerAnnotation = mClass.getAnnotation(DesignerComponent.class); + SimpleObject mObjectAnnotation = mClass.getAnnotation(SimpleObject.class); + YailDictionary mMeta = new YailDictionary(); + + if (mDesignerAnnotation != null) { + mMeta.put("androidMinSdk", mDesignerAnnotation.androidMinSdk()); + mMeta.put("category", mDesignerAnnotation.category()); + mMeta.put("dateBuilt", mDesignerAnnotation.dateBuilt()); + mMeta.put("description", mDesignerAnnotation.description()); + mMeta.put("designerHelpDescription", mDesignerAnnotation.designerHelpDescription()); + mMeta.put("external", mObjectAnnotation.external()); + mMeta.put("helpUrl", mDesignerAnnotation.helpUrl()); + mMeta.put("iconName", mDesignerAnnotation.iconName()); + mMeta.put("nonVisible", mDesignerAnnotation.nonVisible()); + mMeta.put("package", mClass.getName()); + mMeta.put("showOnPalette", mDesignerAnnotation.showOnPalette()); + mMeta.put("type", mClass.getSimpleName()); + mMeta.put("version", mDesignerAnnotation.version()); + mMeta.put("versionName", mDesignerAnnotation.versionName()); } - return ""; + return mMeta; } - /** - * Returns the internal name of any component or object. - * - * @param component The component that its internal name needs to be returned. - * @return Internal name of the specified component. - */ - @SimpleFunction(description = "Returns the internal name of any component or object.") - public String GetName(Object component) { - return internal.getClass(component).getName(); - } - - /** - * Sets the specified property of the component to the given value. It can be also - * used to set properties that only exists in Designer. Supported values are: - * "string", "boolean", "integer" and "float". For other values, you should use - * "Any Component" blocks. - * - * @param component The component that property will set for. - * @param name The name of the property that needs to be set. - * @param value The value that needs to be set of the specified property. - */ - @SimpleFunction(description = "Sets the specified property of the component to the given value. It can be also used to set properties that only exists in Designer. Supported values are; 'string', 'boolean', 'integer' and 'float'. For other values, you should use Any Component blocks.") - public void SetProperty(Component component, String name, Object value) { - // The method will be invoked. - try { - Invoke(component, name, YailList.makeList(new Object[] { value })); - } catch (Exception exception) { - throw new YailRuntimeError(exception.getMessage(), "Error"); - } - } - - /** - * Set multiple properties of a component by typing its property name and value. - * It behaves like a Setter property block. It can be also used to set properties - * that only exists in Designer. Supported values are; "string", "boolean", - * "integer" and "float". For other values, you should use Any Component blocks. - * - * @param component The component that property will set for. - * @param properties Properties and their respective values as a dictionary. - * @throws JSONException Creating new instance of JSONObject throws this exception. - */ - @SimpleFunction(description = "Set multiple properties of a component at once.") - public void SetProperties(Component component, YailDictionary properties) throws JSONException { - JSONObject propertyObject = new JSONObject(properties.toString()); - JSONArray names = propertyObject.names(); - - for (int i = 0; i < propertyObject.length(); i++) { - String name = names.getString(i); - Object value = propertyObject.get(name); - Invoke(component, name, YailList.makeList(new Object[] { value })); + @SimpleFunction(description = "Get meta data about events for the specified component.") + public YailDictionary GetEventMeta(Component component) { + Method[] mMethods = component.getClass().getMethods(); + YailDictionary mEvents = new YailDictionary(); + + for (Method mMethod : mMethods) { + SimpleEvent mAnnotation = mMethod.getAnnotation(SimpleEvent.class); + String mName = mMethod.getName(); + YailDictionary mEventMeta = new YailDictionary(); + + if (mAnnotation != null) { + mEventMeta.put("description", mAnnotation.description()); + mEventMeta.put("isDeprecated", (mMethod.getAnnotation(Deprecated.class) != null)); + mEventMeta.put("userVisible", mAnnotation.userVisible()); + mEvents.put(mName, mEventMeta); + } } - } - /** - * Returns the specified properties value of the given component. Can be known - * as a Getter property block. It can be also used to get properties that only - * exists in Designer. - * - * @param component The component that property will get from. - * @param name Name of the desired property. - * @return Value of the specified property of the specified component. - */ - @SimpleFunction(description = "Returns the specified properties value of the given component. Can be known as a Getter property block. It can be also used to get properties that only exists in Designer.") - public Object GetProperty(Component component, String name) { - // The method will be invoked. - try { - return Invoke(component, name, YailList.makeEmptyList()); - } catch (Exception exception) { - // Throw an error when something goes wrong. - throw new YailRuntimeError("" + exception, "Error"); - } + return mEvents; } - /** - * Invokes the specified method of the specified component. - * - * @param component The component that method will be executed for. - * @param name Name of the method that needs to be invoked. - * @param parameters Parameters to pass to the specified method. - * @return Return value of the invoked method. - */ - @SimpleFunction(description = "Invokes a method with parameters.") - public Object Invoke(Component component, String name, YailList parameters) { - // The method will be invoked. - name = name.trim().replace(" ", ""); - - try { - if (component == null) { - throw new YailRuntimeError("Component is not specified.", "Error"); + @SimpleFunction(description = "Get meta data about functions for the specified component.") + public YailDictionary GetFunctionMeta(Component component) { + Method[] mMethods = component.getClass().getMethods(); + YailDictionary mFunctions = new YailDictionary(); + + for (Method mMethod : mMethods) { + SimpleFunction mAnnotation = mMethod.getAnnotation(SimpleFunction.class); + String mName = mMethod.getName(); + YailDictionary mFunctionMeta = new YailDictionary(); + + if (mAnnotation != null) { + mFunctionMeta.put("description", mAnnotation.description()); + mFunctionMeta.put("isDeprecated", (mMethod.getAnnotation(Deprecated.class) != null)); + mFunctionMeta.put("userVisible", mAnnotation.userVisible()); + mFunctions.put(mName, mFunctionMeta); } + } - Method method = internal.findMethod(internal.getClass(component).getMethods(), name.replace(" ", ""), parameters.toArray().length); + return mFunctions; + } - if (method == null) { - throw new YailRuntimeError("Method can't found with that name.", "Error"); - } + @SimpleFunction(description = "Returns the ID of the specified component.") + public String GetId(Component component) { + if (component != null || COMPONENT_IDS.containsKey(component)) { + return COMPONENT_IDS.get(component); + } - Object[] typed_params = parameters.toArray(); - Class[] requested_params = method.getParameterTypes(); - ArrayList params = new ArrayList<>(); - for (int i = 0; i < requested_params.length; i++) { - if ("int".equals(requested_params[i].getName())) { - // Integer - params.add(Integer.parseInt(typed_params[i].toString())); - } else if ("float".equals(requested_params[i].getName())) { - // Float - params.add(Float.parseFloat(typed_params[i].toString())); - } else if ("double".equals(requested_params[i].getName())) { - // Double - params.add(Double.parseDouble(typed_params[i].toString())); - } else if ("java.lang.String".equals(requested_params[i].getName())) { - // String - params.add(typed_params[i].toString()); - } else if ("boolean".equals(requested_params[i].getName())) { - // Boolean - params.add(Boolean.parseBoolean(typed_params[i].toString())); - } else { - params.add(typed_params[i]); - } - } + return ""; + } - Object m = method.invoke(component, params.toArray()); - if (m == null) { - return ""; - } else { - return m; - } - } catch (Exception exception) { - throw new YailRuntimeError(exception.toString(), "Error"); - } + @Deprecated + @SimpleFunction(description = "Do NOT use this function. Use 'GetComponentMeta' as a replacement.") + public String GetName(Component component) { + return component.getClass().getName(); } - /** - * Returns a JSON string with the information of the specified component containing - * all of it's properties, events and methods. - * - * @param component The component that information will be returned for. - * @return Information of the specified component. - * @throws JSONException Thrown by "put()" method from the JSONObject class. - */ - @SimpleFunction(description = "Gives the information of the specified component with all properties, events, methods as JSON text.") - public String ListDetails(Component component) { - Class componentClass = internal.getClass(component); - DesignerComponent designerComponentAnnotation = internal.getAnnotation(DesignerComponent.class, component, null); - JSONObject specifications = new JSONObject(); - - try { - // Alphabetical order - specifications.put("androidMinSdk", designerComponentAnnotation.androidMinSdk()); - specifications.put("category", designerComponentAnnotation.category()); - specifications.put("dateBuilt", designerComponentAnnotation.dateBuilt()); - specifications.put("description", designerComponentAnnotation.description()); - specifications.put("helpUrl", designerComponentAnnotation.helpUrl()); - specifications.put("iconName", designerComponentAnnotation.iconName()); - specifications.put("name", componentClass.getSimpleName()); - specifications.put("nonVisible", designerComponentAnnotation.nonVisible()); - specifications.put("showOnPalette", designerComponentAnnotation.showOnPalette()); - specifications.put("type", componentClass.getName()); - specifications.put("version", designerComponentAnnotation.version()); - specifications.put("versionName", designerComponentAnnotation.versionName()); - } catch(JSONException e) { - e.printStackTrace(); - } + @SimpleFunction(description = "Returns the position of the specified component according to its parent view. Index begins at one.") + public int GetOrder(AndroidViewComponent component) { + View mComponent = (View) component.getView(); + int mIndex = 0; + ViewGroup mParent = (mComponent != null ? (ViewGroup) mComponent.getParent() : null); - Method[] allMethods = componentClass.getMethods(); - JSONArray blockProperties = new JSONArray(); - JSONArray events = new JSONArray(); - JSONArray methods = new JSONArray(); - JSONArray properties = new JSONArray(); - - // Get the component's class and return all methods from it. - // Search for methods. - for (Method mMethod : allMethods) { - JSONObject data = new JSONObject(); - - DesignerProperty designerPropertyAnnotation = internal.getAnnotation(DesignerProperty.class, null, mMethod); - SimpleEvent simpleEventAnnotation = internal.getAnnotation(SimpleEvent.class, null, mMethod); - SimpleFunction simpleFunctionAnnotation = internal.getAnnotation(SimpleFunction.class, null, mMethod); - SimpleProperty simplePropertyAnnotation = internal.getAnnotation(SimpleProperty.class, null, mMethod); - - internal.putInJsonObject(data, "name", mMethod.getName()); - - if (internal.methodHasAnnotation(DesignerProperty.class, mMethod)) { - internal.putInJsonObject(data, "editorType", designerPropertyAnnotation.editorType()); - internal.putInJsonObject(data, "defaultValue", designerPropertyAnnotation.defaultValue()); - internal.putInJsonObject(data, "editorArgs", new JSONArray(Arrays.asList(designerPropertyAnnotation.editorArgs()))); - internal.putInJsonArray(properties, data); - } - - if (internal.methodHasAnnotation(SimpleEvent.class, mMethod)) { - internal.putInJsonObject(data, "description", simpleEventAnnotation.description()); - internal.putInJsonObject(data, "visible", simpleEventAnnotation.userVisible()); + if (mComponent != null && mParent != null) { + mIndex = mParent.indexOfChild(mComponent) + 1; + } - JSONArray params = new JSONArray(); - for (Class param : mMethod.getParameterTypes()) { - internal.putInJsonArray(params, param.getName()); - } + return mIndex; + } - internal.putInJsonObject(data, "parameterTypes", params); - // Missing: "deprecated" - // Missing: "params" - } + @SimpleFunction(description = "Get a properties value.") + public Object GetProperty(Component component, String name) { + return Invoke(component, name, YailList.makeEmptyList()); + } - if (internal.methodHasAnnotation(SimpleFunction.class, mMethod)) { - internal.putInJsonObject(data, "description", simpleFunctionAnnotation.description()); - internal.putInJsonObject(data, "visible", simpleFunctionAnnotation.userVisible()); - internal.putInJsonObject(data, "returnType", mMethod.getReturnType().getSimpleName()); - - JSONArray params = new JSONArray(); - for (Class param : mMethod.getParameterTypes()) { - internal.putInJsonArray(params, param.getName()); + @SimpleFunction(description = "Get meta data about properties for the specified component, including their values.") + public YailDictionary GetPropertyMeta(Component component) { + Method[] mMethods = component.getClass().getMethods(); + YailDictionary mProperties = new YailDictionary(); + + for (Method mMethod : mMethods) { + DesignerProperty mDesignerAnnotation = mMethod.getAnnotation(DesignerProperty.class); + SimpleProperty mPropertyAnnotation = mMethod.getAnnotation(SimpleProperty.class); + String mName = mMethod.getName(); + YailDictionary mPropertyMeta = new YailDictionary(); + Object mValue = Invoke(component, mName, new YailList());; + + if (mPropertyAnnotation != null) { + mPropertyMeta.put("description", mPropertyAnnotation.description()); + mPropertyMeta.put("category", mPropertyAnnotation.category()); + + if (mDesignerAnnotation != null) { + YailDictionary mDesignerMeta = new YailDictionary(); + mDesignerMeta.put("defaultValue", mDesignerAnnotation.defaultValue()); + mDesignerMeta.put("editorArgs", mDesignerAnnotation.editorArgs()); + mDesignerMeta.put("editorType", mDesignerAnnotation.editorType()); + mPropertyMeta.put("designer", mDesignerMeta); } - internal.putInJsonObject(data, "parameterTypes", params); - // Missing: "deprecated" - // Missing: "params" + mPropertyMeta.put("isDeprecated", (mMethod.getAnnotation(Deprecated.class) != null)); + mPropertyMeta.put("isDesignerProperty", (mDesignerAnnotation != null)); + mPropertyMeta.put("userVisible", mPropertyAnnotation.userVisible()); + mPropertyMeta.put("value", mValue); + mProperties.put(mName, mPropertyMeta); } + } - if (internal.methodHasAnnotation(SimpleProperty.class, mMethod)) { - internal.putInJsonObject(data, "category", simplePropertyAnnotation.category()); - internal.putInJsonObject(data, "description", simplePropertyAnnotation.description()); - internal.putInJsonObject(data, "visible", simplePropertyAnnotation.userVisible()); - - String rw = "read-write"; + return mProperties; + } - boolean setter = internal.findMethod(allMethods, mMethod.getName(), 1) != null; - boolean getter = internal.findMethod(allMethods, mMethod.getName(), 0) != null; + @SimpleFunction(description = "Invokes a method with parameters.") + public Object Invoke(Component component, String name, YailList parameters) { + if (component != null) { + Object mInvokedMethod = null; + Method[] mMethods = component.getClass().getMethods(); - if (getter && !setter) { - rw = "read-only"; - internal.putInJsonObject(data, "type", Objects.requireNonNull(internal.findMethod(allMethods, mMethod.getName(), 0)).getReturnType().getSimpleName()); - } else { - rw = (setter && !getter ? "write-only" : "read-write"); - internal.putInJsonObject(data, "type", Objects.requireNonNull(internal.findMethod(allMethods, mMethod.getName(), 1)).getParameterTypes()[0].getSimpleName()); + try { + Object[] mParameters = parameters.toArray(); + Method mMethod = UTIL_INSTANCE.getMethod(mMethods, name, mParameters.length); + + Class[] mRequestedMethodParameters = mMethod.getParameterTypes(); + ArrayList mParametersArrayList = new ArrayList(); + for (int i = 0; i < mRequestedMethodParameters.length; i++) { + if ("int".equals(mRequestedMethodParameters[i].getName())) { + mParametersArrayList.add(Integer.parseInt(mParameters[i].toString())); + } else if ("float".equals(mRequestedMethodParameters[i].getName())) { + mParametersArrayList.add(Float.parseFloat(mParameters[i].toString())); + } else if ("double".equals(mRequestedMethodParameters[i].getName())) { + mParametersArrayList.add(Double.parseDouble(mParameters[i].toString())); + } else if ("java.lang.String".equals(mRequestedMethodParameters[i].getName())) { + mParametersArrayList.add(mParameters[i].toString()); + } else if ("boolean".equals(mRequestedMethodParameters[i].getName())) { + mParametersArrayList.add(Boolean.parseBoolean(mParameters[i].toString())); + } else { + mParametersArrayList.add(mParameters[i]); + } } - boolean isDeprecated = simplePropertyAnnotation.category() == PropertyCategory.DEPRECATED; - internal.putInJsonObject(data, "rw", rw); - internal.putInJsonObject(data, "deprecated", isDeprecated); - - if (simplePropertyAnnotation.category() != PropertyCategory.UNSET) { - internal.putInJsonArray(blockProperties, data); + mInvokedMethod = mMethod.invoke(component, mParametersArrayList.toArray()); + } catch (Exception e) { + throw new YailRuntimeError(e.getMessage(), "DynamicComponents"); + } finally { + if (mInvokedMethod != null) { + return mInvokedMethod; + } else { + return ""; } } + } else { + throw new YailRuntimeError("Component cannot be null.", "DynamicComponents"); } - - internal.putInJsonObject(specifications, "blockProperties", blockProperties); - internal.putInJsonObject(specifications, "events", events); - internal.putInJsonObject(specifications, "methods", methods); - internal.putInJsonObject(specifications, "properties", properties); - - return specifications.toString(); } - /** - * Returns the version number of the extension. - * - * @return Version number of the extension. - */ - @SimpleProperty(description = "Returns the version number of the extension.") - public int Version() { - return DynamicComponents.class.getAnnotation(DesignerComponent.class).version(); + @SimpleFunction(description = "Returns if the specified component was created by the Dynamic Components extension.") + public boolean IsDynamic(Component component) { + return COMPONENTS.containsValue(component); } - /** - * Returns the version name of the extension. - * - * @return Version name of the extension. - */ - @SimpleProperty(description = "Returns the extension version name.") - public String VersionName() { - return DynamicComponents.class.getAnnotation(DesignerComponent.class).versionName(); + @SimpleFunction(description = "Returns the last used ID.") + public Object LastUsedID() { + return lastUsedId; } - protected class Internal { - public Internal() {} + @Deprecated + @SimpleFunction(description = "Do NOT use this function. Use 'GetComponentMeta', 'GetEventMeta', 'GetFunctionMeta', and 'GetPropertyMeta' as replacements.") + public String ListDetails(Component component) { + return ""; + } - public String checkId(Object componentName, String id) { - // Check if ID is used by another Dynamic Component - if (id == null || id.trim().isEmpty()) { - throw new YailRuntimeError("DynamicComponents-AI2: ID can't be blank.", "Invalid ID"); - } else { - if (internal.isIdTaken(id)) { - throw new YailRuntimeError("DynamicComponents-AI2: ID must be unique for all components.", "Duplicate ID"); - } - } + @SimpleFunction(description = "Moves the specified component to the specified view.") + public void Move(AndroidViewComponent arrangement, AndroidViewComponent component) { + View mComponent = (View) component.getView(); + ViewGroup mParent = (mComponent != null ? (ViewGroup) mComponent.getParent() : null); - return internal.getClassName(componentName); - } + mParent.removeView(mComponent); - public void createBySchema(AndroidViewComponent in, int i, JSONArray properties) { - if (!properties.getJSONObject(i).has("in")) { - // If a component JSONObject doesn't contain an "in" key then insert it in the main // component that specified as this method's "in" parameter. - Create(in, properties.getJSONObject(i).getString("type"), properties.getJSONObject(i).getString("id")); - } else { - // Else, insert it in the another component that is specified with an ID. - Create((AndroidViewComponent) GetComponent(properties.getJSONObject(i).getString("in")), properties.getJSONObject(i).getString("type"), properties.getJSONObject(i).getString("id")); - } - } + ViewGroup mArrangement = (ViewGroup) arrangement.getView(); + ViewGroup mTarget = (ViewGroup) mArrangement.getChildAt(0); - public Method findMethod(Method[] methods, String name, Integer paramCount) { - for (Method method : methods) { - // Check for one parameter (setter) method. - if ((method.getName().equals(name.trim())) && (method.getParameterTypes().length == paramCount)) { - return method; - } - } + mTarget.addView(mComponent); + } - return null; - } + @Deprecated + @SimpleFunction(description = "Do NOT use this function. Use 'GenerateID' as a replacement.") + public String RandomUUID() { + return GenerateID(); + } - public Class getClass(Object instance) { - return instance.getClass(); - } + @SimpleFunction(description = "Removes the component with the specified ID from the layout/screen so the ID can be reused.") + public void Remove(String id) { + Object component = COMPONENTS.get(id); - public String getClassName(Object componentName) { - if ((componentName instanceof String) && componentName.toString().contains(".")) { - // Is the componentName a String with package name? - return componentName.toString().replace(" ", ""); - } else if (componentName instanceof String) { - // Is the componentName a String with no pacakge name? - return BASE_PACKAGE + "." + componentName.toString().replace(" ", ""); - } else if (componentName instanceof Component) { - // Is the componentName a Component instance? - return componentName.getClass().getName(); - } else { - // Throw an error if componentName is neither Component or String - throw new YailRuntimeError("DynamicComponents-AI2: Not a Component block or a String.", "Invalid Component"); + if (component != null) { + try { + Method mMethod = component.getClass().getMethod("getView"); + final View mComponent = (View) mMethod.invoke(component); + final ViewGroup mParent = (ViewGroup) mComponent.getParent(); + + if (postOnUiThread) { + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + mParent.removeView(mComponent); + } + }); + } else { + mParent.removeView(mComponent); + } + } catch (Exception e) { + e.printStackTrace(); } + + COMPONENTS.remove(id); + COMPONENT_IDS.remove(component); } + } - public T getAnnotation(Class mClass, Component component, Method mMethod) { - if (component != null) { - return internal.getClass(component).getAnnotation(mClass); - } + @SimpleFunction(description = "Sets the order of the specified component according to its parent view. Typing zero will move the component to the end, index begins at one.") + public void SetOrder(AndroidViewComponent component, int index) { + index = index - 1; + View mComponent = (View) component.getView(); + ViewGroup mParent = (ViewGroup) mComponent.getParent(); - return mMethod.getAnnotation(mClass); - } + mParent.removeView(mComponent); - public boolean methodHasAnnotation(Class mClass, Method mMethod) { - return mMethod.isAnnotationPresent(mClass); - } + int mChildCount = mParent.getChildCount(); + int mIndex = (index > mChildCount ? mChildCount : index); - public boolean isDynamicComponent(Component component) { - return COMPONENTS.containsValue(component); - } + mParent.addView(mComponent, mIndex); + } - public boolean isIdTaken(String id) { - return COMPONENTS.containsKey(id); - } + @SimpleFunction(description = "Set a property of the specified component, including those only available from the Designer.") + public void SetProperty(Component component, String name, Object value) { + Invoke(component, name, YailList.makeList(new Object[] { + value + })); + } - public String loopReplace(JSONObject json, YailList parameters, String template) { - String modified = template; - - for (int i = 0; i < json.optJSONArray("keys").length(); i++) { - modified = modified.replace("{" + json.optJSONArray("keys").getString(i) + "}", parameters.getString(i).replace("\"", "")); - } + @SimpleFunction(description = "Set multiple properties of the specified component using a dictionary, including those only available from the Designer.") + public void SetProperties(Component component, YailDictionary properties) throws Exception { + JSONObject mProperties = new JSONObject(properties.toString()); + JSONArray mPropertyNames = mProperties.names(); - return modified; + for (int i = 0; i < mProperties.length(); i++) { + String name = mPropertyNames.getString(i); + Object value = mProperties.get(name); + Invoke(component, name, YailList.makeList(new Object[] { value })); } + } - public void parseJson(String id, JSONObject json) throws JSONException { - JSONObject data = new JSONObject(json.toString()); - String KEY = "components"; - - data.remove(KEY); + @SimpleFunction(description = "Uses a JSON Object to create dynamic components. Templates can also contain parameters that will be replaced with the values which are defined from the parameters list.") + public void Schema(AndroidViewComponent in, final String template, final YailList parameters) throws Exception { + JSONObject mScheme = new JSONObject(template); + String newTemplate = template; - if (!"".equals(id)) { - data.put("in", id); - } + if (!template.replace(" ", "").isEmpty() && mScheme.has("components")) { + propertiesArray = new JSONArray(); - PROPERTIESARRAY.put(data); + JSONArray mKeys = (mScheme.has("keys") ? mScheme.getJSONArray("keys") : null); - if (json.has(KEY)) { - for (int i = 0; i < json.getJSONArray(KEY).length(); i++) { - parseJson(data.optString("id", ""), json.getJSONArray(KEY).getJSONObject(i)); + if (mKeys != null && mKeys.length() == parameters.length() - 1) { + for (int i = 0; i < mKeys.length(); i++) { + String keyPercent = "%" + mKeys.getString(i); + String keyBracket = "{" + mKeys.getString(i) + "}"; + String value = parameters.getString(i).replace("\"", ""); + newTemplate = newTemplate.replace(keyPercent, value); + newTemplate = newTemplate.replace(keyBracket, value); } } - } - public void putInJsonArray(JSONArray json, Object value) { - try { - json.put(value); - } catch (JSONException e) { - e.printStackTrace(); - } - } + mScheme = new JSONObject(newTemplate); + UTIL_INSTANCE.parse("", mScheme); + propertiesArray.remove(0); - public void putInJsonObject(JSONObject json, String key, Object value) { - try { - json.put(key, value); - } catch (JSONException e) { - e.printStackTrace(); - } - } - - public String replaceKeys(JSONObject json, YailList parameters, String template) { - String modified = template; - - // Check if JSON contains "keys". - if (json.has("keys")) { - // Throw a runtime error if parameter count is lower than required parameter count. - if (json.optJSONArray("keys").length() > parameters.length()) { - throw new YailRuntimeError("Input parameter count is lower than the requirement!", "Error"); - } else { - /* Replace the template keys with their values. - * For example; - * {0} --> "a value" */ - loopReplace(json, parameters, template); + for (int i = 0; i < propertiesArray.length(); i++) { + if (!propertiesArray.getJSONObject(i).has("id")) { + throw new YailRuntimeError("One or multiple components do not have a specified ID in the template.", "DynamicComponents"); } + + final JSONObject mJson = propertiesArray.getJSONObject(i); + final String mId = mJson.getString("id"); + AndroidViewComponent mRoot = (!mJson.has("in") ? in : (AndroidViewComponent) GetComponent(mJson.getString("in"))); + final String mType = mJson.getString("type"); + + ComponentListener listener = new ComponentListener() { + @Override + public void onCreation(Component component, String id) { + if (id == mId && mJson.has("properties")) { + JSONObject mProperties = mJson.getJSONObject("properties"); + JSONArray keys = mProperties.names(); + + for (int k = 0; k < keys.length(); k++) { + Invoke( + (Component) GetComponent(mId), + keys.getString(k), + YailList.makeList(new Object[] { + mProperties.get(keys.getString(k)) + }) + ); + } + + componentListeners.remove(this); + } + } + }; + + componentListeners.add(listener); + + Create(mRoot, mType, mId); } - return modified; + SchemaCreated(mScheme.getString("name"), parameters); + } else { + throw new YailRuntimeError("The template is empty, or is does not have any components.", "DynamicComponents"); } + } - public void setPropertiesBySchema(int i, JSONArray properties) { - JSONArray keys = properties.getJSONObject(i).getJSONObject("properties").names(); + @SimpleFunction(description = "Returns all IDs of components created with the Dynamic Components extension.") + public YailList UsedIDs() { + Set mKeys = COMPONENTS.keySet(); + return YailList.makeList(mKeys); + } - for (int k = 0; k < keys.length(); k++) { - Invoke( - (Component) GetComponent(properties.getJSONObject(i).getString("id")), - keys.getString(k), - YailList.makeList(new Object[] { properties.getJSONObject(i).getJSONObject("properties").get(keys.getString(k)) }) - ); - } - } + @SimpleProperty(description = "Returns the version of the Dynamic Components extension.") + public int Version() { + return DynamicComponents.class.getAnnotation(DesignerComponent.class).version(); + } + + @SimpleProperty(description = "Returns the version name of the Dynamic Components extension.") + public String VersionName() { + return DynamicComponents.class.getAnnotation(DesignerComponent.class).versionName(); } -} \ No newline at end of file +} diff --git a/src/com/yusufcihan/DynamicComponents/aiwebres/icon.png b/src/com/yusufcihan/DynamicComponents/aiwebres/icon.png index ddca7ea..1c688f6 100644 Binary files a/src/com/yusufcihan/DynamicComponents/aiwebres/icon.png and b/src/com/yusufcihan/DynamicComponents/aiwebres/icon.png differ