package mil.nga.giat.mage.form;
import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.view.ContextThemeWrapper;
import android.support.v7.widget.AppCompatEditText;
import android.support.v7.widget.AppCompatRadioButton;
import android.text.InputType;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.apache.commons.lang3.StringUtils;
import java.io.Serializable;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import mil.nga.giat.mage.R;
import mil.nga.giat.mage.observation.DateTimePickerDialog;
import mil.nga.giat.mage.sdk.datastore.observation.ObservationProperty;
import mil.nga.giat.mage.sdk.utils.ISO8601DateFormatFactory;
/**
* Use this class to build and populate the views concerned with form like information.
*
* @author wiedemanns
*
*/
public class LayoutBaker {
private static final String LOG_NAME = LayoutBaker.class.getName();
private static final String EXTRA_PROPERTY_MAP = "EXTRA_PROPERTY_MAP";
public enum ControlGenerationType {
VIEW, EDIT
}
public static List<View> createControlsFromJson(final AppCompatActivity activity, ControlGenerationType controlGenerationType, JsonObject dynamicFormJson) {
// add the theme to the context
final ContextThemeWrapper context = new ContextThemeWrapper(activity, R.style.AppTheme_PrimaryAccent);
List<View> views = new ArrayList<>();
JsonArray dynamicFormFields = dynamicFormJson.get("fields").getAsJsonArray();
Map<Integer, JsonObject> dynamicFormFieldsCollection = new TreeMap<>();
for (int i = 0; i < dynamicFormFields.size(); i++) {
JsonObject field = dynamicFormFields.get(i).getAsJsonObject();
Integer id = field.get("id").getAsInt();
dynamicFormFieldsCollection.put(id, field);
}
int uniqueChildIdIndex = 10000;
int i = -1;
for (Integer id : dynamicFormFieldsCollection.keySet()) {
i++;
JsonObject field = dynamicFormFieldsCollection.get(id);
// get members
String title = field.get("title").getAsString();
DynamicFormType type = DynamicFormType.TEXTFIELD;
String typeString = field.get("type").getAsString();
if (typeString != null) {
try {
type = DynamicFormType.valueOf(typeString.toUpperCase());
} catch (IllegalArgumentException iae) {
Log.e(LOG_NAME, "Unknown type: " + typeString, iae);
type = DynamicFormType.TEXTFIELD;
}
}
Boolean required = field.get("required").getAsBoolean();
Serializable value = null;
JsonElement jsonValue = field.get("value");
if (jsonValue != null && !jsonValue.isJsonNull()) {
if (jsonValue.isJsonPrimitive()) {
value = jsonValue.getAsString();
} else if (jsonValue.isJsonArray()) {
JsonArray jsonArray = (JsonArray) jsonValue;
ArrayList<String> stringArrayList = new ArrayList<>();
for (JsonElement element: jsonArray) {
if (!element.isJsonNull()) {
stringArrayList.add(element.getAsString());
}
}
value = stringArrayList;
}
}
Boolean archived = false;
JsonElement jsonArchived = field.get("archived");
if (jsonArchived != null && !jsonArchived.isJsonNull() && jsonArchived.isJsonPrimitive()) {
archived = jsonArchived.getAsBoolean();
}
if(archived) {
continue;
}
String name = field.get("name").getAsString();
JsonArray choicesJson = field.get("choices").getAsJsonArray();
Collection<String> choices = new LinkedHashSet<>();
if (choicesJson != null && !choicesJson.isJsonNull()) {
for (int j = 0; j < choicesJson.size(); j++) {
JsonObject choiceJson = choicesJson.get(j).getAsJsonObject();
JsonElement choiceElement = choiceJson.get("title");
if (choiceElement != null) {
String choiceTitle = choiceElement.getAsString();
if (choiceTitle != null) {
choices.add(choiceTitle);
}
} else {
choices.add("");
}
}
}
final float density = context.getResources().getDisplayMetrics().density;
LinearLayout.LayoutParams textParams = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
LinearLayout.LayoutParams controlParams = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
TextView textView = new TextView(context);
textView.setText(title);
textView.setFocusable(false);
textView.setTextIsSelectable(false);
textView.setClickable(false);
switch (controlGenerationType) {
case EDIT:
int controlMarginTop = (int) (5 * density);
int controlMarginBottom = (i == dynamicFormFields.size() - 1) ? (int) (0 * density) : (int) (15 * density);
int controlMarginLeft = (int) (0 * density);
int controlMarginRight = (int) (0 * density);
controlParams.setMargins(controlMarginLeft, controlMarginTop, controlMarginRight, controlMarginBottom);
int textMarginTop = (int) (5 * density);
int textMarginBottom = (int) (0 * density);
int textMarginLeft = (int) (0 * density);
int textMarginRight = (int) (0 * density);
textParams.setMargins(textMarginLeft, textMarginTop, textMarginRight, textMarginBottom);
textView.setTextAppearance(context, mil.nga.giat.mage.R.style.EditTextView);
break;
default:
case VIEW:
controlMarginTop = (int) (5 * density);
controlMarginBottom = (i == dynamicFormFields.size() - 1) ? (int) (0 * density) : (int) (10 * density);
controlMarginLeft = (int) (0 * density);
controlMarginRight = (int) (0 * density);
controlParams.setMargins(controlMarginLeft, controlMarginTop, controlMarginRight, controlMarginBottom);
textMarginTop = (int) (5 * density);
textMarginBottom = (int) (0 * density);
textMarginLeft = (int) (0 * density);
textMarginRight = (int) (5 * density);
textParams.setMargins(textMarginLeft, textMarginTop, textMarginRight, textMarginBottom);
textView.setTextAppearance(context, mil.nga.giat.mage.R.style.ObservationPropertyLabel);
break;
}
textView.setLayoutParams(textParams);
switch (controlGenerationType) {
case EDIT:
final MageEditText editText = new MageEditText(context, null);
editText.setLayoutParams(controlParams);
editText.setId(id);
editText.setHint(title);
editText.setRequired(required);
editText.setPropertyKey(name);
editText.setPropertyValue(value);
switch (type) {
case TEXTFIELD:
case EMAIL:
editText.setPropertyType(MagePropertyType.STRING);
views.add(editText);
break;
case NUMBERFIELD:
final MageNumberControl numberControl = new MageNumberControl(context, null, field.get("min").getAsDouble(), field.get("max").getAsDouble());
numberControl.setLayoutParams(controlParams);
numberControl.setId(id);
numberControl.setHint(title);
numberControl.setRequired(required);
numberControl.setPropertyKey(name);
numberControl.setPropertyValue(value);
views.add(numberControl);
break;
case PASSWORD:
editText.setPropertyType(MagePropertyType.STRING);
editText.getEditText().setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
editText.setPasswordVisibilityToggleEnabled(true);
views.add(editText);
break;
case TEXTAREA:
editText.setPropertyType(MagePropertyType.MULTILINE);
views.add(editText);
break;
case RADIO:
MageRadioGroup mageRadioGroup = new MageRadioGroup(context, null);
mageRadioGroup.setId(id);
mageRadioGroup.setLayoutParams(controlParams);
mageRadioGroup.setRequired(required);
mageRadioGroup.setPropertyKey(name);
mageRadioGroup.setPropertyType(MagePropertyType.MULTICHOICE);
for (String choice : choices) {
AppCompatRadioButton radioButton = new AppCompatRadioButton(context);
radioButton.setId(uniqueChildIdIndex++);
radioButton.setText(choice);
mageRadioGroup.addRadioButton(radioButton);
}
mageRadioGroup.setPropertyValue(value);
views.add(textView);
views.add(mageRadioGroup);
break;
case CHECKBOX:
MageCheckBox checkBox = new MageCheckBox(context, null);
checkBox.setId(id);
checkBox.setLayoutParams(controlParams);
checkBox.setRequired(required);
checkBox.setPropertyKey(name);
checkBox.setPropertyType(MagePropertyType.STRING);
if (value != null && !((String)value).trim().isEmpty()) {
checkBox.setPropertyValue(Boolean.valueOf(((String)value)));
}
views.add(textView);
views.add(checkBox);
break;
case DATE:
// don't create the timestamp control on the edit page
if (name.equals("timestamp")) {
break;
}
final MageEditText mageDateText = new MageEditText(context, null);
mageDateText.setId(id);
mageDateText.setLayoutParams(controlParams);
mageDateText.setHint(title);
mageDateText.getEditText().setFocusableInTouchMode(false);
mageDateText.getEditText().setFocusable(true);
mageDateText.getEditText().setTextIsSelectable(false);
mageDateText.getEditText().setCursorVisible(false);
mageDateText.getEditText().setClickable(false);
mageDateText.setRequired(required);
mageDateText.setPropertyKey(name);
mageDateText.setPropertyType(MagePropertyType.DATE);
if (value != null && !((String)value).trim().isEmpty()) {
try {
DateFormat dateFormat = ISO8601DateFormatFactory.ISO8601();
mageDateText.setPropertyValue(dateFormat.parse(value.toString()));
} catch (ParseException pe) {
Log.e(LOG_NAME, "Problem parsing date.", pe);
}
}
mageDateText.getEditText().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Date date = null;
try {
date = ISO8601DateFormatFactory.ISO8601().parse(mageDateText.getPropertyValue().toString());
} catch (ParseException pe) {
Log.e(LOG_NAME, "Problem parsing date.", pe);
}
DateTimePickerDialog dialog = DateTimePickerDialog.newInstance(date);
dialog.setOnDateTimeChangedListener(new DateTimePickerDialog.OnDateTimeChangedListener() {
@Override
public void onDateTimeChanged(Date date) {
mageDateText.setPropertyValue(date);
}
});
FragmentTransaction ft = activity.getSupportFragmentManager().beginTransaction();
dialog.show(ft, "DATE_TIME_PICKER_DIALOG");
}
});
views.add(mageDateText);
break;
case DROPDOWN:
MageSelectView selectView = new MageSelectView(context, null, field, false);
selectView.setId(id);
selectView.getEditText().setHint(title);
selectView.setLayoutParams(controlParams);
selectView.setRequired(required);
selectView.setPropertyKey(name);
selectView.setPropertyType(MagePropertyType.STRING);
selectView.setPropertyValue(value);
views.add(selectView);
break;
case MULTISELECTDROPDOWN:
MageSelectView multiSelectView = new MageSelectView(context, null, field, true);
multiSelectView.setId(id);
multiSelectView.getEditText().setHint(title);
multiSelectView.setLayoutParams(controlParams);
multiSelectView.setRequired(required);
multiSelectView.setPropertyKey(name);
multiSelectView.setPropertyType(MagePropertyType.STRING);
multiSelectView.setPropertyValue(value);
views.add(multiSelectView);
break;
default:
break;
}
break;
case VIEW:
default:
MageTextView mageTextView = new MageTextView(context, null);
mageTextView.setId(id);
mageTextView.setLayoutParams(controlParams);
mageTextView.setFocusable(false);
mageTextView.setTextIsSelectable(false);
mageTextView.setClickable(false);
mageTextView.setPropertyKey(name);
mageTextView.setPropertyType(MagePropertyType.STRING);
mageTextView.setTextAppearance(context, mil.nga.giat.mage.R.style.ObservationPropertyValue);
mageTextView.setPadding((int) (5 * density), (int) (5 * density), (int) (5 * density), (int) (5 * density));
LinearLayout linearLayout = new LinearLayout(context);
linearLayout.setOrientation(LinearLayout.VERTICAL);
linearLayout.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
switch (type) {
case TEXTFIELD:
case NUMBERFIELD:
case EMAIL:
case RADIO:
linearLayout.addView(textView);
linearLayout.addView(mageTextView);
views.add(linearLayout);
break;
case CHECKBOX:
MageCheckBox mageCheckBox = new MageCheckBox(context, null);
mageCheckBox.setId(id);
mageCheckBox.setLayoutParams(controlParams);
mageCheckBox.setPropertyKey(name);
mageCheckBox.setPropertyType(MagePropertyType.STRING);
if(value != null && !((String)value).trim().isEmpty()) {
mageCheckBox.setPropertyValue(Boolean.valueOf(((String)value)));
}
mageCheckBox.setEnabled(false);
linearLayout.addView(textView);
linearLayout.addView(mageCheckBox);
views.add(linearLayout);
break;
case PASSWORD:
linearLayout.addView(textView);
mageTextView.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
linearLayout.addView(mageTextView);
views.add(linearLayout);
break;
case TEXTAREA:
mageTextView.setPropertyType(MagePropertyType.MULTILINE);
views.add(textView);
views.add(mageTextView);
break;
case DATE:
mageTextView.setPropertyType(MagePropertyType.DATE);
linearLayout.addView(textView);
linearLayout.addView(mageTextView);
views.add(linearLayout);
break;
case DROPDOWN:
mageTextView.setPropertyType(MagePropertyType.STRING);
views.add(textView);
views.add(mageTextView);
break;
case MULTISELECTDROPDOWN:
mageTextView.setPropertyType(MagePropertyType.MULTICHOICE);
views.add(textView);
views.add(mageTextView);
break;
default:
break;
}
break;
}
}
return views;
}
public static void populateLayoutWithControls(final LinearLayout linearLayout, Collection<View> controls) {
for (View control : controls) {
linearLayout.addView(control);
}
}
public static List<View> validateControls(Collection<View> views) {
List<View> invalid = new ArrayList<>();
for (View view : views) {
if (view instanceof MageControl) {
MageControl control = (MageControl) view;
boolean valid = control.validate();
if (!valid) {
invalid.add(view);
}
}
}
return invalid;
}
/**
* Populates the linearLayout from the key, value pairs in the propertiesMap
*
* @param linearLayout
* @param propertiesMap
*/
public static void populateLayoutFromMap(final LinearLayout linearLayout, ControlGenerationType controlGenerationType, final Map<String, ObservationProperty> propertiesMap) {
for (int i = 0; i < linearLayout.getChildCount(); i++) {
View v = linearLayout.getChildAt(i);
if (v instanceof MageControl) {
MageControl mageControl = (MageControl) v;
String propertyKey = mageControl.getPropertyKey();
ObservationProperty property = propertiesMap.get(propertyKey);
Serializable propertyValue = null;
if (property != null && property.getValue() != null) {
propertyValue = property.getValue();
}
mageControl.setPropertyValue(propertyValue);
View textView = linearLayout.getChildAt(Math.max(0, i - 1));
if(textView != null && textView instanceof TextView) {
textView.setVisibility(View.VISIBLE);
}
v.setVisibility(View.VISIBLE);
if(controlGenerationType.equals(ControlGenerationType.VIEW) && v instanceof MageTextView &&
(propertyValue == null ||
(propertyValue instanceof String && StringUtils.isBlank((String)propertyValue)) ||
(propertyValue instanceof Collection && ((Collection) propertyValue).isEmpty())
)) {
textView = linearLayout.getChildAt(Math.max(0, i - 1));
if(textView != null && textView instanceof TextView) {
textView.setVisibility(View.GONE);
}
v.setVisibility(View.GONE);
}
} else if (v instanceof LinearLayout) {
populateLayoutFromMap((LinearLayout) v, controlGenerationType, propertiesMap);
}
}
}
public static void populateLayoutFromBundle(final LinearLayout linearLayout, ControlGenerationType controlGenerationType, Bundle savedInstanceState) {
Map<String, ObservationProperty> propertiesMap = new HashMap<>();
for (Map.Entry<String, Serializable> entry : ((Map<String, Serializable>) savedInstanceState.getSerializable(EXTRA_PROPERTY_MAP)).entrySet()) {
propertiesMap.put(entry.getKey(), new ObservationProperty(entry.getKey(), entry.getValue()));
}
populateLayoutFromMap(linearLayout, controlGenerationType, propertiesMap);
}
/**
* Returns a map of key value pairs form the layout
*
* @param linearLayout
* @return
*/
public static Map<String, ObservationProperty> populateMapFromLayout(LinearLayout linearLayout) {
Map<String, ObservationProperty> properties = new HashMap<>();
return populateMapFromLayout(linearLayout, properties);
}
private static Map<String, ObservationProperty> populateMapFromLayout(LinearLayout linearLayout, Map<String, ObservationProperty> fields) {
for (int i = 0; i < linearLayout.getChildCount(); i++) {
View v = linearLayout.getChildAt(i);
if (v instanceof MageControl) {
MageControl mageControl = (MageControl) v;
String key = mageControl.getPropertyKey();
Serializable value = mageControl.getPropertyValue();
if (key != null && value != null) {
fields.put(key, new ObservationProperty(key, value));
}
} else if (v instanceof LinearLayout) {
fields.putAll(populateMapFromLayout((LinearLayout) v, fields));
}
}
return fields;
}
public static void populateBundleFromLayout(LinearLayout linearLayout, Bundle outState) {
HashMap<String, Serializable> properties = new HashMap<>();
for (Map.Entry<String, ObservationProperty> entry : populateMapFromLayout(linearLayout).entrySet()) {
properties.put(entry.getKey(), entry.getValue().getValue());
}
outState.putSerializable(EXTRA_PROPERTY_MAP, properties);
}
}