diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..64070e7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,66 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +# Gradle caches +.gradle/ + +# Ignore Android Studio specific files +.idea/ + +# Ignore build output directories +build/ +app/build/ + +# Ignore generated files +gen/ +out/ +generated/ + +# Log files +*.log + +# Bytecode files +*.class + +# Keystore files +*.keystore + +# Apk files +*.apk +*.ap_ + +# IntelliJ IDEA specific files +*.iws +*.ipr +*.iws + +# Windows specific files +Thumbs.db +ehthumbs.db +Desktop.ini + +# macOS specific files +.DS_Store +.AppleDouble +.LSOverride + +# Thumbnails +._* + +# Temporary files created by editors +*.swp +*.swo +*~ + diff --git a/README.md b/README.md index 12e5067..b57979b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# zen-maths-tutor +# MathMantra Maths-Tutor for smart devices diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..d3ecd62 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,51 @@ +plugins { + id("com.android.application") +} + +android { + namespace = "com.zendalona.mathmantra" + compileSdk = 34 + + defaultConfig { + applicationId = "com.zendalona.mathmantra" + minSdk = 30 + targetSdk = 34 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + viewBinding { + enable = true + } +} + +dependencies { + + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("com.google.android.material:material:1.11.0") + implementation("com.airbnb.android:lottie:4.2.2") + implementation("androidx.constraintlayout:constraintlayout:2.1.4") + implementation("androidx.fragment:fragment:1.8.2") + implementation("androidx.activity:activity:1.7.0") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + implementation("com.github.bumptech.glide:glide:4.15.1") + annotationProcessor("com.github.bumptech.glide:compiler:4.15.1") +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..5f9883d --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/zendalona/mathmantra/MainActivity.java b/app/src/main/java/com/zendalona/mathmantra/MainActivity.java new file mode 100644 index 0000000..c4b37a4 --- /dev/null +++ b/app/src/main/java/com/zendalona/mathmantra/MainActivity.java @@ -0,0 +1,80 @@ +package com.zendalona.mathmantra; + +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.view.WindowInsetsCompat; +import androidx.core.view.WindowInsetsControllerCompat; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; + +import android.os.Bundle; +import android.util.Log; + +import com.zendalona.mathmantra.databinding.ActivityMainBinding; +import com.zendalona.mathmantra.ui.DashboardFragment; +import com.zendalona.mathmantra.utils.FragmentNavigation; +import com.zendalona.mathmantra.utils.PermissionManager; + +import java.util.Optional; + +public class MainActivity extends AppCompatActivity implements FragmentNavigation { + private ActivityMainBinding binding; + private PermissionManager permissionManager; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + binding = ActivityMainBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + + // Set immersive full-screen mode + WindowInsetsControllerCompat controller = new WindowInsetsControllerCompat(getWindow(), getWindow().getDecorView()); + controller.hide(WindowInsetsCompat.Type.statusBars() | WindowInsetsCompat.Type.navigationBars()); + controller.setSystemBarsBehavior(WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE); + + if (savedInstanceState == null) loadFragment(new DashboardFragment(), FragmentTransaction.TRANSIT_FRAGMENT_OPEN); + + setSupportActionBar(binding.toolbar); + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) actionBar.setDisplayHomeAsUpEnabled(true); + + + //Permissions management + permissionManager = new PermissionManager(this, new PermissionManager.PermissionCallback() { + @Override + public void onPermissionGranted() { + Log.d("PermissionManager.PermissionCallback", "Granted!"); + } + @Override + public void onPermissionDenied() { + Log.w("PermissionManager.PermissionCallback", "Denied!"); + } + }); + // TODO : ask for the sensor permissions + permissionManager.requestMicrophonePermission(); +// permissionManager.requestAccelerometerPermission(); + } + + @Override + public boolean onSupportNavigateUp() { + loadFragment(new DashboardFragment(), FragmentTransaction.TRANSIT_FRAGMENT_CLOSE); + return true; + } + + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + permissionManager.handlePermissionsResult(requestCode, permissions, grantResults); + } + + public void loadFragment(Fragment fragment, int transition) { + FragmentManager fragmentManager = getSupportFragmentManager(); + FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); + fragmentTransaction.setTransition(transition); + fragmentTransaction.replace(binding.fragmentContainer.getId(), fragment); + // TODO : binding.toolbar.setTitle(); + fragmentTransaction.commit(); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/zendalona/mathmantra/MathMantra.java b/app/src/main/java/com/zendalona/mathmantra/MathMantra.java new file mode 100644 index 0000000..3024c35 --- /dev/null +++ b/app/src/main/java/com/zendalona/mathmantra/MathMantra.java @@ -0,0 +1,13 @@ +package com.zendalona.mathmantra; + +import android.app.Application; +import androidx.appcompat.app.AppCompatDelegate; + +public class MathMantra extends Application { + @Override + public void onCreate() { + super.onCreate(); + // Enforce Light Mode + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); + } +} diff --git a/app/src/main/java/com/zendalona/mathmantra/enums/Difficulty.java b/app/src/main/java/com/zendalona/mathmantra/enums/Difficulty.java new file mode 100644 index 0000000..d361f4d --- /dev/null +++ b/app/src/main/java/com/zendalona/mathmantra/enums/Difficulty.java @@ -0,0 +1,7 @@ +package com.zendalona.mathmantra.enums; + +public enum Difficulty { + EASY, + MEDIUM, + HARD +} diff --git a/app/src/main/java/com/zendalona/mathmantra/enums/Topic.java b/app/src/main/java/com/zendalona/mathmantra/enums/Topic.java new file mode 100644 index 0000000..88c3386 --- /dev/null +++ b/app/src/main/java/com/zendalona/mathmantra/enums/Topic.java @@ -0,0 +1,8 @@ +package com.zendalona.mathmantra.enums; + +public enum Topic { + ADDITION, + SUBTRACTION, + MULTIPLICATION, + DIVISION +} diff --git a/app/src/main/java/com/zendalona/mathmantra/ui/ChooseLessonFragment.java b/app/src/main/java/com/zendalona/mathmantra/ui/ChooseLessonFragment.java new file mode 100644 index 0000000..526b2b0 --- /dev/null +++ b/app/src/main/java/com/zendalona/mathmantra/ui/ChooseLessonFragment.java @@ -0,0 +1,155 @@ +package com.zendalona.mathmantra.ui; + +import android.content.Context; +import android.os.Bundle; + +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentTransaction; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.zendalona.mathmantra.databinding.FragmentChooseLessonBinding; +import com.zendalona.mathmantra.utils.FragmentNavigation; + +import java.util.ArrayList; +import java.util.Arrays; + +public class ChooseLessonFragment extends Fragment { + private FragmentChooseLessonBinding binding; + private FragmentNavigation navigationListener; + + + @Override + public void onAttach(Context context) { + super.onAttach(context); + if (context instanceof FragmentNavigation) navigationListener = (FragmentNavigation) context; + else throw new RuntimeException(context.toString() + " must implement FragmentNavigation"); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + binding = FragmentChooseLessonBinding.inflate(inflater, container, false); + ArrayList contents = new ArrayList<>(); + + + //Zero + binding.chooseLesson1.setOnClickListener(v -> { + contents.addAll(Arrays.asList("A long time ago in India, there was a very smart person named Aryabhatta. He loved learning about the stars and numbers. Aryabhatta did something very important for all of us: he helped people understand the number zero.", + + "Imagine you have a few sweets, but then you give them all away. You don't have any sweets left, right? Aryabhatta showed everyone that we can use the number zero to show that there's nothing left. Before Aryabhatta, people didn't have a way to write down 'zero' or 'nothing,' so his idea was really special.", + + "Aryabhatta's discovery of zero was like finding a missing piece of a puzzle. It helped people count and do math better. Thanks to him, we can do things like counting, adding, and even using technology like computers, all because we understand zero. Aryabhatta's ideas are still very important today, and we're grateful for his great work in India!", + + "Zero is a special number. It means nothing. When we have zero cookies, it means we have no cookies at all.", + + " Zero is also like a superhero in math! It helps us count, even when there's nothing.", + + " For example, when we start counting, we say zero, one, two, three. Zero is the start. ", + + " And if you add zero to any number, that number stays the same.", + + " Zero is cool because it helps us know when there is nothing.")); + sendDataToNarratorFragment(contents); + }); + + //Addition + binding.chooseLesson2.setOnClickListener(v -> { + contents.addAll(Arrays.asList("A long time ago, people wanted to find out how to combine things. Imagine you have two apples and then you get two more apples. Now you have four apples! This is called addition, and it's a way to find out how many things you have when you put them together.", + + "Addition is like making a bigger group. When you have a few toys and then get some more, you can count them all together using addition. It's like a magic trick that shows us the total number of things we have.", + + "Addition helps us in our daily lives. When you count your toys, candies, or even books, you’re using addition. It makes counting easier and faster.", + + "Thanks to addition, we can easily find out the total when we bring things together. It's a simple yet powerful tool we use every day!")); + sendDataToNarratorFragment(contents); + }); + + + //Subtraction + binding.chooseLesson3.setOnClickListener(v -> { + contents.addAll(Arrays.asList("Imagine you have five balloons, but then one flies away. Now you have four left. This is called subtraction, and it's how we find out how many things are left when we take some away.", + + "Subtraction is like taking things out of a group. When you share your cookies with friends, you can use subtraction to see how many you have left. It's a way to see what remains after giving some away.", + + "Subtraction is very helpful in our daily lives. Whether you’re counting how many candies you’ve eaten or how many toys you’ve given away, subtraction helps you know what’s left.", + + "Thanks to subtraction, we can figure out what happens when we take things away. It’s a simple and useful way to keep track of what we still have.")); + + sendDataToNarratorFragment(contents); + }); + + //Multiplication + binding.chooseLesson4.setOnClickListener(v -> { + contents.addAll(Arrays.asList("Once upon a time, people needed a faster way to add the same number many times. Imagine you have three bags, and each bag has four apples. To find out how many apples you have in total, you can use multiplication!", + + "Multiplication is like adding the same number again and again, but much faster. Instead of saying four plus four plus four, you can simply say three times four, which gives you twelve.", + + "Multiplication helps us when we need to group things quickly. Whether you're arranging chairs or counting stars in the sky, multiplication makes it easy to find out the total.", + + "Thanks to multiplication, we can solve big counting problems in a snap. It's like a shortcut that makes math easier!")); + sendDataToNarratorFragment(contents); + }); + + + //Division + binding.chooseLesson5.setOnClickListener(v -> { + contents.addAll(Arrays.asList("Imagine you have twelve cookies, and you want to share them equally with three friends. How many cookies will each friend get? This is where division comes in!", + + "Division is like sharing things equally among groups. It helps us find out how many things each person gets when we split them up. In our example, twelve cookies divided by three friends means each friend gets four cookies.", + + "Division is very useful in our daily lives. When you're sharing candies, books, or toys, division helps you make sure everyone gets the same amount.", + + "Thanks to division, we can split things evenly and fairly. It's a handy tool for making sure everyone gets their fair share!")); + sendDataToNarratorFragment(contents); + }); + + + //Percentage + binding.chooseLesson6.setOnClickListener(v -> { + contents.addAll(Arrays.asList("Long ago, people needed a way to talk about parts of a whole. Imagine you have a big pizza with 100 slices, and you eat 25 slices. You ate 25% of the pizza!", + + "Percentage is like a way to describe parts of something using the number 100. If you have 100 candies and eat 10, you ate 10% of the candies. It helps us understand how big or small a part is compared to the whole.", + + "Percentage is very helpful in daily life. Whether you're shopping, studying, or looking at scores, percentage tells you how much of something you have or need.", + + "Thanks to percentage, we can easily compare different amounts. It's a simple way to understand parts of a whole in everyday situations!")); + sendDataToNarratorFragment(contents); + }); + + + //Time + binding.chooseLesson7.setOnClickListener(v -> { + contents.addAll(Arrays.asList("A long time ago, people needed a way to measure how long things take. Imagine you’re playing a game, and you want to know how long you’ve been playing. This is where time comes in!", + + "Time is like a way to measure how long things last. We use clocks to see how many hours, minutes, or seconds have passed. It helps us know when to start and stop doing things.", + + "Time is very important in our daily lives. It helps us wake up in the morning, go to school, play, and even know when it's time to sleep.", + + "Thanks to time, we can organize our day and make sure we do everything we need to. It's a special tool that helps us manage our day-to-day activities!")); + sendDataToNarratorFragment(contents); + }); + + + return binding.getRoot(); + } + + private void sendDataToNarratorFragment(ArrayList contents){ + Bundle bundle = new Bundle(); + bundle.putStringArrayList("contents",contents); + + NarratorFragment narratorFragment = new NarratorFragment(); + + narratorFragment.setArguments(bundle); + + if (navigationListener != null) navigationListener.loadFragment(narratorFragment, FragmentTransaction.TRANSIT_FRAGMENT_OPEN); + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/zendalona/mathmantra/ui/CountNumbersFragment.java b/app/src/main/java/com/zendalona/mathmantra/ui/CountNumbersFragment.java new file mode 100644 index 0000000..35c9794 --- /dev/null +++ b/app/src/main/java/com/zendalona/mathmantra/ui/CountNumbersFragment.java @@ -0,0 +1,149 @@ +package com.zendalona.mathmantra.ui; + +import android.app.AlertDialog; +import android.os.Bundle; +import android.speech.RecognitionListener; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; + +import androidx.fragment.app.Fragment; + +import com.bumptech.glide.Glide; +import com.zendalona.mathmantra.R; +import com.zendalona.mathmantra.databinding.DialogResultBinding; +import com.zendalona.mathmantra.databinding.FragmentCountNumbersBinding; +import com.zendalona.mathmantra.utils.PermissionManager; +import com.zendalona.mathmantra.utils.RandomValueGenerator; +import com.zendalona.mathmantra.utils.SpeechRecognitionUtility; +import com.zendalona.mathmantra.utils.TTSUtility; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class CountNumbersFragment extends Fragment { + + private FragmentCountNumbersBinding binding; + private SpeechRecognitionUtility speechRecognitionUtil; + private TTSUtility tts; + private PermissionManager permissionManager; + private List correctSequence; + private RandomValueGenerator random; + private final int COUNT_RANGE = 50; + private String answer; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + binding = FragmentCountNumbersBinding.inflate(inflater, container, false); + random = new RandomValueGenerator(); + tts = new TTSUtility(requireActivity()); + answer = startGame(); + + // Initialize PermissionManager + permissionManager = new PermissionManager(requireActivity(), new PermissionManager.PermissionCallback() { + @Override + public void onPermissionGranted() { + // Initialize SpeechRecognitionUtility iff permission is granted + speechRecognitionUtil = new SpeechRecognitionUtility(requireActivity(), new SpeechRecognitionUtility.SpeechRecognitionCallback() { + @Override + public void onResults(String spokenText) { + binding.micAnimationView.pauseAnimation(); + String normalizedSpokenText = spokenText.replaceAll("\\s+", ""); + boolean isCorrect = normalizedSpokenText.equalsIgnoreCase(answer); + + if(!isCorrect) { + //tts.speak("You missed"); + List missingNumbers = correctSequence.stream().map(String::valueOf).collect(Collectors.toList()); + //TODO : missingNumbers.removeAll(spokenNumbers); + } + showResultDialog(isCorrect); + Log.d("Recite Numbers : spoken:-", spokenText); + Log.d("Recite Numbers : answerFormed:-", answer); + } + + @Override + public void onError(int error) { + binding.micAnimationView.pauseAnimation(); + Toast.makeText(requireActivity(),"Error counting" + error, Toast.LENGTH_SHORT).show(); + Log.e("Error in speech recognition : ", String.valueOf(error)); + } + }); + } + + @Override + public void onPermissionDenied() { + Toast.makeText(getContext(), "Permission Denied", Toast.LENGTH_SHORT).show(); + } + }); + + permissionManager.requestMicrophonePermission(); + + // Set up button listener to start listening + binding.startListeningButton.setOnClickListener(v -> { + binding.micAnimationView.playAnimation(); + if(speechRecognitionUtil != null) speechRecognitionUtil.startListening(); + else Log.d("CountNumbers :: startListeningBtnClicked", "speechRecognitionUtil value is null!"); + }); + + return binding.getRoot(); + } + + private String startGame() { + // Initialize the correct sequence and other variables + correctSequence = new ArrayList<>(); + StringBuilder answer = new StringBuilder(); + int[] range = random.generateNumberRangeForCount(COUNT_RANGE); + int start = range[0]; + int end = range[1]; + String message = "Count from " + start + " to " + end; + binding.countFromRangeTv.setText(message); + tts.speak(message); + for (int i = start; i <= end; i++) { + correctSequence.add(i); + answer.append(i); + } + return answer.toString(); + } + + private void showResultDialog(boolean isCorrect) { + String message = isCorrect ? "Very Good" : "Try again"; + int gifResource = isCorrect ? R.drawable.right : R.drawable.wrong; + + LayoutInflater inflater = getLayoutInflater(); + DialogResultBinding dialogBinding = DialogResultBinding.inflate(inflater); + View dialogView = dialogBinding.getRoot(); + + tts.speak(message); + + // Load the GIF using Glide + Glide.with(this) + .asGif() + .load(gifResource) + .into(dialogBinding.gifImageView); + + dialogBinding.messageTextView.setText(message); + + new AlertDialog.Builder(requireContext()) + .setView(dialogView) + .setPositiveButton("Continue", (dialog, which) -> { + dialog.dismiss(); + answer = startGame(); + }) + .create() + .show(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + if (speechRecognitionUtil != null) speechRecognitionUtil.destroy(); + if (tts != null) tts.shutdown(); + binding = null; + } + + + +} diff --git a/app/src/main/java/com/zendalona/mathmantra/ui/DashboardFragment.java b/app/src/main/java/com/zendalona/mathmantra/ui/DashboardFragment.java new file mode 100644 index 0000000..27443e4 --- /dev/null +++ b/app/src/main/java/com/zendalona/mathmantra/ui/DashboardFragment.java @@ -0,0 +1,61 @@ +package com.zendalona.mathmantra.ui; + +import android.content.Context; +import android.os.Bundle; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentTransaction; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.zendalona.mathmantra.databinding.FragmentDashboardBinding; +import com.zendalona.mathmantra.utils.FragmentNavigation; + +public class DashboardFragment extends Fragment { + + private FragmentDashboardBinding binding; + private FragmentNavigation navigationListener; + + + @Override + public void onAttach(Context context) { + super.onAttach(context); + if (context instanceof FragmentNavigation) navigationListener = (FragmentNavigation) context; + else throw new RuntimeException(context.toString() + " must implement FragmentNavigation"); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + binding = FragmentDashboardBinding.inflate(inflater, container, false); + + binding.ringBellCv.setOnClickListener(v -> { + if (navigationListener != null) navigationListener.loadFragment(new RingBellFragment(),FragmentTransaction.TRANSIT_FRAGMENT_OPEN); + }); + binding.tapTablaCv.setOnClickListener(v -> { + if (navigationListener != null) navigationListener.loadFragment(new TapTablaFragment(),FragmentTransaction.TRANSIT_FRAGMENT_OPEN); + }); + binding.speakNumbersCv.setOnClickListener(v -> { + if (navigationListener != null) navigationListener.loadFragment(new CountNumbersFragment(),FragmentTransaction.TRANSIT_FRAGMENT_OPEN); + }); + binding.ttsCv.setOnClickListener(v -> { + if (navigationListener != null) navigationListener.loadFragment(new ChooseLessonFragment(),FragmentTransaction.TRANSIT_FRAGMENT_OPEN); + }); + binding.mcqCv.setOnClickListener(v -> { + if (navigationListener != null) navigationListener.loadFragment(new MCQFragment(),FragmentTransaction.TRANSIT_FRAGMENT_OPEN); + }); + binding.mathQuizCv.setOnClickListener(v -> { + if (navigationListener != null) navigationListener.loadFragment(new MathQuizFragment(),FragmentTransaction.TRANSIT_FRAGMENT_OPEN); + }); + binding.numberLineCv.setOnClickListener(v -> { + if (navigationListener != null) navigationListener.loadFragment(new NumberLineFragment(),FragmentTransaction.TRANSIT_FRAGMENT_OPEN); + }); + + return binding.getRoot(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/zendalona/mathmantra/ui/MCQFragment.java b/app/src/main/java/com/zendalona/mathmantra/ui/MCQFragment.java new file mode 100644 index 0000000..fd9ce65 --- /dev/null +++ b/app/src/main/java/com/zendalona/mathmantra/ui/MCQFragment.java @@ -0,0 +1,128 @@ +package com.zendalona.mathmantra.ui; + +import android.app.AlertDialog; +import android.os.Bundle; + +import androidx.fragment.app.Fragment; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.bumptech.glide.Glide; +import com.zendalona.mathmantra.R; +import com.zendalona.mathmantra.databinding.DialogResultBinding; +import com.zendalona.mathmantra.databinding.FragmentMCQBinding; +import com.zendalona.mathmantra.enums.Difficulty; +import com.zendalona.mathmantra.utils.RandomValueGenerator; +import com.zendalona.mathmantra.utils.TTSUtility; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +public class MCQFragment extends Fragment { + + private FragmentMCQBinding binding; + private RandomValueGenerator random; + private TTSUtility tts; + + public MCQFragment() { + } + + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + binding = FragmentMCQBinding.inflate(inflater, container, false); + random = new RandomValueGenerator(); + tts = new TTSUtility(requireActivity()); + generateNewQuestion(); + return binding.getRoot(); + } + + private void generateNewQuestion() { + int topic = random.generateQuestionTopic(); + int[] numbers; + String operator = "+"; + switch (topic){ + case 1 : + numbers = random.generateSubtractionValues(Difficulty.EASY); + operator = "-"; + break; + case 2 : + numbers = random.generateMultiplicationValues(Difficulty.EASY); + operator = "*"; + break; + case 3 : + numbers = random.generateDivisionValues(Difficulty.EASY); + operator = "/"; + break; + default: numbers = random.generateAdditionValues(Difficulty.EASY); + } + StringBuilder questionBuilder = new StringBuilder(); + questionBuilder.append(numbers[0]) + .append(operator) + .append(numbers[1]) + .append(" = ?"); + binding.questionTv.setText(questionBuilder); + int[] options = random.generateDivisionValues(Difficulty.EASY); + List choices = new ArrayList<>(); + choices.add(options[0]); + choices.add(options[1]); + choices.add(options[2]); + choices.add(numbers[2]); + + Collections.shuffle(choices); + binding.optionA.setText(String.valueOf(choices.get(0))); + binding.optionB.setText(String.valueOf(choices.get(1))); + binding.optionC.setText(String.valueOf(choices.get(2))); + binding.optionD.setText(String.valueOf(choices.get(3))); + + binding.optionA + .setOnClickListener(v -> showResultDialog(Integer.parseInt(binding.optionA.getText().toString()) == numbers[2])); + binding.optionB + .setOnClickListener(v -> showResultDialog(Integer.parseInt(binding.optionB.getText().toString()) == numbers[2])); + binding.optionC + .setOnClickListener(v -> showResultDialog(Integer.parseInt(binding.optionC.getText().toString()) == numbers[2])); + binding.optionD + .setOnClickListener(v -> showResultDialog(Integer.parseInt(binding.optionD.getText().toString()) == numbers[2])); + } + + private void showResultDialog(boolean isCorrect) { + String message = isCorrect ? "Right Answer" : "Wrong Answer"; + int gifResource = isCorrect ? R.drawable.right : R.drawable.wrong; + tts.speak(message); + + LayoutInflater inflater = getLayoutInflater(); + DialogResultBinding dialogBinding = DialogResultBinding.inflate(inflater); + View dialogView = dialogBinding.getRoot(); + + // Load the GIF using Glide + Glide.with(this) + .asGif() + .load(gifResource) + .into(dialogBinding.gifImageView); + + dialogBinding.messageTextView.setText(message); + + new AlertDialog.Builder(requireContext()) + .setView(dialogView) + .setPositiveButton("Continue", (dialog, which) -> { + dialog.dismiss(); + generateNewQuestion(); + }) + .create() + .show(); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/zendalona/mathmantra/ui/MathQuizFragment.java b/app/src/main/java/com/zendalona/mathmantra/ui/MathQuizFragment.java new file mode 100644 index 0000000..42f9e53 --- /dev/null +++ b/app/src/main/java/com/zendalona/mathmantra/ui/MathQuizFragment.java @@ -0,0 +1,82 @@ +package com.zendalona.mathmantra.ui; + +import android.app.AlertDialog; +import android.os.Bundle; + +import androidx.fragment.app.Fragment; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import com.zendalona.mathmantra.R; +import com.zendalona.mathmantra.databinding.DialogResultBinding; +import com.zendalona.mathmantra.databinding.FragmentMathQuizBinding; +import com.zendalona.mathmantra.enums.Difficulty; +import com.zendalona.mathmantra.utils.RandomValueGenerator; + +public class MathQuizFragment extends Fragment { + + private FragmentMathQuizBinding binding; + private RandomValueGenerator random; + + public MathQuizFragment() { + // Required empty public constructor + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + binding = FragmentMathQuizBinding.inflate(inflater, container, false); + random = new RandomValueGenerator(); + generateNewQuestion(); + return binding.getRoot(); + } + + private void generateNewQuestion() { + int numbers[] = random.generateAdditionValues(Difficulty.EASY); + StringBuilder questionBuilder = new StringBuilder(); + questionBuilder.append(numbers[0]) + .append("+") + .append(numbers[1]) + .append(" = ?"); + binding.answerEt.setText(""); + binding.questionTv.setText(questionBuilder); + binding.submitAnswerBtn + .setOnClickListener(v -> showResultDialog(Integer.parseInt(binding.answerEt.getText().toString()) == numbers[2])); + } + + private void showResultDialog(boolean isCorrect) { + String message = isCorrect ? "Right Answer" : "Wrong Answer"; + int gifResource = isCorrect ? R.drawable.right : R.drawable.wrong; + + LayoutInflater inflater = getLayoutInflater(); + DialogResultBinding dialogBinding = DialogResultBinding.inflate(inflater); + View dialogView = dialogBinding.getRoot(); + + // Load the GIF using Glide + Glide.with(this) + .asGif() + .load(gifResource) + .into(dialogBinding.gifImageView); + + dialogBinding.messageTextView.setText(message); + + new AlertDialog.Builder(requireContext()) + .setView(dialogView) + .setPositiveButton("OK", (dialog, which) -> { + dialog.dismiss(); + generateNewQuestion(); + }) + .create() + .show(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/zendalona/mathmantra/ui/NarratorFragment.java b/app/src/main/java/com/zendalona/mathmantra/ui/NarratorFragment.java new file mode 100644 index 0000000..c898f6d --- /dev/null +++ b/app/src/main/java/com/zendalona/mathmantra/ui/NarratorFragment.java @@ -0,0 +1,71 @@ +package com.zendalona.mathmantra.ui; + +import android.os.Bundle; +import androidx.fragment.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import com.zendalona.mathmantra.databinding.FragmentNarratorBinding; +import com.zendalona.mathmantra.utils.TTSUtility; + +import java.util.ArrayList; + +public class NarratorFragment extends Fragment { + + private FragmentNarratorBinding binding; + private TTSUtility tts; + + private ArrayList theoryContents; + + private int currentIndex = 0; + + public NarratorFragment() { + // Required empty public constructor + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + tts = new TTSUtility(requireContext()); + tts.setSpeechRate(0.9f); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + binding = FragmentNarratorBinding.inflate(inflater, container, false); + Bundle args = getArguments(); + assert args != null; + if(!args.isEmpty()){ + theoryContents = args.getStringArrayList("contents"); + } + + updateTheoryContent(); + + binding.repeatButton.setOnClickListener(v -> tts.speak(binding.theoryText.getText().toString())); + + binding.previousButton.setOnClickListener(v -> { + if(currentIndex == 0) currentIndex++; + currentIndex = (currentIndex - 1) % theoryContents.size(); + updateTheoryContent(); + }); + + binding.nextButton.setOnClickListener(v -> { + currentIndex = (currentIndex + 1) % theoryContents.size(); + updateTheoryContent(); + }); + return binding.getRoot(); + } + + private void updateTheoryContent() { + String content = theoryContents.get(currentIndex); + binding.theoryText.setText(content); + tts.speak(content); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; + tts.shutdown(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/zendalona/mathmantra/ui/NumberLineFragment.java b/app/src/main/java/com/zendalona/mathmantra/ui/NumberLineFragment.java new file mode 100644 index 0000000..fad365c --- /dev/null +++ b/app/src/main/java/com/zendalona/mathmantra/ui/NumberLineFragment.java @@ -0,0 +1,176 @@ +package com.zendalona.mathmantra.ui; + +import android.app.AlertDialog; +import android.content.pm.ActivityInfo; +import android.os.Bundle; + +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.bumptech.glide.Glide; +import com.zendalona.mathmantra.R; +import com.zendalona.mathmantra.databinding.DialogResultBinding; +import com.zendalona.mathmantra.databinding.FragmentNumberLineBinding; +import com.zendalona.mathmantra.enums.Topic; +import com.zendalona.mathmantra.utils.RandomValueGenerator; +import com.zendalona.mathmantra.utils.TTSUtility; +import com.zendalona.mathmantra.viewModels.NumberLineViewModel; + +import java.util.HashMap; +import java.util.Map; + +public class NumberLineFragment extends Fragment { + + private FragmentNumberLineBinding binding; + private NumberLineViewModel viewModel; + private TTSUtility tts; + private RandomValueGenerator random; + private final String CURRENT_POSITION = "You're standing on number : \n"; + private int answer; + private String questionDesc = ""; + private String correctAnswerDesc = ""; + + public NumberLineFragment() { + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + viewModel = new ViewModelProvider(this).get(NumberLineViewModel.class); + tts = new TTSUtility(requireContext()); + tts.setSpeechRate(0.8f); + } + + @Override + public void onResume() { + super.onResume(); + // Lock orientation to landscape when this fragment is visible +// requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + viewModel.reset(); + } + + @Override + public void onPause() { + super.onPause(); +// requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + binding = FragmentNumberLineBinding.inflate(inflater, container, false); +// tts.speak("You're standing on the start of number line, at position 0."); + + random = new RandomValueGenerator(); + setupObservers(); + + correctAnswerDesc = askNewQuestion(0); + + binding.numberLineQuestion.setOnClickListener(v -> tts.speak(questionDesc)); + binding.btnLeft.setOnClickListener(v -> { + viewModel.moveLeft(); + binding.numberLineView.moveLeft(); + }); + binding.btnRight.setOnClickListener(v -> { + viewModel.moveRight(); + binding.numberLineView.moveRight(); + }); + + return binding.getRoot(); + } + + private String askNewQuestion(int position) { + + Topic topic = random.generateNumberLineQuestion()? Topic.ADDITION : Topic.SUBTRACTION; + int unitsToMove = random.generateNumberForCountGame(); + String operator = " plus "; + String direction = " right "; + Map operatorMap = new HashMap<>(); + operatorMap.put("plus","+"); + operatorMap.put("minus","-"); + + switch (topic){ + case ADDITION: + operator = " plus "; + direction = " right "; + answer = position + unitsToMove; + break; + case SUBTRACTION: + operator = " minus "; + direction = " left "; + answer = position - unitsToMove; + break; + } + String questionBrief = "What is " + position + operatorMap.get(operator.trim()) + unitsToMove + "?"; + binding.numberLineQuestion.setText(questionBrief); + questionDesc = + "You're standing on " + position + "." + + "What is " + position + operator + unitsToMove + "?" + + "Move " + unitsToMove + "units to the" + direction + "of Number line."; + + tts.speak(questionDesc); + + return position + operator + unitsToMove + " equals " + answer; + } + + private void setupObservers() { + viewModel.lineStart.observe(getViewLifecycleOwner(), start -> { + int end = viewModel.lineEnd.getValue() != null ? viewModel.lineEnd.getValue() : start + 10; + int position = viewModel.currentPosition.getValue() != null ? viewModel.currentPosition.getValue() : start; + binding.numberLineView.updateNumberLine(start, end, position); + }); + + viewModel.lineEnd.observe(getViewLifecycleOwner(), end -> { + int start = viewModel.lineStart.getValue() != null ? viewModel.lineStart.getValue() : end - 10; + int position = viewModel.currentPosition.getValue() != null ? viewModel.currentPosition.getValue() : start; + binding.numberLineView.updateNumberLine(start, end, position); + }); + + viewModel.currentPosition.observe(getViewLifecycleOwner(), position -> { + binding.currentPositionTv.setText(CURRENT_POSITION + position); +// tts.speak(Integer.toString(position)); + if(position == answer) { + tts.speak("Correct Answer! " + correctAnswerDesc + "."); + appreciateUser(); + } + }); + } + + private void appreciateUser() { + String message = "Good going"; + int gifResource = R.drawable.right; + + LayoutInflater inflater = getLayoutInflater(); + DialogResultBinding dialogBinding = DialogResultBinding.inflate(inflater); + View dialogView = dialogBinding.getRoot(); + + // Load the GIF using Glide + Glide.with(this) + .asGif() + .load(gifResource) + .into(dialogBinding.gifImageView); + + dialogBinding.messageTextView.setText(message); + + + new AlertDialog.Builder(requireContext()) + .setView(dialogView) + .setPositiveButton("Continue", (dialog, which) -> { + dialog.dismiss(); + correctAnswerDesc = askNewQuestion(answer); + }) + .create() + .show(); +// tts.speak("Click on continue!"); + } + + + @Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/zendalona/mathmantra/ui/NumberLineView.java b/app/src/main/java/com/zendalona/mathmantra/ui/NumberLineView.java new file mode 100644 index 0000000..8e65f9a --- /dev/null +++ b/app/src/main/java/com/zendalona/mathmantra/ui/NumberLineView.java @@ -0,0 +1,104 @@ +package com.zendalona.mathmantra.ui; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import androidx.core.content.ContextCompat; +import com.zendalona.mathmantra.R; + +public class NumberLineView extends View { + + private int currentPosition; + private int numberRangeStart; + private int numberRangeEnd; + private final String MASCOT_EMOJI = "\uD83E\uDDCD\u200D♂\uFE0F"; + + private Paint linePaint; + private Paint numberPaint; + private Paint mascotPaint; + + private float gap; + + public NumberLineView(Context context) { + super(context); + init(); + } + + public NumberLineView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public NumberLineView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + linePaint = new Paint(); + linePaint.setColor(ContextCompat.getColor(getContext(), R.color.blue)); + linePaint.setStrokeWidth(12f); + + numberPaint = new Paint(); + numberPaint.setColor(ContextCompat.getColor(getContext(), R.color.lightBlue)); + numberPaint.setTextSize(40f); + + mascotPaint = new Paint(); + mascotPaint.setTextSize(200f); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + drawNumberLine(canvas); + drawMascot(canvas); + } + + private void drawNumberLine(Canvas canvas) { + float startX = getWidth() * 0.01f; + float endX = getWidth() * 0.98f; + float centerY = getHeight() / 2f; + + canvas.drawLine(0 , centerY, getWidth(), centerY, linePaint); + + gap = (endX - startX) / (numberRangeEnd - numberRangeStart); + + // Draw numbers on the number line + Log.d("Drawing number line range : ", numberRangeStart + " to " + numberRangeEnd); + for (int number = numberRangeStart; number <= numberRangeEnd; number++) { + float x = startX + (number - numberRangeStart) * gap; + canvas.drawText(String.valueOf(number), x, centerY + 50f, numberPaint); + } + } + + private void drawMascot(Canvas canvas) { + float centerY = getHeight() / 2f; + float mascotPosition = (currentPosition - numberRangeStart - 0.4f) * gap; + if (mascotPosition < 0) mascotPosition = 0; + if (mascotPosition > getWidth()) mascotPosition = getWidth(); + canvas.drawText(MASCOT_EMOJI, mascotPosition , centerY - 50f, mascotPaint); + } + + public void updateNumberLine(int start, int end, int position) { + this.numberRangeStart = start; + this.numberRangeEnd = end; + this.currentPosition = position; + invalidate(); + } + + public int moveLeft() { + currentPosition--; + invalidate(); + return currentPosition; + } + + public int moveRight() { + currentPosition++; + invalidate(); + return currentPosition; + } + +} diff --git a/app/src/main/java/com/zendalona/mathmantra/ui/RingBellFragment.java b/app/src/main/java/com/zendalona/mathmantra/ui/RingBellFragment.java new file mode 100644 index 0000000..78816ed --- /dev/null +++ b/app/src/main/java/com/zendalona/mathmantra/ui/RingBellFragment.java @@ -0,0 +1,128 @@ +package com.zendalona.mathmantra.ui; + +import android.app.AlertDialog; +import android.os.Bundle; + +import androidx.appcompat.app.ActionBar; +import androidx.fragment.app.Fragment; + +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.bumptech.glide.Glide; +import com.zendalona.mathmantra.R; +import com.zendalona.mathmantra.databinding.DialogResultBinding; +import com.zendalona.mathmantra.databinding.FragmentRingBellBinding; +import com.zendalona.mathmantra.utils.AccelerometerUtility; +import com.zendalona.mathmantra.utils.RandomValueGenerator; +import com.zendalona.mathmantra.utils.ResponseFeedbackDialog; +import com.zendalona.mathmantra.utils.SoundEffectUtility; +import com.zendalona.mathmantra.utils.TTSUtility; + +public class RingBellFragment extends Fragment { + + private FragmentRingBellBinding binding; + private AccelerometerUtility accelerometerUtility; + private SoundEffectUtility soundEffectUtility; + private RandomValueGenerator randomValueGenerator; + private TTSUtility tts; + int count, target; + + public RingBellFragment() { + // Required empty public constructor + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + soundEffectUtility = SoundEffectUtility.getInstance(requireContext()); + accelerometerUtility = new AccelerometerUtility(requireContext()); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + binding = FragmentRingBellBinding.inflate(inflater, container, false); + randomValueGenerator = new RandomValueGenerator(); + tts = new TTSUtility(requireContext()); + startGame(); + return binding.getRoot(); + } + + private void ringBell() { + binding.bellAnimationView.playAnimation(); + soundEffectUtility.playSound(R.raw.bell_ring); + binding.ringCount.setText(String.valueOf(++count)); + if(count == target) appreciateUser(); + } + + private void appreciateUser() { + String message = "Well done"; + int gifResource = R.drawable.right; + + LayoutInflater inflater = getLayoutInflater(); + DialogResultBinding dialogBinding = DialogResultBinding.inflate(inflater); + View dialogView = dialogBinding.getRoot(); + + // Load the GIF using Glide + Glide.with(this) + .asGif() + .load(gifResource) + .into(dialogBinding.gifImageView); + + dialogBinding.messageTextView.setText(message); + + tts.speak("Well done!, Click on continue!"); + + new AlertDialog.Builder(requireContext()) + .setView(dialogView) + .setPositiveButton("Continue", (dialog, which) -> { + dialog.dismiss(); + startGame(); + }) + .create() + .show(); + } + + private void startGame() { + count = 0; + binding.ringCount.setText(String.valueOf(count)); + target = randomValueGenerator.generateNumberForCountGame(); + String targetText = "Ring the bell " + target + " times"; + tts.speak(targetText); + binding.ringMeTv.setText(targetText); + } + + + @Override + public void onResume() { + super.onResume(); + // Start a thread to check for shakes + new Thread(() -> { + while (isVisible()) { + try { + Thread.sleep(200); + } + catch (InterruptedException e) { + Log.d("Accelerometer Thread sleep Error",e.getLocalizedMessage()); + e.printStackTrace(); + } + if (accelerometerUtility.isDeviceShaken()) requireActivity().runOnUiThread(this::ringBell); + } + }).start(); + } + + @Override + public void onPause() { + super.onPause(); + accelerometerUtility.unregisterListener(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; + tts.shutdown(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/zendalona/mathmantra/ui/TapTablaFragment.java b/app/src/main/java/com/zendalona/mathmantra/ui/TapTablaFragment.java new file mode 100644 index 0000000..cf9a2dc --- /dev/null +++ b/app/src/main/java/com/zendalona/mathmantra/ui/TapTablaFragment.java @@ -0,0 +1,107 @@ +package com.zendalona.mathmantra.ui; + +import android.app.AlertDialog; +import android.os.Bundle; +import androidx.fragment.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.bumptech.glide.Glide; +import com.zendalona.mathmantra.R; +import com.zendalona.mathmantra.databinding.DialogResultBinding; +import com.zendalona.mathmantra.databinding.FragmentTapTablaBinding; +import com.zendalona.mathmantra.utils.RandomValueGenerator; +import com.zendalona.mathmantra.utils.SoundEffectUtility; +import com.zendalona.mathmantra.utils.TTSUtility; + +public class TapTablaFragment extends Fragment { + + private FragmentTapTablaBinding binding; + private SoundEffectUtility soundEffectUtility; + private RandomValueGenerator randomValueGenerator; + private TTSUtility tts; + private int count, target; + + public TapTablaFragment() { + // Required empty public constructor + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + soundEffectUtility = SoundEffectUtility.getInstance(requireContext()); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + // Inflate the layout for this fragment + binding = FragmentTapTablaBinding.inflate(inflater, container, false); + randomValueGenerator = new RandomValueGenerator(); + tts = new TTSUtility(requireContext()); + startGame(); + binding.tablaAnimationView.setOnClickListener(v -> onTablaTapped()); + return binding.getRoot(); + } + + private void onTablaTapped() { + binding.tapCount.setText(String.valueOf(++count)); + binding.tablaAnimationView.playAnimation(); + soundEffectUtility.playSound(R.raw.drums_sound); + if(count == target) appreciateUser(); + } + + + private void appreciateUser() { + String message = "Well done"; + int gifResource = R.drawable.right; + + LayoutInflater inflater = getLayoutInflater(); + DialogResultBinding dialogBinding = DialogResultBinding.inflate(inflater); + View dialogView = dialogBinding.getRoot(); + + // Load the GIF using Glide + Glide.with(this) + .asGif() + .load(gifResource) + .into(dialogBinding.gifImageView); + + dialogBinding.messageTextView.setText(message); + + tts.speak("Well done!, Click on continue!"); + + new AlertDialog.Builder(requireContext()) + .setView(dialogView) + .setPositiveButton("Continue", (dialog, which) -> { + dialog.dismiss(); + binding.tapCount.setText("0"); + startGame(); + }) + .create() + .show(); + } + + private void startGame() { + count = 0; + binding.tapMeTv.setText(String.valueOf(count)); + target = randomValueGenerator.generateNumberForCountGame(); + String targetText = "Tap the drum " + target + " times"; + tts.speak(targetText); + binding.tapMeTv.setText(targetText); + } + + + @Override + public void onDestroyView() { + super.onDestroyView(); + // Release resources related to binding + binding = null; + } + + @Override + public void onDestroy() { + super.onDestroy(); + // Release sound effect resources when fragment is destroyed + // FIXME : soundEffectUtility.release(); + } +} diff --git a/app/src/main/java/com/zendalona/mathmantra/utils/AccelerometerUtility.java b/app/src/main/java/com/zendalona/mathmantra/utils/AccelerometerUtility.java new file mode 100644 index 0000000..1b3de8a --- /dev/null +++ b/app/src/main/java/com/zendalona/mathmantra/utils/AccelerometerUtility.java @@ -0,0 +1,74 @@ +package com.zendalona.mathmantra.utils; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.widget.Toast; + +public class AccelerometerUtility implements SensorEventListener { + + private static final float SHAKE_THRESHOLD = 2000.0f; // Threshold for detecting shake + + private SensorManager sensorManager; + private Sensor accelerometer; + private boolean isDeviceShaken = false; + private float lastX, lastY, lastZ; + private long lastUpdate = 0; + + public AccelerometerUtility(Context context) { + sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + if (sensorManager != null) { + accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); + if (accelerometer != null) { + sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI); + } else { + Toast.makeText(context, "Accelerometer Sensor Unavailable", Toast.LENGTH_SHORT).show(); + } + } else { + Toast.makeText(context, "Sensor Manager Unavailable", Toast.LENGTH_SHORT).show(); + } + } + + public void unregisterListener() { + if (sensorManager != null) { + sensorManager.unregisterListener(this); + } + } + + @Override + public void onSensorChanged(SensorEvent event) { + if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { + long currentTime = System.currentTimeMillis(); + if ((currentTime - lastUpdate) > 100) { + long diffTime = (currentTime - lastUpdate); + lastUpdate = currentTime; + + float x = event.values[0]; + float y = event.values[1]; + float z = event.values[2]; + + float speed = Math.abs(x + y + z - lastX - lastY - lastZ) / diffTime * 10000; + if (speed > SHAKE_THRESHOLD) { + isDeviceShaken = true; + } + + lastX = x; + lastY = y; + lastZ = z; + } + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // No need to handle accuracy changes for this use case + } + + public boolean isDeviceShaken() { + boolean shaken = isDeviceShaken; + isDeviceShaken = false; // Reset the flag + return shaken; + } +} diff --git a/app/src/main/java/com/zendalona/mathmantra/utils/FragmentNavigation.java b/app/src/main/java/com/zendalona/mathmantra/utils/FragmentNavigation.java new file mode 100644 index 0000000..f1b0354 --- /dev/null +++ b/app/src/main/java/com/zendalona/mathmantra/utils/FragmentNavigation.java @@ -0,0 +1,7 @@ +package com.zendalona.mathmantra.utils; + +import androidx.fragment.app.Fragment; + +public interface FragmentNavigation { + void loadFragment(Fragment fragment, int transition); +} diff --git a/app/src/main/java/com/zendalona/mathmantra/utils/PermissionManager.java b/app/src/main/java/com/zendalona/mathmantra/utils/PermissionManager.java new file mode 100644 index 0000000..e74036a --- /dev/null +++ b/app/src/main/java/com/zendalona/mathmantra/utils/PermissionManager.java @@ -0,0 +1,72 @@ +package com.zendalona.mathmantra.utils; + +import android.Manifest; +import android.app.Activity; +import android.content.pm.PackageManager; +import android.util.Log; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; +import java.util.Arrays; + +public class PermissionManager { + public static final int REQUEST_CODE_MICROPHONE = 100; + public static final int REQUEST_CODE_ACCELEROMETER = 101; + + private final Activity activity; + private final PermissionCallback callback; + private String permissionToRequest; + private final String TAG = "PermissionManager"; + + public PermissionManager(Activity activity, PermissionCallback callback) { + this.activity = activity; + this.callback = callback; + } + + public void requestMicrophonePermission() { + permissionToRequest = Manifest.permission.RECORD_AUDIO; + Log.i(TAG + " :: requestMicrophonePermission()", permissionToRequest); + requestPermission(); + } + + public void requestAccelerometerPermission() { + permissionToRequest = Manifest.permission.BODY_SENSORS; + Log.i(TAG + " :: requestAccelerometerPermission()", permissionToRequest); + requestPermission(); + } + + private int getRequestCode() { + switch (permissionToRequest){ + case Manifest.permission.RECORD_AUDIO: return REQUEST_CODE_MICROPHONE; + case Manifest.permission.BODY_SENSORS: return REQUEST_CODE_ACCELEROMETER; + default: Log.e("PermissionManager", "Unknown permission: " + permissionToRequest); return -1; + } + } + + private void requestPermission() { + if (permissionToRequest != null) { + if (ContextCompat.checkSelfPermission(activity, permissionToRequest) == PackageManager.PERMISSION_GRANTED) { + if (callback != null) callback.onPermissionGranted(); + } else { + Log.w(TAG + " :: requestPermission()", "Permission not already granted. " + permissionToRequest + "w/" + getRequestCode()); + String[] permissionsToRequest = {Manifest.permission.RECORD_AUDIO, Manifest.permission.BODY_SENSORS}; + Log.i(TAG, "requesting permissions : " + Arrays.toString(permissionsToRequest)); + ActivityCompat.requestPermissions(activity, permissionsToRequest, 101); +// ActivityCompat.requestPermissions(activity, new String[]{permissionToRequest}, 42); + } + } + else Log.e(TAG + " :: requestPermission()", "permissionToRequest value is null"); + } + + public void handlePermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + if(callback != null) { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) callback.onPermissionGranted(); + else callback.onPermissionDenied(); + } + else Log.w(TAG + " :: handlePermissionsResult() ","callback value is null"); + } + + public interface PermissionCallback { + void onPermissionGranted(); + void onPermissionDenied(); + } +} diff --git a/app/src/main/java/com/zendalona/mathmantra/utils/RandomValueGenerator.java b/app/src/main/java/com/zendalona/mathmantra/utils/RandomValueGenerator.java new file mode 100644 index 0000000..2f297a5 --- /dev/null +++ b/app/src/main/java/com/zendalona/mathmantra/utils/RandomValueGenerator.java @@ -0,0 +1,128 @@ +package com.zendalona.mathmantra.utils; + +import com.zendalona.mathmantra.enums.Difficulty; + +import java.util.Random; + +public class RandomValueGenerator { + private Random random; + private final int NO_OF_TOPICS = 4; + + public RandomValueGenerator() { + this.random = new Random(); + } + + public boolean generateNumberLineQuestion(){ + return random.nextBoolean(); + } + + public int generateQuestionTopic(){ + return random.nextInt(NO_OF_TOPICS); + } + + public int generateNumberForCountGame() { + return random.nextInt(16) + 2; + } + + public int[] generateNumberRangeForCount(int upperBound){ + int start = random.nextInt(upperBound - 10); + int end = random.nextInt(11) + start + 6; + return new int[]{start, end}; + } + + public int[] generateAdditionValues(Difficulty difficulty) { + int[] values = new int[3]; + switch (difficulty) { + case EASY: + values[0] = random.nextInt(10) + 1; + values[1] = random.nextInt(10) + 1; + break; + case MEDIUM: + values[0] = random.nextInt(50) + 10; + values[1] = random.nextInt(50) + 10; + break; + case HARD: + values[0] = random.nextInt(500) + 17; + values[1] = random.nextInt(500) + 17; + break; + } + values[2] = values[0] + values[1]; + return values; + } + + public int[] generateSubtractionValues(Difficulty difficulty) { + int[] values = new int[3]; + switch (difficulty) { + case EASY: + values[0] = random.nextInt(10) + 1; + values[1] = random.nextInt(10) + 1; + if (values[1] > values[0]) { + int temp = values[0]; + values[0] = values[1]; + values[1] = temp; + } + break; + case MEDIUM: + values[0] = random.nextInt(50) + 10; + values[1] = random.nextInt(50) + 10; + if (values[1] > values[0]) { + int temp = values[0]; + values[0] = values[1]; + values[1] = temp; + } + break; + case HARD: + values[0] = random.nextInt(500) + 17; + values[1] = random.nextInt(500) + 17; + if (values[1] > values[0]) { + int temp = values[0]; + values[0] = values[1]; + values[1] = temp; + } + break; + } + values[2] = values[0] - values[1]; + return values; + } + + public int[] generateMultiplicationValues(Difficulty difficulty) { + int[] values = new int[3]; + switch (difficulty) { + case EASY: + values[0] = random.nextInt(10) + 1; + values[1] = random.nextInt(10) + 1; + break; + case MEDIUM: + values[0] = random.nextInt(20) + 1; + values[1] = random.nextInt(20) + 1; + break; + case HARD: + values[0] = random.nextInt(50) + 1; + values[1] = random.nextInt(50) + 1; + break; + } + values[2] = values[0] * values[1]; + return values; + } + + public int[] generateDivisionValues(Difficulty difficulty) { + int[] values = new int[3]; + switch (difficulty) { + case EASY: + values[1] = random.nextInt(9) + 1; // Avoid division by zero + values[0] = values[1] * (random.nextInt(10) + 1); + break; + case MEDIUM: + values[1] = random.nextInt(19) + 1; // Avoid division by zero + values[0] = values[1] * (random.nextInt(20) + 1); + break; + case HARD: + values[1] = random.nextInt(49) + 1; // Avoid division by zero + values[0] = values[1] * (random.nextInt(50) + 1); //TODO : Scope for decimal values? + break; + } + values[2] = values[0] / values[1]; + return values; + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/zendalona/mathmantra/utils/ResponseFeedbackDialog.java b/app/src/main/java/com/zendalona/mathmantra/utils/ResponseFeedbackDialog.java new file mode 100644 index 0000000..237f3c0 --- /dev/null +++ b/app/src/main/java/com/zendalona/mathmantra/utils/ResponseFeedbackDialog.java @@ -0,0 +1,74 @@ +package com.zendalona.mathmantra.utils; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.view.LayoutInflater; +import android.view.View; +import androidx.appcompat.app.AlertDialog; +import com.bumptech.glide.Glide; +import com.zendalona.mathmantra.R; +import com.zendalona.mathmantra.databinding.DialogResultBinding; + +import java.util.Random; + +public class ResponseFeedbackDialog { + + private static final int DIALOG_DURATION = 3 * 1000; + private static final String[] POSITIVE_MESSAGES = { + "Awesome!", + "Great job!", + "Well done!", + "Correct!", + "Bravo!" + }; + private static final String[] NEGATIVE_MESSAGES = { + "Try again!", + "Not quite!", + "Oops, wrong answer!", + "Better luck next time!", + "Incorrect!" + }; + + + public static void showFeedbackDialog(Context context, boolean isCorrect, Runnable onDismissAction) { + String message = isCorrect ? getRandomPositiveMessage() : getRandomNegativeMessage(); + int gifResource = isCorrect ? R.drawable.right : R.drawable.wrong; + + LayoutInflater inflater = LayoutInflater.from(context); + DialogResultBinding dialogBinding = DialogResultBinding.inflate(inflater); + View dialogView = dialogBinding.getRoot(); + + Glide.with(context) + .asGif() + .load(gifResource) + .into(dialogBinding.gifImageView); + + dialogBinding.messageTextView.setText(message); + + AlertDialog alertDialog = new AlertDialog.Builder(context) + .setView(dialogView) + .create(); + + alertDialog.show(); + + // Automatically dismiss the dialog after t seconds + new Handler(Looper.getMainLooper()).postDelayed(() -> { + if (alertDialog.isShowing()) { + alertDialog.dismiss(); + if (onDismissAction != null) { + onDismissAction.run(); + } + } + }, DIALOG_DURATION); + } + + private static String getRandomPositiveMessage() { + return POSITIVE_MESSAGES[new Random().nextInt(POSITIVE_MESSAGES.length)]; + } + + private static String getRandomNegativeMessage() { + return NEGATIVE_MESSAGES[new Random().nextInt(NEGATIVE_MESSAGES.length)]; + } + +} diff --git a/app/src/main/java/com/zendalona/mathmantra/utils/SoundEffectUtility.java b/app/src/main/java/com/zendalona/mathmantra/utils/SoundEffectUtility.java new file mode 100644 index 0000000..fadfe24 --- /dev/null +++ b/app/src/main/java/com/zendalona/mathmantra/utils/SoundEffectUtility.java @@ -0,0 +1,62 @@ +package com.zendalona.mathmantra.utils; + +import android.content.Context; +import android.media.AudioAttributes; +import android.media.SoundPool; +import android.util.Log; + +import java.util.HashMap; +import java.util.Map; + +public class SoundEffectUtility { + private static SoundEffectUtility instance; + private SoundPool soundPool; + private Context context; + private Map soundMap; // Map to hold sound resource IDs and their corresponding SoundPool IDs + + // Singleton pattern to ensure a single instance of SoundEffectUtility + public static synchronized SoundEffectUtility getInstance(Context context) { + if (instance == null) { + instance = new SoundEffectUtility(context.getApplicationContext()); + } + return instance; + } + + private SoundEffectUtility(Context context) { + this.context = context; + this.soundMap = new HashMap<>(); + + AudioAttributes audioAttributes = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_MEDIA) + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .build(); + + soundPool = new SoundPool.Builder() + .setAudioAttributes(audioAttributes) + .setMaxStreams(1) + .build(); + } + + public void loadSound(int soundResId) { + int soundId = soundPool.load(context, soundResId, 1); + soundMap.put(soundResId, soundId); // Map the resource ID to the SoundPool ID + } + + public void playSound(int soundResId) { + Integer soundId = soundMap.get(soundResId); + if (soundId != null) { + soundPool.play(soundId, 1.0f, 1.0f, 1, 0, 1.0f); + Log.d("Sound played",soundId.toString()); + } else { + this.loadSound(soundResId); + this.playSound(soundResId); + } + } + + public void release() { + if (soundPool != null) { + soundPool.release(); + soundPool = null; + } + } +} diff --git a/app/src/main/java/com/zendalona/mathmantra/utils/SpeechRecognitionUtility.java b/app/src/main/java/com/zendalona/mathmantra/utils/SpeechRecognitionUtility.java new file mode 100644 index 0000000..8acc579 --- /dev/null +++ b/app/src/main/java/com/zendalona/mathmantra/utils/SpeechRecognitionUtility.java @@ -0,0 +1,103 @@ +package com.zendalona.mathmantra.utils; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.speech.RecognitionListener; +import android.speech.RecognizerIntent; +import android.speech.SpeechRecognizer; +import android.util.Log; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.Locale; + +public class SpeechRecognitionUtility { + + private Context context; + private SpeechRecognizer speechRecognizer; + private SpeechRecognitionCallback callback; + private final String TAG = "SpeechRecognitionUtility"; + + public SpeechRecognitionUtility(Context context, SpeechRecognitionCallback callback) { + this.context = context; + this.callback = callback; + this.speechRecognizer = SpeechRecognizer.createSpeechRecognizer(context); + this.speechRecognizer.setRecognitionListener(new RecognitionListener() { + @Override + public void onReadyForSpeech(Bundle params) { + Log.d(TAG, "Ready for speech"); + } + + @Override + public void onBeginningOfSpeech() { + Log.d(TAG, "Beginning of speech"); + } + + @Override + public void onRmsChanged(float rmsdB) { + // handle changes in volume + } + + @Override + public void onBufferReceived(byte[] buffer) { + // handle raw audio data + } + + @Override + public void onEndOfSpeech() { + Log.d(TAG, "End of speech"); + } + + @Override + public void onError(int error) { + Log.e(TAG, "Error: " + error); + if (callback != null) callback.onError(error); + } + + @Override + public void onResults(Bundle results) { + ArrayList matches = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION); + if (matches != null && !matches.isEmpty()) { + String spokenText = matches.get(0); + Log.d(TAG, "Heard: " + spokenText); + if (callback != null) callback.onResults(spokenText); + } + } + + @Override + public void onPartialResults(Bundle partialResults) { + // TODO : check partial results + } + + @Override + public void onEvent(int eventType, Bundle params) { + + } + }); + } + + public void startListening() { + Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); + intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM); + intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault()); + + speechRecognizer.startListening(intent); + } + + public void stopListening() { + if (speechRecognizer != null) { + speechRecognizer.stopListening(); + } + } + + public void destroy() { + if (speechRecognizer != null) speechRecognizer.destroy(); + } + + public interface SpeechRecognitionCallback { + void onResults(String spokenText); + void onError(int error); + } + +} diff --git a/app/src/main/java/com/zendalona/mathmantra/utils/TTSUtility.java b/app/src/main/java/com/zendalona/mathmantra/utils/TTSUtility.java new file mode 100644 index 0000000..b5306b4 --- /dev/null +++ b/app/src/main/java/com/zendalona/mathmantra/utils/TTSUtility.java @@ -0,0 +1,44 @@ +package com.zendalona.mathmantra.utils; + +import android.content.Context; +import android.speech.tts.TextToSpeech; +import java.util.Locale; + +public class TTSUtility { + private TextToSpeech tts; + private boolean isInitialized = false; + + public TTSUtility(Context context) { + tts = new TextToSpeech(context, status -> { + if (status == TextToSpeech.SUCCESS) { +// int result = tts.setLanguage(Locale.US); + int result = tts.setLanguage(Locale.forLanguageTag("en-IN")); + isInitialized = result != TextToSpeech.LANG_MISSING_DATA + && result != TextToSpeech.LANG_NOT_SUPPORTED; + } + }); + } + + public void speak(String text) { + if (isInitialized) { + tts.speak(text, TextToSpeech.QUEUE_FLUSH, null, null); + } + } + + public void setSpeechRate(float rate) { + tts.setSpeechRate(rate); + } + + public void stop() { + if (tts != null) { + tts.stop(); + } + } + + public void shutdown() { + if (tts != null) { + tts.stop(); + tts.shutdown(); + } + } +} diff --git a/app/src/main/java/com/zendalona/mathmantra/viewModels/NumberLineViewModel.java b/app/src/main/java/com/zendalona/mathmantra/viewModels/NumberLineViewModel.java new file mode 100644 index 0000000..721af9d --- /dev/null +++ b/app/src/main/java/com/zendalona/mathmantra/viewModels/NumberLineViewModel.java @@ -0,0 +1,68 @@ +package com.zendalona.mathmantra.viewModels; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +public class NumberLineViewModel extends ViewModel { + + private final MutableLiveData _lineStart = new MutableLiveData<>(-5); + private final MutableLiveData _lineEnd = new MutableLiveData<>(5); + private final MutableLiveData _currentPosition = new MutableLiveData<>(0); + + public LiveData lineStart = _lineStart; + public LiveData lineEnd = _lineEnd; + public LiveData currentPosition = _currentPosition; + + public void reset() { + _lineStart.setValue(-5); + _lineEnd.setValue(5); + _currentPosition.setValue(0); + } + + public void moveRight() { + Integer currentPositionValue = _currentPosition.getValue(); + Integer lineEndValue = _lineEnd.getValue(); + if (currentPositionValue != null && lineEndValue != null) { + if (currentPositionValue < lineEndValue) { + _currentPosition.setValue(currentPositionValue + 1); + } else { + shiftRight(); + } + } + } + + public void moveLeft() { + Integer currentPositionValue = _currentPosition.getValue(); + Integer lineStartValue = _lineStart.getValue(); + if (currentPositionValue != null && lineStartValue != null) { + if (currentPositionValue > lineStartValue) { + _currentPosition.setValue(currentPositionValue - 1); + } else { + shiftLeft(); + } + } + } + + private void shiftRight() { + Integer lineEndValue = _lineEnd.getValue(); + if (lineEndValue != null) { + int newStart = lineEndValue + 1; + int newEnd = newStart + 10; + _lineStart.setValue(newStart); + _lineEnd.setValue(newEnd); + _currentPosition.setValue(newStart); + } + } + + private void shiftLeft() { + Integer lineStartValue = _lineStart.getValue(); + if (lineStartValue != null) { + int newEnd = lineStartValue - 1; + int newStart = newEnd - 10; + _lineStart.setValue(newStart); + _lineEnd.setValue(newEnd); + _currentPosition.setValue(newEnd); + } + } +} diff --git a/app/src/main/res/drawable/bell.png b/app/src/main/res/drawable/bell.png new file mode 100644 index 0000000..80b39b8 Binary files /dev/null and b/app/src/main/res/drawable/bell.png differ diff --git a/app/src/main/res/drawable/books.png b/app/src/main/res/drawable/books.png new file mode 100644 index 0000000..b3dca12 Binary files /dev/null and b/app/src/main/res/drawable/books.png differ diff --git a/app/src/main/res/drawable/drum.png b/app/src/main/res/drawable/drum.png new file mode 100644 index 0000000..e2f305a Binary files /dev/null and b/app/src/main/res/drawable/drum.png differ diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/mcq.png b/app/src/main/res/drawable/mcq.png new file mode 100644 index 0000000..edee1f9 Binary files /dev/null and b/app/src/main/res/drawable/mcq.png differ diff --git a/app/src/main/res/drawable/mic.png b/app/src/main/res/drawable/mic.png new file mode 100644 index 0000000..b2bb356 Binary files /dev/null and b/app/src/main/res/drawable/mic.png differ diff --git a/app/src/main/res/drawable/molepointingup.png b/app/src/main/res/drawable/molepointingup.png new file mode 100644 index 0000000..34d0f52 Binary files /dev/null and b/app/src/main/res/drawable/molepointingup.png differ diff --git a/app/src/main/res/drawable/molereading.png b/app/src/main/res/drawable/molereading.png new file mode 100644 index 0000000..3bab0ed Binary files /dev/null and b/app/src/main/res/drawable/molereading.png differ diff --git a/app/src/main/res/drawable/numberline.png b/app/src/main/res/drawable/numberline.png new file mode 100644 index 0000000..5d358bf Binary files /dev/null and b/app/src/main/res/drawable/numberline.png differ diff --git a/app/src/main/res/drawable/right.gif b/app/src/main/res/drawable/right.gif new file mode 100644 index 0000000..606a18a Binary files /dev/null and b/app/src/main/res/drawable/right.gif differ diff --git a/app/src/main/res/drawable/test.png b/app/src/main/res/drawable/test.png new file mode 100644 index 0000000..f134087 Binary files /dev/null and b/app/src/main/res/drawable/test.png differ diff --git a/app/src/main/res/drawable/wrong.gif b/app/src/main/res/drawable/wrong.gif new file mode 100644 index 0000000..cadbb0b Binary files /dev/null and b/app/src/main/res/drawable/wrong.gif differ diff --git a/app/src/main/res/font/ubuntu.xml b/app/src/main/res/font/ubuntu.xml new file mode 100644 index 0000000..959cfb0 --- /dev/null +++ b/app/src/main/res/font/ubuntu.xml @@ -0,0 +1,7 @@ + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..1643f18 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,31 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_result.xml b/app/src/main/res/layout/dialog_result.xml new file mode 100644 index 0000000..2f9da06 --- /dev/null +++ b/app/src/main/res/layout/dialog_result.xml @@ -0,0 +1,28 @@ + + + + + + + diff --git a/app/src/main/res/layout/fragment_choose_lesson.xml b/app/src/main/res/layout/fragment_choose_lesson.xml new file mode 100644 index 0000000..78befad --- /dev/null +++ b/app/src/main/res/layout/fragment_choose_lesson.xml @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_count_numbers.xml b/app/src/main/res/layout/fragment_count_numbers.xml new file mode 100644 index 0000000..89897a5 --- /dev/null +++ b/app/src/main/res/layout/fragment_count_numbers.xml @@ -0,0 +1,46 @@ + + + + + + + + + + +