package org.azavea.otm.fields;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import org.azavea.helpers.Logger;
import org.azavea.otm.App;
import org.azavea.otm.R;
import org.azavea.otm.data.PendingEdit;
import org.azavea.otm.data.PendingEditDescription;
import org.azavea.otm.data.Plot;
import org.azavea.otm.ui.PendingItemDisplay;
import org.azavea.otm.ui.TreeInfoDisplay;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.List;
/**
* An abstract class for rendering and updating fields displayed on the TreeInfoDisplay and
* TreeEditDisplay activities
* <p>
* The makeField method examines the JSON and dispatches out to the proper Field subclass
* <p>
* In order to add a new type of Field it is necessary to implement 'renderForEdit' and 'getEditedValue'
* <p>
* It may also be necessary to implement 'formatValue', if the value to be displayed in display mode
* is different from what is returned by 'getEditedValue'
* <p>
* If the field you are adding uses a button or a textbox in edit mode, you should subclass
* ButtonField or TextField respectively
*/
public abstract class Field {
public static final String TREE_SPECIES = "tree.species";
public static final String TREE_DIAMETER = "tree.diameter";
public static final String DATE_TYPE = "date";
public static final String CHOICE_TYPE = "choice";
public static final String MULTICHOICE_TYPE = "multichoice";
// This is the view control, either button or EditText, which has the user value
protected View valueView = null;
/**
* The property name from Plot which will contain the data to display or
* edit. Nested resources are separated by '.' notation
*/
public final String key;
/**
* Label to identify the field on a view
*/
public final String label;
/**
* Does the current user have permission to edit?
*/
public final boolean canEdit;
/**
* How to format units
*/
public final String format;
public final String infoUrl;
protected Field(JSONObject fieldDef) {
key = fieldDef.optString("field_key");
label = fieldDef.optString("display_name");
canEdit = fieldDef.optBoolean("can_write");
format = fieldDef.optString("data_type");
// NOTE: Not enabled for OTM2 yet
infoUrl = fieldDef.optString("info_url");
}
protected Field(String key, String label) {
this.key = key;
this.label = label;
canEdit = false;
format = null;
infoUrl = null;
}
public static Field makeField(JSONObject fieldDef) {
String format = fieldDef.optString("data_type");
String key = fieldDef.optString("field_key");
if (CHOICE_TYPE.equals(format)) {
return new ChoiceField(fieldDef);
} else if (MULTICHOICE_TYPE.equals(format)) {
return new MultiChoiceField(fieldDef);
} else if (DATE_TYPE.equals(format)) {
return new DateField(fieldDef);
} else if (TREE_SPECIES.equals(key)) {
return new SpeciesField(fieldDef);
} else if (TREE_DIAMETER.equals(key)) {
return new DiameterField(fieldDef);
} else {
return new TextField(fieldDef);
}
}
/**
* Render a view to display the given plot field in edit mode
* <p>
* The Field will not be displayed if this method returns null
*/
public abstract View renderForEdit(LayoutInflater layout, Plot plot, Activity activity, ViewGroup parent);
/**
* Gets the edited value for use when updating
*
* @return The edited value - may be of any type
*/
protected abstract Object getEditedValue();
/*
* Render a view to display the given plot field in view mode
*/
public View renderForDisplay(LayoutInflater layout, Plot plot, Activity activity, ViewGroup parent) throws JSONException {
// our ui elements
View container = layout.inflate(R.layout.plot_field_row, parent, false);
TextView label = (TextView) container.findViewById(R.id.field_label);
TextView fieldValue = (TextView) container.findViewById(R.id.field_value);
View infoButton = container.findViewById(R.id.info);
View pendingButton = container.findViewById(R.id.pending);
// set the label (simple)
label.setText(this.label);
// is this field pending (based on its own notion of pending.)
Boolean pending = isKeyPending(this.key, plot);
// Determine the current value of the field and update the ui. (Based on current
// value or value of simple pending edit
String value;
if (!pending) {
value = formatValueIfPresent(getValueForKey(this.key, plot));
} else {
value = plot.getValueForLatestPendingEdit(this.key);
}
fieldValue.setText(value);
// If the key is pending, display the arrow UI, and set up its click handler
//
// Note that the semantics of the bindPendingEditClickHandler function take
// a key into the pending edit array, and an optional related field.
if (pending) {
bindPendingEditClickHandler(pendingButton, this.key, plot, activity);
pendingButton.setVisibility(View.VISIBLE);
}
// If the field has a URL attached to it as an info description (IE for pests) display the link.
if (!TextUtils.isEmpty(this.infoUrl)) {
infoButton.setVisibility(View.VISIBLE);
bindInfoButtonClickHandler(infoButton, this.infoUrl, activity);
}
return container;
}
public void update(Plot plot) throws Exception {
// If there is no valueView, this field was not rendered for edit
if (this.valueView != null) {
Object currentValue = getEditedValue();
plot.setValueForKey(key, currentValue);
}
}
public void receiveActivityResult(int resultCode, Intent data) {
Logger.warning("Received intent data for a field which doesn't start an activity. Ignoring the intent result.");
}
protected String formatValueIfPresent(Object value) {
// If there is no value, return an unspecified value
if (JSONObject.NULL.equals(value) || value.equals("")) {
return App.getAppInstance().getResources().getString(R.string.unspecified_field_value);
}
return formatValue(value);
}
/**
* Format the value with any units, if provided in the definition
*/
protected String formatValue(Object value) {
return value.toString();
}
private static Object getValueForKey(String key, Plot plot) throws JSONException {
PendingEditDescription pending = plot.getPendingEditForKey(key);
if (pending != null) {
return plot.getPendingEditForKey(key).getLatestValue();
} else {
return plot.getValueForKey(key);
}
}
private static boolean isKeyPending(String key, Plot plot) throws JSONException {
PendingEditDescription pending = plot.getPendingEditForKey(key);
return pending != null;
}
/*
*
* key : the index into the pending edit array (IE Species) related field:
* the value to return. (IE Species Name)
*
* If related field is null, return the plain value for the field. (Example,
* when key is DBH, we want the numeric value.)
*/
private void bindPendingEditClickHandler(View b, final String key, final Plot model,
final Context context) {
b.setOnClickListener(v -> {
// initialize the intent, and load it with some initial values
Intent pendingItemDisplay = new Intent(context, PendingItemDisplay.class);
pendingItemDisplay.putExtra("label", label);
pendingItemDisplay.putExtra("currentValue", formatValueIfPresent(model.getValueForKey(key)));
pendingItemDisplay.putExtra("key", key);
// Now create an array of pending values, [{id: X, value: "42",
// username: "sam"}, ...]
PendingEditDescription pendingEditDescription;
try {
pendingEditDescription = model.getPendingEditForKey(key);
List<PendingEdit> pendingEdits = pendingEditDescription.getPendingEdits();
JSONArray serializedPendingEdits = new JSONArray();
for (PendingEdit pendingEdit : pendingEdits) {
// The value is the plain pending edit's value, or the value of the PE's
// related field. (IE retrieve Species Name instead of a species ID.)
String value = formatValueIfPresent(pendingEdit.getValue());
// Continue on loading all of the pending edit data into
// the serializedPendingEdit object
JSONObject serializedPendingEdit = new JSONObject();
serializedPendingEdit.put("id", pendingEdit.getId());
serializedPendingEdit.put("value", value);
serializedPendingEdit.put("username", pendingEdit.getUsername());
try {
serializedPendingEdit.put("date", pendingEdit.getSubmittedTime().toLocaleString());
} catch (Exception e) {
e.printStackTrace();
serializedPendingEdit.put("date", "");
}
// and then append this edit onto the rest of them.
serializedPendingEdits.put(serializedPendingEdit);
}
pendingItemDisplay.putExtra("pending", serializedPendingEdits.toString());
// And start the target activity
Activity a = (Activity) context;
a.startActivityForResult(pendingItemDisplay, TreeInfoDisplay.EDIT_REQUEST);
} catch (JSONException e1) {
Toast.makeText(context, "Sorry, pending edits not available.", Toast.LENGTH_SHORT).show();
e1.printStackTrace();
}
});
}
private void bindInfoButtonClickHandler(View infoButton, final String url, final Context context) {
infoButton.setOnClickListener(v -> {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
Activity a = (Activity) context;
a.startActivity(browserIntent);
});
}
}