Skip to content

Commit

Permalink
Merge pull request #5553 from grzesiek2010/COLLECT-5486
Browse files Browse the repository at this point in the history
Made form navigation language-direction aware
  • Loading branch information
grzesiek2010 authored Apr 24, 2023
2 parents 023c3e9 + 24295e8 commit 30b67ea
Show file tree
Hide file tree
Showing 17 changed files with 181 additions and 215 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@
import org.odk.collect.android.external.InstancesContract;
import org.odk.collect.android.formentry.BackgroundAudioPermissionDialogFragment;
import org.odk.collect.android.formentry.BackgroundAudioViewModel;
import org.odk.collect.android.formentry.FormAnimation;
import org.odk.collect.android.formentry.FormAnimationType;
import org.odk.collect.android.formentry.FormEndView;
import org.odk.collect.android.formentry.FormEntryMenuDelegate;
import org.odk.collect.android.formentry.FormEntryViewModel;
Expand Down Expand Up @@ -138,7 +140,7 @@
import org.odk.collect.android.listeners.AdvanceToNextListener;
import org.odk.collect.android.listeners.FormLoaderListener;
import org.odk.collect.android.listeners.SavePointListener;
import org.odk.collect.android.listeners.SwipeHandler;
import org.odk.collect.android.formentry.SwipeHandler;
import org.odk.collect.android.listeners.WidgetValueChangedListener;
import org.odk.collect.android.logic.ImmutableDisplayableQuestion;
import org.odk.collect.android.projects.CurrentProjectProvider;
Expand Down Expand Up @@ -297,10 +299,6 @@ public void allowSwiping(boolean doSwipe) {
swipeHandler.setAllowSwiping(doSwipe);
}

enum AnimationType {
LEFT, RIGHT, FADE
}

private boolean showNavigationButtons;

