Skip to content

Commit f85c377

Browse files
Merge pull request #41 from D4rK7355608/codex/add-interactive-quiz-module
Add basic quiz module
2 parents a07b30a + 3ed1d99 commit f85c377

File tree

14 files changed

+489
-0
lines changed

14 files changed

+489
-0
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,15 @@
321321
android:exported="false"
322322
android:label="@string/navigation_drawer"
323323
android:parentActivityName=".ui.screens.android.lessons.navigation.drawer.NavigationDrawerActivity" />
324+
<activity
325+
android:name=".ui.screens.quiz.QuizActivity"
326+
android:exported="false"
327+
android:label="@string/quiz_title"
328+
android:parentActivityName=".ui.screens.quiz.QuizActivity" />
329+
330+
<receiver
331+
android:name=".notifications.receivers.QuizReminderReceiver"
332+
android:exported="false" />
324333

325334
<service
326335
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
[
2+
{
3+
"question": "What language is primarily used for Android app development?",
4+
"options": ["Python", "Java", "Swift", "Ruby"],
5+
"answer": 1
6+
},
7+
{
8+
"question": "Which file declares your app's activities?",
9+
"options": ["build.gradle", "AndroidManifest.xml", "strings.xml", "MainActivity.java"],
10+
"answer": 1
11+
},
12+
{
13+
"question": "Where do you store string resources?",
14+
"options": ["AndroidManifest.xml", "build.gradle", "strings.xml", "colors.xml"],
15+
"answer": 2
16+
},
17+
{
18+
"question": "Which layout allows positioning views relative to others?",
19+
"options": ["LinearLayout", "ConstraintLayout", "FrameLayout", "TableLayout"],
20+
"answer": 1
21+
},
22+
{
23+
"question": "What component shows a scrollable list of items?",
24+
"options": ["RecyclerView", "TextView", "ImageView", "Button"],
25+
"answer": 0
26+
}
27+
]
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.d4rk.androidtutorials.java.data.model;
2+
3+
/**
4+
* Model representing a multiple-choice quiz question.
5+
*/
6+
public record QuizQuestion(
7+
String question,
8+
String[] options,
9+
int answerIndex
10+
) {}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.d4rk.androidtutorials.java.notifications.managers;
2+
3+
import android.app.AlarmManager;
4+
import android.app.PendingIntent;
5+
import android.content.Context;
6+
import android.content.Intent;
7+
8+
import com.d4rk.androidtutorials.java.notifications.receivers.QuizReminderReceiver;
9+
10+
import java.util.concurrent.TimeUnit;
11+
12+
/**
13+
* Utility for scheduling daily quiz reminder notifications.
14+
*/
15+
public class QuizReminderManager {
16+
17+
private final AlarmManager alarmManager;
18+
private final PendingIntent reminderIntent;
19+
20+
public QuizReminderManager(Context context) {
21+
alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
22+
reminderIntent = PendingIntent.getBroadcast(
23+
context,
24+
1,
25+
new Intent(context, QuizReminderReceiver.class),
26+
PendingIntent.FLAG_IMMUTABLE
27+
);
28+
}
29+
30+
/** Schedule a repeating daily reminder. */
31+
public void scheduleDailyReminder() {
32+
long trigger = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1);
33+
alarmManager.setRepeating(
34+
AlarmManager.RTC_WAKEUP,
35+
trigger,
36+
TimeUnit.DAYS.toMillis(1),
37+
reminderIntent
38+
);
39+
}
40+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.d4rk.androidtutorials.java.notifications.receivers;
2+
3+
import android.content.BroadcastReceiver;
4+
import android.content.Context;
5+
import android.content.Intent;
6+
7+
import androidx.work.OneTimeWorkRequest;
8+
import androidx.work.WorkManager;
9+
10+
import com.d4rk.androidtutorials.java.notifications.workers.QuizReminderWorker;
11+
12+
/**
13+
* BroadcastReceiver that enqueues a {@link QuizReminderWorker}.
14+
*/
15+
public class QuizReminderReceiver extends BroadcastReceiver {
16+
@Override
17+
public void onReceive(Context context, Intent intent) {
18+
OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(
19+
QuizReminderWorker.class)
20+
.build();
21+
WorkManager.getInstance(context).enqueue(request);
22+
}
23+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.d4rk.androidtutorials.java.notifications.workers;
2+
3+
import android.app.NotificationChannel;
4+
import android.app.NotificationManager;
5+
import android.app.PendingIntent;
6+
import android.content.Context;
7+
import android.content.Intent;
8+
import android.os.Build;
9+
10+
import androidx.annotation.NonNull;
11+
import androidx.annotation.RequiresApi;
12+
import androidx.core.app.NotificationCompat;
13+
import androidx.work.Worker;
14+
import androidx.work.WorkerParameters;
15+
16+
import com.d4rk.androidtutorials.java.R;
17+
import com.d4rk.androidtutorials.java.ui.screens.quiz.QuizActivity;
18+
19+
/**
20+
* Worker that displays the daily quiz reminder notification.
21+
*/
22+
public class QuizReminderWorker extends Worker {
23+
24+
public QuizReminderWorker(@NonNull Context context, @NonNull WorkerParameters params) {
25+
super(context, params);
26+
}
27+
28+
@RequiresApi(api = Build.VERSION_CODES.O)
29+
@NonNull
30+
@Override
31+
public Result doWork() {
32+
NotificationManager manager = (NotificationManager) getApplicationContext()
33+
.getSystemService(Context.NOTIFICATION_SERVICE);
34+
String channelId = "quiz_reminder_channel";
35+
NotificationChannel channel = new NotificationChannel(
36+
channelId,
37+
getApplicationContext().getString(R.string.quiz_reminder_title),
38+
NotificationManager.IMPORTANCE_HIGH
39+
);
40+
manager.createNotificationChannel(channel);
41+
42+
Intent intent = new Intent(getApplicationContext(), QuizActivity.class);
43+
PendingIntent pendingIntent = PendingIntent.getActivity(
44+
getApplicationContext(), 0, intent, PendingIntent.FLAG_IMMUTABLE);
45+
46+
NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(), channelId)
47+
.setSmallIcon(R.drawable.ic_check_circle)
48+
.setContentTitle(getApplicationContext().getString(R.string.quiz_reminder_title))
49+
.setContentText(getApplicationContext().getString(R.string.quiz_reminder_body))
50+
.setAutoCancel(true)
51+
.setContentIntent(pendingIntent);
52+
53+
manager.notify(1001, builder.build());
54+
return Result.success();
55+
}
56+
}

