Skip to content

Commit ecac21e

Browse files
authored
Merge pull request #7109 from grzesiek2010/COLLECT-6988
Add support for x-timed-grid
2 parents f7c1269 + 7848c43 commit ecac21e

36 files changed

Lines changed: 1839 additions & 5 deletions

.circleci/config.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,9 +264,9 @@ jobs:
264264
name: Check APK size hasn't increased
265265
command: |
266266
if [[ -n "$GOOGLE_MAPS_API_KEY" ]]; then \
267-
./check-size.sh 23117869
267+
./check-size.sh 23300000
268268
else
269-
./check-size.sh 13863000
269+
./check-size.sh 14000000
270270
fi
271271
272272
- run:
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.odk.collect.androidshared.ui
2+
3+
import android.content.Context
4+
import androidx.annotation.StringRes
5+
import com.google.android.material.dialog.MaterialAlertDialogBuilder
6+
7+
object DialogUtils {
8+
@JvmStatic
9+
fun show(
10+
context: Context,
11+
@StringRes titleRes: Int,
12+
@StringRes messageRes: Int,
13+
) {
14+
MaterialAlertDialogBuilder(context)
15+
.setTitle(titleRes)
16+
.setMessage(messageRes)
17+
.setPositiveButton(org.odk.collect.strings.R.string.ok, null)
18+
.show()
19+
}
20+
}

