diff --git a/app/build.gradle b/app/build.gradle
index 6166c369e..f3e57d22d 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -39,6 +39,18 @@ android {
buildFeatures {
dataBinding true
}
+
+ // Configure only for each module that uses Java 8
+ // language features (either in its source code or
+ // through dependencies).
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ // For Kotlin projects
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
}
dependencies {
@@ -53,6 +65,7 @@ dependencies {
// KTX
implementation 'androidx.core:core-ktx:1.3.1'
+ implementation "androidx.fragment:fragment-ktx:1.2.5"
// Navigation
implementation "android.arch.navigation:navigation-fragment-ktx:1.0.0-rc02"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ebac42795..3122546fb 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -18,6 +18,8 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.example.android.guesstheword">
+
+
-
-
+
diff --git a/app/src/main/java/com/example/android/guesstheword/screens/game/GameFragment.kt b/app/src/main/java/com/example/android/guesstheword/screens/game/GameFragment.kt
index 0dbe12118..3a39c6aec 100644
--- a/app/src/main/java/com/example/android/guesstheword/screens/game/GameFragment.kt
+++ b/app/src/main/java/com/example/android/guesstheword/screens/game/GameFragment.kt
@@ -17,13 +17,16 @@
package com.example.android.guesstheword.screens.game
import android.os.Bundle
+import android.os.VibrationEffect
+import android.os.Vibrator
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import androidx.databinding.DataBindingUtil
+import androidx.core.content.getSystemService
import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.Observer
import androidx.navigation.fragment.NavHostFragment.findNavController
-import com.example.android.guesstheword.R
import com.example.android.guesstheword.databinding.GameFragmentBinding
/**
@@ -31,111 +34,59 @@ import com.example.android.guesstheword.databinding.GameFragmentBinding
*/
class GameFragment : Fragment() {
- // The current word
- private var word = ""
-
- // The current score
- private var score = 0
-
- // The list of words - the front of the list is the next word to guess
- private lateinit var wordList: MutableList
-
- private lateinit var binding: GameFragmentBinding
-
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?): View? {
-
- // Inflate view and obtain an instance of the binding class
- binding = DataBindingUtil.inflate(
- inflater,
- R.layout.game_fragment,
- container,
- false
- )
-
- resetList()
- nextWord()
-
- binding.correctButton.setOnClickListener { onCorrect() }
- binding.skipButton.setOnClickListener { onSkip() }
- updateScoreText()
- updateWordText()
- return binding.root
-
- }
-
- /**
- * Resets the list of words and randomizes the order
- */
- private fun resetList() {
- wordList = mutableListOf(
- "queen",
- "hospital",
- "basketball",
- "cat",
- "change",
- "snail",
- "soup",
- "calendar",
- "sad",
- "desk",
- "guitar",
- "home",
- "railway",
- "zebra",
- "jelly",
- "car",
- "crow",
- "trade",
- "bag",
- "roll",
- "bubble"
- )
- wordList.shuffle()
- }
+ private val viewModel by viewModels()
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? = GameFragmentBinding.inflate(inflater)
+ .apply {
+ this.viewmodel = viewModel
+ this.lifecycleOwner = viewLifecycleOwner
+ }
+ .also {
+ viewModel.run {
+ eventGameFinish.observe(viewLifecycleOwner, Observer { hasFinished ->
+ handleEventGameFinish(hasFinished)
+ })
+ eventBuzz.observe(viewLifecycleOwner, Observer { buzzType ->
+ handleEventBuzz(buzzType)
+ })
+ }
+ }
+ .root
/**
* Called when the game is finished
*/
- private fun gameFinished() {
- val action = GameFragmentDirections.actionGameToScore(score)
- findNavController(this).navigate(action)
+ private fun handleEventGameFinish(hasFinished: Boolean) {
+ if (hasFinished) {
+ val action = GameFragmentDirections.actionGameToScore(viewModel.score.value ?: 0)
+ findNavController(this).navigate(action)
+ viewModel.onGameFinishComplete()
+ }
}
/**
- * Moves to the next word in the list
+ *
*/
- private fun nextWord() {
- //Select and remove a word from the list
- if (wordList.isEmpty()) {
- gameFinished()
- } else {
- word = wordList.removeAt(0)
+ private fun handleEventBuzz(type: GameViewModel.BuzzType) {
+ if (type != GameViewModel.BuzzType.NO_BUZZ) {
+ buzz(type.pattern)
+ viewModel.onBuzzComplete()
}
- updateWordText()
- updateScoreText()
- }
-
- /** Methods for buttons presses **/
-
- private fun onSkip() {
- score--
- nextWord()
- }
-
- private fun onCorrect() {
- score++
- nextWord()
- }
-
- /** Methods for updating the UI **/
-
- private fun updateWordText() {
- binding.wordText.text = word
-
}
- private fun updateScoreText() {
- binding.scoreText.text = score.toString()
+ @Suppress("DEPRECATION")
+ private fun buzz(pattern: LongArray) {
+ activity?.getSystemService()
+ ?.let { vibrator ->
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
+ vibrator.vibrate(VibrationEffect.createWaveform(pattern, -1))
+ } else {
+ vibrator.vibrate(pattern, -1)
+ }
+ }
}
}
diff --git a/app/src/main/java/com/example/android/guesstheword/screens/game/GameViewModel.kt b/app/src/main/java/com/example/android/guesstheword/screens/game/GameViewModel.kt
new file mode 100644
index 000000000..2e7e7189b
--- /dev/null
+++ b/app/src/main/java/com/example/android/guesstheword/screens/game/GameViewModel.kt
@@ -0,0 +1,160 @@
+package com.example.android.guesstheword.screens.game
+
+import android.os.CountDownTimer
+import android.text.format.DateUtils
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.Transformations
+import androidx.lifecycle.ViewModel
+
+private val CORRECT_BUZZ_PATTERN = longArrayOf(100, 100, 100, 100, 100, 100)
+private val PANIC_BUZZ_PATTERN = longArrayOf(0, 200)
+private val GAME_OVER_BUZZ_PATTERN = longArrayOf(0, 2000)
+private val NO_BUZZ_PATTERN = longArrayOf(0)
+
+class GameViewModel : ViewModel() {
+
+ companion object {
+ // These represent different important times
+ // This is when the game is over
+ private const val DONE = 0L
+
+ // This is the number of milliseconds in a second
+ private const val ONE_SECOND = 1000L
+
+ // This is the total time of the game
+ private const val COUNTDOWN_TIME = 60000L
+
+ // This is when we must start to buzz a panic pattern
+ private const val COUNTDOWN_PANIC_SECONDS = 10L
+ }
+
+ // These are the three different types of buzzing in the game. Buzz pattern is the number of
+ // milliseconds each interval of buzzing and non-buzzing takes.
+ enum class BuzzType(val pattern: LongArray) {
+ CORRECT(CORRECT_BUZZ_PATTERN),
+ COUNTDOWN_PANIC(PANIC_BUZZ_PATTERN),
+ GAME_OVER(GAME_OVER_BUZZ_PATTERN),
+ NO_BUZZ(NO_BUZZ_PATTERN)
+ }
+
+ // The current word
+ private val _word = MutableLiveData("")
+ val word: LiveData
+ get() = _word
+
+ // The current score
+ private val _score = MutableLiveData(0)
+ val score: LiveData
+ get() = _score
+
+ private val _eventGameFinish = MutableLiveData(false)
+ val eventGameFinish: LiveData
+ get() = _eventGameFinish
+
+ private val _eventBuzz = MutableLiveData(BuzzType.NO_BUZZ)
+ val eventBuzz: LiveData
+ get() = _eventBuzz
+
+ // The list of words - the front of the list is the next word to guess
+ private lateinit var wordList: MutableList
+
+ private val timer: CountDownTimer
+
+ private val _currentTime = MutableLiveData(0)
+ val currentTime: LiveData
+ get() = _currentTime
+
+ val elapsedTime: LiveData
+ get() = Transformations.map(currentTime) { time -> DateUtils.formatElapsedTime(time) }
+
+ init {
+ resetList()
+ nextWord()
+ timer = object : CountDownTimer(COUNTDOWN_TIME, ONE_SECOND) {
+ override fun onTick(millisUntilFinished: Long) = onTimerTick(millisUntilFinished)
+ override fun onFinish() = finishGame()
+ }
+ timer.start()
+ }
+
+ private fun onTimerTick(millisUntilFinished: Long) {
+ val secondsToFinished = millisUntilFinished / ONE_SECOND
+ _currentTime.value = secondsToFinished
+ if (secondsToFinished <= COUNTDOWN_PANIC_SECONDS) {
+ _eventBuzz.value = BuzzType.COUNTDOWN_PANIC
+ }
+ }
+
+ private fun finishGame() {
+ _currentTime.value = DONE
+ _eventGameFinish.value = true
+ _eventBuzz.value = BuzzType.GAME_OVER
+ }
+
+ /**
+ * Resets the list of words and randomizes the order
+ */
+ private fun resetList() {
+ wordList = mutableListOf(
+ "queen",
+ "hospital",
+ "basketball",
+ "cat",
+ "change",
+ "snail",
+ "soup",
+ "calendar",
+ "sad",
+ "desk",
+ "guitar",
+ "home",
+ "railway",
+ "zebra",
+ "jelly",
+ "car",
+ "crow",
+ "trade",
+ "bag",
+ "roll",
+ "bubble"
+ )
+ wordList.shuffle()
+ }
+
+ /**
+ * Moves to the next word in the list
+ */
+ private fun nextWord() {
+ if (wordList.isEmpty()) {
+ resetList()
+ }
+ //Select and remove a word from the list
+ _word.value = wordList.removeAt(0)
+ }
+
+ /** Methods for buttons presses **/
+ fun onSkip() {
+ _score.value = score.value?.minus(1)
+ nextWord()
+ }
+
+ fun onCorrect() {
+ _score.value = score.value?.plus(1)
+ _eventBuzz.value = BuzzType.CORRECT
+ nextWord()
+ }
+
+ fun onGameFinishComplete() {
+ _eventGameFinish.value = false
+ }
+
+ fun onBuzzComplete() {
+ _eventBuzz.value = BuzzType.NO_BUZZ
+ }
+
+ override fun onCleared() {
+ super.onCleared()
+ timer.cancel()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/android/guesstheword/screens/score/ScoreFragment.kt b/app/src/main/java/com/example/android/guesstheword/screens/score/ScoreFragment.kt
index 63bcb6191..867b89f10 100644
--- a/app/src/main/java/com/example/android/guesstheword/screens/score/ScoreFragment.kt
+++ b/app/src/main/java/com/example/android/guesstheword/screens/score/ScoreFragment.kt
@@ -22,6 +22,8 @@ import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
+import androidx.lifecycle.Observer
+import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import com.example.android.guesstheword.R
@@ -48,8 +50,18 @@ class ScoreFragment : Fragment() {
// Get args using by navArgs property delegate
val scoreFragmentArgs by navArgs()
- binding.scoreText.text = scoreFragmentArgs.score.toString()
- binding.playAgainButton.setOnClickListener { onPlayAgain() }
+ val viewModelFactory = ScoreViewModelFactory(scoreFragmentArgs.score)
+ val viewModel = ViewModelProvider(this, viewModelFactory)
+ .get(ScoreViewModel::class.java)
+ binding.viewmodel = viewModel
+ binding.lifecycleOwner = viewLifecycleOwner
+
+ viewModel.eventPlayAgain.observe(viewLifecycleOwner, Observer { playAgain ->
+ if (playAgain) {
+ onPlayAgain()
+ viewModel.onPlayAgainComplete()
+ }
+ })
return binding.root
}
diff --git a/app/src/main/java/com/example/android/guesstheword/screens/score/ScoreViewModel.kt b/app/src/main/java/com/example/android/guesstheword/screens/score/ScoreViewModel.kt
new file mode 100644
index 000000000..4b3346da0
--- /dev/null
+++ b/app/src/main/java/com/example/android/guesstheword/screens/score/ScoreViewModel.kt
@@ -0,0 +1,24 @@
+package com.example.android.guesstheword.screens.score
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+
+class ScoreViewModel(finalScore: Int) : ViewModel() {
+
+ private val _score = MutableLiveData(finalScore)
+ val score: LiveData
+ get() = _score
+
+ private val _eventPlayAgain = MutableLiveData(false)
+ val eventPlayAgain: LiveData
+ get() = _eventPlayAgain
+
+ fun onPlayAgain() {
+ _eventPlayAgain.value = true
+ }
+
+ fun onPlayAgainComplete() {
+ _eventPlayAgain.value = false
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/android/guesstheword/screens/score/ScoreViewModelFactory.kt b/app/src/main/java/com/example/android/guesstheword/screens/score/ScoreViewModelFactory.kt
new file mode 100644
index 000000000..b50510e86
--- /dev/null
+++ b/app/src/main/java/com/example/android/guesstheword/screens/score/ScoreViewModelFactory.kt
@@ -0,0 +1,14 @@
+package com.example.android.guesstheword.screens.score
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import java.lang.IllegalArgumentException
+
+class ScoreViewModelFactory(private val finalScore: Int) : ViewModelProvider.Factory {
+ override fun create(modelClass: Class): T {
+ if (modelClass.isAssignableFrom(ScoreViewModel::class.java)) {
+ return ScoreViewModel(finalScore) as T
+ }
+ throw IllegalArgumentException("Unknown viewModel class: $modelClass")
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/game_fragment.xml b/app/src/main/res/layout/game_fragment.xml
index 8fbd82f0c..732b7f4f5 100644
--- a/app/src/main/res/layout/game_fragment.xml
+++ b/app/src/main/res/layout/game_fragment.xml
@@ -18,6 +18,12 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
+
+
+
+
+ tools:text="Current Score: 2" />