Skip to content

Commit

Permalink
Add a User Failure event Listener (#25) (#29)
Browse files Browse the repository at this point in the history
* Add a User Failure event Listener
* Adding tests for the new listener
  • Loading branch information
cortinico authored Oct 19, 2018
1 parent d68fb94 commit e4beb38
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.view.View;
import android.view.WindowManager;

import com.ncorti.slidetoact.SlideToActView;
Expand All @@ -19,6 +20,8 @@
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.swipeRight;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static com.ncorti.slidetoact.example.testutil.SlideViewActions.clickCenter;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;

@RunWith(AndroidJUnit4.class)
Expand Down Expand Up @@ -81,6 +84,52 @@ public void onSlideReset(@NonNull SlideToActView view) {
assertTrue(flag[0]);
}

@Test
public void testOnSlideClickListener() throws Throwable {
final boolean[] flag = {false};
mActivityRule.runOnUiThread(new Runnable() {
@Override
public void run() {
mActivityRule.getActivity().findViewById(R.id.slide_1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
flag[0] = true;
}
});
}
});
onView(withId(R.id.slide_1)).perform(click());
assertTrue(flag[0]);
}

@Test
public void testOnSlideUserFailedListener_withUserFailure() {
final boolean[] flag = {false};
((SlideToActView) mActivityRule.getActivity().findViewById(R.id.slide_1)).setOnSlideUserFailedListener(new SlideToActView.OnSlideUserFailedListener() {
@Override
public void onSlideFailed(@NonNull SlideToActView view, boolean isOutside) {
// We test if the user clicked outside of the cursor.
flag[0] = isOutside;
}
});
onView(withId(R.id.slide_1)).perform(clickCenter());
assertTrue(flag[0]);
}

@Test
public void testOnSlideUserFailedListener_withCorrectSwipe() {
final boolean[] flag = {false};
((SlideToActView) mActivityRule.getActivity().findViewById(R.id.slide_1)).setOnSlideUserFailedListener(new SlideToActView.OnSlideUserFailedListener() {
@Override
public void onSlideFailed(@NonNull SlideToActView view, boolean isOutside) {
// We test if the user clicked outside of the cursor.
flag[0] = isOutside;
}
});
onView(withId(R.id.slide_1)).perform(swipeRight());
assertFalse(flag[0]);
}

@Test
public void testOnSlideAnimationEventListener() throws InterruptedException {
final boolean[] flag = {false, false, false, false};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.ncorti.slidetoact.example.testutil;

import android.support.test.espresso.ViewAction;
import android.support.test.espresso.action.CoordinatesProvider;
import android.support.test.espresso.action.GeneralClickAction;
import android.support.test.espresso.action.Press;
import android.support.test.espresso.action.Tap;
import android.view.View;

public class SlideViewActions {
/**
* Perform a click in the center of the View.
*/
public static ViewAction clickCenter() {
return new GeneralClickAction(
Tap.SINGLE,
new CoordinatesProvider() {
@Override
public float[] calculateCoordinates(View view) {

final int[] screenPos = new int[2];
view.getLocationOnScreen(screenPos);
final float width = view.getWidth();
final float height = view.getHeight();

final float centerX = screenPos[0] + (width / 2);
final float centerY = screenPos[1] + (height / 2);

float[] coordinates = {centerX, centerY};

return coordinates;
}
},
Press.FINGER);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ public void onSlideReset(@NonNull SlideToActView view) {
log.append("\n" + getTime() + " onSlideReset");
}
});
slide.setOnSlideUserFailedListener(new SlideToActView.OnSlideUserFailedListener() {
@Override
public void onSlideFailed(@NonNull SlideToActView view, boolean isOutside) {
log.append("\n" + getTime() + " onSlideUserFailed - Clicked outside: " + isOutside);
}
});
slide.setOnSlideToActAnimationEventListener(new SlideToActView.OnSlideToActAnimationEventListener() {
@Override
public void onSlideCompleteAnimationStarted(@NonNull SlideToActView view, float threshold) {
Expand Down
38 changes: 36 additions & 2 deletions slidetoact/src/main/java/com/ncorti/slidetoact/SlideToActView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ class SlideToActView(context: Context,
var onSlideToActAnimationEventListener: OnSlideToActAnimationEventListener? = null
var onSlideCompleteListener: OnSlideCompleteListener? = null
var onSlideResetListener: OnSlideResetListener? = null
var onSlideUserFailedListener: OnSlideUserFailedListener? = null

init {
val actualOuterColor : Int
Expand Down Expand Up @@ -337,6 +338,12 @@ class SlideToActView(context: Context,
}
}

// Intentionally override `performClick` to do not lose accessibility support.
@Suppress("RedundantOverride")
override fun performClick(): Boolean {
return super.performClick()
}

override fun onTouchEvent(event: MotionEvent?): Boolean {
if (event != null && isEnabled) {
when (event.action) {
Expand All @@ -345,14 +352,16 @@ class SlideToActView(context: Context,
mFlagMoving = true
mLastX = event.x
parent.requestDisallowInterceptTouchEvent(true)
} else {
// Clicking outside the area -> User failed, notify the listener.
onSlideUserFailedListener?.onSlideFailed(this, true)
}
performClick()
}
MotionEvent.ACTION_UP -> {
mFlagMoving = false
parent.requestDisallowInterceptTouchEvent(false)
if ((mPosition > 0 && isLocked) || (mPosition > 0 && mPositionPerc < mGraceValue)) {
// Check for grace value

val positionAnimator = ValueAnimator.ofInt(mPosition, 0)
positionAnimator.duration = 300
positionAnimator.addUpdateListener {
Expand All @@ -363,7 +372,13 @@ class SlideToActView(context: Context,
} else if (mPosition > 0 && mPositionPerc >= mGraceValue) {
isEnabled = false // Fully disable touch events
startAnimationComplete()
} else if (mFlagMoving && mPosition == 0) {
// mFlagMoving == true means user successfully grabbed the slider,
// but mPosition == 0 means that the slider is released at the beginning
// so either a Tap or the user slided back.
onSlideUserFailedListener?.onSlideFailed(this, false)
}
mFlagMoving = false
}
MotionEvent.ACTION_MOVE -> {
if (mFlagMoving) {
Expand Down Expand Up @@ -675,6 +690,25 @@ class SlideToActView(context: Context,
fun onSlideReset(view: SlideToActView)
}

/**
* Event handler for the user failure with the Widget.
* You can subscribe to this event to get notified when the user is wrongly
* interacting with the widget to eventually educate it:
*
* - The user clicked outside of the cursor
* - The user slided but left when the cursor was back to zero
*
* You can use this listener to show a Toast or other messages.
*/
interface OnSlideUserFailedListener {
/**
* Called when user failed to interact with the slider slide
* @param view The SlideToActView who created the event
* @param isOutside True if user pressed outside the cursor
*/
fun onSlideFailed(view: SlideToActView, isOutside: Boolean)
}

/**
* Outline provider for the SlideToActView.
* This outline will suppress the shadow (till the moment when Android will support
Expand Down

0 comments on commit e4beb38

Please sign in to comment.