Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -22,6 +31,10 @@ android {
kotlinOptions {
jvmTarget = '1.8'
}

buildFeatures {
viewBinding = true
}
}

dependencies {
Expand All @@ -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")
}
8 changes: 6 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:label="Quiz"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Quiz.First">
android:theme="@style/Theme.Quiz.Purple">
<activity
android:name=".MainActivity"
android:screenOrientation="portrait">
Expand All @@ -18,6 +18,10 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ResultActivity"
android:screenOrientation="portrait"
android:theme="@style/Style.Quiz.Purple" />
</application>

</manifest>
9 changes: 9 additions & 0 deletions app/src/main/java/com/rsschool/quiz/ActionListener.kt
Original file line number Diff line number Diff line change
@@ -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
}
107 changes: 107 additions & 0 deletions app/src/main/java/com/rsschool/quiz/BlankFragment.kt
Original file line number Diff line number Diff line change
@@ -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
}

}
54 changes: 54 additions & 0 deletions app/src/main/java/com/rsschool/quiz/DataObjectAccess.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.rsschool.quiz

// Этот класс реализует доступ к базе данных, в которой хранятся User и QuizObject объекты
class DataObjectAccess {

// Таблица БД
private val quizGames: List<QuizObject> = 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<Int, Int> = 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("[email protected]", "Результат квиза: $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)
}
74 changes: 71 additions & 3 deletions app/src/main/java/com/rsschool/quiz/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -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()
}

}
39 changes: 39 additions & 0 deletions app/src/main/java/com/rsschool/quiz/NumberAdapter.kt
Original file line number Diff line number Diff line change
@@ -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
}

}
9 changes: 9 additions & 0 deletions app/src/main/java/com/rsschool/quiz/QuizObject.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.rsschool.quiz

import java.io.Serializable

data class QuizObject(val question: String,
val answers: List<String>,
val numberQuestion: Int,
val numberCorrectAnswer: Int
) : Serializable
Loading