diff --git a/android/src/main/java/org/openforis/collect/android/gui/SubmitDataToCollectActivity.java b/android/src/main/java/org/openforis/collect/android/gui/SubmitDataToCollectActivity.java index 04bdbd6b..4699f818 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/SubmitDataToCollectActivity.java +++ b/android/src/main/java/org/openforis/collect/android/gui/SubmitDataToCollectActivity.java @@ -14,6 +14,7 @@ import com.google.gson.Gson; import com.google.gson.JsonObject; +import org.apache.commons.lang3.StringUtils; import org.json.JSONException; import org.json.JSONObject; import org.openforis.collect.R; @@ -236,7 +237,7 @@ private class UploadBackupFileTask extends AsyncTask { @Override protected String doInBackground(String... args) { - String uploadUrl = remoteAddress + DATA_RESTORE_ENDPOINT; + String uploadUrl = StringUtils.removeEnd(remoteAddress, "/") + DATA_RESTORE_ENDPOINT; Log.d(LOG_TAG, "Uploading data file to: " + uploadUrl); try { diff --git a/android/src/main/java/org/openforis/collect/android/gui/SurveyNodeActivity.java b/android/src/main/java/org/openforis/collect/android/gui/SurveyNodeActivity.java index f101677f..887cf264 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/SurveyNodeActivity.java +++ b/android/src/main/java/org/openforis/collect/android/gui/SurveyNodeActivity.java @@ -160,6 +160,11 @@ public void onNodeChanged(NodeEvent event, UiNode node, Map nodeChanges) { if (!node.equals(selectedNode) && nodeChanges.containsKey(node)) { Set validationErrors = nodeChanges.get(node).validationErrors; diff --git a/android/src/main/java/org/openforis/collect/android/gui/breadcrumbs/NodeParentsFragment.java b/android/src/main/java/org/openforis/collect/android/gui/breadcrumbs/NodeParentsFragment.java index f6b48907..2e66170c 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/breadcrumbs/NodeParentsFragment.java +++ b/android/src/main/java/org/openforis/collect/android/gui/breadcrumbs/NodeParentsFragment.java @@ -5,14 +5,18 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Button; +import android.widget.CompoundButton; import android.widget.HorizontalScrollView; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.appcompat.widget.AppCompatButton; import androidx.appcompat.widget.AppCompatImageButton; import androidx.appcompat.widget.AppCompatImageView; +import androidx.appcompat.widget.AppCompatToggleButton; import androidx.fragment.app.Fragment; import org.openforis.collect.R; @@ -22,6 +26,7 @@ import org.openforis.collect.android.viewmodel.UiEntityCollection; import org.openforis.collect.android.viewmodel.UiInternalNode; import org.openforis.collect.android.viewmodel.UiNode; +import org.openforis.collect.android.viewmodel.UiRecord; import java.util.ArrayList; import java.util.Collections; @@ -34,6 +39,7 @@ * A fragment representing the parents of a node. */ public class NodeParentsFragment extends Fragment { + public static final String AVOID_NOTIFICATION_TAG = "avoidNotification"; private Attrs attrs; public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -55,10 +61,57 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa ? createSurveySelectedView() : createCurrentNodeView(node); parentsContainer.addView(currentNodeView); - super.onViewCreated(view, savedInstanceState); + + ViewHolder viewHolder = new ViewHolder(); + viewHolder.recordLockButton = initRecordLockButton(view); + view.setTag(viewHolder); + return view; } + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + updateRecordLockButtonView(view); + } + + private AppCompatToggleButton initRecordLockButton(View view) { + final AppCompatToggleButton recordLockButton = view.findViewById(R.id.record_lock_button); + + UiNode selectedNode = ServiceLocator.surveyService().selectedNode(); + final UiRecord record = selectedNode.getUiRecord(); + + recordLockButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton button, boolean checked) { + if (AVOID_NOTIFICATION_TAG.equals((button.getTag()))) return; + + record.setEditLocked(checked); + updateRecordLockButtonView(null); + ServiceLocator.surveyService().notifyRecordEditLockChange(checked); + } + }); + return recordLockButton; + } + + private void updateRecordLockButtonView(View view) { + ViewHolder viewHolder = (ViewHolder) (view == null ? getView() : view).getTag(); + AppCompatToggleButton recordLockButton = viewHolder.recordLockButton; + UiNode selectedNode = ServiceLocator.surveyService().selectedNode(); + UiInternalNode node = selectedNode.getParent(); + final UiRecord record = selectedNode.getUiRecord(); + boolean checked = record != null ? record.isEditLocked() : false; + if (node.getParent() == null || record == null || record.isNewRecord()) { + recordLockButton.setVisibility(View.GONE); + } else { + recordLockButton.setTag(AVOID_NOTIFICATION_TAG); + recordLockButton.setChecked(checked); + recordLockButton.setTag(null); + } + int iconKey = checked ? R.attr.lockIcon : R.attr.lockOpenIcon; + recordLockButton.setButtonDrawable(attrs.resourceId(iconKey)); + } + private View createSurveySelectedView() { ImageView imageView = new AppCompatImageView(getActivity()); imageView.setImageResource(R.drawable.ic_menu_home); @@ -132,4 +185,8 @@ private String getLabel(UiNode node) { } return label; } + + private class ViewHolder { + AppCompatToggleButton recordLockButton; + } } diff --git a/android/src/main/java/org/openforis/collect/android/gui/components/OptionButton.java b/android/src/main/java/org/openforis/collect/android/gui/components/OptionButton.java index 976f723b..82bd1af6 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/components/OptionButton.java +++ b/android/src/main/java/org/openforis/collect/android/gui/components/OptionButton.java @@ -62,6 +62,12 @@ public void setChecked(boolean checked) { } } + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + button.setEnabled(enabled); + } + @Override public void setOnClickListener(@Nullable final OnClickListener listener) { super.setOnClickListener(new OnClickListener() { diff --git a/android/src/main/java/org/openforis/collect/android/gui/detail/AbstractNodeCollectionDetailFragment.java b/android/src/main/java/org/openforis/collect/android/gui/detail/AbstractNodeCollectionDetailFragment.java index c42f65f0..a5ac85cd 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/detail/AbstractNodeCollectionDetailFragment.java +++ b/android/src/main/java/org/openforis/collect/android/gui/detail/AbstractNodeCollectionDetailFragment.java @@ -22,11 +22,11 @@ import org.openforis.collect.android.gui.SurveyNodeActivity; import org.openforis.collect.android.gui.list.EntityListAdapter; import org.openforis.collect.android.gui.util.Dialogs; +import org.openforis.collect.android.gui.util.PreferencesUtils; import org.openforis.collect.android.gui.util.Tasks; import org.openforis.collect.android.gui.util.Views; import org.openforis.collect.android.viewmodel.Definition; import org.openforis.collect.android.viewmodel.UiAttribute; -import org.openforis.collect.android.viewmodel.UiEntity; import org.openforis.collect.android.viewmodel.UiEntityCollection; import org.openforis.collect.android.viewmodel.UiEntityCollectionDefinition; import org.openforis.collect.android.viewmodel.UiInternalNode; @@ -37,7 +37,6 @@ import org.openforis.collect.android.viewmodel.UiRecordCollection; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Timer; @@ -49,6 +48,7 @@ public abstract class AbstractNodeCollectionDetailFragment extends NodeDetailFragment { private static final Typeface HEADER_TYPEFACE = Typeface.DEFAULT_BOLD; + private static final String SHOW_RECORD_LOCKED_MESSAGE_PREFERENCE_KEY = "SHOW_RECORD_LOCKED_MESSAGE"; private EntityListAdapter adapter; private Timer adapterUpdateTimer; @@ -68,7 +68,10 @@ public void onViewCreated(View view, Bundle savedInstanceState) { initializeAddButton(holder); } } + holder.entitiesListView = view.findViewById(R.id.entity_list); view.setTag(holder); + + updateEditableState(); } } @@ -95,6 +98,14 @@ public void onClick(View v) { }); } + @Override + protected void updateEditableState() { + super.updateEditableState(); + ViewHolder viewHolder = getViewHolder(); + if (viewHolder == null) return; + Views.toggleVisibility(viewHolder.addButtonSwitcher, !isRecordEditLocked()); + } + @Override public void onNodeChange(UiNode node, Map nodeChanges) { super.onNodeChange(node, nodeChanges); @@ -171,12 +182,41 @@ private void startEditNodeTask(final int position) { final T nodeCollection = node(); Runnable task = new Runnable() { public void run() { - UiInternalNode selectedNode = getSelectedNode(position, nodeCollection); - UiNode firstEditableChild = selectedNode.getFirstEditableChild(); - if (firstEditableChild != null) { - nodeNavigator().navigateTo(firstEditableChild.getId()); + final UiInternalNode selectedNode = getSelectedNode(position, nodeCollection); + final Runnable navigateToFirstEditableNode = new Runnable() { + @Override + public void run() { + UiNode firstEditableChild = selectedNode.getFirstEditableChild(); + if (firstEditableChild != null) { + nodeNavigator().navigateTo(firstEditableChild.getId()); + } + } + }; + if (selectedNode instanceof UiRecord + && ((UiRecord) selectedNode).isEditLocked() + && PreferencesUtils.getPreference(getActivity(), SHOW_RECORD_LOCKED_MESSAGE_PREFERENCE_KEY, Boolean.class, true)) { + showRecordEditLockedMessage(navigateToFirstEditableNode); + } else { + navigateToFirstEditableNode.run(); } } + + private void showRecordEditLockedMessage(final Runnable onOk) { + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + Dialogs.info(getActivity(), R.string.info, R.string.record_edit_locked_message, new Dialogs.RunnableWithDoNotShowAgain() { + @Override + public void run(boolean doNotShowAgain) { + if (doNotShowAgain) { + PreferencesUtils.setPreference(getActivity(), SHOW_RECORD_LOCKED_MESSAGE_PREFERENCE_KEY, !doNotShowAgain); + } + onOk.run(); + } + }, true); + } + }); + } }; if (nodeCollection instanceof UiRecordCollection) { Tasks.runSlowTask(getActivity(), task); @@ -211,6 +251,8 @@ public void onItemClick(AdapterView parent, View view, int position, long id) adapterUpdateTimer = new Timer(); adapterUpdateTimer.schedule(new AdapterUpdaterTask(), 60000, 60000); + + updateEditableState(); } private void buildDynamicHeaderPart(View rootView) { @@ -295,9 +337,15 @@ public void run() { } } - private class ViewHolder { + protected ViewHolder getViewHolder() { + View view = getView(); + if (view == null) return null; + return (ViewHolder) view.getTag(); + } + + protected class ViewHolder { ViewSwitcher addButtonSwitcher; View addButton; - + ListView entitiesListView; } } diff --git a/android/src/main/java/org/openforis/collect/android/gui/detail/EntityCollectionDetailFragment.java b/android/src/main/java/org/openforis/collect/android/gui/detail/EntityCollectionDetailFragment.java index 7bfdf3f2..5ade329d 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/detail/EntityCollectionDetailFragment.java +++ b/android/src/main/java/org/openforis/collect/android/gui/detail/EntityCollectionDetailFragment.java @@ -2,10 +2,12 @@ import android.os.Bundle; import android.view.View; +import android.widget.ListView; import android.widget.TextView; import org.apache.commons.lang3.StringUtils; import org.openforis.collect.R; +import org.openforis.collect.android.gui.list.NodeListAdapter; import org.openforis.collect.android.gui.util.Views; import org.openforis.collect.android.viewmodel.UiEntityCollection; import org.openforis.collect.android.viewmodel.UiInternalNode; @@ -21,7 +23,21 @@ public class EntityCollectionDetailFragment extends AbstractNodeCollectionDetail public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); this.validationMessagesContainer = view.findViewById(R.id.validation_error_container); - this.validationMessagesTextView = (TextView) view.findViewById(R.id.validation_error_messages); + this.validationMessagesTextView = view.findViewById(R.id.validation_error_messages); + } + + @Override + protected void updateEditableState() { + super.updateEditableState(); + ViewHolder viewHolder = getViewHolder(); + if (viewHolder == null) return; + + ListView listView = viewHolder.entitiesListView; + NodeListAdapter adapter = ((NodeListAdapter) listView.getAdapter()); + if (adapter != null) { + adapter.setSelectionEnabled(!isRecordEditLocked()); + adapter.notifyDataSetChanged(); + } } protected UiInternalNode addNode() { diff --git a/android/src/main/java/org/openforis/collect/android/gui/detail/NodeDetailFragment.java b/android/src/main/java/org/openforis/collect/android/gui/detail/NodeDetailFragment.java index f84ff299..c5233e8a 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/detail/NodeDetailFragment.java +++ b/android/src/main/java/org/openforis/collect/android/gui/detail/NodeDetailFragment.java @@ -30,6 +30,7 @@ import org.openforis.collect.android.viewmodel.UiInternalNode; import org.openforis.collect.android.viewmodel.UiNode; import org.openforis.collect.android.viewmodel.UiNodeChange; +import org.openforis.collect.android.viewmodel.UiRecord; import org.openforis.collect.android.viewmodel.UiRecordCollection; import java.util.List; @@ -211,6 +212,19 @@ public void onNodeChange(UiNode node, Map nodeChanges) { } } + protected boolean isRecordEditLocked() { + UiRecord record = node == null ? null : node.getUiRecord(); + return record != null && record.isEditLocked(); + } + + public void onRecordEditLockChange(boolean locked) { + updateEditableState(); + } + + protected void updateEditableState() { + // to be implemented by subclasses + } + public void onDeselect() { selected = false; // Empty default implementation @@ -226,7 +240,7 @@ public void onSelected() { if (view == null || !node.isRelevant()) hideKeyboard(); else { - if (view instanceof EditText && view.isEnabled()) + if (view instanceof EditText && view.isEnabled() && !isRecordEditLocked()) showKeyboard(view); else { hideKeyboard(); diff --git a/android/src/main/java/org/openforis/collect/android/gui/detail/SavableNodeDetailFragment.java b/android/src/main/java/org/openforis/collect/android/gui/detail/SavableNodeDetailFragment.java index b25a6a51..839e4bc1 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/detail/SavableNodeDetailFragment.java +++ b/android/src/main/java/org/openforis/collect/android/gui/detail/SavableNodeDetailFragment.java @@ -5,7 +5,6 @@ import android.view.View; import android.view.ViewGroup; -import org.openforis.collect.R; import org.openforis.collect.android.gui.ServiceLocator; import org.openforis.collect.android.gui.input.SavableComponent; import org.openforis.collect.android.viewmodel.UiNode; @@ -61,5 +60,10 @@ public void onNodeChange(UiNode node, Map attributeChanges } } - + public void onRecordEditLockChange(boolean locked) { + super.onRecordEditLockChange(locked); + if (savableComponent != null) { + savableComponent.onRecordEditLockChange(locked); + } + } } diff --git a/android/src/main/java/org/openforis/collect/android/gui/input/AttributeCollectionComponent.java b/android/src/main/java/org/openforis/collect/android/gui/input/AttributeCollectionComponent.java index a6c4be20..fed17bb6 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/input/AttributeCollectionComponent.java +++ b/android/src/main/java/org/openforis/collect/android/gui/input/AttributeCollectionComponent.java @@ -11,6 +11,7 @@ import org.openforis.collect.android.viewmodel.UiAttributeCollection; import org.openforis.collect.android.viewmodel.UiNode; import org.openforis.collect.android.viewmodel.UiNodeChange; +import org.openforis.collect.android.viewmodel.UiRecord; import org.openforis.collect.android.viewmodel.UiValidationError; import java.util.Map; @@ -21,6 +22,7 @@ */ public abstract class AttributeCollectionComponent extends SavableComponent { protected final UiAttributeCollection attributeCollection; + private View addAttributeButton; protected AttributeCollectionComponent(UiAttributeCollection attributeCollection, SurveyService surveyService, FragmentActivity context) { super(surveyService, context); @@ -71,13 +73,22 @@ public void setupView(ViewGroup view) { if (addListener == null) return; - final View addAttributeButton = view.findViewById(R.id.action_add_node); + addAttributeButton = view.findViewById(R.id.action_add_node); if (addAttributeButton == null) throw new IllegalStateException(getClass().getSimpleName() + " specifies onAddAttribute, but view doesn't contain button with id action_add_node"); addAttributeButton.setOnClickListener(addListener); } + protected boolean isRecordEditLocked() { + UiRecord record = attributeCollection == null ? null : attributeCollection.getUiRecord(); + return record == null ? true: record.isEditLocked(); + } + @Override + protected void updateEditableState() { + + } + protected final void notifyAboutAttributeCollectionChange(Set changedAttributes) { surveyService.updateAttributes(changedAttributes); } diff --git a/android/src/main/java/org/openforis/collect/android/gui/input/AttributeComponent.java b/android/src/main/java/org/openforis/collect/android/gui/input/AttributeComponent.java index bcb8d7b0..d24637d1 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/input/AttributeComponent.java +++ b/android/src/main/java/org/openforis/collect/android/gui/input/AttributeComponent.java @@ -13,6 +13,7 @@ import org.openforis.collect.android.viewmodel.UiAttribute; import org.openforis.collect.android.viewmodel.UiNode; import org.openforis.collect.android.viewmodel.UiNodeChange; +import org.openforis.collect.android.viewmodel.UiRecord; import org.openforis.collect.android.viewmodel.UiValidationError; import java.util.Iterator; @@ -152,6 +153,11 @@ public final void validateNode() { if (validationErrors != null) setValidationError(validationErrors); } + + protected boolean isRecordEditLocked() { + UiRecord record = attribute == null ? null : attribute.getUiRecord(); + return record == null ? true: record.isEditLocked(); + } public String toString() { return getClass().getSimpleName() + " for " + attribute; diff --git a/android/src/main/java/org/openforis/collect/android/gui/input/AudioFileAttributeComponent.java b/android/src/main/java/org/openforis/collect/android/gui/input/AudioFileAttributeComponent.java index f1fdb961..9b9e0b54 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/input/AudioFileAttributeComponent.java +++ b/android/src/main/java/org/openforis/collect/android/gui/input/AudioFileAttributeComponent.java @@ -235,4 +235,13 @@ protected void updateViewState() { Views.toggleVisibility(deleteBtn, file.exists() && !recording && !playing); Views.toggleVisibility(selectFileBtn, !recording && !playing); } + + @Override + protected void updateEditableState() { + boolean canEdit = !isRecordEditLocked(); + recordBtn.setEnabled(canEdit); + stopBtn.setEnabled(canEdit); + deleteBtn.setEnabled(canEdit); + selectFileBtn.setEnabled(canEdit); + } } diff --git a/android/src/main/java/org/openforis/collect/android/gui/input/AutoCompleteCodeAttributeComponent.java b/android/src/main/java/org/openforis/collect/android/gui/input/AutoCompleteCodeAttributeComponent.java index 196dfd13..ca44ebf8 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/input/AutoCompleteCodeAttributeComponent.java +++ b/android/src/main/java/org/openforis/collect/android/gui/input/AutoCompleteCodeAttributeComponent.java @@ -16,6 +16,7 @@ import org.openforis.collect.android.CodeListService; import org.openforis.collect.android.SurveyService; import org.openforis.collect.android.gui.util.ClearableAutoCompleteTextView; +import org.openforis.collect.android.gui.util.Views; import org.openforis.collect.android.viewmodel.UiCode; import org.openforis.collect.android.viewmodel.UiCodeAttribute; @@ -30,9 +31,11 @@ */ class AutoCompleteCodeAttributeComponent extends CodeAttributeComponent { private final ClearableAutoCompleteTextView autoComplete; + private final TextView readonlyTextView; private final ExecutorService executor = Executors.newSingleThreadExecutor(); private final LinearLayout layout; - private EditText qualifierInput; + private final EditText qualifierInput; + private final TextView qualifierReadonlyText; private Map uiCodeByValue = new HashMap(); private UiCode selectedCode; @@ -42,7 +45,6 @@ class AutoCompleteCodeAttributeComponent extends CodeAttributeComponent { layout = new LinearLayout(context); layout.setOrientation(LinearLayout.VERTICAL); autoComplete = new ClearableAutoCompleteTextView(context); - layout.addView(autoComplete); autoComplete.setThreshold(1); autoComplete.setSingleLine(); autoComplete.setHint(R.string.hint_code_autocomplete); @@ -83,6 +85,12 @@ public void run() { saveNode(); } }); + // init readonly textview (visible when attribute is not editable) + readonlyTextView = new TextView(context); + readonlyTextView.setTextSize(20); + + qualifierReadonlyText = createQualifierReadonlyText(context, attribute.getQualifier()); + initOptions(); } @@ -92,10 +100,7 @@ protected TextView errorMessageContainerView() { protected void setSelectedCode(UiCode code) { this.selectedCode = code; - if (codeList.isQualifiable(selectedCode)) - showQualifier(); - else - hideQualifier(); + updateEditableState(); } protected UiCode selectedCode() { @@ -136,34 +141,34 @@ public View getDefaultFocusedView() { } private void setText(String text) { - if (text.equals(autoComplete.getText().toString())) - return; - // Hack to prevent pop-up from opening when setting text - // http://www.grokkingandroid.com/how-androids-autocompletetextview-nearly-drove-me-nuts/ - UiCodeAdapter adapter = (UiCodeAdapter) autoComplete.getAdapter(); - autoComplete.setAdapter(null); - autoComplete.setText(text); - autoComplete.setAdapter(adapter); - } - - private void showQualifier() { - uiHandler.post(new Runnable() { - public void run() { - if (layout.getChildCount() == 1) { - layout.addView(qualifierInput); - showKeyboard(qualifierInput); - } - } - }); + if (!text.equals(autoComplete.getText().toString())) { + // Hack to prevent pop-up from opening when setting text + // http://www.grokkingandroid.com/how-androids-autocompletetextview-nearly-drove-me-nuts/ + UiCodeAdapter adapter = (UiCodeAdapter) autoComplete.getAdapter(); + autoComplete.setAdapter(null); + autoComplete.setText(text); + autoComplete.setAdapter(adapter); + } + if (!text.equals(readonlyTextView.getText().toString())) { + readonlyTextView.setText(text); + } } - private void hideQualifier() { - uiHandler.post(new Runnable() { - public void run() { - hideKeyboard(); - layout.removeView(qualifierInput); - } - }); + @Override + protected void updateEditableState() { + boolean editable = !isRecordEditLocked(); + boolean editableOld = Views.hasChild(layout, autoComplete); + boolean qualifiable = attribute != null && codeList.isQualifiable(attribute.getCode()); + boolean qualifiableOld = Views.hasChild(layout, editable ? qualifierInput: qualifierReadonlyText); + if (editable == editableOld && qualifiable != qualifiableOld) { + // do nothing + return; + } + layout.removeAllViews(); + layout.addView(editable ? autoComplete : readonlyTextView); + if (qualifiable) { + layout.addView(editable ? qualifierInput: qualifierReadonlyText); + } } private class LoadCodesTask implements Runnable { @@ -179,14 +184,13 @@ public void run() { for (UiCode code : codes) uiCodeByValue.put(code.getValue(), code); setAdapter(codes, uiHandler); - if (codeList.isQualifiable(attribute.getCode())) - showQualifier(); } private void setAdapter(final List codes, Handler uiHandler) { uiHandler.post(new Runnable() { public void run() { autoComplete.setAdapter(new UiCodeAdapter(context, codes)); + updateEditableState(); } }); } diff --git a/android/src/main/java/org/openforis/collect/android/gui/input/BarcodeTextAttributeComponent.java b/android/src/main/java/org/openforis/collect/android/gui/input/BarcodeTextAttributeComponent.java index 5ab5a45f..84d03baf 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/input/BarcodeTextAttributeComponent.java +++ b/android/src/main/java/org/openforis/collect/android/gui/input/BarcodeTextAttributeComponent.java @@ -32,6 +32,8 @@ public class BarcodeTextAttributeComponent extends TextAttributeComponent { private View view; private boolean editEnabled = false; + private Button captureBtn; + private Switch textEditSwitch; public BarcodeTextAttributeComponent(UiTextAttribute attribute, SurveyService surveyService, FragmentActivity context) { super(attribute, surveyService, context); @@ -41,7 +43,7 @@ public BarcodeTextAttributeComponent(UiTextAttribute attribute, SurveyService su protected void initializeInputView() { view = context.getLayoutInflater().inflate(R.layout.barcode_attribute, null); - Button captureBtn = view.findViewById(R.id.barcode_capture_btn); + captureBtn = view.findViewById(R.id.barcode_capture_btn); captureBtn.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { ((SurveyNodeActivity) getContext()).setBarcodeCaptureListener(BarcodeTextAttributeComponent.this); @@ -59,10 +61,12 @@ public void onClick(View v) { textContainer.addView(editText, 0); initializeEnableTextEditSwitch(); + + updateEditableState(); } private void initializeEnableTextEditSwitch() { - Switch textEditSwitch = view.findViewById(R.id.barcode_text_edit_switch); + textEditSwitch = view.findViewById(R.id.barcode_text_edit_switch); textEditSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { editEnabled = isChecked; @@ -101,6 +105,15 @@ public void barcodeCaptured(Barcode barcode) { } private void updateViewState() { - editText.setEnabled(editEnabled); + boolean editable = !isRecordEditLocked(); + editText.setEnabled(editable && editEnabled); + captureBtn.setEnabled(editable); + textEditSwitch.setEnabled(editable); + } + + @Override + protected void updateEditableState() { + super.updateEditableState(); + updateViewState(); } } diff --git a/android/src/main/java/org/openforis/collect/android/gui/input/BooleanAttributeComponent.java b/android/src/main/java/org/openforis/collect/android/gui/input/BooleanAttributeComponent.java index 74ed6e3a..627ba430 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/input/BooleanAttributeComponent.java +++ b/android/src/main/java/org/openforis/collect/android/gui/input/BooleanAttributeComponent.java @@ -25,6 +25,7 @@ protected BooleanAttributeComponent(UiBooleanAttribute attribute, SurveyService super(attribute, surveyService, context); checked = attribute.getValue(); radioGroup = createRadioGroup(); + updateEditableState(); } private RadioGroup createRadioGroup() { @@ -34,14 +35,14 @@ private RadioGroup createRadioGroup() { radioGroup.addView(yes); radioGroup.addView(no); - if (checked != null && checked) { - yes.setSelected(true); - radioGroup.check(TRUE_ID); - } - - if (checked != null && !checked) { - no.setSelected(true); - radioGroup.check(FALSE_ID); + if (checked != null) { + if (checked) { + yes.setSelected(true); + radioGroup.check(TRUE_ID); + } else { + no.setSelected(true); + radioGroup.check(FALSE_ID); + } } radioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { @@ -50,6 +51,7 @@ public void onCheckedChanged(RadioGroup group, int checkedId) { saveNode(); } }); + radioGroup.setEnabled(!isRecordEditLocked()); return radioGroup; } @@ -79,4 +81,9 @@ public boolean hasChanged() { || (checked == null && previouslyChecked != null && previouslyChecked) || (checked != null && checked && previouslyChecked == null); } + + @Override + protected void updateEditableState() { + radioGroup.setEnabled(!isRecordEditLocked()); + } } diff --git a/android/src/main/java/org/openforis/collect/android/gui/input/CheckboxCodeAttributeCollectionComponent.java b/android/src/main/java/org/openforis/collect/android/gui/input/CheckboxCodeAttributeCollectionComponent.java index d49e9a05..1d92e079 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/input/CheckboxCodeAttributeCollectionComponent.java +++ b/android/src/main/java/org/openforis/collect/android/gui/input/CheckboxCodeAttributeCollectionComponent.java @@ -7,6 +7,7 @@ import android.view.inputmethod.EditorInfo; import android.widget.EditText; import android.widget.LinearLayout; +import android.widget.RadioButton; import android.widget.TextView; import androidx.appcompat.widget.AppCompatEditText; @@ -19,6 +20,7 @@ import org.openforis.collect.android.gui.components.OptionButton; import org.openforis.collect.android.gui.util.AndroidVersion; import org.openforis.collect.android.gui.util.Keyboard; +import org.openforis.collect.android.gui.util.Views; import org.openforis.collect.android.viewmodel.UiAttribute; import org.openforis.collect.android.viewmodel.UiAttributeCollection; import org.openforis.collect.android.viewmodel.UiCode; @@ -44,7 +46,8 @@ class CheckboxCodeAttributeCollectionComponent extends CodeAttributeCollectionCo private final SparseArray codeByViewId = new SparseArray(); private final Map attributesByCode = new HashMap(); private final LinearLayout layout; - private EditText qualifierInput; + private final EditText qualifierInput; + private final TextView qualifierReadonlyText; private final ExecutorService executor = Executors.newSingleThreadExecutor(); private final AtomicBoolean qualified = new AtomicBoolean(); @@ -56,6 +59,9 @@ class CheckboxCodeAttributeCollectionComponent extends CodeAttributeCollectionCo UiCodeAttribute attribute = (UiCodeAttribute) uiNode; attributesByCode.put(attribute.getCode(), attribute); } + qualifierInput = createQualifierInput(); + qualifierReadonlyText = CodeAttributeComponent.createQualifierReadonlyText(context, null); + initOptions(); } @@ -134,7 +140,6 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { return false; } }); - editText.setText(qualifier(codeList.getQualifiableCode())); editText.setSingleLine(); return editText; } @@ -158,14 +163,16 @@ private void showQualifier() { uiHandler.post(new Runnable() { public void run() { if (layout.getChildCount() == codeList.getCodes().size()) { - layout.addView(qualifierInput); - showKeyboard(qualifierInput); + boolean editable = !isRecordEditLocked(); + layout.addView(editable ? qualifierInput : qualifierReadonlyText); + if (editable) { + showKeyboard(qualifierInput); + } } } }); } - private String qualifier(UiCode code) { UiCodeAttribute attribute = attributesByCode.get(code); return attribute == null ? null : attribute.getQualifier(); @@ -177,10 +184,38 @@ private void hideQualifier() { public void run() { Keyboard.hide(context); layout.removeView(qualifierInput); + layout.removeView(qualifierReadonlyText); } }); } + @Override + protected void updateEditableState() { + super.updateEditableState(); + boolean editable = !isRecordEditLocked(); + int childCount = layout.getChildCount(); + for(int i = 0; i < childCount; i++) { + View view = layout.getChildAt(i); + if (view instanceof OptionButton) { + ((OptionButton) view).setEnabled(editable); + } + } + if (qualified.get()) { + if (editable) { + if (!Views.hasChild(layout, qualifierInput)) { + layout.removeView(qualifierReadonlyText); + layout.addView(qualifierInput); + } + } else { + if (!Views.hasChild(layout, qualifierReadonlyText)) { + layout.removeView(qualifierInput); + layout.addView(qualifierReadonlyText); + } + } + } + + } + private class LoadCodesTask implements Runnable { public void run() { initCodeList(); @@ -195,7 +230,6 @@ public void run() { layoutParams.setMargins(px(context, 1), px(context, 1), px(context, 1), px(context, 15)); - qualifierInput = createQualifierInput(); java.util.List codes = codeList.getCodes(); for (int i = 0; i < codes.size(); i++) { final UiCode code = codes.get(i); @@ -233,6 +267,10 @@ public void onClick(View v) { } }); } + String qualifierText = qualifier(codeList.getQualifiableCode()); + qualifierInput.setText(qualifierText); + qualifierReadonlyText.setText(qualifierText); + updateEditableState(); } }); } diff --git a/android/src/main/java/org/openforis/collect/android/gui/input/CodeAttributeComponent.java b/android/src/main/java/org/openforis/collect/android/gui/input/CodeAttributeComponent.java index 4228ca29..3eedc668 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/input/CodeAttributeComponent.java +++ b/android/src/main/java/org/openforis/collect/android/gui/input/CodeAttributeComponent.java @@ -1,7 +1,6 @@ package org.openforis.collect.android.gui.input; import android.app.Activity; -import android.content.Context; import android.view.KeyEvent; import android.view.View; import android.view.inputmethod.EditorInfo; @@ -52,7 +51,7 @@ public static CodeAttributeComponent create(UiCodeAttribute attribute, SurveySer protected abstract UiCode selectedCode(); - public final void onAttributeChange(UiAttribute changedAttribute) { + public void onAttributeChange(UiAttribute changedAttribute) { if (changedAttribute != attribute && codeListService.isParentCodeAttribute(changedAttribute, attribute)) { UiCode newParentCode = ((UiCodeAttribute) changedAttribute).getCode(); if (newParentCode == parentCode) return; @@ -146,6 +145,15 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { editText.setHint(R.string.hint_code_qualifier_specify); return editText; } + + protected static TextView createQualifierReadonlyText(Activity context, String text) { + TextView textView = new TextView(context); + textView.setTextSize(20); + if (text != null) { + textView.setText(text); + } + return textView; + } } diff --git a/android/src/main/java/org/openforis/collect/android/gui/input/CoordinateAttributeComponent.java b/android/src/main/java/org/openforis/collect/android/gui/input/CoordinateAttributeComponent.java index bfda3f03..946164a0 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/input/CoordinateAttributeComponent.java +++ b/android/src/main/java/org/openforis/collect/android/gui/input/CoordinateAttributeComponent.java @@ -4,6 +4,7 @@ import android.location.Location; import android.net.Uri; import android.text.Editable; +import android.text.InputType; import android.text.TextWatcher; import android.view.Gravity; import android.view.KeyEvent; @@ -40,6 +41,9 @@ import java.text.NumberFormat; import java.text.ParseException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.Locale; import static android.text.InputType.TYPE_CLASS_NUMBER; @@ -61,6 +65,7 @@ public class CoordinateAttributeComponent extends AttributeComponent editableInputFields = Arrays.asList(vh.xView, vh.yView, vh.accuracyView, vh.altitudeView); + for (TextView editableInputField : editableInputFields) { + if (editableInputField != null) { + editableInputField.setInputType(getNumericFieldInputType()); + } + } + boolean editable = !isRecordEditLocked(); + vh.srsSpinner.setEnabled(editable); + Views.toggleVisibility(vh.startStopButton, editable); + } + + private int getNumericFieldInputType() { + return isRecordEditLocked() || attribute.getDefinition().onlyChangedByDevice + ? InputType.TYPE_NULL + : TYPE_CLASS_NUMBER | TYPE_NUMBER_FLAG_SIGNED | TYPE_NUMBER_FLAG_DECIMAL; + } + private class ViewHolder { LinearLayout view; Spinner srsSpinner; @@ -306,11 +330,11 @@ private TextView createNumberInput(Double value) { final TextView input = new AppCompatEditText(context); input.setSingleLine(); - input.setMinWidth(100); + input.setMinWidth(150); if (value != null) input.setText(formatDouble(value)); - input.setInputType(TYPE_CLASS_NUMBER | TYPE_NUMBER_FLAG_SIGNED | TYPE_NUMBER_FLAG_DECIMAL); + input.setInputType(getNumericFieldInputType()); input.setOnFocusChangeListener(new View.OnFocusChangeListener() { public void onFocusChange(View v, boolean hasFocus) { @@ -348,7 +372,7 @@ private TextView createNumberOutput(Double value) { output.setSingleLine(); if (value != null) output.setText(formatDouble(value)); - output.setInputType(TYPE_CLASS_NUMBER | TYPE_NUMBER_FLAG_SIGNED | TYPE_NUMBER_FLAG_DECIMAL); + output.setInputType(getNumericFieldInputType()); return output; } diff --git a/android/src/main/java/org/openforis/collect/android/gui/input/DateAttributeComponent.java b/android/src/main/java/org/openforis/collect/android/gui/input/DateAttributeComponent.java index 94da0e12..8e0fb1e0 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/input/DateAttributeComponent.java +++ b/android/src/main/java/org/openforis/collect/android/gui/input/DateAttributeComponent.java @@ -63,7 +63,6 @@ protected void updateAttributeValue(String newValue) { protected void afterEditTextCreated(EditText input) { selectedDateView = input; - selectedDateView.setInputType(InputType.TYPE_NULL); selectedDateView.setHint(context.getResources().getString(R.string.hint_date_pattern) + " "); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT); selectedDateView.setLayoutParams(params); @@ -74,6 +73,11 @@ public void onClick(View v) { }); } + @Override + protected int determineInputType() { + return InputType.TYPE_NULL; + } + protected View toInputView() { return view; } @@ -83,6 +87,8 @@ private void selectDate(Date date) { } private void openDatePicker() { + if (isRecordEditLocked()) return; + saveNode(); hideKeyboard(); DatePickerFragment newFragment = new DatePickerFragment(); diff --git a/android/src/main/java/org/openforis/collect/android/gui/input/DocumentFileAttributeComponent.java b/android/src/main/java/org/openforis/collect/android/gui/input/DocumentFileAttributeComponent.java index cc8f6c3c..fea7eddc 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/input/DocumentFileAttributeComponent.java +++ b/android/src/main/java/org/openforis/collect/android/gui/input/DocumentFileAttributeComponent.java @@ -27,6 +27,7 @@ public class DocumentFileAttributeComponent extends FileAttributeComponent { private final View inputView; + private Button galleryButton; private Button viewSelectedFileButton; private ImageButton removeButton; @@ -83,16 +84,16 @@ public void run() { } private void setupGalleryButton() { - Button button = inputView.findViewById(R.id.file_attribute_select); + galleryButton = inputView.findViewById(R.id.file_attribute_select); if (canShowGallery()) { - button.setCompoundDrawablesWithIntrinsicBounds(null, new Attrs(context).drawable(R.attr.openIcon), null, null); - button.setOnClickListener(new View.OnClickListener() { + galleryButton.setCompoundDrawablesWithIntrinsicBounds(null, new Attrs(context).drawable(R.attr.openIcon), null, null); + galleryButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { showGallery(); } }); } else { - button.setVisibility(View.GONE); + galleryButton.setVisibility(View.GONE); } } @@ -121,6 +122,13 @@ public void documentSelected(Uri uri) { protected void updateViewState() { Views.toggleVisibility(viewSelectedFileButton, file.exists()); - Views.toggleVisibility(removeButton, file.exists()); + boolean canEdit = !isRecordEditLocked(); + Views.toggleVisibility(galleryButton, canEdit); + Views.toggleVisibility(removeButton, file.exists() && canEdit); + } + + @Override + protected void updateEditableState() { + updateViewState(); } } diff --git a/android/src/main/java/org/openforis/collect/android/gui/input/DoubleAttributeComponent.java b/android/src/main/java/org/openforis/collect/android/gui/input/DoubleAttributeComponent.java index bd52e473..da34a02d 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/input/DoubleAttributeComponent.java +++ b/android/src/main/java/org/openforis/collect/android/gui/input/DoubleAttributeComponent.java @@ -1,5 +1,6 @@ package org.openforis.collect.android.gui.input; +import android.text.InputType; import android.widget.EditText; import androidx.fragment.app.FragmentActivity; @@ -35,9 +36,21 @@ protected void setValueOnAttribute(Double value) { attribute.setValue(value); } - protected void afterEditTextCreated(EditText input) { - super.afterEditTextCreated(input); - input.setInputType(TYPE_CLASS_NUMBER | TYPE_NUMBER_FLAG_SIGNED | TYPE_NUMBER_FLAG_DECIMAL); - input.setKeyListener(new DecimalSeparatorAwareKeyListener()); + @Override + protected void updateEditableState() { + super.updateEditableState(); + if (isRecordEditLocked()) { + editText.setKeyListener(null); + } else { + editText.setKeyListener(new DecimalSeparatorAwareKeyListener()); + } + } + + @Override + protected int determineInputType() { + if (isRecordEditLocked()) { + return InputType.TYPE_NULL; + } + return TYPE_CLASS_NUMBER | TYPE_NUMBER_FLAG_SIGNED | TYPE_NUMBER_FLAG_DECIMAL; } } diff --git a/android/src/main/java/org/openforis/collect/android/gui/input/EditTextAttributeComponent.java b/android/src/main/java/org/openforis/collect/android/gui/input/EditTextAttributeComponent.java index 9caff769..4a4fcc52 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/input/EditTextAttributeComponent.java +++ b/android/src/main/java/org/openforis/collect/android/gui/input/EditTextAttributeComponent.java @@ -1,6 +1,7 @@ package org.openforis.collect.android.gui.input; import android.text.Editable; +import android.text.InputType; import android.text.TextWatcher; import android.view.KeyEvent; import android.view.View; @@ -34,6 +35,7 @@ protected void initializeInputView() { protected EditText initializeEditText() { editText = createEditText(); + afterEditTextCreated(editText); return editText; } @@ -106,6 +108,8 @@ protected String formattedAttributeValue() { protected EditText createEditText() { final EditText editText = new AppCompatEditText(context); + editText.setInputType(determineInputType()); + onEditTextCreated(editText); editText.setSingleLine(); @@ -140,7 +144,18 @@ public void afterTextChanged(Editable s) { } } }); - afterEditTextCreated(editText); return editText; } + + protected int determineInputType() { + return InputType.TYPE_CLASS_TEXT; + } + + @Override + protected void updateEditableState() { + editText.setInputType(determineInputType()); + if (isRecordEditLocked()) { + hideKeyboard(); + } + } } diff --git a/android/src/main/java/org/openforis/collect/android/gui/input/ImageFileAttributeComponent.java b/android/src/main/java/org/openforis/collect/android/gui/input/ImageFileAttributeComponent.java index 227e4c1b..bcaaf0dd 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/input/ImageFileAttributeComponent.java +++ b/android/src/main/java/org/openforis/collect/android/gui/input/ImageFileAttributeComponent.java @@ -36,6 +36,8 @@ public class ImageFileAttributeComponent extends FileAttributeComponent { private static final int MAX_DISPLAY_HEIGHT = 500; private final View inputView; + private Button captureButton; + private Button galleryButton; private ImageButton removeButton; private ImageButton rotateImageButton; private ImageView thumbnailImageView; @@ -112,9 +114,9 @@ private void rotateImage() { } private void setupCaptureButton() { - Button button = inputView.findViewById(R.id.file_attribute_capture); + captureButton = inputView.findViewById(R.id.file_attribute_capture); if (canCapture()) { - button.setOnClickListener(new View.OnClickListener() { + captureButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { Runnable captureImageRunnable = new Runnable() { public void run() { @@ -130,7 +132,7 @@ public void run() { } }); } else { - button.setVisibility(View.GONE); + captureButton.setVisibility(View.GONE); } } @@ -166,16 +168,16 @@ protected void capture() { } private void setupGalleryButton() { - Button button = inputView.findViewById(R.id.file_attribute_select); + galleryButton = inputView.findViewById(R.id.file_attribute_select); if (canShowGallery()) { - button.setCompoundDrawablesWithIntrinsicBounds(null, new Attrs(context).drawable(R.attr.openIcon), null, null); - button.setOnClickListener(new View.OnClickListener() { + galleryButton.setCompoundDrawablesWithIntrinsicBounds(null, new Attrs(context).drawable(R.attr.openIcon), null, null); + galleryButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { showGallery(); } }); } else { - button.setVisibility(View.GONE); + galleryButton.setVisibility(View.GONE); } } @@ -252,8 +254,13 @@ protected void updateViewState() { } else { hideThumbnail(); } - Views.toggleVisibility(removeButton, file.exists()); - Views.toggleVisibility(rotateImageButton, file.exists()); + boolean canAddNew = !isRecordEditLocked(); + Views.toggleVisibility(captureButton, canAddNew); + Views.toggleVisibility(galleryButton, canAddNew); + + boolean canDeleteOrModify = file.exists() && !isRecordEditLocked(); + Views.toggleVisibility(removeButton, canDeleteOrModify); + Views.toggleVisibility(rotateImageButton, canDeleteOrModify); } private File createTempImageFile() { @@ -271,4 +278,8 @@ private File createTempImageFile() { } } + @Override + protected void updateEditableState() { + updateViewState(); + } } diff --git a/android/src/main/java/org/openforis/collect/android/gui/input/IntegerAttributeComponent.java b/android/src/main/java/org/openforis/collect/android/gui/input/IntegerAttributeComponent.java index 0aec7368..86e6a9a6 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/input/IntegerAttributeComponent.java +++ b/android/src/main/java/org/openforis/collect/android/gui/input/IntegerAttributeComponent.java @@ -38,10 +38,14 @@ protected void setValueOnAttribute(Integer value) { protected void onEditTextCreated(EditText input) { super.onEditTextCreated(input); - input.setInputType(InputType.TYPE_CLASS_NUMBER | TYPE_NUMBER_FLAG_SIGNED); input.setFilters(new InputFilter[]{new InputFilter.LengthFilter(13)}); //10 digits + 3 grouping characters } + @Override + protected int determineInputType() { + return isRecordEditLocked() ? InputType.TYPE_NULL : InputType.TYPE_CLASS_NUMBER | TYPE_NUMBER_FLAG_SIGNED; + } + @Override protected void initializeNumberTextWatcher(EditText input) { numberTextWatcher = new NumberTextWatcher(input, false); diff --git a/android/src/main/java/org/openforis/collect/android/gui/input/NumericAttributeComponent.java b/android/src/main/java/org/openforis/collect/android/gui/input/NumericAttributeComponent.java index ea0e68f1..08fd1450 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/input/NumericAttributeComponent.java +++ b/android/src/main/java/org/openforis/collect/android/gui/input/NumericAttributeComponent.java @@ -95,7 +95,16 @@ protected void initializeNumberTextWatcher(EditText input) { @Override protected void afterEditTextCreated(EditText input) { super.afterEditTextCreated(input); - input.addTextChangedListener(numberTextWatcher); + updateEditableState(); + } + + @Override + protected void updateEditableState() { + super.updateEditableState(); + editText.removeTextChangedListener(numberTextWatcher); + if (!isRecordEditLocked()) { + editText.addTextChangedListener(numberTextWatcher); + } } private void setNotANumberError() { diff --git a/android/src/main/java/org/openforis/collect/android/gui/input/RadioCodeAttributeComponent.java b/android/src/main/java/org/openforis/collect/android/gui/input/RadioCodeAttributeComponent.java index d76a2343..75c248d2 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/input/RadioCodeAttributeComponent.java +++ b/android/src/main/java/org/openforis/collect/android/gui/input/RadioCodeAttributeComponent.java @@ -6,12 +6,15 @@ import android.widget.EditText; import android.widget.LinearLayout; import android.widget.RadioGroup; +import android.widget.TextView; import androidx.fragment.app.FragmentActivity; import org.openforis.collect.android.CodeListService; import org.openforis.collect.android.SurveyService; import org.openforis.collect.android.gui.components.OptionButton; +import org.openforis.collect.android.gui.util.Views; +import org.openforis.collect.android.viewmodel.UiAttribute; import org.openforis.collect.android.viewmodel.UiCode; import org.openforis.collect.android.viewmodel.UiCodeAttribute; @@ -29,6 +32,7 @@ class RadioCodeAttributeComponent extends CodeAttributeComponent { private final LinearLayout layout; private final LinearLayout radioButtonsWrapperLayout; private final EditText qualifierInput; + private final TextView qualifierReadonlyText; private final ExecutorService executor = Executors.newSingleThreadExecutor(); private Integer selectedViewId; @@ -39,11 +43,16 @@ class RadioCodeAttributeComponent extends CodeAttributeComponent { radioButtonsWrapperLayout = new LinearLayout(context); radioButtonsWrapperLayout.setOrientation(LinearLayout.VERTICAL); layout.addView(radioButtonsWrapperLayout); - qualifierInput = CodeAttributeComponent.createQualifierInput(context, attribute.getQualifier(), new Runnable() { + + // qualifier fields + String qualifier = attribute.getQualifier(); + qualifierInput = CodeAttributeComponent.createQualifierInput(context, qualifier, new Runnable() { public void run() { saveNode(); } }); + qualifierReadonlyText = createQualifierReadonlyText(context, qualifier); + initOptions(); } @@ -67,36 +76,41 @@ protected void initOptions() { executor.execute(new LoadCodesTask()); } - protected String qualifier(UiCode selectedCode) { - return qualifierInput.getText().toString(); + @Override + public void onAttributeChange(UiAttribute changedAttribute) { + super.onAttributeChange(changedAttribute); + if (changedAttribute == attribute && codeList.isQualifiable(selectedCode())) { + qualifierReadonlyText.setText(attribute.getQualifier()); + } } - private void showQualifier() { - uiHandler.post(new Runnable() { - public void run() { - if (layout.getChildCount() == 1) { - layout.addView(qualifierInput); - showKeyboard(qualifierInput); - } - } - }); + @Override + protected void updateEditableState() { + super.updateEditableState(); + boolean editable = !isRecordEditLocked(); + int numRadioButtons = radioButtonsWrapperLayout.getChildCount(); + for (int i = 0; i < numRadioButtons; i++) { + View rb = radioButtonsWrapperLayout.getChildAt(i); + rb.setEnabled(editable); + } + if (codeList.isQualifiable(selectedCode())) { + Views.addChild(layout, editable ? qualifierInput : qualifierReadonlyText); + layout.removeView(editable ? qualifierReadonlyText : qualifierInput); + } else { + layout.removeView(qualifierInput); + layout.removeView(qualifierReadonlyText); + } } - private void hideQualifier() { - uiHandler.post(new Runnable() { - public void run() { - hideKeyboard(); - layout.removeView(qualifierInput); - } - }); + protected String qualifier(UiCode selectedCode) { + return qualifierInput.getText().toString(); } + private class LoadCodesTask implements Runnable { public void run() { initCodeList(); addRadioButtons(codeList.getCodes()); - if (codeList.isQualifiable(attribute.getCode())) - showQualifier(); } private void addRadioButtons(final List codes) { @@ -116,48 +130,45 @@ public void run() { selectedViewId = rb.getId(); } } + updateEditableState(); } }); } } private OptionButton addRadioButton(RadioGroup.LayoutParams layoutParams, int index, UiCode code, boolean selected) { - if (! enumerator || selected) { //if it's enumerator, show only selected code - OptionButton rb = new OptionButton(context, OptionButton.DisplayType.RADIOBUTTON); - rb.setId(index + 1); - rb.setLabel(code.toString()); - rb.setDescription(code.getDescription()); - rb.setLayoutParams(layoutParams); - if (!enumerator) { - rb.setOnClickListener(new View.OnClickListener() { - public void onClick(View view) { - UiCode code = codeByViewId.get(view.getId()); - boolean wasChecked = isAttributeCode(code); - if (selectedViewId != null && view.getId() != selectedViewId) { - OptionButton oldSelectedView = radioButtonsWrapperLayout.findViewById(selectedViewId); - oldSelectedView.setChecked(false); - } - boolean checked = !wasChecked; - selectedViewId = checked ? view.getId() : null; + if (enumerator && !selected) return null; //if it's enumerator, show only selected code + + OptionButton rb = new OptionButton(context, OptionButton.DisplayType.RADIOBUTTON); + rb.setId(index + 1); + rb.setLabel(code.toString()); + rb.setDescription(code.getDescription()); + rb.setLayoutParams(layoutParams); + if (!enumerator) { + rb.setOnClickListener(new View.OnClickListener() { + public void onClick(View view) { + UiCode code = codeByViewId.get(view.getId()); + boolean wasChecked = isAttributeCode(code); + if (selectedViewId != null && view.getId() != selectedViewId) { + OptionButton oldSelectedView = radioButtonsWrapperLayout.findViewById(selectedViewId); + oldSelectedView.setChecked(false); + } + boolean checked = !wasChecked; + selectedViewId = checked ? view.getId() : null; - if (codeList.isQualifiable(selectedCode())) - showQualifier(); - else - hideQualifier(); + ((OptionButton) view).setChecked(checked); - ((OptionButton) view).setChecked(checked); + updateEditableState(); - saveNode(); - } - }); - } - rb.setChecked(selected); - radioButtonsWrapperLayout.addView(rb); - codeByViewId.put(rb.getId(), code); - return rb; - } else { - return null; + saveNode(); + } + }); } + rb.setChecked(selected); + rb.setEnabled(!isRecordEditLocked()); + radioButtonsWrapperLayout.addView(rb); + codeByViewId.put(rb.getId(), code); + return rb; } } diff --git a/android/src/main/java/org/openforis/collect/android/gui/input/SavableComponent.java b/android/src/main/java/org/openforis/collect/android/gui/input/SavableComponent.java index 007150f8..37713bbb 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/input/SavableComponent.java +++ b/android/src/main/java/org/openforis/collect/android/gui/input/SavableComponent.java @@ -63,6 +63,17 @@ public void setupView(ViewGroup view) { public abstract void onNodeChange(UiNode node, Map nodeChanges); + public void onRecordEditLockChange(boolean locked) { + if (locked) { + hideKeyboard(); + } + updateEditableState(); + } + + protected void updateEditableState() { + // to be extended by subclasses + } + public abstract boolean hasChanged(); protected abstract void resetValidationErrors(); @@ -209,6 +220,9 @@ public boolean hasChanged() { protected void resetValidationErrors() { } + + @Override + protected void updateEditableState() {} } private static class UnsupportedAttributeComponent extends AttributeComponent { @@ -234,5 +248,9 @@ protected boolean updateAttributeIfChanged() { protected View toInputView() { return view; } + + @Override + protected void updateEditableState() {} + } } diff --git a/android/src/main/java/org/openforis/collect/android/gui/input/TaxonAttributeComponent.java b/android/src/main/java/org/openforis/collect/android/gui/input/TaxonAttributeComponent.java index d1415d0f..556fc129 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/input/TaxonAttributeComponent.java +++ b/android/src/main/java/org/openforis/collect/android/gui/input/TaxonAttributeComponent.java @@ -16,6 +16,7 @@ import org.openforis.collect.android.SurveyService; import org.openforis.collect.android.gui.ServiceLocator; import org.openforis.collect.android.gui.util.ClearableAutoCompleteTextView; +import org.openforis.collect.android.gui.util.Views; import org.openforis.collect.android.viewmodel.UITaxonVernacularName; import org.openforis.collect.android.viewmodel.UiTaxon; import org.openforis.collect.android.viewmodel.UiTaxonAttribute; @@ -26,6 +27,7 @@ public class TaxonAttributeComponent extends AttributeComponent { private LinearLayout layout; private ClearableAutoCompleteTextView autoComplete; + private TextView taxonReadonlyTextView; private TextView vernacularNameTextView; private UiTaxon selectedTaxon; private boolean textChangingNotificationEnabled = true; @@ -36,12 +38,19 @@ protected TaxonAttributeComponent(UiTaxonAttribute attribute, SurveyService surv vernacularNameTextView = new TextView(context); vernacularNameTextView.setPadding(8, 16, 8, 0); + taxonReadonlyTextView = new TextView(context); + taxonReadonlyTextView.setTextSize(20); + taxonReadonlyTextView.setPadding(8, 16, 8, 0); + createAutoComplete(attribute, context); layout = new LinearLayout(context); layout.setOrientation(LinearLayout.VERTICAL); layout.addView(autoComplete); + layout.addView(taxonReadonlyTextView); layout.addView(vernacularNameTextView); + + updateEditableState(); } private void createAutoComplete(UiTaxonAttribute attribute, FragmentActivity context) { @@ -170,6 +179,14 @@ private void setText(String text) { autoComplete.setAdapter(null); autoComplete.setText(text); autoComplete.setAdapter(adapter); + taxonReadonlyTextView.setText(text); textChangingNotificationEnabled = true; } + + @Override + protected void updateEditableState() { + boolean editable = !isRecordEditLocked(); + Views.toggleVisibility(autoComplete, editable); + Views.toggleVisibility(taxonReadonlyTextView, !editable); + } } diff --git a/android/src/main/java/org/openforis/collect/android/gui/input/TextAttributeComponent.java b/android/src/main/java/org/openforis/collect/android/gui/input/TextAttributeComponent.java index aad5f89b..9f6dd165 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/input/TextAttributeComponent.java +++ b/android/src/main/java/org/openforis/collect/android/gui/input/TextAttributeComponent.java @@ -7,6 +7,7 @@ import androidx.fragment.app.FragmentActivity; import org.openforis.collect.android.SurveyService; +import org.openforis.collect.android.gui.util.Keyboard; import org.openforis.collect.android.viewmodel.UiTextAttribute; import org.openforis.collect.android.viewmodel.UiTextAttributeDefinition; @@ -30,10 +31,18 @@ protected void updateAttributeValue(String newValue) { protected void onEditTextCreated(EditText input) { super.onEditTextCreated(input); UiTextAttributeDefinition def = (UiTextAttributeDefinition) attribute.getDefinition(); - int inputType = def.isAutoUppercase() ? InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS : InputType.TYPE_TEXT_FLAG_CAP_SENTENCES; - input.setInputType(inputType); if (def.isAutoUppercase()) { input.setFilters(new InputFilter[]{new InputFilter.AllCaps()}); } } + + @Override + protected int determineInputType() { + if (isRecordEditLocked()) { + Keyboard.hide(getContext()); + return InputType.TYPE_NULL; + } + UiTextAttributeDefinition def = (UiTextAttributeDefinition) attribute.getDefinition(); + return def.isAutoUppercase() ? InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS : InputType.TYPE_TEXT_FLAG_CAP_SENTENCES; + } } diff --git a/android/src/main/java/org/openforis/collect/android/gui/input/TimeAttributeComponent.java b/android/src/main/java/org/openforis/collect/android/gui/input/TimeAttributeComponent.java index df6fb5c9..9c3a720c 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/input/TimeAttributeComponent.java +++ b/android/src/main/java/org/openforis/collect/android/gui/input/TimeAttributeComponent.java @@ -65,7 +65,6 @@ protected void onEditTextCreated(EditText input) { super.onEditTextCreated(input); selectedTimeView = input; selectedTimeView.setHint(context.getResources().getString(R.string.hint_time_pattern) + " "); - selectedTimeView.setInputType(InputType.TYPE_NULL); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT); selectedTimeView.setLayoutParams(params); selectedTimeView.setOnClickListener(new View.OnClickListener() { @@ -75,11 +74,18 @@ public void onClick(View v) { }); } + @Override + protected int determineInputType() { + return InputType.TYPE_NULL; + } + protected View toInputView() { return view; } private void openTimePicker() { + if (isRecordEditLocked()) return; + saveNode(); hideKeyboard(); TimePickerFragment newFragment = new TimePickerFragment(); diff --git a/android/src/main/java/org/openforis/collect/android/gui/list/EntityListAdapter.java b/android/src/main/java/org/openforis/collect/android/gui/list/EntityListAdapter.java index b3144da8..b9600727 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/list/EntityListAdapter.java +++ b/android/src/main/java/org/openforis/collect/android/gui/list/EntityListAdapter.java @@ -154,10 +154,13 @@ protected int layoutResourceId() { protected void onPrepareView(final UiNode node, View row) { final CheckBox checkbox = (CheckBox) row.findViewById(R.id.nodeSelectedForAction); Definition parentDef = node.getParent().getDefinition(); - if (parentDef instanceof UiEntityCollectionDefinition && - ((UiEntityCollectionDefinition) parentDef).isEnumerated()) { + if (!isSelectionEnabled() || + (parentDef instanceof UiEntityCollectionDefinition && + ((UiEntityCollectionDefinition) parentDef).isEnumerated()) + ) { Views.hide(checkbox); } else { + Views.show(checkbox); checkbox.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { if (checkbox.isChecked()) { diff --git a/android/src/main/java/org/openforis/collect/android/gui/list/NodeListAdapter.java b/android/src/main/java/org/openforis/collect/android/gui/list/NodeListAdapter.java index 545c84ea..365177cc 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/list/NodeListAdapter.java +++ b/android/src/main/java/org/openforis/collect/android/gui/list/NodeListAdapter.java @@ -44,6 +44,7 @@ public class NodeListAdapter extends BaseAdapter { protected final UiInternalNode parentNode; private final Attrs attrs; private List nodes; + protected boolean selectionEnabled; public NodeListAdapter(FragmentActivity activity, UiInternalNode parentNode) { this.activity = activity; @@ -210,6 +211,14 @@ public void notifyDataSetChanged() { super.notifyDataSetChanged(); } + public boolean isSelectionEnabled() { + return selectionEnabled; + } + + public void setSelectionEnabled(boolean selectionEnabled) { + this.selectionEnabled = selectionEnabled; + } + private int iconResource(UiNode node) { if (!node.isRelevant()) return 0; diff --git a/android/src/main/java/org/openforis/collect/android/gui/pager/NodePagerFragment.java b/android/src/main/java/org/openforis/collect/android/gui/pager/NodePagerFragment.java index a887ba1f..e1295005 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/pager/NodePagerFragment.java +++ b/android/src/main/java/org/openforis/collect/android/gui/pager/NodePagerFragment.java @@ -110,6 +110,12 @@ synchronized public void onNodeChange(UiNode node, Map nod nodePathDetailsFragment.nodeChanged(node); } + synchronized public void onRecordEditLockChange(boolean locked) { + for (NodeDetailFragment fragment : fragmentsByNode.values()) { + fragment.onRecordEditLockChange(locked); + } + } + private void setupPager(View view) { pager = view.findViewById(R.id.attributePager); pagerAdapter = new NodePagerAdapter(getChildFragmentManager(), fragmentsByNode); diff --git a/android/src/main/java/org/openforis/collect/android/gui/util/Dialogs.java b/android/src/main/java/org/openforis/collect/android/gui/util/Dialogs.java index 9afb1ae3..f77c1fd7 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/util/Dialogs.java +++ b/android/src/main/java/org/openforis/collect/android/gui/util/Dialogs.java @@ -42,20 +42,44 @@ public static AlertDialog info(Context context, int titleKey, String message, fi return showDialog(context, context.getResources().getString(titleKey), message, android.R.drawable.ic_dialog_info, runOnPositiveButtonClick); } - private static AlertDialog showDialog(Context context, String title, String message, int icon, final Runnable runOnPositiveButtonClick) { - AlertDialog dialog = new AlertDialog.Builder(context) + public static AlertDialog info(Context context, int titleKey, int messageKey, + final RunnableWithDoNotShowAgain runOnPositiveButtonClick, boolean showDoNotShowAgainOption) { + return showDialog(context, context.getResources().getString(titleKey), context.getString(messageKey), + android.R.drawable.ic_dialog_info, runOnPositiveButtonClick, showDoNotShowAgainOption); + } + + private static AlertDialog showDialog(Context context, String title, String message, int icon, Runnable runOnPositiveButtonClick) { + return showDialog(context, title, message, icon, runOnPositiveButtonClick, false); + } + + private static AlertDialog showDialog(Context context, String title, String message, int icon, + final Runnable runOnPositiveButtonClick, final boolean showDoNotShowAgainOption) { + AlertDialog.Builder builder = new AlertDialog.Builder(context) .setCancelable(false) .setTitle(title) .setMessage(message) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { if (runOnPositiveButtonClick != null) { runOnPositiveButtonClick.run(); } } }) - .setIcon(icon) - .create(); + .setIcon(icon); + if (showDoNotShowAgainOption) { + builder.setNeutralButton(R.string.do_not_show_again, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + if (runOnPositiveButtonClick == null) return; + + if (runOnPositiveButtonClick instanceof RunnableWithDoNotShowAgain) { + ((RunnableWithDoNotShowAgain) runOnPositiveButtonClick).run(true); + } else { + runOnPositiveButtonClick.run(); + } + } + }); + } + AlertDialog dialog = builder.create(); dialog.show(); return dialog; } @@ -136,4 +160,13 @@ public void run() { Tasks.runDelayed(predicateVerifier, 100); } } + + public abstract static class RunnableWithDoNotShowAgain implements Runnable { + @Override + public void run() { + run(false); + } + + public abstract void run(boolean doNotShowAgain); + } } diff --git a/android/src/main/java/org/openforis/collect/android/gui/util/PreferencesUtils.java b/android/src/main/java/org/openforis/collect/android/gui/util/PreferencesUtils.java new file mode 100644 index 00000000..6d1ac34f --- /dev/null +++ b/android/src/main/java/org/openforis/collect/android/gui/util/PreferencesUtils.java @@ -0,0 +1,44 @@ +package org.openforis.collect.android.gui.util; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +public abstract class PreferencesUtils { + + public static T getPreference(Context context, String key, Class type) { + return getPreference(context, key, type, null); + } + public static T getPreference(Context context, String key, Class type, T defaultValue) { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); + if (type == String.class) { + return (T) preferences.getString(key, (String) defaultValue); + } else if (type == Boolean.class) { + return (T) new Boolean(preferences.getBoolean(key, (Boolean) defaultValue)); + } else if (type == Float.class) { + return (T) new Float(preferences.getFloat(key, (Float) defaultValue)); + } else if (type == Integer.class) { + return (T) new Integer(preferences.getInt(key, (Integer) defaultValue)); + } else if (type == Long.class) { + return (T) new Long(preferences.getLong(key, (Long) defaultValue)); + } + return null; + } + + public static void setPreference(Context context, String key, T value) { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); + SharedPreferences.Editor editor = preferences.edit(); + if (value instanceof String) { + editor.putString(key, (String) value); + } else if (value instanceof Boolean) { + editor.putBoolean(key, (Boolean) value); + } else if (value instanceof Integer) { + editor.putInt(key, (Integer) value); + } else if (value instanceof Float) { + editor.putFloat(key, (Float) value); + } else if (value instanceof Long) { + editor.putLong(key, (Long) value); + } + editor.commit(); + } +} diff --git a/android/src/main/java/org/openforis/collect/android/gui/util/Views.java b/android/src/main/java/org/openforis/collect/android/gui/util/Views.java index b577b26f..e8587ebf 100644 --- a/android/src/main/java/org/openforis/collect/android/gui/util/Views.java +++ b/android/src/main/java/org/openforis/collect/android/gui/util/Views.java @@ -76,4 +76,18 @@ public static T findDescendant(View parent, int viewId) { } return null; } + + public static boolean hasChild(ViewGroup viewGroup, View child) { + int childCount = viewGroup.getChildCount(); + for (int i = 0; i < childCount; i++) { + if (viewGroup.getChildAt(i) == child) return true; + } + return false; + } + + public static void addChild(ViewGroup viewGroup, View child) { + if (!hasChild(viewGroup, child)) { + viewGroup.addView(child); + } + } } diff --git a/android/src/main/res/drawable/ic_lock_black_24dp.xml b/android/src/main/res/drawable/ic_lock_black_24dp.xml new file mode 100644 index 00000000..36cc0ed3 --- /dev/null +++ b/android/src/main/res/drawable/ic_lock_black_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/android/src/main/res/drawable/ic_lock_open_black_24dp.xml b/android/src/main/res/drawable/ic_lock_open_black_24dp.xml new file mode 100644 index 00000000..89c86aca --- /dev/null +++ b/android/src/main/res/drawable/ic_lock_open_black_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/android/src/main/res/drawable/ic_lock_open_white_24dp.xml b/android/src/main/res/drawable/ic_lock_open_white_24dp.xml new file mode 100644 index 00000000..7f4c3149 --- /dev/null +++ b/android/src/main/res/drawable/ic_lock_open_white_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/android/src/main/res/drawable/ic_lock_white_24dp.xml b/android/src/main/res/drawable/ic_lock_white_24dp.xml new file mode 100644 index 00000000..6c6b2603 --- /dev/null +++ b/android/src/main/res/drawable/ic_lock_white_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/android/src/main/res/drawable/ic_record_locked.xml b/android/src/main/res/drawable/ic_record_locked.xml new file mode 100644 index 00000000..c55e4177 --- /dev/null +++ b/android/src/main/res/drawable/ic_record_locked.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/src/main/res/drawable/ic_record_locked_black.xml b/android/src/main/res/drawable/ic_record_locked_black.xml new file mode 100644 index 00000000..4f242b3f --- /dev/null +++ b/android/src/main/res/drawable/ic_record_locked_black.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/src/main/res/drawable/ic_record_locked_white.xml b/android/src/main/res/drawable/ic_record_locked_white.xml new file mode 100644 index 00000000..c55e4177 --- /dev/null +++ b/android/src/main/res/drawable/ic_record_locked_white.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/src/main/res/layout/fragment_node_parents.xml b/android/src/main/res/layout/fragment_node_parents.xml index 8984f2f9..77bb8062 100644 --- a/android/src/main/res/layout/fragment_node_parents.xml +++ b/android/src/main/res/layout/fragment_node_parents.xml @@ -1,29 +1,51 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> - + + + android:layout_width="0dp" + app:layout_constraintHorizontal_weight="1" + android:layout_height="40dp" + android:layout_marginTop="4dp" + app:layout_constraintEnd_toStartOf="@id/record_lock_button" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> - - + android:paddingRight="?attr/standardPadding"/> + - - + + + android:layout_width="fill_parent" + android:layout_height="1dp" + android:background="?attr/entityListHeaderBorderColor" /> \ No newline at end of file diff --git a/android/src/main/res/values/attrs.xml b/android/src/main/res/values/attrs.xml index 3152da6e..7482c16c 100644 --- a/android/src/main/res/values/attrs.xml +++ b/android/src/main/res/values/attrs.xml @@ -32,6 +32,8 @@ + + diff --git a/android/src/main/res/values/strings.xml b/android/src/main/res/values/strings.xml index cbe76591..86a9d434 100644 --- a/android/src/main/res/values/strings.xml +++ b/android/src/main/res/values/strings.xml @@ -16,6 +16,7 @@ Yes Show more Show less + Do not show again Record Stop @@ -259,6 +260,7 @@ Modified dd/MM/yyyy + Record is in READ-ONLY mode; to enable editing the record, use the LOCK ICON in the top right corner. Go to record list Record completed Go back to record list? diff --git a/android/src/main/res/values/styles.xml b/android/src/main/res/values/styles.xml index 0df7f795..cd95eb1f 100644 --- a/android/src/main/res/values/styles.xml +++ b/android/src/main/res/values/styles.xml @@ -11,6 +11,8 @@ @drawable/ic_list_white_24dp @drawable/ic_settings_white_24dp @drawable/ic_exit_to_app_white_24dp + @drawable/ic_lock_white_24dp + @drawable/ic_lock_open_white_24dp 16dp 32dp @@ -59,6 +61,8 @@ @drawable/ic_list_black_24dp @drawable/ic_settings_black_24dp @drawable/ic_exit_to_app_black_24dp + @drawable/ic_lock_black_24dp + @drawable/ic_lock_open_black_24dp @android:color/black @color/light_disabled_color diff --git a/model/src/main/java/org/openforis/collect/android/SurveyListener.java b/model/src/main/java/org/openforis/collect/android/SurveyListener.java index b923eb13..224cac50 100644 --- a/model/src/main/java/org/openforis/collect/android/SurveyListener.java +++ b/model/src/main/java/org/openforis/collect/android/SurveyListener.java @@ -2,6 +2,7 @@ import org.openforis.collect.android.viewmodel.UiNode; import org.openforis.collect.android.viewmodel.UiNodeChange; +import org.openforis.collect.android.viewmodel.UiRecord; import java.util.Map; @@ -15,4 +16,6 @@ public interface SurveyListener { void onNodeChanging(UiNode node); void onNodeChanged(NodeEvent event, UiNode node, Map nodeChanges); + + void onRecordEditLockChange(UiRecord record, boolean locked); } diff --git a/model/src/main/java/org/openforis/collect/android/SurveyService.java b/model/src/main/java/org/openforis/collect/android/SurveyService.java index e932fb09..8689853f 100644 --- a/model/src/main/java/org/openforis/collect/android/SurveyService.java +++ b/model/src/main/java/org/openforis/collect/android/SurveyService.java @@ -7,7 +7,6 @@ import java.io.IOException; import java.io.InputStream; import java.util.Collection; -import java.util.List; import java.util.Set; /** @@ -40,6 +39,8 @@ public interface SurveyService { void notifyAttributeChanging(UiAttribute attribute); + void notifyRecordEditLockChange(boolean locked); + void updateAttributes(Set attributes); void updateAttribute(UiAttribute attribute); diff --git a/model/src/main/java/org/openforis/collect/android/collectadapter/CollectModelBackedSurveyService.java b/model/src/main/java/org/openforis/collect/android/collectadapter/CollectModelBackedSurveyService.java index 501a5690..7097f916 100644 --- a/model/src/main/java/org/openforis/collect/android/collectadapter/CollectModelBackedSurveyService.java +++ b/model/src/main/java/org/openforis/collect/android/collectadapter/CollectModelBackedSurveyService.java @@ -99,6 +99,10 @@ public void visit(Node node, int idx) { }); // update CollectModelManager internal variables collectModelManager.recordSelected(record); + + if (recordWillBeUpdated) { + uiRecord.setEditLocked(true); + } return uiRecord; } @@ -265,6 +269,17 @@ public void notifyAttributeChanging(UiAttribute attribute) { listener.onNodeChanging(attribute); } + @Override + public void notifyRecordEditLockChange(boolean locked) { + if (listener == null) return; + + UiNode selectedNode = selectedNode(); + if (selectedNode == null) return; + + UiRecord record = selectedNode.getUiRecord(); + listener.onRecordEditLockChange(record, locked); + } + public void updateAttributes(Set attributes) { if (attributes == null) return; diff --git a/model/src/main/java/org/openforis/collect/android/collectadapter/CollectModelManager.java b/model/src/main/java/org/openforis/collect/android/collectadapter/CollectModelManager.java index 857b3221..d55d743f 100644 --- a/model/src/main/java/org/openforis/collect/android/collectadapter/CollectModelManager.java +++ b/model/src/main/java/org/openforis/collect/android/collectadapter/CollectModelManager.java @@ -171,6 +171,7 @@ public CollectSurvey call() throws Exception { public UiRecord addRecord(String entityName, UiSurvey survey) { CollectRecord record = recordManager.create(selectedSurvey, entityName, user, latestSurveyVersion(), null, CollectRecord.Step.CLEANSING); UiRecord uiRecord = modelConverter.toUiRecord(record, survey); + uiRecord.setNewRecord(true); recordNodes = new RecordNodes(record); return uiRecord; } diff --git a/model/src/main/java/org/openforis/collect/android/viewmodel/UiRecord.java b/model/src/main/java/org/openforis/collect/android/viewmodel/UiRecord.java index 33d08111..b4937fa6 100644 --- a/model/src/main/java/org/openforis/collect/android/viewmodel/UiRecord.java +++ b/model/src/main/java/org/openforis/collect/android/viewmodel/UiRecord.java @@ -6,6 +6,9 @@ * @author Daniel Wiell */ public class UiRecord extends UiEntity { + + private boolean newRecord = false; + private boolean editLocked = false; private Map nodeById = new HashMap(); public UiRecord(int id, Definition definition, UiRecordCollection recordCollection, Placeholder placeholder) { @@ -90,4 +93,20 @@ public List getKeyAttributes() { return Collections.unmodifiableList(keyAttributes); } } + + public boolean isNewRecord() { + return newRecord; + } + + public void setNewRecord(boolean newRecord) { + this.newRecord = newRecord; + } + + public boolean isEditLocked() { + return editLocked; + } + + public void setEditLocked(boolean editLocked) { + this.editLocked = editLocked; + } } \ No newline at end of file diff --git a/model/src/test/groovy/org/openforis/collect/android/collectadapter/CodeListTest.groovy b/model/src/test/groovy/org/openforis/collect/android/collectadapter/CodeListTest.groovy index 5d592e4e..e88de8cf 100644 --- a/model/src/test/groovy/org/openforis/collect/android/collectadapter/CodeListTest.groovy +++ b/model/src/test/groovy/org/openforis/collect/android/collectadapter/CodeListTest.groovy @@ -182,6 +182,9 @@ class CodeListTest extends Specification { changeEvents.add(node: node, changes: changes) } - + @Override + void onRecordEditLockChange(UiRecord uiRecord, boolean locked) { + // Do nothing + } } }