Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Menu buttons for FormHierarchy #2763

Merged
merged 23 commits into from
Jan 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1bd5b54
Create stub options menu for FormHierarchy
cooperka Nov 14, 2018
986f1ab
Add 'add' button
cooperka Nov 14, 2018
e494465
Add 'go up' button
cooperka Nov 14, 2018
0c82a13
Factor out static createDeleteRepeatConfirmDialog util
cooperka Nov 16, 2018
57d2b3c
Add 'delete' button
cooperka Nov 18, 2018
f3a3713
Use ?iconColor for new icon fills
cooperka Nov 19, 2018
8e6ba71
Never show add/delete buttons on ViewOnlyFormHierarchy
cooperka Nov 20, 2018
25a6c43
Don't show delete button while picker is active
cooperka Nov 27, 2018
7dcbd7d
Make sure 'go up' icon is always second from the edge
cooperka Nov 27, 2018
4b65181
Make 'up' arrow thicker like the 'jump' arrow
cooperka Nov 27, 2018
02c1647
Clean up form_hierarchy_menu formatting
cooperka Dec 17, 2018
dc963a4
Fix a few PMD rule violations
cooperka Dec 17, 2018
6951d48
Fix unintentionally overloaded method name
cooperka Dec 17, 2018
c48fbde
PR fix: Clean up menu button toggling logic
cooperka Jan 10, 2019
2b93931
PR fix: Clarify getRepeatPromptIndex logic
cooperka Jan 10, 2019
c6cb175
PR fix: Rename showDeleteRepeatConfirmDialog
cooperka Jan 10, 2019
6b6fd8d
PR fix: Clean up screenIndex == null logic
cooperka Jan 10, 2019
4b77e0a
PR fix: More accurately detect beginning of form
cooperka Jan 16, 2019
f9da285
Use screenIndex instead of currIndex to detect beginning
cooperka Jan 18, 2019
48629a6
Handle deleting last indexed repeat group item
cooperka Jan 20, 2019
7955b3d
Handle deleting last remaining repeat group item
cooperka Jan 21, 2019
35cd0b1
Fix crash when trying to get ref to end of form
cooperka Jan 22, 2019
2182652
Check isGroupSizeLocked before showing add/delete buttons
cooperka Jan 22, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1834,39 +1834,11 @@ public void onClick(DialogInterface dialog, int i) {
* Creates a confirm/cancel dialog for deleting repeats.
*/
private void createDeleteRepeatConfirmDialog() {
FormController formController = getFormController();

alertDialog = new AlertDialog.Builder(this).create();
String name = formController.getLastRepeatedGroupName();
int repeatcount = formController.getLastRepeatedGroupRepeatCount();
if (repeatcount != -1) {
name += " (" + (repeatcount + 1) + ")";
}
alertDialog.setTitle(getString(R.string.delete_repeat_ask));
alertDialog
.setMessage(getString(R.string.delete_repeat_confirm, name));
DialogInterface.OnClickListener quitListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int i) {
FormController formController = getFormController();
switch (i) {
case BUTTON_POSITIVE: // yes
formController.getAuditEventLogger().logEvent(AuditEvent.AuditEventType.DELETE_REPEAT, null, true);
formController.deleteRepeat();
showNextView();
break;

case BUTTON_NEGATIVE: // no
refreshCurrentView();
break;
}
}
};
alertDialog.setCancelable(false);
alertDialog.setButton(BUTTON_POSITIVE, getString(R.string.discard_group), quitListener);
alertDialog.setButton(BUTTON_NEGATIVE, getString(R.string.delete_repeat_no),
quitListener);
alertDialog.show();
DialogUtils.showDeleteRepeatConfirmDialog(this, () -> {
showNextView();
}, () -> {
refreshCurrentView();
});
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,14 @@
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Button;
import android.widget.TextView;

import org.javarosa.core.model.FormIndex;
import org.javarosa.core.model.GroupDef;
import org.javarosa.core.model.IFormElement;
import org.javarosa.core.model.instance.TreeReference;
import org.javarosa.form.api.FormEntryCaption;
import org.javarosa.form.api.FormEntryController;
Expand All @@ -38,6 +41,7 @@
import org.odk.collect.android.exception.JavaRosaException;
import org.odk.collect.android.logic.FormController;
import org.odk.collect.android.logic.HierarchyElement;
import org.odk.collect.android.utilities.DialogUtils;
import org.odk.collect.android.utilities.FormEntryPromptUtils;
import org.odk.collect.android.views.ODKView;

