Par Clément Lavallée et Adrian-Paul Carrières
Après que l'utilisateur se soit connecté, il arrive sur la page de Menu qui est composé de différents fragments. Le principal est le fragment Home sur lequel on trouve le bouton pour commencer un quiz. Il y a aussi un fragment Score avec la liste des scores triés par ordre descendant et un fragment Friends qui n'a pas encore d'utilité.
En haut à droite du menu se trouve un bouton pour se déconnecter. Lors de la déconnexion, on retire le token qui a été créé lorsque l'utilisateur s'est connecté et on retourne sur la page de connexion.
public void deconnecterUtilisteur(MenuItem item) {
Utilisateur utilisateur = accesseurDAO.chercherUtilisateurParNom(nomUtilisateur);
accesseurDAO.retirerToken(utilisateur);
Intent intent = new Intent(this, LoginActivity.class);
startActivity(intent);
}
Les questions étant une liste de 5 questions (modifiable via la constante), on doit pouvoir les consommer. Nous avons décidé d'utiliser pour cela une stack, afin de pop les questions les unes après les autres.
On les récupère pour cela à la création de l'affichage grâce à l'API :
private void callApi() {
GetDataService service = RetrofitClientInstance.getRetrofitInstance().create(GetDataService.class);
Call<ApiResponse> call = service.getQuestions(NOMBRE_QUESTIONS, TYPE_QUESTIONS);
call.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(Call<ApiResponse> call, Response<ApiResponse> response) {
//Questions
Question[] questions = response.body().getResults();
for (Question question : questions) {
stackQuestions.push(question);
}
nouvelleQuestion();
}
@Override
public void onFailure(Call<ApiResponse> call, Throwable t) {
afficherToast("Une erreur est survenue.");
}
});
}
On va ensuite gérer le fait de passer à une nouvelle question (ou à la première !).
Chaque question étant minutée, il faut annuler le timer en cours (pour éviter que la fonction onFinish se déclenche pour rien) et remettre à 30 l'afficheur et la progress bar à 0.
private void nouvelleQuestion() {
//Utile dans le cas de la première question vu qu'il n'y a pas encore eu de timer
if (timer != null) {
timer.cancel();
temps_timer = 30;
pbar.setProgress(0);
}
On gère le cas où la stack serait vide (c'est à dire qu'il n'y a plus de questions)
S'il reste des questions, on en récupère une, on fait une liste de réponse dans laquelle on commenc par ajouter la bonne réponse puis la liste des mauvais réponses. On mélange le tout.
On gère l'affichage de la question et des réponses dans les boutons.
if (!stackQuestions.empty()) {
questionActuelle = stackQuestions.pop();
reponses = new ArrayList<>();
reponses.add(questionActuelle.getBonneReponse());
reponses.addAll(Arrays.asList(questionActuelle.getListeFaussesReponses()));
Collections.shuffle(reponses);
for (int i = 0; i < reponses.size(); i++) {
boutons[i].setText(reponses.get(i));
}
textViewQuestion.setText(questionActuelle.getQuestion());
On crée un minuteur qui va changer l'affichage du temps restant et de la progress bar, et, en arrivant à 0 passe à la question suivante (on revient donc au début de cette fonction).
timer = new CountDownTimer(30000, 1000) {
String s;
@Override
public void onTick(long millisUntilFinished) {
pbar.incrementProgressBy(1);
--temps_timer;
s = "" + temps_timer;
tps_restant.setText(s);
}
@Override
public void onFinish() {
pbar.incrementProgressBy(1);
temps_timer = 30;
s = "" + temps_timer;
tps_restant.setText(s);
afficherToast("Too slow");
nouvelleQuestion();
}
}.start();
Dans le cas où il ne reste plus de question, on va maintenant gérer le score.
On va le persister dans la base de donnée et mettre un minuteur pour gérer le fait d'attendre une seconde avant de revenir au menu.
} else {
//Passer au score !
accesseurScore.ajouterScore(new Score(nomUtilisateur, score));
CountDownTimer timerFinal = new CountDownTimer(1000, 1000) {
@Override
public void onTick(long millisUntilFinished) {
}
@Override
public void onFinish() {
afficherToast("Your score : "+score);
}
}.start();
this.finish();
}
}
Quant aux réponses, on change la couleur en fonction de si c'est une bonne réponse ou non (par rapport au texte du bouton et de la question actuelle).
On a ensuite un minuteur pour permettre à l'utilisateur de voir si c'est une bonne ou une mauvaise réponse. On en profite pour bloquer l'interaction aux boutons pour éviter de sélectionner plusieurs d'entre eux.
public void choixReponse(View v) {
if (((Button) v).getText() == questionActuelle.getBonneReponse()) {
v.setBackgroundColor(v.getContext().getResources().getColor(color.green));
score += temps_timer;
String scoreAsString = "" + score;
scoreJ.setText(scoreAsString);
} else {
v.setBackgroundColor(v.getContext().getResources().getColor(color.red));
}
toggleBouton(false);
CountDownTimer timer = new CountDownTimer(1000, 1000) {
@Override
public void onTick(long millisUntilFinished) {
}
@Override
public void onFinish() {
v.setBackgroundColor(v.getContext().getResources().getColor(color.white));
toggleBouton(true);
nouvelleQuestion();
}
};
timer.start();
}
private void toggleBouton(boolean etat){
for(int i = 0; i < boutons.length; i++){
boutons[i].setEnabled(etat);
}
}
On utilise la librairie Retrofit pour gérer les requêtes à l'API.
Je me suis basé sur le tutoriel suivant pour cela :
Retrofit- A simple Android tutorial
On peut modifier les paramètres de l'API via la requête, et cela se fait dans le code en changeant la constante NOMBRE_QUESTIONS de QCMActivity.
Dans BaseDeDonnees.java on peut voir notre utilisation de la base de données locale pour persister des informations, dont l'utilisateur, les scores et un genre de token d'identification pour sauter l'écran de connexion.
On y accède via les DAO, un peu de cette manière.
public List<Utilisateur> listerUtilisateurs(){
String LISTER_UTILISATEUR = "SELECT * FROM utilisateur";
Cursor curseur = accesseurBaseDeDonnees.getReadableDatabase().rawQuery(LISTER_UTILISATEUR, null);
this.listeUtilisateurs.clear();
int indexId_utilisateur = curseur.getColumnIndex("id_utilisateur");
int indexNom = curseur.getColumnIndex("nom");
int indexMotDePasse = curseur.getColumnIndex("mot_de_passe");
for(curseur.moveToFirst(); !curseur.isAfterLast(); curseur.moveToNext()){
int id_utilisateur = curseur.getInt(indexId_utilisateur);
String nom = curseur.getString(indexNom);
String motDePasse = curseur.getString(indexMotDePasse);
listeUtilisateurs.add(new Utilisateur(id_utilisateur, nom, motDePasse));
}
Log.d("userDAO", listeUtilisateurs.get(0).toString());
curseur.close();
return listeUtilisateurs;
}
Un point notable est l'utilisation de Prepared Statement pour se simplifier l'écriture des requêtes :
public void ajouterUtilisateur(Utilisateur utilisateur){
SQLiteDatabase db = accesseurBaseDeDonnees.getWritableDatabase();
SQLiteStatement query = db.compileStatement("INSERT INTO utilisateur(id_utilisateur, nom ,mot_de_passe) VALUES(null, ?, ?)");
query.bindString(1, utilisateur.getNom());
query.bindString(2, utilisateur.getMotDePasse());
query.execute();
listerUtilisateurs();
}