Skip to content

Commit

Permalink
Feature: record lock (#33)
Browse files Browse the repository at this point in the history
* added record lock icons

* fixed lock icon layout

* locking record (WIP)

* make input components read-only (WIP)

* make components readonly (coordinate, number, text, code)

* make components readonly (code)

* code cleanup

* make multiple code attrributes readonly

* image component - fixed readonly state

* barcode attribute component: made it readable

* show record edit locked message

* fixed record edit lock info dialog

* disable entity selection when record edit is locked

* fixed entity list items not selectable on new record creation

* fixed compilation error

---------

Co-authored-by: Stefano Ricci <[email protected]>
  • Loading branch information
SteRiccio and SteRiccio authored Apr 2, 2024
1 parent 2be977b commit 7323e81
Show file tree
Hide file tree
Showing 52 changed files with 788 additions and 174 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -236,7 +237,7 @@ private class UploadBackupFileTask extends AsyncTask<String, Integer, String> {

@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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,11 @@ public void onNodeChanged(NodeEvent event, UiNode node, Map<UiNode, UiNodeChange
support.onNodeChanged(node); // TODO: Only do this if one of the child nodes updated its status or relevance
}

@Override
public void onRecordEditLockChange(UiRecord record, boolean locked) {
nodePagerFragment().onRecordEditLockChange(locked);
}

private void notifyOnValidationErrors(UiNode node, Map<UiNode, UiNodeChange> nodeChanges) {
if (!node.equals(selectedNode) && nodeChanges.containsKey(node)) {
Set<UiValidationError> validationErrors = nodeChanges.get(node).validationErrors;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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) {
Expand All @@ -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);
Expand Down Expand Up @@ -132,4 +185,8 @@ private String getLabel(UiNode node) {
}
return label;
}

private class ViewHolder {
AppCompatToggleButton recordLockButton;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -49,6 +48,7 @@
public abstract class AbstractNodeCollectionDetailFragment<T extends UiInternalNode> extends NodeDetailFragment<T> {

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;

Expand All @@ -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();
}
}

Expand All @@ -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<UiNode, UiNodeChange> nodeChanges) {
super.onNodeChange(node, nodeChanges);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -211,6 +212,19 @@ public void onNodeChange(UiNode node, Map<UiNode, UiNodeChange> 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
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -61,5 +60,10 @@ public void onNodeChange(UiNode node, Map<UiNode, UiNodeChange> attributeChanges
}
}


public void onRecordEditLockChange(boolean locked) {
super.onRecordEditLockChange(locked);
if (savableComponent != null) {
savableComponent.onRecordEditLockChange(locked);
}
}
}
Loading

0 comments on commit 7323e81

Please sign in to comment.