app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/main/MainActivity.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import com.d4rk.androidtutorials.java.databinding.ActivityMainBinding;
3232
import com.d4rk.androidtutorials.java.notifications.managers.AppUpdateNotificationsManager;
3333
import com.d4rk.androidtutorials.java.notifications.managers.AppUsageNotificationsManager;
34+
import com.d4rk.androidtutorials.java.notifications.managers.QuizReminderManager;
3435
import com.d4rk.androidtutorials.java.ui.components.navigation.BottomSheetMenuFragment;
3536
import com.d4rk.androidtutorials.java.ui.screens.startup.StartupActivity;
3637
import com.d4rk.androidtutorials.java.ui.screens.startup.StartupViewModel;
@@ -258,6 +259,8 @@ protected void onResume() {
258259
}
259260
AppUsageNotificationsManager appUsageNotificationsManager = new AppUsageNotificationsManager(this);
260261
appUsageNotificationsManager.scheduleAppUsageCheck();
262+
QuizReminderManager quizReminderManager = new QuizReminderManager(this);
263+
quizReminderManager.scheduleDailyReminder();
261264
appUpdateNotificationsManager.checkAndSendUpdateNotification();
262265
checkForImmediateUpdate();
263266
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package com.d4rk.androidtutorials.java.ui.screens.quiz;
2+
3+
import android.os.Bundle;
4+
import android.view.MenuItem;
5+
6+
import androidx.annotation.NonNull;
7+
import androidx.appcompat.app.ActionBar;
8+
import androidx.appcompat.app.AppCompatActivity;
9+
import androidx.lifecycle.ViewModelProvider;
10+
11+
import com.d4rk.androidtutorials.java.R;
12+
import com.d4rk.androidtutorials.java.data.model.QuizQuestion;
13+
import com.d4rk.androidtutorials.java.databinding.ActivityQuizBinding;
14+
import com.d4rk.androidtutorials.java.utils.EdgeToEdgeDelegate;
15+
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
16+
import android.view.LayoutInflater;
17+
import android.view.View;
18+
import android.widget.TextView;
19+
import com.airbnb.lottie.LottieAnimationView;
20+
21+
/**
22+
* Activity that displays a simple multiple-choice quiz.
23+
*/
24+
public class QuizActivity extends AppCompatActivity {
25+
26+
private ActivityQuizBinding binding;
27+
private QuizViewModel viewModel;
28+
29+
@Override
30+
protected void onCreate(Bundle savedInstanceState) {
31+
super.onCreate(savedInstanceState);
32+
binding = ActivityQuizBinding.inflate(getLayoutInflater());
33+
setContentView(binding.getRoot());
34+
35+
EdgeToEdgeDelegate edgeToEdgeDelegate = new EdgeToEdgeDelegate(this);
36+
edgeToEdgeDelegate.applyEdgeToEdge(binding.container);
37+
38+
ActionBar actionBar = getSupportActionBar();
39+
if (actionBar != null) {
40+
actionBar.setDisplayHomeAsUpEnabled(true);
41+
}
42+
43+
viewModel = new ViewModelProvider(this).get(QuizViewModel.class);
44+
if (viewModel.getTotalQuestions() == 0) {
45+
new MaterialAlertDialogBuilder(this)
46+
.setMessage(R.string.quiz_no_more_questions)
47+
.setPositiveButton(android.R.string.ok, (d, w) -> finish())
48+
.setCancelable(false)
49+
.show();
50+
return;
51+
}
52+
showQuestion(viewModel.getCurrentQuestion());
53+
54+
binding.buttonNext.setOnClickListener(v -> onNextClicked());
55+
}
56+
57+
private void onNextClicked() {
58+
int selectedId = binding.optionsGroup.getCheckedRadioButtonId();
59+
int selectedIndex = -1;
60+
if (selectedId == binding.option1.getId()) {
61+
selectedIndex = 0;
62+
} else if (selectedId == binding.option2.getId()) {
63+
selectedIndex = 1;
64+
} else if (selectedId == binding.option3.getId()) {
65+
selectedIndex = 2;
66+
} else if (selectedId == binding.option4.getId()) {
67+
selectedIndex = 3;
68+
}
69+
if (selectedIndex != -1) {
70+
viewModel.answer(selectedIndex);
71+
}
72+
if (viewModel.getCurrentIndex().getValue() >= viewModel.getTotalQuestions()) {
73+
showResult();
74+
} else {
75+
showQuestion(viewModel.getCurrentQuestion());
76+
binding.optionsGroup.clearCheck();
77+
}
78+
}
79+
80+
private void showQuestion(QuizQuestion question) {
81+
if (question == null) {
82+
return;
83+
}
84+
binding.textQuestion.setText(question.question());
85+
binding.option1.setText(question.options()[0]);
86+
binding.option2.setText(question.options()[1]);
87+
binding.option3.setText(question.options()[2]);
88+
binding.option4.setText(question.options()[3]);
89+
}
90+
91+
private void showResult() {
92+
int score = viewModel.getScore().getValue();
93+
int total = viewModel.getTotalQuestions();
94+
View view = LayoutInflater.from(this).inflate(R.layout.dialog_quiz_result, null, false);
95+
TextView textResult = view.findViewById(R.id.text_result);
96+
textResult.setText(getString(R.string.quiz_finished, score, total));
97+
LottieAnimationView animationView = view.findViewById(R.id.animation_success);
98+
animationView.playAnimation();
99+
new MaterialAlertDialogBuilder(this)
100+
.setView(view)
101+
.setPositiveButton(android.R.string.ok, (d, w) -> finish())
102+
.setCancelable(false)
103+
.show();
104+
}
105+
106+
@Override
107+
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
108+
if (item.getItemId() == android.R.id.home) {
109+
finish();
110+
return true;
111+
}
112+
return super.onOptionsItemSelected(item);
113+
}
114+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.d4rk.androidtutorials.java.ui.screens.quiz;
2+
3+
import android.app.Application;
4+
5+
import androidx.annotation.NonNull;
6+
import androidx.lifecycle.AndroidViewModel;
7+
import androidx.lifecycle.LiveData;
8+
import androidx.lifecycle.MutableLiveData;
9+
10+
import com.d4rk.androidtutorials.java.data.model.QuizQuestion;
11+
import com.d4rk.androidtutorials.java.ui.screens.quiz.repository.QuizRepository;
12+
13+
import java.util.List;
14+
15+
/**
16+
* ViewModel managing quiz state and scoring.
17+
*/
18+
public class QuizViewModel extends AndroidViewModel {
19+
20+
private final List<QuizQuestion> questions;
21+
private final MutableLiveData<Integer> currentIndex = new MutableLiveData<>(0);
22+
private final MutableLiveData<Integer> score = new MutableLiveData<>(0);
23+
24+
public QuizViewModel(@NonNull Application application) {
25+
super(application);
26+
QuizRepository repository = new QuizRepository(application);
27+
questions = repository.loadQuestions();
28+
}
29+
30+
public QuizQuestion getCurrentQuestion() {
31+
if (questions.isEmpty()) return null;
32+
int index = currentIndex.getValue();
33+
return questions.get(Math.min(index, questions.size() - 1));
34+
}
35+
36+
public LiveData<Integer> getCurrentIndex() {
37+
return currentIndex;
38+
}
39+
40+
public LiveData<Integer> getScore() {
41+
return score;
42+
}
43+
44+
public void answer(int optionIndex) {
45+
QuizQuestion question = getCurrentQuestion();
46+
if (question != null && optionIndex == question.answerIndex()) {
47+
score.setValue(score.getValue() + 1);
48+
}
49+
currentIndex.setValue(currentIndex.getValue() + 1);
50+
}
51+
52+
public int getTotalQuestions() {
53+
return questions.size();
54+
}
55+
}

0 commit comments

Comments
 (0)