diff --git a/app/build.gradle b/app/build.gradle index 2507e8c8..f6914d93 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,11 +8,20 @@ android { buildToolsVersion "30.0.3" defaultConfig { - applicationId "com.rsschool.quiz" - minSdkVersion 22 + applicationId "by.godevelopment.fandroidviewpager2" + minSdkVersion 23 targetSdkVersion 30 versionCode 1 versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } } compileOptions { @@ -22,6 +31,10 @@ android { kotlinOptions { jvmTarget = '1.8' } + + buildFeatures { + viewBinding = true + } } dependencies { @@ -31,4 +44,14 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'com.google.android.material:material:1.3.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + testImplementation 'junit:junit:4.+' + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + + implementation 'com.google.android.material:material:1.3.0' + implementation "androidx.viewpager2:viewpager2:1.0.0" + + // val fragment_version = "1.3.4" + // implementation("androidx.fragment:fragment-ktx:$fragment_version") } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 26555680..ba99c480 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,10 +5,10 @@ + android:theme="@style/Theme.Quiz.Purple"> @@ -18,6 +18,10 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/rsschool/quiz/ActionListener.kt b/app/src/main/java/com/rsschool/quiz/ActionListener.kt new file mode 100644 index 00000000..0b1f849c --- /dev/null +++ b/app/src/main/java/com/rsschool/quiz/ActionListener.kt @@ -0,0 +1,9 @@ +package com.rsschool.quiz + +interface ActionListener { + fun nextFragment() + fun backFragment() + fun runResultFragment() + fun addAnswer(numberQuest: Int, numberAnswer: Int) + fun checkAnswersCount(): Boolean +} \ No newline at end of file diff --git a/app/src/main/java/com/rsschool/quiz/BlankFragment.kt b/app/src/main/java/com/rsschool/quiz/BlankFragment.kt new file mode 100644 index 00000000..b11d4606 --- /dev/null +++ b/app/src/main/java/com/rsschool/quiz/BlankFragment.kt @@ -0,0 +1,107 @@ +package com.rsschool.quiz + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.rsschool.quiz.databinding.FragmentQuizBinding + + +// Вне тела класса создаем константу для ключа аргумента, который будем передавать в каждый новый экземпляр фрагмента. +const val ARG_OBJECT = "object" + +// Этот фрагмент будет использоваться для каждого нового экрана в приложении, мы будем создавать новый его экземпляр и передавать туда его порядковый номер. +class BlankFragment : Fragment() { + + // Крутая штука, что бы словить null и получить исключение именно здесь, а не в методах класса + private var _listener: ActionListener? = null + private val listener get() = _listener!! + private var _binding: FragmentQuizBinding? = null + private val binding get() = _binding!! + + private var currentFragment = 0 + + // Добавляем сслыку на MainActivity через контекст + override fun onAttach(context: Context) { + super.onAttach(context) + _listener = context as ActionListener + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + + // return inflater.inflate(R.layout.fragment_blank, container, false) + _binding = FragmentQuizBinding.inflate(inflater, container, false) + return binding.root + } + + // В переопределенном методе onViewCreated получаем аргумент, находим текстовое поле и передаем туда значение аргумента для отображения. + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + + // Берем аргумент Bundle arguments, если он имеет ключ ARG_OBJECT и применяем лямбду + arguments?.takeIf { it.containsKey(ARG_OBJECT) }?.apply { + + // Чтобы получить отправленные данные при загрузке из Bundle arguments, можно воспользоваться методом get(), в который передается ключ объекта: + val quizObject: QuizObject = get(ARG_OBJECT) as QuizObject + + // Для условий + currentFragment = quizObject.numberQuestion + + // Заполняем текстовые формы + binding.toolbar.title = "Question ${quizObject.numberQuestion}" + binding.question.text = quizObject.question + binding.optionOne.text = quizObject.answers[0] + binding.optionTwo.text = quizObject.answers[1] + binding.optionThree.text = quizObject.answers[2] + binding.optionFour.text = quizObject.answers[3] + binding.optionFive.text = quizObject.answers[4] + + // Блокируем кнопку до выбора ответа + binding.nextButton.isEnabled = false + // Блокируем кнопки первого фрагмента + if (currentFragment < 2) { + binding.previousButton.isEnabled = false + binding.toolbar.navigationIcon = null + } + + // Меняем надпись на последнем фрагменте + if (currentFragment > 4) binding.nextButton.text = "Submit" + } + + binding.nextButton.setOnClickListener { + if (listener.checkAnswersCount()) listener.runResultFragment() + else listener.nextFragment() + } + + binding.previousButton.setOnClickListener { + listener.backFragment() + } + + binding.toolbar.setOnClickListener { + listener.backFragment() + } + + binding.radioGroup.setOnCheckedChangeListener { _, checkId -> + when (checkId) { + binding.optionOne.id -> listener.addAnswer(currentFragment, 1) + binding.optionTwo.id -> listener.addAnswer(currentFragment, 2) + binding.optionThree.id -> listener.addAnswer(currentFragment, 3) + binding.optionFour.id -> listener.addAnswer(currentFragment, 4) + binding.optionFive.id -> listener.addAnswer(currentFragment, 5) + } + // Когда выбор ответа сделан - освобождаем кнопку + binding.nextButton.isEnabled = true + } + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/rsschool/quiz/DataObjectAccess.kt b/app/src/main/java/com/rsschool/quiz/DataObjectAccess.kt new file mode 100644 index 00000000..5f87a6ed --- /dev/null +++ b/app/src/main/java/com/rsschool/quiz/DataObjectAccess.kt @@ -0,0 +1,54 @@ +package com.rsschool.quiz + +// Этот класс реализует доступ к базе данных, в которой хранятся User и QuizObject объекты +class DataObjectAccess { + + // Таблица БД + private val quizGames: List = listOf( + QuizObject("Вы готовы, дети?", listOf("Да, капитан!", "Да!", "Нет.", "Нет, капитан", "буль-буль-буль"), 1, 1), + QuizObject("Ктоооооооо... Кто проживает на дне океана?", listOf("Рыбки", "Губка Боб Квадратные Штаны!", "Водолаз", "Никто не проживает", "Terror from the Deep"), 2,2), + QuizObject("Жёлтая губка, малыш без изъяна?", listOf("С изъяном", "Не малышь", "Губка Боб Квадратные Штаны!", "Синяя губка", "Жёлтая подводная лодка"), 3,3), + QuizObject("Кто побеждает всегда и везде?", listOf("Годзилла", "Рэмбо", "Капитан Америка", "Губка Боб Квадратные Штаны!", "Фиолетовый из Повер Рэнджерс"), 4,4), + QuizObject("Кто также ловок, как рыба в воде?", listOf("Другая рыба", "Аквамен", "Глубина", "Тазик залитый бетоном", "Губка Боб Квадратные Штаны!"), 5,5), + ) + + // Номер вопроса - Номер ответа + private val numAnswers: HashMap = hashMapOf() + + // hashMap позволяет нам удобно перезаписывать уже выбранные ответы + fun addAnswer(numberQuest: Int, numberAnswer: Int) { + numAnswers[numberQuest-1] = numberAnswer + // if (numberQuest > 1 && numberQuest < quizGames.size) numAnswers[numberQuest-1] = numberAnswer + } + + // Счетчик ответов + fun checkAnswers(): Boolean { + return numAnswers.size == quizGames.size + } + + // Метод написания отчета и подсчета правильных ответов + fun getResultMessage(): User { + var result = 0 + var history = "История вопросов и ответов.\n" + + for (x in 0..quizGames.lastIndex) { + + val y = numAnswers[x]!! + if (quizGames[x].numberCorrectAnswer == numAnswers[x]) ++result + history += "\nВопрос: ${quizGames[x].question}\nВаш ответ: ${quizGames[x].answers[y - 1]}\nПравильный ответ: ${quizGames[x].answers[quizGames[x].numberCorrectAnswer - 1]}" + } + + return User("jesus@haven.com", "Результат квиза: $result из 5.", history) + } + + // Для создания фрагментов + fun getQuizObject(num: Int) : QuizObject { + return if (num <= quizGames.lastIndex) quizGames[num] else nullQuizObject + } + + // Для ViewPager2 + fun getSize() : Int = quizGames.lastIndex + 1 + + // Патерн Null-объект + private val nullQuizObject = QuizObject("nullQuizObject", listOf("nullQuizObject", "nullQuizObject", "nullQuizObject", "nullQuizObject", "nullQuizObject"), 1,1) +} \ No newline at end of file diff --git a/app/src/main/java/com/rsschool/quiz/MainActivity.kt b/app/src/main/java/com/rsschool/quiz/MainActivity.kt index 77bd5424..9e9627d4 100644 --- a/app/src/main/java/com/rsschool/quiz/MainActivity.kt +++ b/app/src/main/java/com/rsschool/quiz/MainActivity.kt @@ -1,11 +1,79 @@ package com.rsschool.quiz -import androidx.appcompat.app.AppCompatActivity +import android.content.Intent import android.os.Bundle +import androidx.fragment.app.FragmentActivity +import androidx.viewpager2.widget.ViewPager2 +import com.rsschool.quiz.databinding.ActivityMainBinding + + +const val EXTRA_MESSAGE = "com.rsschool.quiz.MESSAGE" + +// MainActivity Унаследуем от FragmentActivity. +class MainActivity : FragmentActivity(), ActionListener { + // Объявим переменные для адаптера и вьюпейджера. + private lateinit var adapter: NumberAdapter + private lateinit var viewPager: ViewPager2 + + // Ресурсы + private lateinit var binding: ActivityMainBinding + private val dataQuiz = DataObjectAccess() -class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) + + // Чтобы создать объект класса ResultProfileBinding, надо вызвать статический метод inflate() + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + + // В методе onCreate инициализируем адаптер, передав ему текущее активити как владелец жизненного цикла + adapter = NumberAdapter(this) + + // находим вьюпейджер по идентификатору и передаем ему адаптер. + viewPager = binding.pager + viewPager.adapter = adapter + + // Слушаем изменение фрагментов и меняем цвет statusBar + viewPager.registerOnPageChangeCallback(object:ViewPager2.OnPageChangeCallback(){ + override fun onPageSelected(position: Int) { + when(position){ + 0 -> window.statusBarColor = getColor(R.color.secondaryDarkColorPurple) + 1 -> window.statusBarColor = getColor(R.color.secondaryDarkColorBlue) + 2 -> window.statusBarColor = getColor(R.color.secondaryDarkColorGreen) + 3 -> window.statusBarColor = getColor(R.color.secondaryDarkColorYellow) + 4 -> window.statusBarColor = getColor(R.color.secondaryDarkColorOrange) + else -> window.statusBarColor = getColor(R.color.secondaryDarkColor) + } + } + } + ) + } + + // Методы для кнопок, менять фрагменты + override fun nextFragment() { + ++viewPager.currentItem } + + override fun backFragment() { + --viewPager.currentItem + } + + // Метод реализации меню результата закостылил созданием нового фрагмента и удалением предыдущего + override fun runResultFragment() { + + val intent = Intent(this, ResultActivity::class.java).apply { + putExtra(EXTRA_MESSAGE, dataQuiz.getResultMessage()) + } + startActivity(intent) + finish() + } + + override fun addAnswer(numberQuest: Int, numberAnswer: Int) { + dataQuiz.addAnswer(numberQuest, numberAnswer) + } + + override fun checkAnswersCount(): Boolean { + return dataQuiz.checkAnswers() + } + } \ No newline at end of file diff --git a/app/src/main/java/com/rsschool/quiz/NumberAdapter.kt b/app/src/main/java/com/rsschool/quiz/NumberAdapter.kt new file mode 100644 index 00000000..0daa43fd --- /dev/null +++ b/app/src/main/java/com/rsschool/quiz/NumberAdapter.kt @@ -0,0 +1,39 @@ +package com.rsschool.quiz + +import android.os.Bundle +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.viewpager2.adapter.FragmentStateAdapter + +// Адаптер - специальный компонент, который связывает источник данных с виджетом списка. +// Создадим адаптер, задачей которого будет предоставление фрагментов для слайдера: +class NumberAdapter(private val fragment: FragmentActivity) : FragmentStateAdapter(fragment) { + + // Набор данных, которые свяжем со списком + private val dao = DataObjectAccess() + + // Требуется переопределить метод getItemCount, возвращающий общее количество элементов списка. + override fun getItemCount(): Int = dao.getSize() + + // Переопределим метод createFragment, возвращающий фрагмент для каждого элемента слайдера. + override fun createFragment(position: Int): Fragment { + + // Перед созданием фрагмента устанавливаем тему приложения + when (position) { + 0 -> fragment.theme?.applyStyle(R.style.Theme_Quiz_Purple, true) + 1 -> fragment.theme?.applyStyle(R.style.Theme_Quiz_Blue, true) + 2 -> fragment.theme?.applyStyle(R.style.Theme_Quiz_Green, true) + 3 -> fragment.theme?.applyStyle(R.style.Theme_Quiz_Yellow, true) + 4 -> fragment.theme?.applyStyle(R.style.Theme_Quiz_Orange, true) + else -> fragment.theme?.applyStyle(R.style.Theme_Quiz_First, true) + } + + val fragment = BlankFragment() + fragment.arguments = Bundle().apply { + // Передаем данные в созданный фрагмент + putSerializable(ARG_OBJECT, dao.getQuizObject(position)) + } + return fragment + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/rsschool/quiz/QuizObject.kt b/app/src/main/java/com/rsschool/quiz/QuizObject.kt new file mode 100644 index 00000000..6cd18474 --- /dev/null +++ b/app/src/main/java/com/rsschool/quiz/QuizObject.kt @@ -0,0 +1,9 @@ +package com.rsschool.quiz + +import java.io.Serializable + +data class QuizObject(val question: String, + val answers: List, + val numberQuestion: Int, + val numberCorrectAnswer: Int +) : Serializable \ No newline at end of file diff --git a/app/src/main/java/com/rsschool/quiz/ResultActivity.kt b/app/src/main/java/com/rsschool/quiz/ResultActivity.kt new file mode 100644 index 00000000..c60e9368 --- /dev/null +++ b/app/src/main/java/com/rsschool/quiz/ResultActivity.kt @@ -0,0 +1,56 @@ +package com.rsschool.quiz + +import android.content.Intent +import android.net.Uri +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.view.View +import com.rsschool.quiz.databinding.ActivityResultBinding + +class ResultActivity : AppCompatActivity() { + + private lateinit var binding: ActivityResultBinding + private lateinit var userObject: User + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_result) + + // Чтобы создать объект класса ResultProfileBinding, надо вызвать статический метод inflate() + binding = ActivityResultBinding.inflate(layoutInflater) + setContentView(binding.root) + + // Get the Intent that started this activity and extract the string + userObject = intent.getSerializableExtra(EXTRA_MESSAGE) as User + + // Capture the layout's TextView and set the string as its text + binding.textResult.apply { + text = userObject.resultMessage + } + } + + // android:onClick + fun sendEmail(view: View) { + + val intent = Intent(Intent.ACTION_SENDTO).apply { + data = Uri.parse("mailto:" + userObject.email) // only email apps should handle this + putExtra(Intent.EXTRA_SUBJECT, userObject.resultMessage) + putExtra(Intent.EXTRA_TEXT, userObject.resultQuiz) + } + if (intent.resolveActivity(packageManager) != null) { + startActivity(intent) + } + } + + // android:onClick + fun restartQuiz(view: View) { + val intent = Intent(this, MainActivity::class.java) + startActivity(intent) + finish() + } + + // android:onClick + fun exitApp(view: View) { + finish() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/rsschool/quiz/TestDAO.kt b/app/src/main/java/com/rsschool/quiz/TestDAO.kt new file mode 100644 index 00000000..808b8e0c --- /dev/null +++ b/app/src/main/java/com/rsschool/quiz/TestDAO.kt @@ -0,0 +1,25 @@ +package com.rsschool.quiz + + fun main(args: Array){ + println("Hello Kotlin") + val dataQuiz = DataObjectAccess() + + dataQuiz.addAnswer(1, 1) + dataQuiz.addAnswer(2, 2) + dataQuiz.addAnswer(3, 3) + dataQuiz.addAnswer(4, 2) + dataQuiz.addAnswer(5, 2) + + //println(dataQuiz.numAnswers.toString()) + + print("кол-во ответов: ") + //println(dataQuiz.numAnswers.size) + println(dataQuiz.checkAnswers()) + + if (dataQuiz.checkAnswers()) { + val result = dataQuiz.getResultMessage() + println(result.email) + println(result.resultMessage) + println(result.resultQuiz) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/rsschool/quiz/User.kt b/app/src/main/java/com/rsschool/quiz/User.kt new file mode 100644 index 00000000..377a7c8d --- /dev/null +++ b/app/src/main/java/com/rsschool/quiz/User.kt @@ -0,0 +1,8 @@ +package com.rsschool.quiz + +import java.io.Serializable + +data class User(val email: String, + val resultMessage: String, + val resultQuiz: String +) : Serializable \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 4fc24441..b5abed55 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -6,10 +6,10 @@ android:layout_height="match_parent" tools:context=".MainActivity"> - + + + + +