|
| 1 | +package com.yusufcihan.DynamicComponents; |
| 2 | + |
| 3 | +import com.google.appinventor.components.annotations.*; |
| 4 | +import com.google.appinventor.components.runtime.*; |
| 5 | +import com.google.appinventor.components.common.*; |
| 6 | +import com.google.appinventor.components.runtime.util.YailList; |
| 7 | +import com.google.appinventor.components.runtime.errors.YailRuntimeError; |
| 8 | + |
| 9 | +import java.util.Hashtable; |
| 10 | +import java.util.Iterator; |
| 11 | +import java.util.ArrayList; |
| 12 | +import java.util.List; |
| 13 | +import java.lang.reflect.InvocationTargetException; |
| 14 | +import java.lang.reflect.Constructor; |
| 15 | +import java.lang.reflect.Method; |
| 16 | +import java.lang.reflect.Parameter; |
| 17 | +import java.lang.reflect.Modifier; |
| 18 | +import java.util.Arrays; |
| 19 | +import java.util.Set; |
| 20 | +import java.lang.Boolean; |
| 21 | + |
| 22 | +import android.view.View; |
| 23 | +import android.view.ViewGroup; |
| 24 | + |
| 25 | +@DesignerComponent(version = 3, |
| 26 | + description = "Dynamic Components extension to create any type of dynamic component in any arrangement.<br><br>- by Yusuf Cihan", |
| 27 | + category = ComponentCategory.EXTENSION, |
| 28 | + nonVisible = true, |
| 29 | + iconName = "https://yusufcihan.com/img/dynamiccomponents.png") |
| 30 | +@SimpleObject(external = true) |
| 31 | +public class DynamicComponents extends AndroidNonvisibleComponent implements Component { |
| 32 | + |
| 33 | + // Variables |
| 34 | + private Hashtable<String, Object> COMPONENTS = new Hashtable<String, Object>(); |
| 35 | + private List<String> blacklist = Arrays.asList("CopyHeight", "CopyWidth", "wait", "onClick", "Column", "Row", "setLastWidth", "setLastHeight"); |
| 36 | + private String BASE_PACKAGE = "com.google.appinventor.components.runtime"; |
| 37 | + private String LAST_ID = ""; |
| 38 | + |
| 39 | + public DynamicComponents(ComponentContainer container) { |
| 40 | + super(container.$form()); |
| 41 | + } |
| 42 | + |
| 43 | + private String BasePackage() { |
| 44 | + return BASE_PACKAGE; |
| 45 | + } |
| 46 | + |
| 47 | + private void BasePackage(String packageName) { |
| 48 | + BASE_PACKAGE = packageName; |
| 49 | + } |
| 50 | + |
| 51 | + // ------------------------ |
| 52 | + // MAIN METHODS |
| 53 | + // ------------------------ |
| 54 | + |
| 55 | + @SimpleFunction(description = "Create a dynamic component that you want. It supports all components that added to App Inventor sources. Use 'in' parameter to specify the arrangement or canvas which new component will be placed in. Type the name of component in the 'componentName' section. (case sensitive) Use any component blocks to edit dynamic component's properties! ") |
| 56 | + public void Create(AndroidViewComponent in, String componentName, String id) { |
| 57 | + Object component = null; |
| 58 | + LAST_ID = id; |
| 59 | + // Check if id is used by another created dynamic component. |
| 60 | + if (!COMPONENTS.containsKey(id)) |
| 61 | + { |
| 62 | + try |
| 63 | + { |
| 64 | + // Return the component class by looking the its name. |
| 65 | + Class clasz = Class.forName(BASE_PACKAGE + "." + componentName); |
| 66 | + // Create constructor object for creating a new instance. |
| 67 | + Constructor constructor = clasz.getConstructor(new Class[] { ComponentContainer.class }); |
| 68 | + // Create a new instance of specified component. |
| 69 | + component = constructor.newInstance((ComponentContainer)in); |
| 70 | + } |
| 71 | + catch (Exception e) |
| 72 | + { |
| 73 | + // Throw a runtime error when something goes wrong. |
| 74 | + throw new YailRuntimeError(e.getMessage(),"Error"); |
| 75 | + } |
| 76 | + COMPONENTS.put(id, component); |
| 77 | + } |
| 78 | + else |
| 79 | + { |
| 80 | + // Throw a runtime error when ID is already used for another component. |
| 81 | + throw new YailRuntimeError("This ID is already used, please pick another.","Duplicate ID"); |
| 82 | + } |
| 83 | + |
| 84 | + } |
| 85 | + |
| 86 | + @SimpleFunction(description = "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.") |
| 87 | + public void Remove(String id) { |
| 88 | + Method m = null; |
| 89 | + Object cmp = null; |
| 90 | + // Don't do anything if id is not in the components list. |
| 91 | + if (COMPONENTS.containsKey(id) == false) |
| 92 | + return; |
| 93 | + |
| 94 | + try |
| 95 | + { |
| 96 | + // Get the component. |
| 97 | + cmp = COMPONENTS.get(id); |
| 98 | + // Remove its id from components list. |
| 99 | + COMPONENTS.remove(id); |
| 100 | + // Hide the component. |
| 101 | + ((AndroidViewComponent)cmp).Visible(false); |
| 102 | + } |
| 103 | + catch (Exception eh) { } |
| 104 | + } |
| 105 | + |
| 106 | + @SimpleFunction(description = "Returns last used ID.") |
| 107 | + public String LastUsedID() { |
| 108 | + return LAST_ID; |
| 109 | + } |
| 110 | + |
| 111 | + @SimpleFunction(description = "Returns the component's itself for setting properties. Component needs to be created with Create block. Type an ID which you typed in Create block to return the component.") |
| 112 | + public Object GetComponent(String id) { |
| 113 | + return COMPONENTS.get(id); |
| 114 | + } |
| 115 | + |
| 116 | + @SimpleFunction(description = "Returns the component type name.") |
| 117 | + public String GetName(Object component) { |
| 118 | + return component.getClass().getName(); |
| 119 | + } |
| 120 | + |
| 121 | + @SimpleFunction(description = "Removes all created dynamic components. Same as Remove block, but for all created components.") |
| 122 | + public void RemoveAll() { |
| 123 | + Set<String> keys = COMPONENTS.keySet(); |
| 124 | + for(String key: keys){ |
| 125 | + Remove(key); |
| 126 | + } |
| 127 | + } |
| 128 | + |
| 129 | + @SimpleFunction(description = "Get all available properties of a component which can be set from Designer as list along with types. Can be used to learn the properties of any component which is not static.") |
| 130 | + public YailList GetDesignerProperties(Object component) { |
| 131 | + // A list which includes designer properties. |
| 132 | + ArrayList names = new ArrayList(); |
| 133 | + // Get the component's class and return all methods from it. |
| 134 | + Method[] methods = component.getClass().getMethods(); |
| 135 | + for (Method mtd : methods) |
| 136 | + { |
| 137 | + // Read for @DesignerProperty annotations. |
| 138 | + // So we can learn which method is used as property setter/getter. |
| 139 | + if ((mtd.getDeclaredAnnotations().length == 2) && (mtd.isAnnotationPresent(DesignerProperty.class))) |
| 140 | + { |
| 141 | + // Get the DesignerProperty annotation. |
| 142 | + DesignerProperty n = mtd.getAnnotation(DesignerProperty.class); |
| 143 | + // Add editorType value and method name to the list. |
| 144 | + names.add(YailList.makeList(new String[] { |
| 145 | + mtd.getName(), |
| 146 | + n.editorType() |
| 147 | + })); |
| 148 | + } |
| 149 | + } |
| 150 | + // Return the list. |
| 151 | + return YailList.makeList(names); |
| 152 | + } |
| 153 | + |
| 154 | + @SimpleFunction(description = "Set a property of a component by typing its name.") |
| 155 | + public void SetProperty(Object component, String propertyName, Object propertyValue) { |
| 156 | + // Read methods of the component. |
| 157 | + Method[] methods = component.getClass().getMethods(); |
| 158 | + // The method will be invoked. |
| 159 | + Method method = null; |
| 160 | + // Class for casting purpose. |
| 161 | + Class caster = null; |
| 162 | + try |
| 163 | + { |
| 164 | + for (Method mtd : methods) |
| 165 | + { |
| 166 | + // Check for one parametered (setter) method. |
| 167 | + if((mtd.getName() == propertyName) && (mtd.getParameterCount() == 1)) |
| 168 | + { |
| 169 | + // Save it for later. |
| 170 | + caster = mtd.getParameterTypes()[0]; |
| 171 | + method = mtd; |
| 172 | + break; |
| 173 | + } |
| 174 | + } |
| 175 | + // Invoke the saved method. |
| 176 | + method.invoke(component, propertyValue); |
| 177 | + } |
| 178 | + catch (Exception eh) |
| 179 | + { |
| 180 | + // Throw an error when something goes wrong. |
| 181 | + throw new YailRuntimeError(eh.getMessage().toString(),"Error"); |
| 182 | + } |
| 183 | + } |
| 184 | + |
| 185 | + @SimpleFunction(description = "Get property value of a component.") |
| 186 | + public Object GetProperty(Object component, String propertyName) { |
| 187 | + // Read methods of the component. |
| 188 | + Method[] methods = component.getClass().getMethods(); |
| 189 | + // The method will be invoked. |
| 190 | + Method method = null; |
| 191 | + try |
| 192 | + { |
| 193 | + for (Method mtd : methods) |
| 194 | + { |
| 195 | + // Check for zero parametered (getter) method. |
| 196 | + if((mtd.getName() == propertyName) && (mtd.getParameterCount() == 0)) |
| 197 | + { |
| 198 | + // Save it for later. |
| 199 | + method = mtd; |
| 200 | + break; |
| 201 | + } |
| 202 | + } |
| 203 | + // Invoke the saved method and return its return value. |
| 204 | + return method.invoke(component); |
| 205 | + } |
| 206 | + catch (Exception eh) |
| 207 | + { |
| 208 | + // Throw an error when something goes wrong. |
| 209 | + throw new YailRuntimeError(eh.getMessage().toString(),"Error"); |
| 210 | + } |
| 211 | + } |
| 212 | + |
| 213 | + @SimpleFunction(description = "Returns the ID of component. Component needs to be created by Create block. Otherwise it will return -1.") |
| 214 | + public String GetId(Object component) { |
| 215 | + return getKeyFromValue(COMPONENTS, component); |
| 216 | + } |
| 217 | + |
| 218 | + // ------------------------ |
| 219 | + // PRIVATE METHODS |
| 220 | + // ------------------------ |
| 221 | + |
| 222 | + // Getting key from value, found on: |
| 223 | + // http://www.java2s.com/Code/Java/Collections-Data-Structure/GetakeyfromvaluewithanHashMap.htm |
| 224 | + public String getKeyFromValue(Hashtable hm, Object value) { |
| 225 | + for (Object o : hm.keySet()) { |
| 226 | + if (hm.get(o).equals(value)) { |
| 227 | + return (String)o; |
| 228 | + } |
| 229 | + } |
| 230 | + return ""; |
| 231 | + } |
| 232 | +} |
0 commit comments