@Inject
Expand Down Expand Up @@ -1438,7 +1436,7 @@ public void onScreenRefresh() {
int event = getFormController().getEvent();

SwipeHandler.View current = createView(event, false);
showView(current, AnimationType.FADE);
showView(current, FormAnimationType.FADE);

formIndexAnimationHandler.setLastIndex(getFormController().getFormIndex());
}
Expand All @@ -1449,12 +1447,12 @@ private void animateToNextView(int event) {
case FormEntryController.EVENT_GROUP:
// create a savepoint
nonblockingCreateSavePointData();
showView(createView(event, true), AnimationType.RIGHT);
showView(createView(event, true), FormAnimationType.RIGHT);
break;
case FormEntryController.EVENT_END_OF_FORM:
case FormEntryController.EVENT_REPEAT:
case EVENT_PROMPT_NEW_REPEAT:
showView(createView(event, true), AnimationType.RIGHT);
showView(createView(event, true), FormAnimationType.RIGHT);
break;
case FormEntryController.EVENT_REPEAT_JUNCTURE:
Timber.i("Repeat juncture: %s", getFormController().getFormIndex().getReference());
Expand All @@ -1468,15 +1466,15 @@ private void animateToNextView(int event) {

private void animateToPreviousView(int event) {
SwipeHandler.View next = createView(event, false);
showView(next, AnimationType.LEFT);
showView(next, FormAnimationType.LEFT);
}

/**
* Displays the View specified by the parameter 'next', animating both the
* current view and next appropriately given the AnimationType. Also updates
* the progress bar.
*/
public void showView(SwipeHandler.View next, AnimationType from) {
public void showView(SwipeHandler.View next, FormAnimationType from) {
invalidateOptionsMenu();

// disable notifications...
Expand All @@ -1489,7 +1487,7 @@ public void showView(SwipeHandler.View next, AnimationType from) {

// logging of the view being shown is already done, as this was handled
// by createView()
switch (from) {
switch (FormAnimation.getAnimationTypeBasedOnLanguageDirection(this, from)) {
case RIGHT:
inAnimation = loadAnimation(this,
R.anim.push_left_in);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import org.odk.collect.android.utilities.Appearances;
import org.odk.collect.audioclips.Clip;
import org.odk.collect.imageloader.GlideImageLoader;
import org.odk.collect.strings.localization.LocalizedApplicationKt;

import java.io.File;
import java.util.ArrayList;
Expand All @@ -58,7 +59,6 @@
import static org.odk.collect.android.formentry.media.FormMediaUtils.getClip;
import static org.odk.collect.android.formentry.media.FormMediaUtils.getClipID;
import static org.odk.collect.android.formentry.media.FormMediaUtils.getPlayableAudioURI;
import static org.odk.collect.android.widgets.QuestionWidget.isRTL;

public abstract class AbstractSelectListAdapter extends RecyclerView.Adapter<AbstractSelectListAdapter.ViewHolder>
implements Filterable {
Expand Down Expand Up @@ -137,8 +137,8 @@ void setUpButton(TextView button, int index) {
button.setTextSize(TypedValue.COMPLEX_UNIT_DIP, QuestionFontSizeUtils.getQuestionFontSize());
button.setText(HtmlUtils.textToHtml(prompt.getSelectChoiceText(filteredItems.get(index))));
button.setTag(items.indexOf(filteredItems.get(index)));
button.setGravity(isRTL() ? Gravity.END : Gravity.START);
button.setTextAlignment(isRTL() ? View.TEXT_ALIGNMENT_TEXT_END : View.TEXT_ALIGNMENT_TEXT_START);
button.setGravity(LocalizedApplicationKt.isLTR(context) ? Gravity.START : Gravity.END);
button.setTextAlignment(LocalizedApplicationKt.isLTR(context) ? View.TEXT_ALIGNMENT_TEXT_START : View.TEXT_ALIGNMENT_TEXT_END);
}

boolean isItemSelected(List<Selection> selectedItems, @NonNull Selection item) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.odk.collect.android.formentry

import android.content.Context
import org.odk.collect.strings.localization.isLTR

enum class FormAnimationType {
LEFT, RIGHT, FADE
}

object FormAnimation {
@JvmStatic
fun getAnimationTypeBasedOnLanguageDirection(context: Context, formAnimationType: FormAnimationType): FormAnimationType {
return if (context.isLTR()) {
formAnimationType
} else {
when (formAnimationType) {
FormAnimationType.LEFT -> FormAnimationType.RIGHT
FormAnimationType.RIGHT -> FormAnimationType.LEFT
else -> FormAnimationType.FADE
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import androidx.core.widget.NestedScrollView;

import org.odk.collect.android.R;
import org.odk.collect.android.listeners.SwipeHandler;
import org.odk.collect.android.utilities.FormNameUtils;

public class FormEndView extends SwipeHandler.View {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@
import org.odk.collect.android.formentry.media.PromptAutoplayer;
import org.odk.collect.android.formentry.questions.QuestionTextSizeHelper;
import org.odk.collect.android.javarosawrapper.FormController;
import org.odk.collect.android.listeners.SwipeHandler;
import org.odk.collect.android.listeners.WidgetValueChangedListener;
import org.odk.collect.android.utilities.ContentUriHelper;
import org.odk.collect.android.utilities.ExternalAppIntentProvider;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package org.odk.collect.android.formentry

import android.content.Context
import android.view.GestureDetector
import android.view.MotionEvent
import android.widget.FrameLayout
import androidx.core.widget.NestedScrollView
import org.odk.collect.android.utilities.FlingRegister
import org.odk.collect.androidshared.utils.ScreenUtils
import org.odk.collect.settings.keys.ProjectKeys
import org.odk.collect.shared.settings.Settings
import org.odk.collect.strings.localization.isLTR
import kotlin.math.abs
import kotlin.math.atan2

class SwipeHandler(context: Context, generalSettings: Settings) {
val gestureDetector: GestureDetector
private val onSwipe: OnSwipeListener
private var view: View? = null
private var allowSwiping = true
private var beenSwiped = false
private val generalSettings: Settings

interface OnSwipeListener {
fun onSwipeBackward()
fun onSwipeForward()
}

init {
gestureDetector = GestureDetector(context, GestureListener())
onSwipe = context as OnSwipeListener
this.generalSettings = generalSettings
}

fun setView(view: View?) {
this.view = view
}

fun setAllowSwiping(allowSwiping: Boolean) {
this.allowSwiping = allowSwiping
}

fun setBeenSwiped(beenSwiped: Boolean) {
this.beenSwiped = beenSwiped
}

fun beenSwiped() = beenSwiped

inner class GestureListener : GestureDetector.OnGestureListener {
override fun onDown(event: MotionEvent) = false
override fun onSingleTapUp(e: MotionEvent) = false

override fun onShowPress(e: MotionEvent) = Unit
override fun onLongPress(e: MotionEvent) = Unit

override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
// The onFling() captures the 'up' event so our view thinks it gets long pressed. We don't want that, so cancel it.
view?.cancelLongPress()
return false
}

override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
if (view == null) {
return false
}

FlingRegister.flingDetected()

if (generalSettings.getString(ProjectKeys.KEY_NAVIGATION)!!.contains(ProjectKeys.NAVIGATION_SWIPE) && allowSwiping) {
// Looks for user swipes. If the user has swiped, move to the appropriate screen.

// For all screens a swipe is left/right of at least .25" and up/down of less than .25" OR left/right of > .5"
val xpixellimit = (ScreenUtils.xdpi(view!!.context) * .25).toInt()
val ypixellimit = (ScreenUtils.ydpi(view!!.context) * .25).toInt()

if (view != null && view!!.shouldSuppressFlingGesture()) {
return false
}

if (beenSwiped) {
return false
}

val diffX = abs(e1.x - e2.x)
val diffY = abs(e1.y - e2.y)

if (view != null && canScrollVertically() && getGestureAngle(diffX, diffY) > 30) {
return false
}

if (diffX > xpixellimit && diffY < ypixellimit || diffX > xpixellimit * 2) {
beenSwiped = true
if (e1.x > e2.x) {
if (view!!.context.isLTR()) {
onSwipe.onSwipeForward()
} else {
onSwipe.onSwipeBackward()
}
} else {
if (view!!.context.isLTR()) {
onSwipe.onSwipeBackward()
} else {
onSwipe.onSwipeForward()
}
}
return true
}
}
return false
}

private fun getGestureAngle(diffX: Float, diffY: Float): Double {
return Math.toDegrees(atan2(diffY.toDouble(), diffX.toDouble()))
}

private fun canScrollVertically(): Boolean {
val scrollView = view!!.verticalScrollView

return if (scrollView != null) {
val screenHeight = scrollView.height
val viewHeight = scrollView.getChildAt(0).height
viewHeight > screenHeight
} else {
false
}
}
}

abstract class View(context: Context) : FrameLayout(context) {
abstract fun shouldSuppressFlingGesture(): Boolean
abstract val verticalScrollView: NestedScrollView?
}
}
Loading

0 comments on commit 30b67ea

Please sign in to comment.