-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Guidance hint solution that resolves #2007 #2105
Changes from 14 commits
b5fb693
a729e9a
0257eeb
c29f833
f6b42dc
6fc91cf
fa67b0f
9449e31
66b5cd2
eadb0ad
c7b8f7d
39df324
2254319
368ea98
5ccab73
8c77dc9
c7062fc
6218717
f0a5061
dc25767
b0fdd53
3f7a0d4
80e417c
c1f5ab7
32d00a6
d3b8278
48164cf
10ca320
de39e4c
64b99b0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
package org.odk.collect.android; | ||
|
||
|
||
import android.content.Context; | ||
import android.content.Intent; | ||
import android.content.res.AssetManager; | ||
import android.os.Environment; | ||
import android.support.test.InstrumentationRegistry; | ||
import android.support.test.espresso.intent.rule.IntentsTestRule; | ||
import android.support.test.runner.AndroidJUnit4; | ||
import android.text.TextUtils; | ||
|
||
import org.apache.commons.io.IOUtils; | ||
import org.javarosa.form.api.FormEntryPrompt; | ||
import org.junit.Before; | ||
import org.junit.BeforeClass; | ||
import org.junit.Rule; | ||
import org.junit.Test; | ||
import org.junit.runner.RunWith; | ||
import org.mockito.Mock; | ||
import org.odk.collect.android.activities.FormEntryActivity; | ||
import org.odk.collect.android.application.Collect; | ||
import org.odk.collect.android.preferences.GeneralSharedPreferences; | ||
import org.odk.collect.android.preferences.GuidanceHint; | ||
import org.odk.collect.android.preferences.PreferenceKeys; | ||
import org.odk.collect.android.utilities.ActivityAvailability; | ||
|
||
import java.io.File; | ||
import java.io.FileOutputStream; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.io.OutputStream; | ||
|
||
import tools.fastlane.screengrab.Screengrab; | ||
import tools.fastlane.screengrab.UiAutomatorScreenshotStrategy; | ||
|
||
import static android.support.test.espresso.Espresso.onView; | ||
import static android.support.test.espresso.action.ViewActions.click; | ||
import static android.support.test.espresso.assertion.ViewAssertions.matches; | ||
import static android.support.test.espresso.matcher.ViewMatchers.withId; | ||
import static android.support.test.espresso.matcher.ViewMatchers.withText; | ||
import static junit.framework.Assert.assertFalse; | ||
import static org.odk.collect.android.activities.FormEntryActivity.EXTRA_TESTING_PATH; | ||
|
||
@RunWith(AndroidJUnit4.class) | ||
public class GuidanceHintFormTest { | ||
|
||
private static final String GUIDANCE_SAMPLE_FORM = "guidance_hint_form.xml"; | ||
private static final String FORMS_DIRECTORY = "/odk/forms/"; | ||
|
||
@Rule | ||
public FormEntryActivityTestRule activityTestRule = new FormEntryActivityTestRule(); | ||
|
||
@Mock | ||
private ActivityAvailability activityAvailability; | ||
|
||
//region Test prep. | ||
@BeforeClass | ||
public static void copyFormToSdCard() throws IOException { | ||
String pathname = formPath(); | ||
if (new File(pathname).exists()) { | ||
return; | ||
} | ||
|
||
AssetManager assetManager = InstrumentationRegistry.getContext().getAssets(); | ||
InputStream inputStream = assetManager.open(GUIDANCE_SAMPLE_FORM); | ||
|
||
File outFile = new File(pathname); | ||
OutputStream outputStream = new FileOutputStream(outFile); | ||
|
||
IOUtils.copy(inputStream, outputStream); | ||
} | ||
|
||
@BeforeClass | ||
public static void beforeAll() { | ||
Screengrab.setDefaultScreenshotStrategy(new UiAutomatorScreenshotStrategy()); | ||
} | ||
|
||
@Before | ||
public void prepareDependencies() { | ||
FormEntryActivity activity = activityTestRule.getActivity(); | ||
activity.setActivityAvailability(activityAvailability); | ||
activity.setShouldOverrideAnimations(true); | ||
} | ||
|
||
@Test | ||
public void guidanceVisibilityContentTest() { | ||
GeneralSharedPreferences.getInstance().save(PreferenceKeys.KEY_GUIDANCE_HINT, GuidanceHint.YesCollapsed.toString()); | ||
|
||
FormEntryPrompt prompt = Collect.getInstance().getFormController().getQuestionPrompt(); | ||
|
||
String guidance = prompt.getSpecialFormQuestionText(prompt.getQuestion().getHelpTextID(), "guidance"); | ||
|
||
assertFalse(TextUtils.isEmpty(guidance)); | ||
|
||
onView(withId(R.id.help_text_view)).perform(click()); | ||
|
||
Screengrab.screenshot("guidance_hint"); | ||
|
||
onView(withId(R.id.guidance_text_view)).check(matches(withText(guidance))); | ||
|
||
} | ||
|
||
//region Helper methods. | ||
private static String formPath() { | ||
return Environment.getExternalStorageDirectory().getPath() | ||
+ FORMS_DIRECTORY | ||
+ GUIDANCE_SAMPLE_FORM; | ||
} | ||
|
||
|
||
|
||
//region Custom TestRule. | ||
private class FormEntryActivityTestRule extends IntentsTestRule<FormEntryActivity> { | ||
|
||
FormEntryActivityTestRule() { | ||
super(FormEntryActivity.class); | ||
} | ||
|
||
@Override | ||
protected Intent getActivityIntent() { | ||
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); | ||
Intent intent = new Intent(context, FormEntryActivity.class); | ||
|
||
intent.putExtra(EXTRA_TESTING_PATH, formPath()); | ||
|
||
return intent; | ||
} | ||
} | ||
//endregion | ||
|
||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package org.odk.collect.android.listeners; | ||
|
||
/** | ||
* Generic Result interface that returns a result of type T | ||
* from another thread or function. | ||
* | ||
*/ | ||
public interface Result<T> { | ||
void onComplete(T result); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package org.odk.collect.android.preferences; | ||
|
||
public enum GuidanceHint { | ||
Yes("yes"), | ||
YesCollapsed("yes_collapsed"), | ||
No("no"); | ||
|
||
private final String name; | ||
|
||
GuidanceHint(String s) { | ||
name = s; | ||
} | ||
|
||
public static GuidanceHint get(String name) { | ||
for (GuidanceHint hint : GuidanceHint.values()) { | ||
if (hint.name.equals(name)) { | ||
|
||
return hint; | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
|
||
public String toString() { | ||
return this.name; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,7 @@ public final class PreferenceKeys { | |
public static final String KEY_CONSTRAINT_BEHAVIOR = "constraint_behavior"; | ||
public static final String KEY_HIGH_RESOLUTION = "high_resolution"; | ||
public static final String KEY_IMAGE_SIZE = "image_size"; | ||
public static final String KEY_GUIDANCE_HINT = "guidance_hint"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indentation here should match that of other values. |
||
public static final String KEY_INSTANCE_SYNC = "instance_sync"; | ||
|
||
// form_metadata_preferences.xml | ||
|
@@ -66,6 +67,7 @@ public final class PreferenceKeys { | |
public static final String NAVIGATION_BUTTONS = "buttons"; | ||
private static final String GOOGLE_MAPS = "google_maps"; | ||
private static final String AUTOSEND_OFF = "off"; | ||
private static final String GUIDANCE_HINT_OFF = "no"; | ||
static final String GOOGLE_MAPS_BASEMAP_DEFAULT = "streets"; | ||
static final String OSM_BASEMAP_KEY = "osmdroid"; | ||
static final String OSM_MAPS_BASEMAP_DEFAULT = "openmap_streets"; | ||
|
@@ -77,6 +79,7 @@ private static HashMap<String, Object> getHashMap() { | |
hashMap.put(KEY_USERNAME, ""); | ||
// form_management_preferences.xml | ||
hashMap.put(KEY_AUTOSEND, AUTOSEND_OFF); | ||
hashMap.put(KEY_GUIDANCE_HINT, GUIDANCE_HINT_OFF); | ||
hashMap.put(KEY_DELETE_AFTER_SEND, false); | ||
hashMap.put(KEY_COMPLETED_DEFAULT, true); | ||
hashMap.put(KEY_CONSTRAINT_BEHAVIOR, CONSTRAINT_BEHAVIOR_ON_SWIPE); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,23 @@ | ||
package org.odk.collect.android.utilities; | ||
|
||
import android.support.v4.view.animation.PathInterpolatorCompat; | ||
import android.view.View; | ||
import android.view.ViewGroup; | ||
import android.view.animation.Animation; | ||
import android.view.animation.Interpolator; | ||
import android.view.animation.ScaleAnimation; | ||
import android.view.animation.Transformation; | ||
|
||
import org.odk.collect.android.listeners.Result; | ||
|
||
/** | ||
* Created by Ing. Oscar G. Medina Cruz on 18/06/2016. | ||
*/ | ||
public class AnimateUtils { | ||
|
||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove extra blank line |
||
private static final Interpolator easeInOutQuart = PathInterpolatorCompat.create(0.77f, 0f, 0.175f, 1f); | ||
|
||
private AnimateUtils() { | ||
|
||
} | ||
|
@@ -39,4 +47,117 @@ public void onAnimationRepeat(Animation animation) { | |
}); | ||
view.startAnimation(scaleInAnimation); | ||
} | ||
|
||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove extra blank line |
||
// Added animation related code and inspiration from this Stack Overflow Question | ||
// https://stackoverflow.com/questions/4946295/android-expand-collapse-animation | ||
|
||
public static Animation expand(final View view, Result<Boolean> result) { | ||
int matchParentMeasureSpec = View.MeasureSpec.makeMeasureSpec(((View) view.getParent()).getWidth(), View.MeasureSpec.EXACTLY); | ||
int wrapContentMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); | ||
view.measure(matchParentMeasureSpec, wrapContentMeasureSpec); | ||
final int targetHeight = view.getMeasuredHeight(); | ||
|
||
// Older versions of android (pre API 21) cancel animations for views with a height of 0 so use 1 instead. | ||
view.getLayoutParams().height = 1; | ||
view.setVisibility(View.VISIBLE); | ||
|
||
Animation animation = new Animation() { | ||
@Override | ||
protected void applyTransformation(float interpolatedTime, Transformation t) { | ||
|
||
view.getLayoutParams().height = interpolatedTime == 1 | ||
? ViewGroup.LayoutParams.WRAP_CONTENT | ||
: (int) (targetHeight * interpolatedTime); | ||
|
||
view.requestLayout(); | ||
} | ||
|
||
@Override | ||
public boolean willChangeBounds() { | ||
return true; | ||
} | ||
}; | ||
|
||
animation.setInterpolator(easeInOutQuart); | ||
animation.setDuration(computeDurationFromHeight(view)); | ||
animation.setAnimationListener(new Animation.AnimationListener() { | ||
@Override | ||
public void onAnimationStart(Animation animation) { | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove extra blank line |
||
//triggered when animation starts. | ||
} | ||
|
||
@Override | ||
public void onAnimationEnd(Animation animation) { | ||
result.onComplete(true); | ||
} | ||
|
||
@Override | ||
public void onAnimationRepeat(Animation animation) { | ||
//triggered when animation repeats. | ||
} | ||
}); | ||
view.startAnimation(animation); | ||
|
||
return animation; | ||
} | ||
|
||
public static Animation collapse(final View view, Result<Boolean> result) { | ||
final int initialHeight = view.getMeasuredHeight(); | ||
|
||
Animation a = new Animation() { | ||
@Override | ||
protected void applyTransformation(float interpolatedTime, Transformation t) { | ||
if (interpolatedTime == 1) { | ||
view.setVisibility(View.GONE); | ||
} else { | ||
view.getLayoutParams().height = initialHeight - (int) (initialHeight * interpolatedTime); | ||
view.requestLayout(); | ||
} | ||
} | ||
|
||
@Override | ||
public boolean willChangeBounds() { | ||
return true; | ||
} | ||
}; | ||
|
||
a.setInterpolator(easeInOutQuart); | ||
|
||
int durationMillis = computeDurationFromHeight(view); | ||
a.setDuration(durationMillis); | ||
|
||
a.setAnimationListener(new Animation.AnimationListener() { | ||
@Override | ||
public void onAnimationStart(Animation animation) { | ||
//triggered when animation starts. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove extra blank line |
||
} | ||
|
||
@Override | ||
public void onAnimationEnd(Animation animation) { | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove extra blank line |
||
result.onComplete(true); | ||
} | ||
|
||
@Override | ||
public void onAnimationRepeat(Animation animation) { | ||
//triggered when animation repeats. | ||
|
||
} | ||
}); | ||
|
||
view.startAnimation(a); | ||
|
||
|
||
return a; | ||
} | ||
|
||
private static int computeDurationFromHeight(View view) { | ||
// 1dp/ms * multiplier | ||
return (int) (view.getMeasuredHeight() / view.getContext().getResources().getDisplayMetrics().density); | ||
} | ||
|
||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove extra blank line.