Panagiotis Triantafyllou

questionnaire part3

1 package ly.warp.sdk.activities; 1 package ly.warp.sdk.activities;
2 2
3 import android.app.Activity; 3 import android.app.Activity;
4 +import android.app.AlertDialog;
4 import android.content.Intent; 5 import android.content.Intent;
5 import android.os.Bundle; 6 import android.os.Bundle;
6 import android.os.Handler; 7 import android.os.Handler;
...@@ -34,6 +35,7 @@ import com.google.common.util.concurrent.MoreExecutors; ...@@ -34,6 +35,7 @@ import com.google.common.util.concurrent.MoreExecutors;
34 import com.google.common.util.concurrent.SettableFuture; 35 import com.google.common.util.concurrent.SettableFuture;
35 36
36 import org.json.JSONArray; 37 import org.json.JSONArray;
38 +import org.json.JSONObject;
37 39
38 import java.util.ArrayList; 40 import java.util.ArrayList;
39 import java.util.LinkedHashMap; 41 import java.util.LinkedHashMap;
...@@ -80,6 +82,8 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup ...@@ -80,6 +82,8 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup
80 private LinearLayout mSectionsContainer; 82 private LinearLayout mSectionsContainer;
81 private RelativeLayout mSectionsLoading; 83 private RelativeLayout mSectionsLoading;
82 private int mCurrentQuestionIndex = 0; 84 private int mCurrentQuestionIndex = 0;
85 + private ArrayList<User.QuestionnaireAnswer> mQuestionnaireAnswers = new ArrayList<>();
86 + private String[] mCustom1Selections = new String[3];
83 private SettableFuture<ArrayList<CarouselItem>> carouselFuture = SettableFuture.create(); 87 private SettableFuture<ArrayList<CarouselItem>> carouselFuture = SettableFuture.create();
84 private SettableFuture<LinkedHashMap<String, ArrayList<Couponset>>> couponsetsFuture = SettableFuture.create(); 88 private SettableFuture<LinkedHashMap<String, ArrayList<Couponset>>> couponsetsFuture = SettableFuture.create();
85 89
...@@ -117,7 +121,7 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup ...@@ -117,7 +121,7 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup
117 public void onSuccess(List<Object> result) { 121 public void onSuccess(List<Object> result) {
118 runOnUiThread(() -> { 122 runOnUiThread(() -> {
119 User user = WarplyManagerHelper.getUser(); 123 User user = WarplyManagerHelper.getUser();
120 - if (user != null && user.getQuestionnaireAnswers() == null && user.getQuestionnaire() != null) { 124 + if (user != null && (user.getQuestionnaireAnswers() == null || user.getQuestionnaireAnswers().isEmpty()) && user.getQuestionnaire() != null) {
121 new Handler(getMainLooper()).postDelayed(() -> showQuestionnaireDialog(user.getQuestionnaire()), 1000); 125 new Handler(getMainLooper()).postDelayed(() -> showQuestionnaireDialog(user.getQuestionnaire()), 1000);
122 } 126 }
123 }); 127 });
...@@ -138,8 +142,10 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup ...@@ -138,8 +142,10 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup
138 @Override 142 @Override
139 public void onClick(View v) { 143 public void onClick(View v) {
140 if (v.getId() == R.id.profile_icon) { 144 if (v.getId() == R.id.profile_icon) {
141 -// Intent myIntent = new Intent(HomeActivity.this, ProfileActivity.class); 145 + User user = WarplyManagerHelper.getUser();
142 -// startActivity(myIntent); 146 + if (user != null && user.getQuestionnaire() != null) {
147 + showQuestionnaireDialog(user.getQuestionnaire());
148 + }
143 return; 149 return;
144 } 150 }
145 if (v.getId() == R.id.ll_my_coupons) { 151 if (v.getId() == R.id.ll_my_coupons) {
...@@ -558,6 +564,18 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup ...@@ -558,6 +564,18 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup
558 }); 564 });
559 565
560 mCurrentQuestionIndex = 0; 566 mCurrentQuestionIndex = 0;
567 + mCustom1Selections = new String[3];
568 +
569 + // Seed session answers from server answers if they exist (button trigger / re-open case)
570 + // This ensures prefill works correctly when the user opens the questionnaire again
571 + User user = WarplyManagerHelper.getUser();
572 + ArrayList<User.QuestionnaireAnswer> serverAnswers = (user != null) ? user.getQuestionnaireAnswers() : null;
573 + if (serverAnswers != null && !serverAnswers.isEmpty()) {
574 + mQuestionnaireAnswers = new ArrayList<>(serverAnswers);
575 + } else {
576 + mQuestionnaireAnswers = new ArrayList<>();
577 + }
578 +
561 renderQuestion(questionnaire, dialogView, bottomSheetDialog); 579 renderQuestion(questionnaire, dialogView, bottomSheetDialog);
562 580
563 bottomSheetDialog.setOnShowListener(dialog -> { 581 bottomSheetDialog.setOnShowListener(dialog -> {
...@@ -607,6 +625,18 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup ...@@ -607,6 +625,18 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup
607 625
608 List<Integer> selectedPositions = new ArrayList<>(); 626 List<Integer> selectedPositions = new ArrayList<>();
609 627
628 + // Look up existing answer from session answers
629 + // mQuestionnaireAnswers is already seeded with server answers on dialog open,
630 + // so this single lookup covers both the auto-trigger and button-trigger cases
631 + User.QuestionnaireAnswer existingAnswer = null;
632 + for (User.QuestionnaireAnswer qa : mQuestionnaireAnswers) {
633 + if (qa.getQuestionId() == question.getId()) {
634 + existingAnswer = qa;
635 + break;
636 + }
637 + }
638 + final User.QuestionnaireAnswer prefillAnswer = existingAnswer;
639 +
610 if ("select".equals(type)) { 640 if ("select".equals(type)) {
611 tvQuestionHeaderSubtitle.setVisibility(View.GONE); 641 tvQuestionHeaderSubtitle.setVisibility(View.GONE);
612 642
...@@ -614,8 +644,11 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup ...@@ -614,8 +644,11 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup
614 TextView tvDropdownTitle = dropdownHeader.findViewById(R.id.tv_dropdown_option_header); 644 TextView tvDropdownTitle = dropdownHeader.findViewById(R.id.tv_dropdown_option_header);
615 ImageView ivArrow = dropdownHeader.findViewById(R.id.iv_terms_arrow); 645 ImageView ivArrow = dropdownHeader.findViewById(R.id.iv_terms_arrow);
616 646
617 - String optionHeaderText = options != null && options.length() > 0 ? options.optString(0) : "Επιλέξτε"; 647 + // Prefill: if answer exists, show it; otherwise show first option as placeholder
618 - tvDropdownTitle.setText(optionHeaderText); 648 + String prefillText = (prefillAnswer != null && !prefillAnswer.getAnswerList().isEmpty())
649 + ? prefillAnswer.getAnswerList().get(0)
650 + : (options != null && options.length() > 0 ? options.optString(0) : "Επιλέξτε");
651 + tvDropdownTitle.setText(prefillText);
619 WarpUtils.renderCustomFont(this, R.font.ping_lcg_regular, tvDropdownTitle); 652 WarpUtils.renderCustomFont(this, R.font.ping_lcg_regular, tvDropdownTitle);
620 653
621 llOptionsContainer.addView(dropdownHeader); 654 llOptionsContainer.addView(dropdownHeader);
...@@ -672,6 +705,15 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup ...@@ -672,6 +705,15 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup
672 } else if ("custom1".equals(type)) { 705 } else if ("custom1".equals(type)) {
673 String[] headers = {"Ενήλικες", "Παιδιά έως 9 ετών", "Παιδιά 10-18 ετών"}; 706 String[] headers = {"Ενήλικες", "Παιδιά έως 9 ετών", "Παιδιά 10-18 ετών"};
674 707
708 + // Prefill mCustom1Selections from existing answer
709 + mCustom1Selections = new String[3];
710 + if (prefillAnswer != null) {
711 + ArrayList<String> prefillList = prefillAnswer.getAnswerList();
712 + for (int k = 0; k < Math.min(prefillList.size(), 3); k++) {
713 + mCustom1Selections[k] = prefillList.get(k);
714 + }
715 + }
716 +
675 if (options != null) { 717 if (options != null) {
676 for (int i = 0; i < options.length(); i++) { 718 for (int i = 0; i < options.length(); i++) {
677 Object outerItem = options.opt(i); 719 Object outerItem = options.opt(i);
...@@ -705,7 +747,10 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup ...@@ -705,7 +747,10 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup
705 ); 747 );
706 dropdownHeader.setLayoutParams(dropdownParams); 748 dropdownHeader.setLayoutParams(dropdownParams);
707 749
708 - tvDropdownTitle.setText("Επιλέξτε"); 750 + // Prefill: show saved selection or default placeholder
751 + String custom1Prefill = (mCustom1Selections[i] != null && !mCustom1Selections[i].isEmpty())
752 + ? mCustom1Selections[i] : "Επιλέξτε";
753 + tvDropdownTitle.setText(custom1Prefill);
709 WarpUtils.renderCustomFont(this, R.font.ping_lcg_regular, tvDropdownTitle); 754 WarpUtils.renderCustomFont(this, R.font.ping_lcg_regular, tvDropdownTitle);
710 755
711 llCustomContainer.addView(dropdownHeader); 756 llCustomContainer.addView(dropdownHeader);
...@@ -749,8 +794,7 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup ...@@ -749,8 +794,7 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup
749 794
750 final int headerIndex = i; 795 final int headerIndex = i;
751 listPopupWindow.setOnItemClickListener((parent, view, position, id) -> { 796 listPopupWindow.setOnItemClickListener((parent, view, position, id) -> {
752 - selectedPositions.clear(); 797 + mCustom1Selections[headerIndex] = popupOptions.get(position);
753 - selectedPositions.add(headerIndex * 100 + position);
754 tvDropdownTitle.setText(popupOptions.get(position)); 798 tvDropdownTitle.setText(popupOptions.get(position));
755 listPopupWindow.dismiss(); 799 listPopupWindow.dismiss();
756 }); 800 });
...@@ -778,7 +822,15 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup ...@@ -778,7 +822,15 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup
778 TextView tvOptionTitle = optionView.findViewById(R.id.tv_option_title); 822 TextView tvOptionTitle = optionView.findViewById(R.id.tv_option_title);
779 tvOptionTitle.setText(optionText); 823 tvOptionTitle.setText(optionText);
780 WarpUtils.renderCustomFont(this, R.font.ping_lcg_bold, tvOptionTitle); 824 WarpUtils.renderCustomFont(this, R.font.ping_lcg_bold, tvOptionTitle);
825 +
826 + // Prefill: check if this option was previously selected
827 + boolean isPrefilled = prefillAnswer != null && prefillAnswer.getAnswerList().contains(optionText);
828 + if (isPrefilled) {
829 + llOptionContainer.setBackground(AppCompatResources.getDrawable(this, R.drawable.shape_questionnaire_option_selected));
830 + selectedPositions.add(position);
831 + } else {
781 llOptionContainer.setBackground(AppCompatResources.getDrawable(this, R.drawable.shape_questionnaire_option_unselected)); 832 llOptionContainer.setBackground(AppCompatResources.getDrawable(this, R.drawable.shape_questionnaire_option_unselected));
833 + }
782 834
783 optionView.setOnClickListener(v -> { 835 optionView.setOnClickListener(v -> {
784 if ("string".equals(type)) { 836 if ("string".equals(type)) {
...@@ -810,15 +862,81 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup ...@@ -810,15 +862,81 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup
810 } 862 }
811 863
812 llNextButton.setOnClickListener(v -> { 864 llNextButton.setOnClickListener(v -> {
865 + saveAnswer(question, selectedPositions, options, type);
813 if (mCurrentQuestionIndex < questionnaire.getQuestions().size() - 1) { 866 if (mCurrentQuestionIndex < questionnaire.getQuestions().size() - 1) {
814 mCurrentQuestionIndex++; 867 mCurrentQuestionIndex++;
815 renderQuestion(questionnaire, dialogView, dialog); 868 renderQuestion(questionnaire, dialogView, dialog);
816 } else { 869 } else {
817 - // Final screen, leave action empty as requested 870 + // Final screen: submit answers to the backend
871 + String questionnaireId = questionnaire.getQuestionnaireId();
872 + WarplyManager.saveQuestionnaire(mQuestionnaireAnswers, questionnaireId, new CallbackReceiver<JSONObject>() {
873 + @Override
874 + public void onSuccess(JSONObject result) {
875 + dialog.dismiss();
876 + WarplyManager.getUser(mUserReceiver);
877 +
878 + if (!isFinishing()) {
879 + new AlertDialog.Builder(HomeActivity.this)
880 + .setTitle(getString(R.string.demo_questionnaire_title))
881 + .setMessage("Οι απαντήσεις σου αποθηκεύτηκαν επιτυχώς!")
882 + .setPositiveButton("OK", (d, which) -> d.dismiss())
883 + .show();
884 + }
885 + }
886 +
887 + @Override
888 + public void onFailure(int errorCode) {
889 + dialog.dismiss();
890 + if (!isFinishing()) {
891 + new AlertDialog.Builder(HomeActivity.this)
892 + .setTitle(getString(R.string.demo_questionnaire_title))
893 + .setMessage("Κάτι πήγε στραβά. Παρακαλώ δοκίμασε ξανά.")
894 + .setPositiveButton("OK", (d, which) -> d.dismiss())
895 + .show();
896 + }
897 + }
898 + });
818 } 899 }
819 }); 900 });
820 } 901 }
821 902
903 + private void saveAnswer(User.Question question, List<Integer> selectedPositions, JSONArray options, String type) {
904 + // Remove any previously saved answer for this question_id (handles going back + re-answering)
905 + mQuestionnaireAnswers.removeIf(a -> a.getQuestionId() == question.getId());
906 +
907 + User.QuestionnaireAnswer answer = new User.QuestionnaireAnswer();
908 + answer.setQuestionId(question.getId());
909 + answer.setVersion(question.getVersion());
910 +
911 + if ("string".equals(type) || "select".equals(type)) {
912 + if (!selectedPositions.isEmpty()) {
913 + JSONArray arr = new JSONArray();
914 + arr.put(options.optString(selectedPositions.get(0)));
915 + answer.setAnswer(arr);
916 + answer.setSingleAnswer(true);
917 + mQuestionnaireAnswers.add(answer);
918 + }
919 + } else if ("multi".equals(type)) {
920 + if (!selectedPositions.isEmpty()) {
921 + JSONArray arr = new JSONArray();
922 + for (int pos : selectedPositions) {
923 + arr.put(options.optString(pos));
924 + }
925 + answer.setAnswer(arr);
926 + answer.setSingleAnswer(false);
927 + mQuestionnaireAnswers.add(answer);
928 + }
929 + } else if ("custom1".equals(type)) {
930 + JSONArray arr = new JSONArray();
931 + for (String sel : mCustom1Selections) {
932 + arr.put(sel != null ? sel : "");
933 + }
934 + answer.setAnswer(arr);
935 + answer.setSingleAnswer(false);
936 + mQuestionnaireAnswers.add(answer);
937 + }
938 + }
939 +
822 private final CallbackReceiver<ArrayList<CarouselItem>> mCarouselCallback = new CallbackReceiver<ArrayList<CarouselItem>>() { 940 private final CallbackReceiver<ArrayList<CarouselItem>> mCarouselCallback = new CallbackReceiver<ArrayList<CarouselItem>>() {
823 @Override 941 @Override
824 public void onSuccess(ArrayList<CarouselItem> result) { 942 public void onSuccess(ArrayList<CarouselItem> result) {
...@@ -882,4 +1000,14 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup ...@@ -882,4 +1000,14 @@ public class HomeActivity extends Activity implements View.OnClickListener, Coup
882 Toast.makeText(HomeActivity.this, "COUPONS ERROR", Toast.LENGTH_SHORT).show(); 1000 Toast.makeText(HomeActivity.this, "COUPONS ERROR", Toast.LENGTH_SHORT).show();
883 } 1001 }
884 }; 1002 };
1003 +
1004 + private final CallbackReceiver<User> mUserReceiver = new CallbackReceiver<User>() {
1005 + @Override
1006 + public void onSuccess(User user) {
1007 + }
1008 +
1009 + @Override
1010 + public void onFailure(int errorCode) {
1011 + }
1012 + };
885 } 1013 }
......
...@@ -72,7 +72,7 @@ public class User implements Parcelable, Serializable { ...@@ -72,7 +72,7 @@ public class User implements Parcelable, Serializable {
72 private String msisdn; 72 private String msisdn;
73 private String profileMetadata; 73 private String profileMetadata;
74 private Questionnaire questionnaire; 74 private Questionnaire questionnaire;
75 - private JSONObject questionnaireAnswers; 75 + private ArrayList<QuestionnaireAnswer> questionnaireAnswers;
76 private JSONObject userAnswers; 76 private JSONObject userAnswers;
77 private String userId; 77 private String userId;
78 private JSONArray userSegments; 78 private JSONArray userSegments;
...@@ -128,7 +128,18 @@ public class User implements Parcelable, Serializable { ...@@ -128,7 +128,18 @@ public class User implements Parcelable, Serializable {
128 this.msisdn = optNullableString(json, MSISDN); 128 this.msisdn = optNullableString(json, MSISDN);
129 this.profileMetadata = optNullableString(json, PROFILE_METADATA); 129 this.profileMetadata = optNullableString(json, PROFILE_METADATA);
130 this.questionnaire = json.isNull(QUESTIONNAIRE) ? null : new Questionnaire(json.optJSONObject(QUESTIONNAIRE)); 130 this.questionnaire = json.isNull(QUESTIONNAIRE) ? null : new Questionnaire(json.optJSONObject(QUESTIONNAIRE));
131 - this.questionnaireAnswers = json.optJSONObject(QUESTIONNAIRE_ANSWERS); 131 + JSONArray answersArr = json.optJSONArray(QUESTIONNAIRE_ANSWERS);
132 + if (answersArr != null) {
133 + this.questionnaireAnswers = new ArrayList<>();
134 + for (int i = 0; i < answersArr.length(); i++) {
135 + JSONObject answerJson = answersArr.optJSONObject(i);
136 + if (answerJson != null) {
137 + this.questionnaireAnswers.add(new QuestionnaireAnswer(answerJson));
138 + }
139 + }
140 + } else {
141 + this.questionnaireAnswers = null;
142 + }
132 this.userAnswers = json.optJSONObject(USER_ANSWERS); 143 this.userAnswers = json.optJSONObject(USER_ANSWERS);
133 this.userId = optNullableString(json, USER_ID); 144 this.userId = optNullableString(json, USER_ID);
134 this.userSegments = json.optJSONArray(USER_SEGMENTS); 145 this.userSegments = json.optJSONArray(USER_SEGMENTS);
...@@ -164,7 +175,18 @@ public class User implements Parcelable, Serializable { ...@@ -164,7 +175,18 @@ public class User implements Parcelable, Serializable {
164 } 175 }
165 try { 176 try {
166 String questionnaireAnswersStr = source.readString(); 177 String questionnaireAnswersStr = source.readString();
167 - this.questionnaireAnswers = questionnaireAnswersStr != null ? new JSONObject(questionnaireAnswersStr) : null; 178 + if (questionnaireAnswersStr != null) {
179 + this.questionnaireAnswers = new ArrayList<>();
180 + JSONArray answersArr = new JSONArray(questionnaireAnswersStr);
181 + for (int i = 0; i < answersArr.length(); i++) {
182 + JSONObject answerJson = answersArr.optJSONObject(i);
183 + if (answerJson != null) {
184 + this.questionnaireAnswers.add(new QuestionnaireAnswer(answerJson));
185 + }
186 + }
187 + } else {
188 + this.questionnaireAnswers = null;
189 + }
168 } catch (JSONException e) { 190 } catch (JSONException e) {
169 this.questionnaireAnswers = null; 191 this.questionnaireAnswers = null;
170 } 192 }
...@@ -194,7 +216,15 @@ public class User implements Parcelable, Serializable { ...@@ -194,7 +216,15 @@ public class User implements Parcelable, Serializable {
194 dest.writeString(this.consumerMetadata != null ? this.consumerMetadata.toString() : null); 216 dest.writeString(this.consumerMetadata != null ? this.consumerMetadata.toString() : null);
195 dest.writeString(this.favoriteCouponsets != null ? this.favoriteCouponsets.toString() : null); 217 dest.writeString(this.favoriteCouponsets != null ? this.favoriteCouponsets.toString() : null);
196 dest.writeString(this.questionnaire != null ? this.questionnaire.toString() : null); 218 dest.writeString(this.questionnaire != null ? this.questionnaire.toString() : null);
197 - dest.writeString(this.questionnaireAnswers != null ? this.questionnaireAnswers.toString() : null); 219 + if (this.questionnaireAnswers != null) {
220 + JSONArray answersArr = new JSONArray();
221 + for (QuestionnaireAnswer qa : this.questionnaireAnswers) {
222 + answersArr.put(qa.toJSONObject());
223 + }
224 + dest.writeString(answersArr.toString());
225 + } else {
226 + dest.writeString(null);
227 + }
198 dest.writeString(this.userAnswers != null ? this.userAnswers.toString() : null); 228 dest.writeString(this.userAnswers != null ? this.userAnswers.toString() : null);
199 dest.writeString(this.userSegments != null ? this.userSegments.toString() : null); 229 dest.writeString(this.userSegments != null ? this.userSegments.toString() : null);
200 } 230 }
...@@ -215,7 +245,15 @@ public class User implements Parcelable, Serializable { ...@@ -215,7 +245,15 @@ public class User implements Parcelable, Serializable {
215 jObj.put(MSISDN, this.msisdn != null ? this.msisdn : JSONObject.NULL); 245 jObj.put(MSISDN, this.msisdn != null ? this.msisdn : JSONObject.NULL);
216 jObj.put(PROFILE_METADATA, this.profileMetadata != null ? this.profileMetadata : JSONObject.NULL); 246 jObj.put(PROFILE_METADATA, this.profileMetadata != null ? this.profileMetadata : JSONObject.NULL);
217 jObj.put(QUESTIONNAIRE, this.questionnaire != null ? this.questionnaire.toJSONObject() : JSONObject.NULL); 247 jObj.put(QUESTIONNAIRE, this.questionnaire != null ? this.questionnaire.toJSONObject() : JSONObject.NULL);
218 - jObj.put(QUESTIONNAIRE_ANSWERS, this.questionnaireAnswers != null ? this.questionnaireAnswers : JSONObject.NULL); 248 + if (this.questionnaireAnswers != null) {
249 + JSONArray answersArr = new JSONArray();
250 + for (QuestionnaireAnswer qa : this.questionnaireAnswers) {
251 + answersArr.put(qa.toJSONObject());
252 + }
253 + jObj.put(QUESTIONNAIRE_ANSWERS, answersArr);
254 + } else {
255 + jObj.put(QUESTIONNAIRE_ANSWERS, JSONObject.NULL);
256 + }
219 jObj.put(USER_ANSWERS, this.userAnswers != null ? this.userAnswers : JSONObject.NULL); 257 jObj.put(USER_ANSWERS, this.userAnswers != null ? this.userAnswers : JSONObject.NULL);
220 jObj.put(USER_ID, this.userId != null ? this.userId : JSONObject.NULL); 258 jObj.put(USER_ID, this.userId != null ? this.userId : JSONObject.NULL);
221 jObj.put(USER_SEGMENTS, this.userSegments != null ? this.userSegments : JSONObject.NULL); 259 jObj.put(USER_SEGMENTS, this.userSegments != null ? this.userSegments : JSONObject.NULL);
...@@ -312,7 +350,7 @@ public class User implements Parcelable, Serializable { ...@@ -312,7 +350,7 @@ public class User implements Parcelable, Serializable {
312 return questionnaire; 350 return questionnaire;
313 } 351 }
314 352
315 - public JSONObject getQuestionnaireAnswers() { 353 + public ArrayList<QuestionnaireAnswer> getQuestionnaireAnswers() {
316 return questionnaireAnswers; 354 return questionnaireAnswers;
317 } 355 }
318 356
...@@ -372,7 +410,7 @@ public class User implements Parcelable, Serializable { ...@@ -372,7 +410,7 @@ public class User implements Parcelable, Serializable {
372 this.questionnaire = questionnaire; 410 this.questionnaire = questionnaire;
373 } 411 }
374 412
375 - public void setQuestionnaireAnswers(JSONObject questionnaireAnswers) { 413 + public void setQuestionnaireAnswers(ArrayList<QuestionnaireAnswer> questionnaireAnswers) {
376 this.questionnaireAnswers = questionnaireAnswers; 414 this.questionnaireAnswers = questionnaireAnswers;
377 } 415 }
378 416
...@@ -417,6 +455,108 @@ public class User implements Parcelable, Serializable { ...@@ -417,6 +455,108 @@ public class User implements Parcelable, Serializable {
417 return listData; 455 return listData;
418 } 456 }
419 457
458 + public static class QuestionnaireAnswer implements Parcelable, Serializable {
459 + private int questionId;
460 + private JSONArray answer; // Stored as JSONArray internally for uniformity
461 + private int version;
462 + private boolean isSingleAnswer; // Tracks original format for serialization
463 +
464 + public QuestionnaireAnswer() {}
465 +
466 + public QuestionnaireAnswer(JSONObject json) {
467 + if (json != null) {
468 + this.questionId = json.optInt("question_id", -1);
469 + this.version = json.optInt("version", 0);
470 + Object rawAnswer = json.opt("answer");
471 + if (rawAnswer instanceof JSONArray) {
472 + this.answer = (JSONArray) rawAnswer;
473 + this.isSingleAnswer = false;
474 + } else {
475 + // Single string answer — wrap into JSONArray for uniformity
476 + this.answer = new JSONArray();
477 + this.answer.put(rawAnswer != null ? rawAnswer.toString() : "");
478 + this.isSingleAnswer = true;
479 + }
480 + }
481 + }
482 +
483 + protected QuestionnaireAnswer(Parcel in) {
484 + questionId = in.readInt();
485 + version = in.readInt();
486 + isSingleAnswer = in.readByte() != 0;
487 + try {
488 + String answerStr = in.readString();
489 + answer = answerStr != null ? new JSONArray(answerStr) : null;
490 + } catch (JSONException e) {
491 + answer = null;
492 + }
493 + }
494 +
495 + @Override
496 + public void writeToParcel(Parcel dest, int flags) {
497 + dest.writeInt(questionId);
498 + dest.writeInt(version);
499 + dest.writeByte((byte) (isSingleAnswer ? 1 : 0));
500 + dest.writeString(answer != null ? answer.toString() : null);
501 + }
502 +
503 + @Override
504 + public int describeContents() {
505 + return 0;
506 + }
507 +
508 + public static final Creator<QuestionnaireAnswer> CREATOR = new Creator<QuestionnaireAnswer>() {
509 + @Override
510 + public QuestionnaireAnswer createFromParcel(Parcel in) {
511 + return new QuestionnaireAnswer(in);
512 + }
513 +
514 + @Override
515 + public QuestionnaireAnswer[] newArray(int size) {
516 + return new QuestionnaireAnswer[size];
517 + }
518 + };
519 +
520 + public JSONObject toJSONObject() {
521 + JSONObject jObj = new JSONObject();
522 + try {
523 + jObj.put("question_id", this.questionId);
524 + jObj.put("version", this.version);
525 + // Serialize back to original format
526 + if (this.isSingleAnswer && this.answer != null && this.answer.length() > 0) {
527 + jObj.put("answer", this.answer.optString(0));
528 + } else {
529 + jObj.put("answer", this.answer != null ? this.answer : JSONObject.NULL);
530 + }
531 + } catch (JSONException e) {
532 + if (WarpConstants.DEBUG) e.printStackTrace();
533 + }
534 + return jObj;
535 + }
536 +
537 + public int getQuestionId() { return questionId; }
538 + public void setQuestionId(int questionId) { this.questionId = questionId; }
539 + public JSONArray getAnswer() { return answer; }
540 + public void setAnswer(JSONArray answer) { this.answer = answer; }
541 + public boolean isSingleAnswer() { return isSingleAnswer; }
542 + public void setSingleAnswer(boolean singleAnswer) { isSingleAnswer = singleAnswer; }
543 + public int getVersion() { return version; }
544 + public void setVersion(int version) { this.version = version; }
545 +
546 + /**
547 + * Convenience method to get the answer as a list of strings.
548 + */
549 + public ArrayList<String> getAnswerList() {
550 + ArrayList<String> list = new ArrayList<>();
551 + if (answer != null) {
552 + for (int i = 0; i < answer.length(); i++) {
553 + list.add(answer.optString(i));
554 + }
555 + }
556 + return list;
557 + }
558 + }
559 +
420 public static class Questionnaire implements Parcelable, Serializable { 560 public static class Questionnaire implements Parcelable, Serializable {
421 private String questionnaireId; 561 private String questionnaireId;
422 private ArrayList<Question> questions; 562 private ArrayList<Question> questions;
......
...@@ -204,6 +204,18 @@ public interface ApiService { ...@@ -204,6 +204,18 @@ public interface ApiService {
204 @Header(WarpConstants.HEADER_SIGNATURE) String signature, 204 @Header(WarpConstants.HEADER_SIGNATURE) String signature,
205 @Header(WarpConstants.HEADER_AUTHORIZATION) String bearer); 205 @Header(WarpConstants.HEADER_AUTHORIZATION) String bearer);
206 206
207 + @Headers("Content-Type: application/json")
208 + @POST("/oauth/{appUuid}/context")
209 + Call<ResponseBody> saveQuestionnaire(@Path("appUuid") String appUuid,
210 + @Body RequestBody request,
211 + @Header(WarpConstants.HEADER_DATE) String timeStamp,
212 + @Header(WarpConstants.HEADER_LOYALTY_BUNDLE_ID) String bundleId,
213 + @Header(WarpConstants.HEADER_UNIQUE_DEVICE_ID) String deviceId,
214 + @Header(WarpConstants.HEADER_CHANNEL) String channel,
215 + @Header(WarpConstants.HEADER_WEB_ID) String webId,
216 + @Header(WarpConstants.HEADER_SIGNATURE) String signature,
217 + @Header(WarpConstants.HEADER_AUTHORIZATION) String bearer);
218 +
207 // =========================================================== 219 // ===========================================================
208 // Getter & Setter 220 // Getter & Setter
209 // =========================================================== 221 // ===========================================================
......
...@@ -1549,4 +1549,112 @@ public class WarplyManager { ...@@ -1549,4 +1549,112 @@ public class WarplyManager {
1549 }); 1549 });
1550 return future; 1550 return future;
1551 } 1551 }
1552 +
1553 + public static void saveQuestionnaire(ArrayList<User.QuestionnaireAnswer> answers, String questionnaireId, final CallbackReceiver<JSONObject> receiver) {
1554 + WarpUtils.log("************* WARPLY Questionnaire Request ********************");
1555 + WarpUtils.log("[WARP Trace] WARPLY Questionnaire Request is active");
1556 + WarpUtils.log("**************************************************");
1557 +
1558 + ApiService service = ApiClient.getRetrofitInstance().create(ApiService.class);
1559 + ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(1));
1560 +
1561 + ListenableFuture<JSONObject> futureFilters = saveQuestionnaireRetro(answers, questionnaireId, service);
1562 +
1563 + ListenableFuture<List<Object>> allResultsFuture = Futures.allAsList(futureFilters);
1564 + ListenableFuture<JSONObject> mergedResultFuture = Futures.transformAsync(allResultsFuture, results -> {
1565 + JSONObject resultFilters = (JSONObject) results.get(0);
1566 + return executorService.submit(() -> resultFilters);
1567 + }, executorService);
1568 +
1569 + Futures.addCallback(mergedResultFuture, new FutureCallback<JSONObject>() {
1570 + @Override
1571 + public void onSuccess(JSONObject mergedResult) {
1572 + executorService.shutdownNow();
1573 + new Handler(Looper.getMainLooper()).post(() -> receiver.onSuccess(mergedResult));
1574 + }
1575 +
1576 + @Override
1577 + public void onFailure(Throwable throwable) {
1578 + executorService.shutdownNow();
1579 + new Handler(Looper.getMainLooper()).post(() -> receiver.onFailure(2));
1580 + }
1581 + }, executorService);
1582 + }
1583 +
1584 + private static ListenableFuture<JSONObject> saveQuestionnaireRetro(ArrayList<User.QuestionnaireAnswer> answers, String questionnaireId, ApiService service) {
1585 + SettableFuture<JSONObject> future = SettableFuture.create();
1586 +
1587 + String timeStamp = DateFormat.format("yyyy-MM-dd hh:mm:ss", System.currentTimeMillis()).toString();
1588 + String apiKey = WarpUtils.getApiKey(Warply.getWarplyContext());
1589 + String webId = WarpUtils.getWebId(Warply.getWarplyContext());
1590 +
1591 + // Serialize answers list to JSONArray using toJSONObject() on each element
1592 + JSONArray answersJsonArray = new JSONArray();
1593 + for (User.QuestionnaireAnswer answer : answers) {
1594 + answersJsonArray.put(answer.toJSONObject());
1595 + }
1596 +
1597 + Map<String, Object> jsonParamsDetails = new ArrayMap<>();
1598 + Map<String, Object> jsonParams = new ArrayMap<>();
1599 + jsonParams.put("action", "handle_user_details");
1600 + jsonParams.put("process", "questionnaire");
1601 + jsonParams.put("del_empty", false);
1602 + Map<String, Object> jsonParamsQuestionnaireAnswers = new ArrayMap<>();
1603 + jsonParamsQuestionnaireAnswers.put("questionnaire_answers", answersJsonArray);
1604 + jsonParamsQuestionnaireAnswers.put("questionnaire_id", questionnaireId);
1605 + jsonParamsQuestionnaireAnswers.put("first_write", false);
1606 + jsonParams.put("data", jsonParamsQuestionnaireAnswers);
1607 + jsonParamsDetails.put("consumer_data", jsonParams);
1608 +
1609 + RequestBody detailsRequest = RequestBody.create(MediaType.get("application/json; charset=utf-8"), (new JSONObject(jsonParamsDetails)).toString());
1610 + Call<ResponseBody> detailsCall = service.saveQuestionnaire(
1611 + WarplyProperty.getAppUuid(Warply.getWarplyContext()),
1612 + detailsRequest,
1613 + timeStamp,
1614 + "android:" + Warply.getWarplyContext().getPackageName(),
1615 + new WarplyDeviceInfoCollector(Warply.getWarplyContext()).getUniqueDeviceId(),
1616 + "mobile",
1617 + webId,
1618 + WarpUtils.produceSignature(apiKey + timeStamp),
1619 + "Bearer " + WarplyDBHelper.getInstance(Warply.getWarplyContext()).getAuthValue("access_token")
1620 + );
1621 + detailsCall.enqueue(new Callback<ResponseBody>() {
1622 + @Override
1623 + public void onResponse(@NonNull Call<ResponseBody> call, @NonNull Response<ResponseBody> response) {
1624 + if (response.code() == 200 && response.body() != null) {
1625 + JSONObject jobjDetailsResponse = null;
1626 + try {
1627 + jobjDetailsResponse = new JSONObject(response.body().string());
1628 + } catch (Exception e) {
1629 + e.printStackTrace();
1630 + }
1631 + if (jobjDetailsResponse != null && jobjDetailsResponse.has("status") && jobjDetailsResponse.optString("status", "2").equals("1")) {
1632 + JSONObject newResult = new JSONObject();
1633 + try {
1634 + newResult.putOpt("status", 1);
1635 + newResult.putOpt("message", "Success");
1636 + future.set(newResult);
1637 + } catch (JSONException e) {
1638 + e.printStackTrace();
1639 + future.set(new JSONObject());
1640 + }
1641 + } else {
1642 + future.set(new JSONObject());
1643 + }
1644 + } else if (String.valueOf(response.code()).startsWith("5")) {
1645 + future.set(new JSONObject());
1646 + } else {
1647 +// future.set(new CouponsetsList());
1648 + future.setException(new Throwable());
1649 + }
1650 + }
1651 +
1652 + @Override
1653 + public void onFailure(@NonNull Call<ResponseBody> call, @NonNull Throwable t) {
1654 +// future.set(new CouponsetsList());
1655 + future.setException(new Throwable());
1656 + }
1657 + });
1658 + return future;
1659 + }
1552 } 1660 }
......