Migrated quiz module from Java to Kotlin (#5952)

* Rename .java to .kt

* Migrated quiz module to Kotlin

* unit test failing fixed

* unit test failing fixed
This commit is contained in:
Saifuddin Adenwala 2024-11-24 15:47:05 +05:30 committed by GitHub
parent bafae821e2
commit 00cfd83521
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 658 additions and 628 deletions

View file

@ -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<QuizQuestion> 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();
}
}

View file

@ -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<QuizQuestion>()
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()
}
}

View file

@ -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);
}
}

View file

@ -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)
}
}

View file

@ -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<QuizQuestion> 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<QuizQuestion> getQuiz() {
return quiz;
}
}

View file

@ -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<QuizQuestion> = 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<QuizQuestion> {
return quiz
}
}

View file

@ -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 <T>
*/
public static <T> void startActivityWithFlags(Context context, Class<T> 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();
}
}

View file

@ -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 <T> startActivityWithFlags(context: Context, cls: Class<T>, 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<View>(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<ImageView>(R.id.alert_image)
screenShotImage.setImageBitmap(screenshot)
val shareMessage = view.findViewById<TextView>(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()
}
}

View file

@ -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<CompoundButton> 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);
}
};
}

View file

@ -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<CompoundButton> = 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<RadioButton>(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
}
}
}