diff --git a/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/SurveyActionsListener.java b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/SurveyActionsListener.java new file mode 100644 index 000000000..209999a2c --- /dev/null +++ b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/SurveyActionsListener.java @@ -0,0 +1,7 @@ +package org.onebusaway.android.ui.survey; + +public interface SurveyActionsListener { + void onSkipSurvey(); + void onRemindMeLater(); + void onCancelSurvey(); +} diff --git a/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/SurveyDialogActions.java b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/SurveyDialogActions.java new file mode 100644 index 000000000..7bdf0fc41 --- /dev/null +++ b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/SurveyDialogActions.java @@ -0,0 +1,25 @@ +package org.onebusaway.android.ui.survey; + +public class SurveyDialogActions { + + private static SurveyActionsListener listener; + + public static void setDialogActionListener(SurveyActionsListener dialogListener) { + listener = dialogListener; + } + + public static void handleSkipSurvey() { + if (listener == null) return; + listener.onSkipSurvey(); + } + + public static void handleRemindMeLater() { + if (listener == null) return; + listener.onRemindMeLater(); + } + + public static void handleCancel() { + if (listener == null) return; + listener.onCancelSurvey(); + } +} diff --git a/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/SurveyManager.java b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/SurveyManager.java index dd1404220..18c9157c5 100644 --- a/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/SurveyManager.java +++ b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/SurveyManager.java @@ -37,7 +37,7 @@ import java.util.List; import java.util.Objects; -public class SurveyManager { +public class SurveyManager implements SurveyActionsListener { private final Context context; private final StudyRequestListener studyRequestListener; private final SubmitSurveyRequestListener submitSurveyRequestListener; @@ -69,6 +69,7 @@ public SurveyManager(Context context, Boolean fromArrivalsList, StudyRequestList this.studyRequestListener = studyRequestListener; this.submitSurveyRequestListener = submitSurveyRequestListener; this.isVisibleOnStops = fromArrivalsList; + setupSurveyDismissDialog(); } public void requestSurveyData() { @@ -87,18 +88,18 @@ private void updateSurveyData() { switch (externalSurveyResult) { case SurveyUtils.EXTERNAL_SURVEY_WITHOUT_HERO_QUESTION: SurveyViewUtils.showSharedInfoDetailsTextView(context, surveyView, questionsList.get(0).getContent().getEmbedded_data_fields()); - SurveyViewUtils.showExternalSurveyButtons(surveyView); + SurveyViewUtils.showExternalSurveyButtons(context, surveyView); handleOpenExternalSurvey(surveyView, questionsList.get(0).getContent().getUrl()); break; case SurveyUtils.EXTERNAL_SURVEY_WITH_HERO_QUESTION: externalSurveyUrl = questionsList.get(1).getContent().getUrl(); SurveyViewUtils.showSharedInfoDetailsTextView(context, surveyView, questionsList.get(1).getContent().getEmbedded_data_fields()); - SurveyViewUtils.showHeroQuestionButtons(surveyView); + SurveyViewUtils.showHeroQuestionButtons(context, surveyView); handleNextButton(surveyView); break; default: - SurveyViewUtils.showHeroQuestionButtons(surveyView); + SurveyViewUtils.showHeroQuestionButtons(context, surveyView); handleNextButton(surveyView); break; } @@ -180,7 +181,7 @@ public void showAllSurveyQuestions() { surveyBottomSheet = SurveyViewUtils.createSurveyBottomSheetDialog(context); initSurveyQuestionsBottomSheet(context); SurveyViewUtils.setupBottomSheetBehavior(surveyBottomSheet); - SurveyViewUtils.setupBottomSheetCloseButton(surveyBottomSheet); + SurveyViewUtils.setupBottomSheetCloseButton(context, surveyBottomSheet); handleSubmitSurveyButton(Objects.requireNonNull(surveyBottomSheet.findViewById(R.id.submit_btn))); surveyBottomSheet.show(); } @@ -280,8 +281,9 @@ public void onSubmitSurveyResponseReceived(SubmitSurveyResponse response) { /** - * Handles the external survey process after responding to a hero question. + * Handles the external survey open process after responding to a hero question. */ + private void handleExternalSurvey() { if (externalSurveyUrl == null) return; @@ -294,9 +296,9 @@ private void handleExternalSurvey() { */ public void handleCompleteSurvey() { StudyResponse.Surveys currentSurvey = mStudyResponse.getSurveys().get(curSurveyIndex); - SurveyDbHelper.markSurveyAsCompleted(context, currentSurvey); - // Remove the hero question view - if (isVisibleOnStops) arrivalsList.removeHeaderView(surveyView); + SurveyDbHelper.markSurveyAsCompletedOrSkipped(context, currentSurvey, SurveyDbHelper.SURVEY_COMPLETED); + // Remove the hero question view from the arrivals list if it was previously visible on the stops view + handleRemoveSurveyFromArrivalsHeader(); } public void onSubmitSurveyFail() { @@ -309,4 +311,35 @@ public void setCurrentStop(ObaStop stop) { Log.d("CurrentStopID", currentStop.getId() + " "); Log.d("CurrentStopRoutes", Arrays.toString(currentStop.getRouteIds())); } + + public void handleRemoveSurveyFromArrivalsHeader() { + if (!isVisibleOnStops || arrivalsList == null) return; + arrivalsList.removeHeaderView(surveyView); + } + + public void setupSurveyDismissDialog() { + SurveyDialogActions.setDialogActionListener(this); + SurveyViewUtils.createDismissSurveyDialog(context); + } + + /** + * Handles skipping the survey. The survey will be marked as skipped in the database with a state value of 2 + * indicating it was not completed by the user. + */ + @Override + public void onSkipSurvey() { + handleRemoveSurveyFromArrivalsHeader(); + StudyResponse.Surveys currentSurvey = mStudyResponse.getSurveys().get(curSurveyIndex); + SurveyDbHelper.markSurveyAsCompletedOrSkipped(context, currentSurvey, SurveyDbHelper.SURVEY_SKIPPED); + } + + @Override + public void onRemindMeLater() { + // TODO: implement + } + + @Override + public void onCancelSurvey() { + // By default will dismiss the survey + } } diff --git a/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/SurveyLocalData.java b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/SurveyPreferences.java similarity index 85% rename from onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/SurveyLocalData.java rename to onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/SurveyPreferences.java index 032e06215..a40027c2f 100644 --- a/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/SurveyLocalData.java +++ b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/SurveyPreferences.java @@ -2,13 +2,10 @@ import android.content.Context; import android.content.SharedPreferences; -import org.json.JSONObject; -import org.json.JSONException; -import java.util.HashMap; -import java.util.Iterator; + import java.util.UUID; -public class SurveyLocalData { +public class SurveyPreferences { private static final String PREFS_NAME = "survey_pref"; private static final String UUID_KEY = "my_uuid"; diff --git a/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/entity/Survey.kt b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/entity/Survey.kt index c4e79294b..d331c2fcc 100644 --- a/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/entity/Survey.kt +++ b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/entity/Survey.kt @@ -13,5 +13,5 @@ import androidx.room.PrimaryKey )] ) data class Survey( - @PrimaryKey val survey_id: Int, val study_id: Int, val name: String + @PrimaryKey val survey_id: Int, val study_id: Int, val name: String, val state:Int ) diff --git a/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/utils/SurveyDbHelper.kt b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/utils/SurveyDbHelper.kt index 5e9f5a87e..0acc714d0 100644 --- a/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/utils/SurveyDbHelper.kt +++ b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/utils/SurveyDbHelper.kt @@ -13,17 +13,21 @@ import org.onebusaway.android.ui.survey.entity.Survey import org.onebusaway.android.ui.survey.repository.SurveyRepository class SurveyDbHelper { + companion object { private val coroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + const val SURVEY_COMPLETED = 1 + const val SURVEY_SKIPPED = 2 + @JvmStatic - fun markSurveyAsCompleted(context: Context, survey: StudyResponse.Surveys) { + fun markSurveyAsCompletedOrSkipped(context: Context, survey: StudyResponse.Surveys, state:Int) { val surveyRepo = SurveyRepository(context) val newStudy = Study( survey.study.id, survey.study.name, survey.study.description, true ) - val newSurvey = Survey(survey.id, survey.study.id, survey.name) + val newSurvey = Survey(survey.id, survey.study.id, survey.name, state) coroutineScope.launch { try { surveyRepo.addOrUpdateStudy(newStudy) diff --git a/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/utils/SurveyUtils.java b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/utils/SurveyUtils.java index 3e68be26a..b25684398 100644 --- a/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/utils/SurveyUtils.java +++ b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/utils/SurveyUtils.java @@ -15,7 +15,7 @@ import org.onebusaway.android.R; import org.onebusaway.android.io.elements.ObaStop; import org.onebusaway.android.io.request.survey.model.StudyResponse; -import org.onebusaway.android.ui.survey.SurveyLocalData; +import org.onebusaway.android.ui.survey.SurveyPreferences; import java.util.ArrayList; import java.util.List; @@ -35,11 +35,11 @@ public class SurveyUtils { public static String getUserUUID(Context context) { - if (SurveyLocalData.getUserUUID(context) == null) { + if (SurveyPreferences.getUserUUID(context) == null) { UUID uuid = UUID.randomUUID(); - SurveyLocalData.saveUserUUID(context, uuid); + SurveyPreferences.saveUserUUID(context, uuid); } - return SurveyLocalData.getUserUUID(context); + return SurveyPreferences.getUserUUID(context); } public static List getSelectedCheckBoxAnswer(View view) { diff --git a/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/utils/SurveyViewUtils.java b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/utils/SurveyViewUtils.java index aa1678af7..e797ef0bc 100644 --- a/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/utils/SurveyViewUtils.java +++ b/onebusaway-android/src/main/java/org/onebusaway/android/ui/survey/utils/SurveyViewUtils.java @@ -1,5 +1,7 @@ package org.onebusaway.android.ui.survey.utils; +import android.annotation.SuppressLint; +import android.app.AlertDialog; import android.content.Context; import android.graphics.drawable.Drawable; import android.util.TypedValue; @@ -24,26 +26,31 @@ import org.onebusaway.android.R; import org.onebusaway.android.io.request.survey.model.StudyResponse; +import org.onebusaway.android.ui.survey.SurveyDialogActions; import java.util.List; public class SurveyViewUtils { - public static void showHeroQuestionButtons(View surveyView) { - showCloseBtn(surveyView); + public static void showHeroQuestionButtons(Context context, View surveyView) { + handleCLoseButton(context, surveyView); Button next = surveyView.findViewById(R.id.nextBtn); next.setVisibility(View.VISIBLE); } - public static void showExternalSurveyButtons(View surveyView) { - showCloseBtn(surveyView); + public static void showExternalSurveyButtons(Context context, View surveyView) { + handleCLoseButton(context, surveyView); Button openExternalSurveyBtn = surveyView.findViewById(R.id.openExternalSurveyBtn); openExternalSurveyBtn.setVisibility(View.VISIBLE); } - public static void showCloseBtn(View surveyView) { + public static void handleCLoseButton(Context context, View surveyView) { ImageButton closeBtn = surveyView.findViewById(R.id.close_btn); closeBtn.setVisibility(View.VISIBLE); + + closeBtn.setOnClickListener(v -> { + createDismissSurveyDialog(context).show(); + }); } public static void showQuestion(Context context, View rootView, StudyResponse.Surveys.Questions heroQuestion, String questionType) { @@ -76,6 +83,20 @@ public static void setupBottomSheetBehavior(BottomSheetDialog bottomSheet) { View parentLayout = bottomSheetDialog.findViewById(R.id.design_bottom_sheet); if (parentLayout != null) { BottomSheetBehavior behavior = BottomSheetBehavior.from(parentLayout); + // This disable bottom sheet draggable swipe + behavior.addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() { + @Override + public void onStateChanged(@NonNull View bottomSheet, int newState) { + if (newState == BottomSheetBehavior.STATE_DRAGGING) { + behavior.setState(BottomSheetBehavior.STATE_EXPANDED); + } + } + + @Override + public void onSlide(@NonNull View bottomSheet, float slideOffset) { + } + }); + ViewGroup.LayoutParams layoutParams = parentLayout.getLayoutParams(); layoutParams.height = WindowManager.LayoutParams.MATCH_PARENT; parentLayout.setLayoutParams(layoutParams); @@ -85,15 +106,29 @@ public static void setupBottomSheetBehavior(BottomSheetDialog bottomSheet) { } public static BottomSheetDialog createSurveyBottomSheetDialog(Context context) { - BottomSheetDialog bottomSheet = new BottomSheetDialog(context); + BottomSheetDialog bottomSheet = new BottomSheetDialog(context) { + @SuppressLint("MissingSuperCall") + @Override + public void onBackPressed() { + // Show a confirmation dialog when back is pressed + handleDismissSurveyBottomSheet(context, this); + } + }; bottomSheet.setContentView(R.layout.survey_questions_view); return bottomSheet; + } - public static void setupBottomSheetCloseButton(BottomSheetDialog bottomSheet) { + public static void handleDismissSurveyBottomSheet(Context context, BottomSheetDialog surveyBottomSheet) { + new AlertDialog.Builder(context).setMessage(R.string.are_you_sure_you_want_to_dismiss_the_survey).setPositiveButton(context.getString(R.string.rt_yes), (dialog, which) -> { + surveyBottomSheet.hide(); + }).setNegativeButton(context.getString(R.string.rt_no), null).show(); + } + + public static void setupBottomSheetCloseButton(Context context, BottomSheetDialog bottomSheet) { ImageButton closeBtn = bottomSheet.findViewById(R.id.close_btn); if (closeBtn != null) { - closeBtn.setOnClickListener(v -> bottomSheet.dismiss()); + closeBtn.setOnClickListener(v -> handleDismissSurveyBottomSheet(context, bottomSheet)); } } @@ -189,7 +224,16 @@ private static CheckBox createCheckBox(Context ctx, StudyResponse.Surveys.Questi return createButton(ctx, question, position, CheckBox.class); } - public static void showSharedInfoDetailsTextView(Context context,View surveyView, List questions) { + /** + * Displays a TextView containing user information that will be shared with an external survey. + * This method checks if there is any user information (in the form of survey questions) to be shared. + * If available, it constructs a message that lists the shared information and separates each piece of information with a comma. + * + * @param context The Context used to access resources. + * @param surveyView The View containing the TextView where the shared information will be displayed. + * @param questions A List of strings representing the user information that will be shared in the survey. + */ + public static void showSharedInfoDetailsTextView(Context context, View surveyView, List questions) { if (questions.isEmpty()) return; TextView sharedInfoTextView = surveyView.findViewById(R.id.shared_info_tv); StringBuilder surveySharedInfo = new StringBuilder(); @@ -203,7 +247,33 @@ public static void showSharedInfoDetailsTextView(Context context,View surveyView } sharedInfoTextView.setVisibility(View.VISIBLE); sharedInfoTextView.setText(surveySharedInfo); - } + /** + * Creates an AlertDialog for dismissing a survey. + * This dialog allows the user to either skip the survey, be reminded later, or cancel the action. + * + * @param context The context in which the dialog will be displayed. + * @return The AlertDialog instance to be shown configured for the dismiss survey dialog. + */ + public static AlertDialog.Builder createDismissSurveyDialog(Context context) { + AlertDialog.Builder dismissSurveyDialog = + new AlertDialog.Builder(context) + .setTitle(R.string.dismiss_survey_dialog_title) + .setMessage(R.string.dismiss_survey_dialog_body) + .setNegativeButton(R.string.survey_dismiss_dialog_skip_this_survey, (dialog, which) -> { + SurveyDialogActions.handleSkipSurvey(); + }) + .setNeutralButton(R.string.remind_me_latter, (dialog, which) -> { + SurveyDialogActions.handleRemindMeLater(); + }) + .setPositiveButton(R.string.cancel, (dialog, which) -> { + SurveyDialogActions.handleCancel(); + }) + .setCancelable(true); + + dismissSurveyDialog.create(); + return dismissSurveyDialog; + } + } diff --git a/onebusaway-android/src/main/res/values/strings.xml b/onebusaway-android/src/main/res/values/strings.xml index 3d6778bf4..0436b356a 100644 --- a/onebusaway-android/src/main/res/values/strings.xml +++ b/onebusaway-android/src/main/res/values/strings.xml @@ -1257,4 +1257,9 @@ Submitted Successfully Please fill all the questions "This survey will share your " + Are you sure you want to dismiss the survey? + Confirm Survey Dismissal + Remind Me Later\n + Skip This Survey + Your feedback helps transit agencies and developers improve your experience.