diff --git a/app/src/main/java/fr/free/nrw/commons/quiz/QuizActivity.java b/app/src/main/java/fr/free/nrw/commons/quiz/QuizActivity.java deleted file mode 100644 index 8c087b17b5..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/quiz/QuizActivity.java +++ /dev/null @@ -1,146 +0,0 @@ -package fr.free.nrw.commons.quiz; - -import android.content.Intent; -import android.os.Bundle; - -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; -import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat; - -import com.facebook.drawee.drawable.ProgressBarDrawable; -import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder; - -import fr.free.nrw.commons.databinding.ActivityQuizBinding; -import java.util.ArrayList; - -import fr.free.nrw.commons.R; - -public class QuizActivity extends AppCompatActivity { - - private ActivityQuizBinding binding; - private final QuizController quizController = new QuizController(); - private ArrayList quiz = new ArrayList<>(); - private int questionIndex = 0; - private int score; - /** - * isPositiveAnswerChecked : represents yes click event - */ - private boolean isPositiveAnswerChecked; - /** - * isNegativeAnswerChecked : represents no click event - */ - private boolean isNegativeAnswerChecked; - - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - binding = ActivityQuizBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - - quizController.initialize(this); - setSupportActionBar(binding.toolbar.toolbar); - binding.nextButton.setOnClickListener(view -> notKnowAnswer()); - displayQuestion(); - } - - /** - * to move to next question and check whether answer is selected or not - */ - public void setNextQuestion(){ - if ( questionIndex <= quiz.size() && (isPositiveAnswerChecked || isNegativeAnswerChecked)) { - evaluateScore(); - } - } - - public void notKnowAnswer(){ - customAlert("Information", quiz.get(questionIndex).getAnswerMessage()); - } - - /** - * to give warning before ending quiz - */ - @Override - public void onBackPressed() { - new AlertDialog.Builder(this) - .setTitle(getResources().getString(R.string.warning)) - .setMessage(getResources().getString(R.string.quiz_back_button)) - .setPositiveButton(R.string.continue_message, (dialog, which) -> { - final Intent intent = new Intent(this, QuizResultActivity.class); - dialog.dismiss(); - intent.putExtra("QuizResult", score); - startActivity(intent); - }) - .setNegativeButton("Cancel", (dialogInterface, i) -> dialogInterface.dismiss()) - .create() - .show(); - } - - /** - * to display the question - */ - public void displayQuestion() { - quiz = quizController.getQuiz(); - binding.question.questionText.setText(quiz.get(questionIndex).getQuestion()); - binding.questionTitle.setText( - getResources().getString(R.string.question) + - quiz.get(questionIndex).getQuestionNumber() - ); - binding.question.questionImage.setHierarchy(GenericDraweeHierarchyBuilder - .newInstance(getResources()) - .setFailureImage(VectorDrawableCompat.create(getResources(), - R.drawable.ic_error_outline_black_24dp, getTheme())) - .setProgressBarImage(new ProgressBarDrawable()) - .build()); - - binding.question.questionImage.setImageURI(quiz.get(questionIndex).getUrl()); - isPositiveAnswerChecked = false; - isNegativeAnswerChecked = false; - binding.answer.quizPositiveAnswer.setOnClickListener(view -> { - isPositiveAnswerChecked = true; - setNextQuestion(); - }); - binding.answer.quizNegativeAnswer.setOnClickListener(view -> { - isNegativeAnswerChecked = true; - setNextQuestion(); - }); - } - - /** - * to evaluate score and check whether answer is correct or wrong - */ - public void evaluateScore() { - if ((quiz.get(questionIndex).isAnswer() && isPositiveAnswerChecked) || - (!quiz.get(questionIndex).isAnswer() && isNegativeAnswerChecked) ){ - customAlert(getResources().getString(R.string.correct), - quiz.get(questionIndex).getAnswerMessage()); - score++; - } else { - customAlert(getResources().getString(R.string.wrong), - quiz.get(questionIndex).getAnswerMessage()); - } - } - - /** - * to display explanation after each answer, update questionIndex and move to next question - * @param title the alert title - * @param Message the alert message - */ - public void customAlert(final String title, final String Message) { - new AlertDialog.Builder(this) - .setTitle(title) - .setMessage(Message) - .setPositiveButton(R.string.continue_message, (dialog, which) -> { - questionIndex++; - if (questionIndex == quiz.size()) { - final Intent intent = new Intent(this, QuizResultActivity.class); - dialog.dismiss(); - intent.putExtra("QuizResult", score); - startActivity(intent); - } else { - displayQuestion(); - } - }) - .create() - .show(); - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/quiz/QuizActivity.kt b/app/src/main/java/fr/free/nrw/commons/quiz/QuizActivity.kt new file mode 100644 index 0000000000..a243c2637e --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/quiz/QuizActivity.kt @@ -0,0 +1,154 @@ +package fr.free.nrw.commons.quiz + +import android.annotation.SuppressLint +import android.content.Intent +import android.os.Bundle + +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat + +import com.facebook.drawee.drawable.ProgressBarDrawable +import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder + +import fr.free.nrw.commons.databinding.ActivityQuizBinding +import java.util.ArrayList + +import fr.free.nrw.commons.R + + +class QuizActivity : AppCompatActivity() { + + private lateinit var binding: ActivityQuizBinding + private val quizController = QuizController() + private var quiz = ArrayList() + private var questionIndex = 0 + private var score = 0 + + /** + * isPositiveAnswerChecked : represents yes click event + */ + private var isPositiveAnswerChecked = false + + /** + * isNegativeAnswerChecked : represents no click event + */ + private var isNegativeAnswerChecked = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityQuizBinding.inflate(layoutInflater) + setContentView(binding.root) + + quizController.initialize(this) + setSupportActionBar(binding.toolbar.toolbar) + binding.nextButton.setOnClickListener { notKnowAnswer() } + displayQuestion() + } + + /** + * To move to next question and check whether answer is selected or not + */ + fun setNextQuestion() { + if (questionIndex <= quiz.size && (isPositiveAnswerChecked || isNegativeAnswerChecked)) { + evaluateScore() + } + } + + private fun notKnowAnswer() { + customAlert("Information", quiz[questionIndex].answerMessage) + } + + /** + * To give warning before ending quiz + */ + override fun onBackPressed() { + AlertDialog.Builder(this) + .setTitle(getString(R.string.warning)) + .setMessage(getString(R.string.quiz_back_button)) + .setPositiveButton(R.string.continue_message) { dialog, _ -> + val intent = Intent(this, QuizResultActivity::class.java) + dialog.dismiss() + intent.putExtra("QuizResult", score) + startActivity(intent) + } + .setNegativeButton("Cancel") { dialogInterface, _ -> dialogInterface.dismiss() } + .create() + .show() + } + + /** + * To display the question + */ + @SuppressLint("SetTextI18n") + private fun displayQuestion() { + quiz = quizController.getQuiz() + binding.question.questionText.text = quiz[questionIndex].question + binding.questionTitle.text = getString(R.string.question) + quiz[questionIndex].questionNumber + + binding.question.questionImage.hierarchy = GenericDraweeHierarchyBuilder + .newInstance(resources) + .setFailureImage(VectorDrawableCompat.create(resources, R.drawable.ic_error_outline_black_24dp, theme)) + .setProgressBarImage(ProgressBarDrawable()) + .build() + + binding.question.questionImage.setImageURI(quiz[questionIndex].getUrl()) + isPositiveAnswerChecked = false + isNegativeAnswerChecked = false + + binding.answer.quizPositiveAnswer.setOnClickListener { + isPositiveAnswerChecked = true + setNextQuestion() + } + binding.answer.quizNegativeAnswer.setOnClickListener { + isNegativeAnswerChecked = true + setNextQuestion() + } + } + + /** + * To evaluate score and check whether answer is correct or wrong + */ + fun evaluateScore() { + if ( + (quiz[questionIndex].isAnswer && isPositiveAnswerChecked) + || + (!quiz[questionIndex].isAnswer && isNegativeAnswerChecked) + ) { + customAlert( + getString(R.string.correct), + quiz[questionIndex].answerMessage + ) + score++ + } else { + customAlert( + getString(R.string.wrong), + quiz[questionIndex].answerMessage + ) + } + } + + /** + * To display explanation after each answer, update questionIndex and move to next question + * @param title The alert title + * @param message The alert message + */ + fun customAlert(title: String, message: String) { + AlertDialog.Builder(this) + .setTitle(title) + .setMessage(message) + .setPositiveButton(R.string.continue_message) { dialog, _ -> + questionIndex++ + if (questionIndex == quiz.size) { + val intent = Intent(this, QuizResultActivity::class.java) + dialog.dismiss() + intent.putExtra("QuizResult", score) + startActivity(intent) + } else { + displayQuestion() + } + } + .create() + .show() + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/quiz/QuizChecker.java b/app/src/main/java/fr/free/nrw/commons/quiz/QuizChecker.java deleted file mode 100644 index 201c5bfc68..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/quiz/QuizChecker.java +++ /dev/null @@ -1,167 +0,0 @@ -package fr.free.nrw.commons.quiz; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.content.Intent; - -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Singleton; - -import fr.free.nrw.commons.R; -import fr.free.nrw.commons.WelcomeActivity; -import fr.free.nrw.commons.auth.SessionManager; -import fr.free.nrw.commons.kvstore.JsonKvStore; -import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient; -import fr.free.nrw.commons.utils.DialogUtil; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.CompositeDisposable; -import io.reactivex.schedulers.Schedulers; -import timber.log.Timber; - -/** - * fetches the number of images uploaded and number of images reverted. - * Then it calculates the percentage of the images reverted - * if the percentage of images reverted after last quiz exceeds 50% and number of images uploaded is - * greater than 50, then quiz is popped up - */ -@Singleton -public class QuizChecker { - - private int revertCount ; - private int totalUploadCount ; - private boolean isRevertCountFetched; - private boolean isUploadCountFetched; - - private CompositeDisposable compositeDisposable = new CompositeDisposable(); - - private final SessionManager sessionManager; - private final OkHttpJsonApiClient okHttpJsonApiClient; - private final JsonKvStore revertKvStore; - - private static final int UPLOAD_COUNT_THRESHOLD = 5; - private static final String REVERT_PERCENTAGE_FOR_MESSAGE = "50%"; - private final String REVERT_SHARED_PREFERENCE = "revertCount"; - private final String UPLOAD_SHARED_PREFERENCE = "uploadCount"; - - /** - * constructor to set the parameters for quiz - * @param sessionManager - * @param okHttpJsonApiClient - */ - @Inject - public QuizChecker(SessionManager sessionManager, - OkHttpJsonApiClient okHttpJsonApiClient, - @Named("default_preferences") JsonKvStore revertKvStore) { - this.sessionManager = sessionManager; - this.okHttpJsonApiClient = okHttpJsonApiClient; - this.revertKvStore = revertKvStore; - } - - public void initQuizCheck(Activity activity) { - calculateRevertParameterAndShowQuiz(activity); - } - - public void cleanup() { - compositeDisposable.clear(); - } - - /** - * to fet the total number of images uploaded - */ - private void setUploadCount() { - compositeDisposable.add(okHttpJsonApiClient - .getUploadCount(sessionManager.getUserName()) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::setTotalUploadCount, - t -> Timber.e(t, "Fetching upload count failed") - )); - } - - /** - * set the sub Title of Contibutions Activity and - * call function to check for quiz - * @param uploadCount user's upload count - */ - private void setTotalUploadCount(int uploadCount) { - totalUploadCount = uploadCount - revertKvStore.getInt(UPLOAD_SHARED_PREFERENCE, 0); - if ( totalUploadCount < 0){ - totalUploadCount = 0; - revertKvStore.putInt(UPLOAD_SHARED_PREFERENCE, 0); - } - isUploadCountFetched = true; - } - - /** - * To call the API to get reverts count in form of JSONObject - */ - private void setRevertCount() { - compositeDisposable.add(okHttpJsonApiClient - .getAchievements(sessionManager.getUserName()) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - response -> { - if (response != null) { - setRevertParameter(response.getDeletedUploads()); - } - }, throwable -> Timber.e(throwable, "Fetching feedback failed")) - ); - } - - /** - * to calculate the number of images reverted after previous quiz - * @param revertCountFetched count of deleted uploads - */ - private void setRevertParameter(int revertCountFetched) { - revertCount = revertCountFetched - revertKvStore.getInt(REVERT_SHARED_PREFERENCE, 0); - if (revertCount < 0){ - revertCount = 0; - revertKvStore.putInt(REVERT_SHARED_PREFERENCE, 0); - } - isRevertCountFetched = true; - } - - /** - * to check whether the criterion to call quiz is satisfied - */ - private void calculateRevertParameterAndShowQuiz(Activity activity) { - setUploadCount(); - setRevertCount(); - if ( revertCount < 0 || totalUploadCount < 0){ - revertKvStore.putInt(REVERT_SHARED_PREFERENCE, 0); - revertKvStore.putInt(UPLOAD_SHARED_PREFERENCE, 0); - return; - } - if (isRevertCountFetched && isUploadCountFetched && - totalUploadCount >= UPLOAD_COUNT_THRESHOLD && - (revertCount * 100) / totalUploadCount >= 50) { - callQuiz(activity); - } - } - - /** - * Alert which prompts to quiz - */ - @SuppressLint("StringFormatInvalid") - private void callQuiz(Activity activity) { - DialogUtil.showAlertDialog(activity, - activity.getString(R.string.quiz), - activity.getString(R.string.quiz_alert_message, REVERT_PERCENTAGE_FOR_MESSAGE), - activity.getString(R.string.about_translate_proceed), - activity.getString(android.R.string.cancel), - () -> startQuizActivity(activity), - null); - } - - private void startQuizActivity(Activity activity) { - int newRevetSharedPrefs = revertCount + revertKvStore.getInt(REVERT_SHARED_PREFERENCE, 0); - revertKvStore.putInt(REVERT_SHARED_PREFERENCE, newRevetSharedPrefs); - int newUploadCount = totalUploadCount + revertKvStore.getInt(UPLOAD_SHARED_PREFERENCE, 0); - revertKvStore.putInt(UPLOAD_SHARED_PREFERENCE, newUploadCount); - Intent i = new Intent(activity, WelcomeActivity.class); - i.putExtra("isQuiz", true); - activity.startActivity(i); - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/quiz/QuizChecker.kt b/app/src/main/java/fr/free/nrw/commons/quiz/QuizChecker.kt new file mode 100644 index 0000000000..ec74ecf6f3 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/quiz/QuizChecker.kt @@ -0,0 +1,175 @@ +package fr.free.nrw.commons.quiz + +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Intent + +import javax.inject.Inject +import javax.inject.Named +import javax.inject.Singleton + +import fr.free.nrw.commons.R +import fr.free.nrw.commons.WelcomeActivity +import fr.free.nrw.commons.auth.SessionManager +import fr.free.nrw.commons.kvstore.JsonKvStore +import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient +import fr.free.nrw.commons.utils.DialogUtil +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.schedulers.Schedulers +import timber.log.Timber + + +/** + * Fetches the number of images uploaded and number of images reverted. + * Then it calculates the percentage of the images reverted. + * If the percentage of images reverted after the last quiz exceeds 50% and number of images uploaded is + * greater than 50, then the quiz is popped up. + */ +@Singleton +class QuizChecker @Inject constructor( + private val sessionManager: SessionManager, + private val okHttpJsonApiClient: OkHttpJsonApiClient, + @Named("default_preferences") private val revertKvStore: JsonKvStore +) { + + private var revertCount = 0 + private var totalUploadCount = 0 + private var isRevertCountFetched = false + private var isUploadCountFetched = false + + private val compositeDisposable = CompositeDisposable() + + private val UPLOAD_COUNT_THRESHOLD = 5 + private val REVERT_PERCENTAGE_FOR_MESSAGE = "50%" + private val REVERT_SHARED_PREFERENCE = "revertCount" + private val UPLOAD_SHARED_PREFERENCE = "uploadCount" + + /** + * Initializes quiz check by calculating revert parameters and showing quiz if necessary + */ + fun initQuizCheck(activity: Activity) { + calculateRevertParameterAndShowQuiz(activity) + } + + /** + * Clears disposables to avoid memory leaks + */ + fun cleanup() { + compositeDisposable.clear() + } + + /** + * Fetches the total number of images uploaded + */ + private fun setUploadCount() { + compositeDisposable.add( + okHttpJsonApiClient.getUploadCount(sessionManager.userName) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { uploadCount -> setTotalUploadCount(uploadCount) }, + { t -> Timber.e(t, "Fetching upload count failed") } + ) + ) + } + + /** + * Sets the total upload count after subtracting stored preference + * @param uploadCount User's upload count + */ + private fun setTotalUploadCount(uploadCount: Int) { + totalUploadCount = uploadCount - revertKvStore.getInt( + UPLOAD_SHARED_PREFERENCE, + 0 + ) + if (totalUploadCount < 0) { + totalUploadCount = 0 + revertKvStore.putInt(UPLOAD_SHARED_PREFERENCE, 0) + } + isUploadCountFetched = true + } + + /** + * Fetches the revert count using the API + */ + private fun setRevertCount() { + compositeDisposable.add( + okHttpJsonApiClient.getAchievements(sessionManager.userName) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { response -> + response?.let { setRevertParameter(it.deletedUploads) } + }, + { throwable -> Timber.e(throwable, "Fetching feedback failed") } + ) + ) + } + + /** + * Calculates the number of images reverted after the previous quiz + * @param revertCountFetched Count of deleted uploads + */ + private fun setRevertParameter(revertCountFetched: Int) { + revertCount = revertCountFetched - revertKvStore.getInt(REVERT_SHARED_PREFERENCE, 0) + if (revertCount < 0) { + revertCount = 0 + revertKvStore.putInt(REVERT_SHARED_PREFERENCE, 0) + } + isRevertCountFetched = true + } + + /** + * Checks whether the criteria for calling the quiz are satisfied + */ + private fun calculateRevertParameterAndShowQuiz(activity: Activity) { + setUploadCount() + setRevertCount() + + if (revertCount < 0 || totalUploadCount < 0) { + revertKvStore.putInt(REVERT_SHARED_PREFERENCE, 0) + revertKvStore.putInt(UPLOAD_SHARED_PREFERENCE, 0) + return + } + + if (isRevertCountFetched && isUploadCountFetched && + totalUploadCount >= UPLOAD_COUNT_THRESHOLD && + (revertCount * 100) / totalUploadCount >= 50 + ) { + callQuiz(activity) + } + } + + /** + * Displays an alert prompting the user to take the quiz + */ + @SuppressLint("StringFormatInvalid") + private fun callQuiz(activity: Activity) { + DialogUtil.showAlertDialog( + activity, + activity.getString(R.string.quiz), + activity.getString(R.string.quiz_alert_message, REVERT_PERCENTAGE_FOR_MESSAGE), + activity.getString(R.string.about_translate_proceed), + activity.getString(android.R.string.cancel), + { startQuizActivity(activity) }, + null + ) + } + + /** + * Starts the quiz activity and updates preferences for revert and upload counts + */ + private fun startQuizActivity(activity: Activity) { + val newRevertSharedPrefs = revertCount + revertKvStore.getInt(REVERT_SHARED_PREFERENCE, 0) + revertKvStore.putInt(REVERT_SHARED_PREFERENCE, newRevertSharedPrefs) + + val newUploadCount = totalUploadCount + revertKvStore.getInt(UPLOAD_SHARED_PREFERENCE, 0) + revertKvStore.putInt(UPLOAD_SHARED_PREFERENCE, newUploadCount) + + val intent = Intent(activity, WelcomeActivity::class.java).apply { + putExtra("isQuiz", true) + } + activity.startActivity(intent) + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/quiz/QuizController.java b/app/src/main/java/fr/free/nrw/commons/quiz/QuizController.java deleted file mode 100644 index a7b2c94ef2..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/quiz/QuizController.java +++ /dev/null @@ -1,63 +0,0 @@ -package fr.free.nrw.commons.quiz; - -import android.content.Context; - -import java.util.ArrayList; - -import fr.free.nrw.commons.R; - -/** - * controls the quiz in the Activity - */ -public class QuizController { - - ArrayList quiz = new ArrayList<>(); - - private final String URL_FOR_SELFIE = "https://i.imgur.com/0fMYcpM.jpg"; - private final String URL_FOR_TAJ_MAHAL = "https://upload.wikimedia.org/wikipedia/commons/1/15/Taj_Mahal-03.jpg"; - private final String URL_FOR_BLURRY_IMAGE = "https://i.imgur.com/Kepb5jR.jpg"; - private final String URL_FOR_SCREENSHOT = "https://upload.wikimedia.org/wikipedia/commons/thumb/8/8b/Social_media_app_mockup_screenshot.svg/500px-Social_media_app_mockup_screenshot.svg.png"; - private final String URL_FOR_EVENT = "https://upload.wikimedia.org/wikipedia/commons/5/51/HouseBuildingInNorthernVietnam.jpg"; - - public void initialize(Context context){ - QuizQuestion q1 = new QuizQuestion(1, - context.getString(R.string.quiz_question_string), - URL_FOR_SELFIE, - false, - context.getString(R.string.selfie_answer)); - quiz.add(q1); - - QuizQuestion q2 = new QuizQuestion(2, - context.getString(R.string.quiz_question_string), - URL_FOR_TAJ_MAHAL, - true, - context.getString(R.string.taj_mahal_answer)); - quiz.add(q2); - - QuizQuestion q3 = new QuizQuestion(3, - context.getString(R.string.quiz_question_string), - URL_FOR_BLURRY_IMAGE, - false, - context.getString(R.string.blurry_image_answer)); - quiz.add(q3); - - QuizQuestion q4 = new QuizQuestion(4, - context.getString(R.string.quiz_screenshot_question), - URL_FOR_SCREENSHOT, - false, - context.getString(R.string.screenshot_answer)); - quiz.add(q4); - - QuizQuestion q5 = new QuizQuestion(5, - context.getString(R.string.quiz_question_string), - URL_FOR_EVENT, - true, - context.getString(R.string.construction_event_answer)); - quiz.add(q5); - - } - - public ArrayList getQuiz() { - return quiz; - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/quiz/QuizController.kt b/app/src/main/java/fr/free/nrw/commons/quiz/QuizController.kt new file mode 100644 index 0000000000..3cb4f52a67 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/quiz/QuizController.kt @@ -0,0 +1,76 @@ +package fr.free.nrw.commons.quiz + +import android.content.Context + +import java.util.ArrayList + +import fr.free.nrw.commons.R + + +/** + * Controls the quiz in the Activity + */ +class QuizController { + + private val quiz: ArrayList = ArrayList() + + private val URL_FOR_SELFIE = "https://i.imgur.com/0fMYcpM.jpg" + private val URL_FOR_TAJ_MAHAL = "https://upload.wikimedia.org/wikipedia/commons/1/15/Taj_Mahal-03.jpg" + private val URL_FOR_BLURRY_IMAGE = "https://i.imgur.com/Kepb5jR.jpg" + private val URL_FOR_SCREENSHOT = "https://upload.wikimedia.org/wikipedia/commons/thumb/8/8b/Social_media_app_mockup_screenshot.svg/500px-Social_media_app_mockup_screenshot.svg.png" + private val URL_FOR_EVENT = "https://upload.wikimedia.org/wikipedia/commons/5/51/HouseBuildingInNorthernVietnam.jpg" + + fun initialize(context: Context) { + val q1 = QuizQuestion( + 1, + context.getString(R.string.quiz_question_string), + URL_FOR_SELFIE, + false, + context.getString(R.string.selfie_answer) + ) + quiz.add(q1) + + val q2 = QuizQuestion( + 2, + context.getString(R.string.quiz_question_string), + URL_FOR_TAJ_MAHAL, + true, + context.getString(R.string.taj_mahal_answer) + ) + quiz.add(q2) + + val q3 = QuizQuestion( + 3, + context.getString(R.string.quiz_question_string), + URL_FOR_BLURRY_IMAGE, + false, + context.getString(R.string.blurry_image_answer) + ) + quiz.add(q3) + + val q4 = QuizQuestion( + 4, + context.getString(R.string.quiz_screenshot_question), + URL_FOR_SCREENSHOT, + false, + context.getString(R.string.screenshot_answer) + ) + quiz.add(q4) + + val q5 = QuizQuestion( + 5, + context.getString(R.string.quiz_question_string), + URL_FOR_EVENT, + true, + context.getString(R.string.construction_event_answer) + ) + quiz.add(q5) + } + + fun getQuiz(): ArrayList { + return quiz + } +} + + + diff --git a/app/src/main/java/fr/free/nrw/commons/quiz/QuizResultActivity.java b/app/src/main/java/fr/free/nrw/commons/quiz/QuizResultActivity.java deleted file mode 100644 index ec6d1070d0..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/quiz/QuizResultActivity.java +++ /dev/null @@ -1,188 +0,0 @@ -package fr.free.nrw.commons.quiz; - -import android.content.Context; -import android.content.Intent; -import android.graphics.Bitmap; -import android.net.Uri; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; - -import fr.free.nrw.commons.databinding.ActivityQuizResultBinding; -import java.io.File; -import java.io.FileOutputStream; - -import fr.free.nrw.commons.R; -import fr.free.nrw.commons.contributions.MainActivity; - - -/** - * Displays the final score of quiz and congratulates the user - */ -public class QuizResultActivity extends AppCompatActivity { - - private ActivityQuizResultBinding binding; - private final int NUMBER_OF_QUESTIONS = 5; - private final int MULTIPLIER_TO_GET_PERCENTAGE = 20; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - binding = ActivityQuizResultBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - - setSupportActionBar(binding.toolbar.toolbar); - - binding.quizResultNext.setOnClickListener(view -> launchContributionActivity()); - - if ( getIntent() != null) { - Bundle extras = getIntent().getExtras(); - int score = extras.getInt("QuizResult"); - setScore(score); - }else{ - startActivityWithFlags( - this, MainActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP, - Intent.FLAG_ACTIVITY_SINGLE_TOP); - super.onBackPressed(); - } - } - - @Override - protected void onDestroy() { - binding = null; - super.onDestroy(); - } - - /** - * to calculate and display percentage and score - * @param score - */ - public void setScore(int score) { - final int scorePercent = score * MULTIPLIER_TO_GET_PERCENTAGE; - binding.resultProgressBar.setProgress(scorePercent); - binding.tvResultProgress.setText(score +" / " + NUMBER_OF_QUESTIONS); - final String message = getResources().getString(R.string.congratulatory_message_quiz,scorePercent + "%"); - binding.congratulatoryMessage.setText(message); - } - - /** - * to go to Contibutions Activity - */ - public void launchContributionActivity(){ - startActivityWithFlags( - this, MainActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP, - Intent.FLAG_ACTIVITY_SINGLE_TOP); - } - - @Override - public void onBackPressed() { - startActivityWithFlags( - this, MainActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP, - Intent.FLAG_ACTIVITY_SINGLE_TOP); - super.onBackPressed(); - } - - /** - * Function to call intent to an activity - * @param context - * @param cls - * @param flags - * @param - */ - public static void startActivityWithFlags(Context context, Class cls, int... flags) { - Intent intent = new Intent(context, cls); - for (int flag: flags) { - intent.addFlags(flag); - } - context.startActivity(intent); - } - - /** - * to inflate menu - * @param menu - * @return - */ - @Override - public boolean onCreateOptionsMenu(Menu menu) { - // Inflate the menu; this adds items to the action bar if it is present. - getMenuInflater().inflate(R.menu.menu_about, menu); - return true; - } - - /** - * if share option selected then take screenshot and launch alert - * @param item - * @return - */ - @Override - public boolean onOptionsItemSelected(MenuItem item) { - int id = item.getItemId(); - if (id == R.id.share_app_icon) { - View rootView = getWindow().getDecorView().findViewById(android.R.id.content); - Bitmap screenShot = getScreenShot(rootView); - showAlert(screenShot); - } - - return super.onOptionsItemSelected(item); - } - - /** - * to store the screenshot of image in bitmap variable temporarily - * @param view - * @return - */ - public static Bitmap getScreenShot(View view) { - View screenView = view.getRootView(); - screenView.setDrawingCacheEnabled(true); - Bitmap bitmap = Bitmap.createBitmap(screenView.getDrawingCache()); - screenView.setDrawingCacheEnabled(false); - return bitmap; - } - - /** - * share the screenshot through social media - * @param bitmap - */ - void shareScreen(Bitmap bitmap) { - try { - File file = new File(this.getExternalCacheDir(),"screen.png"); - FileOutputStream fOut = new FileOutputStream(file); - bitmap.compress(Bitmap.CompressFormat.PNG, 100, fOut); - fOut.flush(); - fOut.close(); - file.setReadable(true, false); - final Intent intent = new Intent(android.content.Intent.ACTION_SEND); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file)); - intent.setType("image/png"); - startActivity(Intent.createChooser(intent, getString(R.string.share_image_via))); - } catch (Exception e) { - e.printStackTrace(); - } - } - - /** - * It display the alertDialog with Image of screenshot - * @param screenshot - */ - public void showAlert(Bitmap screenshot) { - AlertDialog.Builder alertadd = new AlertDialog.Builder(QuizResultActivity.this); - LayoutInflater factory = LayoutInflater.from(QuizResultActivity.this); - final View view = factory.inflate(R.layout.image_alert_layout, null); - ImageView screenShotImage = view.findViewById(R.id.alert_image); - screenShotImage.setImageBitmap(screenshot); - TextView shareMessage = view.findViewById(R.id.alert_text); - shareMessage.setText(R.string.quiz_result_share_message); - alertadd.setView(view); - alertadd.setPositiveButton(R.string.about_translate_proceed, (dialog, which) -> shareScreen(screenshot)); - alertadd.setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.cancel()); - alertadd.show(); - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/quiz/QuizResultActivity.kt b/app/src/main/java/fr/free/nrw/commons/quiz/QuizResultActivity.kt new file mode 100644 index 0000000000..1d4821ee3d --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/quiz/QuizResultActivity.kt @@ -0,0 +1,192 @@ +package fr.free.nrw.commons.quiz + +import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent +import android.graphics.Bitmap +import android.net.Uri +import android.os.Bundle +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.widget.ImageView +import android.widget.TextView + +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity + +import fr.free.nrw.commons.databinding.ActivityQuizResultBinding +import java.io.File +import java.io.FileOutputStream + +import fr.free.nrw.commons.R +import fr.free.nrw.commons.contributions.MainActivity + + +/** + * Displays the final score of quiz and congratulates the user + */ +class QuizResultActivity : AppCompatActivity() { + + private var binding: ActivityQuizResultBinding? = null + private val NUMBER_OF_QUESTIONS = 5 + private val MULTIPLIER_TO_GET_PERCENTAGE = 20 + + public override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityQuizResultBinding.inflate(layoutInflater) + setContentView(binding?.root) + + setSupportActionBar(binding?.toolbar?.toolbar) + + binding?.quizResultNext?.setOnClickListener { + launchContributionActivity() + } + + intent?.extras?.let { extras -> + val score = extras.getInt("QuizResult", 0) + setScore(score) + } ?: run { + startActivityWithFlags( + this, MainActivity::class.java, + Intent.FLAG_ACTIVITY_CLEAR_TOP, Intent.FLAG_ACTIVITY_SINGLE_TOP + ) + super.onBackPressed() + } + } + + override fun onDestroy() { + binding = null + super.onDestroy() + } + + /** + * To calculate and display percentage and score + * @param score + */ + @SuppressLint("StringFormatInvalid", "SetTextI18n") + fun setScore(score: Int) { + val scorePercent = score * MULTIPLIER_TO_GET_PERCENTAGE + binding?.resultProgressBar?.progress = scorePercent + binding?.tvResultProgress?.text = "$score / $NUMBER_OF_QUESTIONS" + val message = resources.getString(R.string.congratulatory_message_quiz, "$scorePercent%") + binding?.congratulatoryMessage?.text = message + } + + /** + * To go to Contributions Activity + */ + fun launchContributionActivity() { + startActivityWithFlags( + this, MainActivity::class.java, + Intent.FLAG_ACTIVITY_CLEAR_TOP, Intent.FLAG_ACTIVITY_SINGLE_TOP + ) + } + + override fun onBackPressed() { + startActivityWithFlags( + this, MainActivity::class.java, + Intent.FLAG_ACTIVITY_CLEAR_TOP, Intent.FLAG_ACTIVITY_SINGLE_TOP + ) + super.onBackPressed() + } + + /** + * Function to call intent to an activity + * @param context + * @param cls + * @param flags + */ + companion object { + fun startActivityWithFlags(context: Context, cls: Class, vararg flags: Int) { + val intent = Intent(context, cls) + flags.forEach { flag -> intent.addFlags(flag) } + context.startActivity(intent) + } + } + + /** + * To inflate menu + * @param menu + * @return + */ + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + // Inflate the menu; this adds items to the action bar if it is present. + menuInflater.inflate(R.menu.menu_about, menu) + return true + } + + /** + * If share option selected then take screenshot and launch alert + * @param item + * @return + */ + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == R.id.share_app_icon) { + val rootView = window.decorView.findViewById(android.R.id.content) + val screenShot = getScreenShot(rootView) + showAlert(screenShot) + } + return super.onOptionsItemSelected(item) + } + + /** + * To store the screenshot of image in bitmap variable temporarily + * @param view + * @return + */ + fun getScreenShot(view: View): Bitmap { + val screenView = view.rootView + screenView.isDrawingCacheEnabled = true + val bitmap = Bitmap.createBitmap(screenView.drawingCache) + screenView.isDrawingCacheEnabled = false + return bitmap + } + + /** + * Share the screenshot through social media + * @param bitmap + */ + @SuppressLint("SetWorldReadable") + fun shareScreen(bitmap: Bitmap) { + try { + val file = File(this.externalCacheDir, "screen.png") + FileOutputStream(file).use { fOut -> + bitmap.compress(Bitmap.CompressFormat.PNG, 100, fOut) + fOut.flush() + } + file.setReadable(true, false) + val intent = Intent(Intent.ACTION_SEND).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file)) + type = "image/png" + } + startActivity(Intent.createChooser(intent, getString(R.string.share_image_via))) + } catch (e: Exception) { + e.printStackTrace() + } + } + + /** + * It displays the AlertDialog with Image of screenshot + * @param screenshot + */ + fun showAlert(screenshot: Bitmap) { + val alertadd = AlertDialog.Builder(this) + val factory = LayoutInflater.from(this) + val view = factory.inflate(R.layout.image_alert_layout, null) + val screenShotImage = view.findViewById(R.id.alert_image) + screenShotImage.setImageBitmap(screenshot) + val shareMessage = view.findViewById(R.id.alert_text) + shareMessage.setText(R.string.quiz_result_share_message) + alertadd.setView(view) + alertadd.setPositiveButton(R.string.about_translate_proceed) { dialog, _ -> + shareScreen(screenshot) + } + alertadd.setNegativeButton(android.R.string.cancel) { dialog, _ -> + dialog.cancel() + } + alertadd.show() + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/quiz/RadioGroupHelper.java b/app/src/main/java/fr/free/nrw/commons/quiz/RadioGroupHelper.java deleted file mode 100644 index 79756871d8..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/quiz/RadioGroupHelper.java +++ /dev/null @@ -1,64 +0,0 @@ -package fr.free.nrw.commons.quiz; - -import android.app.Activity; -import android.view.View; -import android.widget.CompoundButton; -import android.widget.RadioButton; - -import java.util.ArrayList; -import java.util.List; - -/** - * Used to group to or more radio buttons to ensure - * that at a particular time only one of them is selected - */ -public class RadioGroupHelper { - - public List radioButtons = new ArrayList<>(); - - /** - * Constructor to group radio buttons - * @param radios - */ - public RadioGroupHelper(RadioButton... radios) { - super(); - for (RadioButton rb : radios) { - add(rb); - } - } - - /** - * Constructor to group radio buttons - * @param activity - * @param radiosIDs - */ - public RadioGroupHelper(Activity activity, int... radiosIDs) { - this(activity.findViewById(android.R.id.content),radiosIDs); - } - - /** - * Constructor to group radio buttons - * @param rootView - * @param radiosIDs - */ - public RadioGroupHelper(View rootView, int... radiosIDs) { - super(); - for (int radioButtonID : radiosIDs) { - add(rootView.findViewById(radioButtonID)); - } - } - - private void add(CompoundButton button){ - this.radioButtons.add(button); - button.setOnClickListener(onClickListener); - } - - /** - * listener to ensure only one of the radio button is selected - */ - View.OnClickListener onClickListener = v -> { - for (CompoundButton rb : radioButtons) { - if (rb != v) rb.setChecked(false); - } - }; -} diff --git a/app/src/main/java/fr/free/nrw/commons/quiz/RadioGroupHelper.kt b/app/src/main/java/fr/free/nrw/commons/quiz/RadioGroupHelper.kt new file mode 100644 index 0000000000..8afdf94c5c --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/quiz/RadioGroupHelper.kt @@ -0,0 +1,61 @@ +package fr.free.nrw.commons.quiz + +import android.app.Activity +import android.view.View +import android.widget.CompoundButton +import android.widget.RadioButton + +import java.util.ArrayList + +/** + * Used to group to or more radio buttons to ensure + * that at a particular time only one of them is selected + */ +class RadioGroupHelper { + + val radioButtons: MutableList = ArrayList() + + /** + * Constructor to group radio buttons + * @param radios + */ + constructor(vararg radios: RadioButton) { + for (rb in radios) { + add(rb) + } + } + + /** + * Constructor to group radio buttons + * @param activity + * @param radiosIDs + */ + constructor(activity: Activity, vararg radiosIDs: Int) : this( + *radiosIDs.map { id -> activity.findViewById(id) }.toTypedArray() + ) + + /** + * Constructor to group radio buttons + * @param rootView + * @param radiosIDs + */ + constructor(rootView: View, vararg radiosIDs: Int) { + for (radioButtonID in radiosIDs) { + add(rootView.findViewById(radioButtonID)) + } + } + + private fun add(button: CompoundButton) { + radioButtons.add(button) + button.setOnClickListener(onClickListener) + } + + /** + * listener to ensure only one of the radio button is selected + */ + private val onClickListener = View.OnClickListener { v -> + for (rb in radioButtons) { + if (rb != v) rb.isChecked = false + } + } +}