collect_app/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ dependencies {
274274
implementation project(':db')
275275
implementation project(':open-rosa')
276276
implementation project(':mobile-device-management')
277+
implementation project(':timedgrid')
277278

278279
if (getSecrets().getProperty('MAPBOX_DOWNLOADS_TOKEN', '') != '') {
279280
implementation project(':mapbox')

collect_app/src/main/java/org/odk/collect/android/activities/FormFillingActivity.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@
179179
import org.odk.collect.androidshared.system.ProcessRestoreDetector;
180180
import org.odk.collect.androidshared.ui.DialogFragmentUtils;
181181
import org.odk.collect.androidshared.ui.FragmentFactoryBuilder;
182+
import org.odk.collect.androidshared.ui.DialogUtils;
182183
import org.odk.collect.androidshared.ui.SnackbarUtils;
183184
import org.odk.collect.androidshared.ui.ToastUtils;
184185
import org.odk.collect.async.Scheduler;
@@ -199,6 +200,7 @@
199200
import org.odk.collect.settings.SettingsProvider;
200201
import org.odk.collect.settings.keys.ProjectKeys;
201202
import org.odk.collect.strings.localization.LocalizedActivity;
203+
import org.odk.collect.timedgrid.NavigationWarning;
202204

203205
import java.io.File;
204206
import java.util.HashMap;
@@ -388,7 +390,10 @@ public void allowSwiping(boolean doSwipe) {
388390
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(true) {
389391
@Override
390392
public void handleOnBackPressed() {
391-
if (audioRecorder.isRecording() && !backgroundAudioViewModel.isBackgroundRecording()) {
393+
NavigationWarning navigationWarning = odkView != null ? odkView.isNavigationBlocked() : null;
394+
if (navigationWarning != null) {
395+
DialogUtils.show(FormFillingActivity.this, navigationWarning.getTitleRes(), navigationWarning.getMessageRes());
396+
} else if (audioRecorder.isRecording() && !backgroundAudioViewModel.isBackgroundRecording()) {
392397
// We want the user to stop recording before changing screens
393398
DialogFragmentUtils.showIfNotShowing(RecordingWarningDialogFragment.class, getSupportFragmentManager());
394399
} else {
@@ -496,6 +501,15 @@ public void changeLanguage() {
496501
public void save() {
497502
saveForm(false, InstancesDaoHelper.isInstanceComplete(getFormController()), null, true);
498503
}
504+
},
505+
() -> {
506+
NavigationWarning navigationWarning = odkView != null ? odkView.isNavigationBlocked() : null;
507+
if (navigationWarning != null) {
508+
DialogUtils.show(this, navigationWarning.getTitleRes(), navigationWarning.getMessageRes());
509+
swipeHandler.setBeenSwiped(false);
510+
return false;
511+
}
512+
return true;
499513
}
500514
);
501515

@@ -1203,6 +1217,13 @@ private void moveScreen(Direction direction) {
12031217
return;
12041218
}
12051219

1220+
NavigationWarning navigationWarning = odkView != null ? odkView.isNavigationBlocked() : null;
1221+
if (navigationWarning != null) {
1222+
DialogUtils.show(this, navigationWarning.getTitleRes(), navigationWarning.getMessageRes());
1223+
swipeHandler.setBeenSwiped(false);
1224+
return;
1225+
}
1226+
12061227
if (audioRecorder.isRecording() && !backgroundAudioViewModel.isBackgroundRecording()) {
12071228
// We want the user to stop recording before changing screens
12081229
DialogFragmentUtils.showIfNotShowing(RecordingWarningDialogFragment.class, getSupportFragmentManager());

collect_app/src/main/java/org/odk/collect/android/formentry/FormEntryMenuProvider.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ class FormEntryMenuProvider(
3434
private val backgroundLocationViewModel: BackgroundLocationViewModel,
3535
private val backgroundAudioViewModel: BackgroundAudioViewModel,
3636
private val settingsProvider: SettingsProvider,
37-
private val formEntryMenuClickListener: FormEntryMenuClickListener
37+
private val formEntryMenuClickListener: FormEntryMenuClickListener,
38+
private val beforeMenuItemClick: () -> Boolean
3839
) : MenuProvider {
3940
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
4041
menuInflater.inflate(R.menu.form_menu, menu)
@@ -96,6 +97,10 @@ class FormEntryMenuProvider(
9697
return true
9798
}
9899

100+
if (!beforeMenuItemClick()) {
101+
return true
102+
}
103+
99104
return when (item.itemId) {
100105
R.id.menu_add_repeat -> {
101106
if (audioRecorder.isRecording() && !backgroundAudioViewModel.isBackgroundRecording) {

collect_app/src/main/java/org/odk/collect/android/formentry/ODKView.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@
8989
import org.odk.collect.permissions.PermissionListener;
9090
import org.odk.collect.permissions.PermissionsProvider;
9191
import org.odk.collect.settings.SettingsProvider;
92+
import org.odk.collect.timedgrid.NavigationAwareWidget;
93+
import org.odk.collect.timedgrid.NavigationWarning;
9294

9395
import java.io.File;
9496
import java.io.Serializable;
@@ -97,6 +99,7 @@
9799
import java.util.LinkedHashMap;
98100
import java.util.List;
99101
import java.util.Map;
102+
import java.util.Objects;
100103
import java.util.Set;
101104

102105
import javax.inject.Inject;
@@ -804,4 +807,15 @@ private void updateQuestions(FormEntryPrompt[] prompts) {
804807
this.questions.add(new ImmutableDisplayableQuestion(questionAfterSave));
805808
}
806809
}
810+
811+
@Nullable
812+
public NavigationWarning isNavigationBlocked() {
813+
return widgets.stream()
814+
.filter(widget -> widget instanceof NavigationAwareWidget)
815+
.map(widget -> (NavigationAwareWidget) widget)
816+
.map(NavigationAwareWidget::shouldBlockNavigation)
817+
.filter(Objects::nonNull)
818+
.findFirst()
819+
.orElse(null);
820+
}
807821
}

collect_app/src/main/java/org/odk/collect/android/utilities/Appearances.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ object Appearances {
9191
const val MASKED = "masked"
9292
const val COUNTER = "counter"
9393
const val MULTILINE = "multiline"
94+
const val X_TIMED_GRID = "x-timed-grid"
9495

9596
// Get appearance hint and clean it up so it is lower case, without the search function and never null.
9697
@JvmStatic
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package org.odk.collect.android.widgets
2+
3+
import android.annotation.SuppressLint
4+
import android.content.Context
5+
import org.javarosa.core.model.FormIndex
6+
import org.javarosa.core.model.IFormElement
7+
import org.javarosa.core.model.SelectChoice
8+
import org.javarosa.core.model.data.IAnswerData
9+
import org.javarosa.form.api.FormEntryPrompt
10+
import org.odk.collect.android.activities.FormFillingActivity
11+
import org.odk.collect.android.formentry.FormEntryViewModel
12+
import org.odk.collect.android.formentry.questions.QuestionDetails
13+
import org.odk.collect.android.widgets.items.ItemsWidgetUtils
14+
import org.odk.collect.timedgrid.FormAnswerRefresher
15+
import org.odk.collect.timedgrid.FormControllerFacade
16+
import org.odk.collect.timedgrid.NavigationAwareWidget
17+
import org.odk.collect.timedgrid.NavigationWarning
18+
import org.odk.collect.timedgrid.TimedGridWidgetDelegate
19+
20+
@SuppressLint("ViewConstructor")
21+
class TimedGridWidget(
22+
context: Context,
23+
questionDetails: QuestionDetails,
24+
dependencies: Dependencies,
25+
formEntryViewModel: FormEntryViewModel
26+
) : QuestionWidget(context, dependencies, questionDetails), NavigationAwareWidget {
27+
private val widgetDelegate = TimedGridWidgetDelegate(
28+
context,
29+
questionDetails.prompt,
30+
object : FormControllerFacade {
31+
override fun getFormElements(): List<IFormElement>? {
32+
return formEntryViewModel.formController.getFormDef()?.children
33+
}
34+
35+
override fun getItems(): List<SelectChoice> {
36+
return ItemsWidgetUtils.loadItemsAndHandleErrors(
37+
this@TimedGridWidget, questionDetails.prompt, formEntryViewModel
38+
)
39+
}
40+
41+
override fun saveAnswer(index: FormIndex, answer: IAnswerData) {
42+
formEntryViewModel.formController.saveOneScreenAnswer(index, answer, false)
43+
}
44+
},
45+
object : FormAnswerRefresher {
46+
override fun refreshAnswer(index: FormIndex) {
47+
val activity = context as? FormFillingActivity ?: return
48+
val odkView = activity.currentViewIfODKView ?: return
49+
50+
val widget = odkView.widgets
51+
.filterIsInstance<StringWidget>()
52+
.firstOrNull { it.formEntryPrompt.index == index }
53+
?: return
54+
55+
widget.apply {
56+
setDisplayValueFromModel()
57+
widgetValueChanged()
58+
showAnswerContainer()
59+
}
60+
}
61+
}
62+
) {
63+
widgetValueChanged()
64+
}
65+
66+
init {
67+
render()
68+
}
69+
70+
override fun onCreateWidgetView(context: Context, prompt: FormEntryPrompt, answerFontSize: Int) = widgetDelegate.onCreateWidgetView(this)
71+
72+
override fun getAnswer() = widgetDelegate.getAnswer()
73+
74+
override fun clearAnswer() {}
75+
76+
override fun setOnLongClickListener(l: OnLongClickListener?) {}
77+
78+
override fun onDetachedFromWindow() {
79+
super.onDetachedFromWindow()
80+
widgetDelegate.onDetachedFromWindow()
81+
}
82+
83+
override fun shouldBlockNavigation(): NavigationWarning? = widgetDelegate.shouldBlockNavigation()
84+
}

collect_app/src/main/java/org/odk/collect/android/widgets/WidgetFactory.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,8 @@ public QuestionWidget createWidgetFromPrompt(FormEntryPrompt prompt, Permissions
265265
questionWidget = new LabelWidget(activity, questionDetails, formEntryViewModel, dependencies);
266266
} else if (appearance.contains(Appearances.IMAGE_MAP)) {
267267
questionWidget = new SelectMultiImageMapWidget(activity, questionDetails, formEntryViewModel, dependencies);
268+
} else if (appearance.startsWith(Appearances.X_TIMED_GRID)) {
269+
questionWidget = new TimedGridWidget(activity, questionDetails, dependencies, formEntryViewModel);
268270
} else {
269271
questionWidget = new SelectMultiWidget(activity, questionDetails, formEntryViewModel, dependencies);
270272
}

collect_app/src/test/java/org/odk/collect/android/formentry/FormEntryMenuProviderTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ class FormEntryMenuProviderTest {
6464
backgroundAudioViewModel,
6565
settingsProvider,
6666
formEntryMenuClickListener
67-
)
67+
) { true }
6868

6969
@Test
7070
fun onPrepare_inRepeatQuestion_showsAddRepeat() {

0 commit comments

Comments
 (0)