Expand Down Expand Up @@ -110,6 +114,11 @@ public class FormHierarchyActivity extends CollectAbstractActivity {
*/
private FormIndex screenIndex;

/**
* The toolbar menu.
*/
private Menu optionsMenu;

protected Button jumpPreviousButton;
protected Button jumpBeginningButton;
protected Button jumpEndButton;
Expand Down Expand Up @@ -186,6 +195,102 @@ private void restoreInstanceState(Bundle state) {
}
}

public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.form_hierarchy_menu, menu);
return super.onCreateOptionsMenu(menu);
}

@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);

optionsMenu = menu;
updateOptionsMenu();

return true;
}

private void updateOptionsMenu() {
// Not ready yet. Menu will be updated automatically once it's been prepared.
if (optionsMenu == null) {
return;
}

FormController formController = Collect.getInstance().getFormController();
boolean isAtBeginning = screenIndex.isBeginningOfFormIndex() && !shouldShowRepeatGroupPicker();
boolean shouldShowPicker = shouldShowRepeatGroupPicker();
boolean isInRepeat = !isAtBeginning && formController.getEvent(screenIndex) == FormEntryController.EVENT_REPEAT;
boolean isGroupSizeLocked = shouldShowPicker
? isGroupSizeLocked(repeatGroupPickerIndex) : isGroupSizeLocked(screenIndex);

boolean shouldShowDelete = isInRepeat && !shouldShowPicker && !isGroupSizeLocked;
showDeleteButton(shouldShowDelete);

boolean shouldShowAdd = shouldShowPicker && !isGroupSizeLocked;
showAddButton(shouldShowAdd);

boolean shouldShowGoUp = !isAtBeginning;
showGoUpButton(shouldShowGoUp);
}

/**
* Returns true if the current index is a group that's designated as `noAddRemove`
* (e.g. if `jr:count` is explicitly set).
*/
private boolean isGroupSizeLocked(FormIndex index) {
FormController formController = Collect.getInstance().getFormController();
IFormElement element = formController.getCaptionPrompt(index).getFormElement();
return element instanceof GroupDef && ((GroupDef) element).noAddRemove;
}

/** Override to disable this button. */
protected void showDeleteButton(boolean shouldShow) {
optionsMenu.findItem(R.id.menu_delete_child).setVisible(shouldShow);
}

/** Override to disable this button. */
protected void showAddButton(boolean shouldShow) {
optionsMenu.findItem(R.id.menu_add_child).setVisible(shouldShow);
}

/** Override to disable this button. */
protected void showGoUpButton(boolean shouldShow) {
optionsMenu.findItem(R.id.menu_go_up).setVisible(shouldShow);
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_delete_child:
DialogUtils.showDeleteRepeatConfirmDialog(this, () -> {
if (didDeleteLastRepeatItem()) {
// goUpLevel would put us in a weird state after deleting the last item;
// just go back one event instead.
//
// TODO: This works well in most cases, but if there are 2 repeats in a row,
// and you delete an item from the second repeat, it will send you into the
// first repeat instead of going back a level as expected.
goToPreviousEvent();
} else {
goUpLevel();
}
}, null);
return true;

case R.id.menu_add_child:
FormIndex repeatPromptIndex = getRepeatPromptIndex(repeatGroupPickerIndex);
exitToIndex(repeatPromptIndex);
return true;

case R.id.menu_go_up:
goUpLevel();
return true;

default:
return super.onOptionsItemSelected(item);
}
}

/**
* Configure the navigation buttons at the bottom of the screen.
*/
Expand All @@ -209,6 +314,39 @@ void configureButtons(FormController formController) {
});
}

/**
* After having deleted the current index,
* returns true if the current index was the only item in the repeat group.
*/
private boolean didDeleteLastRepeatItem() {
FormController formController = Collect.getInstance().getFormController();
FormIndex index = formController.getFormIndex();
int event = formController.getEvent(index);

// If we're on item 0, but we will be prompted to add another item next,
// it must be the last remaining item.
return event == FormEntryController.EVENT_PROMPT_NEW_REPEAT
&& index.getElementMultiplicity() == 0;
}

/**
* Similar to {@link #goUpLevel}, but makes a less significant step backward.
* This is only used when the caller knows where to go back to,
* e.g. after deleting the final remaining item in a repeat group.
*/
private void goToPreviousEvent() {
FormController formController = Collect.getInstance().getFormController();
try {
formController.stepToPreviousScreenEvent();
} catch (JavaRosaException e) {
Timber.d(e);
createErrorDialog(e.getCause().getMessage());
return;
}

refreshView();
}

/**
* Navigates "up" in the form hierarchy.
*/
Expand All @@ -223,11 +361,12 @@ protected void goUpLevel() {
repeatGroupPickerIndex = null;
} else {
// Enter the picker if coming from a repeat group.
if (formController.getEvent(screenIndex) == FormEntryController.EVENT_REPEAT) {
int event = formController.getEvent(screenIndex);
if (event == FormEntryController.EVENT_REPEAT || event == FormEntryController.EVENT_PROMPT_NEW_REPEAT) {
repeatGroupPickerIndex = screenIndex;
}

Collect.getInstance().getFormController().stepToOuterScreenEvent();
formController.stepToOuterScreenEvent();
}

refreshView(true);
Expand Down Expand Up @@ -266,6 +405,39 @@ private String getCurrentPath() {
return ODKView.getGroupsPath(groups.toArray(new FormEntryCaption[groups.size()]), hideLastMultiplicity);
}

/**
* Return the index of the "prompt" to add a new child to the given repeat group,
* without changing the current index.
*/
private FormIndex getRepeatPromptIndex(FormIndex repeatIndex) {
FormController formController = Collect.getInstance().getFormController();
FormIndex originalIndex = formController.getFormIndex();

// Temporarily jump to the specified repeat group.
formController.jumpToIndex(repeatIndex);
cooperka marked this conversation as resolved.
Show resolved Hide resolved
String repeatRef = getGroupRef(repeatIndex).toString(false);
String testRef = "";

// There may be nested repeat groups within this group; skip over those.
while (!repeatRef.equals(testRef)) {
int event = formController.stepToNextEventType(FormEntryController.EVENT_PROMPT_NEW_REPEAT);

if (event == FormEntryController.EVENT_END_OF_FORM) {
Timber.w("Failed to find repeat prompt, got end of form instead.");
break;
}

testRef = getGroupRef(formController.getFormIndex()).toString(false);
}

FormIndex result = formController.getFormIndex();

// Reset to where we started from.
formController.jumpToIndex(originalIndex);

return result;
}

/**
* Goes to the start of the hierarchy view based on where the user came from.
* Backs out until the index is at the beginning of a repeat group or the beginning of the form.
Expand Down Expand Up @@ -295,15 +467,15 @@ private void jumpToHierarchyStartIndex() {

screenIndex = potentialStartIndex;

if (potentialStartIndex == null) {
// check to see if the question is at the first level of the hierarchy. If it
// is, display the root level from the beginning.
formController.jumpToIndex(FormIndex.createBeginningOfFormIndex());
} else {
// otherwise we're at a displayable group
formController.jumpToIndex(potentialStartIndex);
// Check to see if the question is at the first level of the hierarchy.
// If it is, display the root level from the beginning.
// Otherwise we're at a displayable group.
if (screenIndex == null) {
screenIndex = FormIndex.createBeginningOfFormIndex();
}

formController.jumpToIndex(screenIndex);

// Now test again. This should be true at this point or we're at the beginning.
if (formController.isDisplayableGroup(formController.getFormIndex())) {
contextGroupRef = getGroupRef(formController);
Expand Down Expand Up @@ -364,6 +536,7 @@ private void refreshView(boolean isGoingUp) {
elementsToDisplay = new ArrayList<>();

jumpToHierarchyStartIndex();
updateOptionsMenu();

int event = formController.getEvent();

Expand Down Expand Up @@ -588,6 +761,15 @@ void onQuestionClicked(FormIndex index) {
finish();
}

/**
* Jumps to the form filling view with the given index shown.
*/
void exitToIndex(FormIndex index) {
Collect.getInstance().getFormController().jumpToIndex(index);
setResult(RESULT_OK);
finish();
}

/**
* When the device back button is pressed, go back to the previous activity, NOT the previous
* level in the hierarchy as the "Go Up" button does.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@ void configureButtons(FormController formController) {
jumpEndButton.setVisibility(View.GONE);
}

@Override
protected void showDeleteButton(boolean shouldShow) {
// Disabled.
}

@Override
protected void showAddButton(boolean shouldShow) {
// Disabled.
}

/**
* Prevents the user from clicking on individual questions to jump into the form-filling view.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,22 @@ public int stepToNextScreenEvent() throws JavaRosaException {
}
}

/**
* Move the current form index to the next event of the given type
* (or the end if none is found).
*/
public int stepToNextEventType(int eventType) {
int event = getEvent();
do {
if (event == FormEntryController.EVENT_END_OF_FORM) {
break;
}
event = stepToNextEvent(FormController.STEP_OVER_GROUP);
} while (event != eventType);

return event;
}

/**
* Move the current form index to the index of the first displayable group
* (that is, a repeatable group or a visible group),
Expand Down
Loading