From dc1515f6208748325896f10db46fc35315ce4321 Mon Sep 17 00:00:00 2001 From: impactrudia Date: Tue, 22 Feb 2022 17:20:24 +0900 Subject: [PATCH 01/10] =?UTF-8?q?feat=20:=20viewModel=20=EC=9D=B4=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=84=A3?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- GuessTheWord-Starter/app/build.gradle | 3 + .../guesstheword/screens/game/GameFragment.kt | 87 ++++++------------- .../screens/game/GameViewModel.kt | 80 +++++++++++++++++ .../screens/score/ScoreFragment.kt | 8 ++ .../screens/score/ScoreViewModel.kt | 13 +++ .../screens/score/ScoreViewModelFactory.kt | 14 +++ 6 files changed, 145 insertions(+), 60 deletions(-) create mode 100644 GuessTheWord-Starter/app/src/main/java/com/example/android/guesstheword/screens/game/GameViewModel.kt create mode 100644 GuessTheWord-Starter/app/src/main/java/com/example/android/guesstheword/screens/score/ScoreViewModel.kt create mode 100644 GuessTheWord-Starter/app/src/main/java/com/example/android/guesstheword/screens/score/ScoreViewModelFactory.kt diff --git a/GuessTheWord-Starter/app/build.gradle b/GuessTheWord-Starter/app/build.gradle index b1848a1c4..710cec948 100755 --- a/GuessTheWord-Starter/app/build.gradle +++ b/GuessTheWord-Starter/app/build.gradle @@ -56,4 +56,7 @@ dependencies { // Navigation implementation "androidx.navigation:navigation-fragment-ktx:2.3.0" implementation "androidx.navigation:navigation-ui-ktx:2.3.0" + + //ViewModel + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' } diff --git a/GuessTheWord-Starter/app/src/main/java/com/example/android/guesstheword/screens/game/GameFragment.kt b/GuessTheWord-Starter/app/src/main/java/com/example/android/guesstheword/screens/game/GameFragment.kt index 57d8b72ac..ad3f78b70 100755 --- a/GuessTheWord-Starter/app/src/main/java/com/example/android/guesstheword/screens/game/GameFragment.kt +++ b/GuessTheWord-Starter/app/src/main/java/com/example/android/guesstheword/screens/game/GameFragment.kt @@ -17,11 +17,15 @@ package com.example.android.guesstheword.screens.game import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.Toast import androidx.databinding.DataBindingUtil import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider +import androidx.navigation.fragment.NavHostFragment import com.example.android.guesstheword.R import com.example.android.guesstheword.databinding.GameFragmentBinding @@ -30,17 +34,10 @@ 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 + private lateinit var viewModel: GameViewModel + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { @@ -52,79 +49,49 @@ class GameFragment : Fragment() { false ) - resetList() - nextWord() + Log.i("GameFragment", "Called ViewModelProvider.get") + viewModel = ViewModelProvider(this).get(GameViewModel::class.java) binding.correctButton.setOnClickListener { onCorrect() } binding.skipButton.setOnClickListener { onSkip() } + binding.endGameButton.setOnClickListener { onEndGame() } 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() - } - - /** Methods for buttons presses **/ + /** Methods for button click handlers **/ private fun onSkip() { - score-- - nextWord() + viewModel.onSkip() + updateWordText() + updateScoreText() } private fun onCorrect() { - score++ - nextWord() - } - - /** - * Moves to the next word in the list - */ - private fun nextWord() { - if (!wordList.isEmpty()) { - //Select and remove a word from the list - word = wordList.removeAt(0) - } + viewModel.onCorrect() updateWordText() updateScoreText() } - /** Methods for updating the UI **/ - private fun updateWordText() { - binding.wordText.text = word + binding.wordText.text = viewModel.word } private fun updateScoreText() { - binding.scoreText.text = score.toString() + binding.scoreText.text = viewModel.score.toString() + } + + private fun onEndGame() { + gameFinished() + } + + private fun gameFinished() { + Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show() + val action = GameFragmentDirections.actionGameToScore() + action.score = viewModel.score + NavHostFragment.findNavController(this).navigate(action) } } diff --git a/GuessTheWord-Starter/app/src/main/java/com/example/android/guesstheword/screens/game/GameViewModel.kt b/GuessTheWord-Starter/app/src/main/java/com/example/android/guesstheword/screens/game/GameViewModel.kt new file mode 100644 index 000000000..1bca08627 --- /dev/null +++ b/GuessTheWord-Starter/app/src/main/java/com/example/android/guesstheword/screens/game/GameViewModel.kt @@ -0,0 +1,80 @@ +package com.example.android.guesstheword.screens.game + +import android.util.Log +import androidx.lifecycle.ViewModel + +class GameViewModel : ViewModel() { + + // The current word + var word = "" + + // The current score + var score = 0 + + // The list of words - the front of the list is the next word to guess + private lateinit var wordList: MutableList + + /** + * Resets the list of words and randomizes the order + */ + 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() + } + + + init { + resetList() + nextWord() + Log.i("GameViewModel", "GameViewModel created!") + } + + /** + * Moves to the next word in the list + */ + private fun nextWord() { + if (!wordList.isEmpty()) { + //Select and remove a word from the list + word = wordList.removeAt(0) + } + + } + + /** Methods for buttons presses **/ + fun onSkip() { + score-- + nextWord() + } + + fun onCorrect() { + score++ + nextWord() + } + + override fun onCleared() { + super.onCleared() + Log.i("GameViewModel", "GameViewModel destroyed!!") + } +} \ No newline at end of file diff --git a/GuessTheWord-Starter/app/src/main/java/com/example/android/guesstheword/screens/score/ScoreFragment.kt b/GuessTheWord-Starter/app/src/main/java/com/example/android/guesstheword/screens/score/ScoreFragment.kt index 4962b73ea..5dd914abe 100755 --- a/GuessTheWord-Starter/app/src/main/java/com/example/android/guesstheword/screens/score/ScoreFragment.kt +++ b/GuessTheWord-Starter/app/src/main/java/com/example/android/guesstheword/screens/score/ScoreFragment.kt @@ -22,6 +22,7 @@ import android.view.View import android.view.ViewGroup import androidx.databinding.DataBindingUtil import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider import androidx.navigation.fragment.navArgs import com.example.android.guesstheword.R import com.example.android.guesstheword.databinding.ScoreFragmentBinding @@ -31,6 +32,9 @@ import com.example.android.guesstheword.databinding.ScoreFragmentBinding */ class ScoreFragment : Fragment() { + private lateinit var viewModel: ScoreViewModel + private lateinit var viewModelFactory: ScoreViewModelFactory + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -45,6 +49,10 @@ class ScoreFragment : Fragment() { false ) + viewModelFactory = ScoreViewModelFactory(ScoreFragmentArgs.fromBundle(requireArguments()).score) + viewModel = ViewModelProvider(this, viewModelFactory).get(ScoreViewModel::class.java) + + binding.scoreText.text = viewModel.score.toString() return binding.root } } diff --git a/GuessTheWord-Starter/app/src/main/java/com/example/android/guesstheword/screens/score/ScoreViewModel.kt b/GuessTheWord-Starter/app/src/main/java/com/example/android/guesstheword/screens/score/ScoreViewModel.kt new file mode 100644 index 000000000..0bed049b9 --- /dev/null +++ b/GuessTheWord-Starter/app/src/main/java/com/example/android/guesstheword/screens/score/ScoreViewModel.kt @@ -0,0 +1,13 @@ +package com.example.android.guesstheword.screens.score + +import android.util.Log +import androidx.lifecycle.ViewModel + +class ScoreViewModel(finalScore: Int) : ViewModel() { + // The final score + var score = finalScore + + init { + Log.i("ScoreViewModel", "Final score is $finalScore") + } +} \ No newline at end of file diff --git a/GuessTheWord-Starter/app/src/main/java/com/example/android/guesstheword/screens/score/ScoreViewModelFactory.kt b/GuessTheWord-Starter/app/src/main/java/com/example/android/guesstheword/screens/score/ScoreViewModelFactory.kt new file mode 100644 index 000000000..73d0c2d56 --- /dev/null +++ b/GuessTheWord-Starter/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 + +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") + } +} \ No newline at end of file From ff8944b31d5bacc19bdfc1fa25a53187e5603d7d Mon Sep 17 00:00:00 2001 From: impactrudia Date: Wed, 23 Feb 2022 22:08:48 +0900 Subject: [PATCH 02/10] =?UTF-8?q?feat=20:=20database=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=20=EC=A3=BC=EC=84=9D=20=ED=95=B4=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../trackmysleepquality/SleepDatabaseTest.kt | 108 +++++++++--------- .../database/SleepDatabase.kt | 32 ++++++ .../database/SleepDatabaseDao.kt | 24 +++- .../database/SleepNight.kt | 15 +++ 4 files changed, 124 insertions(+), 55 deletions(-) diff --git a/TrackMySleepQuality-Starter/app/src/androidTest/java/com/example/android/trackmysleepquality/SleepDatabaseTest.kt b/TrackMySleepQuality-Starter/app/src/androidTest/java/com/example/android/trackmysleepquality/SleepDatabaseTest.kt index 1e5d85669..5e857eeb4 100755 --- a/TrackMySleepQuality-Starter/app/src/androidTest/java/com/example/android/trackmysleepquality/SleepDatabaseTest.kt +++ b/TrackMySleepQuality-Starter/app/src/androidTest/java/com/example/android/trackmysleepquality/SleepDatabaseTest.kt @@ -15,57 +15,57 @@ */ package com.example.android.trackmysleepquality -// -//import androidx.room.Room -//import androidx.test.ext.junit.runners.AndroidJUnit4 -//import androidx.test.platform.app.InstrumentationRegistry -//import com.example.android.trackmysleepquality.database.SleepDatabase -//import com.example.android.trackmysleepquality.database.SleepDatabaseDao -//import com.example.android.trackmysleepquality.database.SleepNight -//import org.junit.Assert.assertEquals -//import org.junit.After -//import org.junit.Before -//import org.junit.Test -//import org.junit.runner.RunWith -//import java.io.IOException -// -// -///** -// * This is not meant to be a full set of tests. For simplicity, most of your samples do not -// * include tests. However, when building the Room, it is helpful to make sure it works before -// * adding the UI. -// */ -// -//@RunWith(AndroidJUnit4::class) -//class SleepDatabaseTest { -// -// private lateinit var sleepDao: SleepDatabaseDao -// private lateinit var db: SleepDatabase -// -// @Before -// fun createDb() { -// val context = InstrumentationRegistry.getInstrumentation().targetContext -// // Using an in-memory database because the information stored here disappears when the -// // process is killed. -// db = Room.inMemoryDatabaseBuilder(context, SleepDatabase::class.java) -// // Allowing main thread queries, just for testing. -// .allowMainThreadQueries() -// .build() -// sleepDao = db.sleepDatabaseDao -// } -// -// @After -// @Throws(IOException::class) -// fun closeDb() { -// db.close() -// } -// -// @Test -// @Throws(Exception::class) -// fun insertAndGetNight() { -// val night = SleepNight() -// sleepDao.insert(night) -// val tonight = sleepDao.getTonight() -// assertEquals(tonight?.sleepQuality, -1) -// } -//} \ No newline at end of file + +import androidx.room.Room +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.example.android.trackmysleepquality.database.SleepDatabase +import com.example.android.trackmysleepquality.database.SleepDatabaseDao +import com.example.android.trackmysleepquality.database.SleepNight +import org.junit.Assert.assertEquals +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import java.io.IOException + + +/** + * This is not meant to be a full set of tests. For simplicity, most of your samples do not + * include tests. However, when building the Room, it is helpful to make sure it works before + * adding the UI. + */ + +@RunWith(AndroidJUnit4::class) +class SleepDatabaseTest { + + private lateinit var sleepDao: SleepDatabaseDao + private lateinit var db: SleepDatabase + + @Before + fun createDb() { + val context = InstrumentationRegistry.getInstrumentation().targetContext + // Using an in-memory database because the information stored here disappears when the + // process is killed. + db = Room.inMemoryDatabaseBuilder(context, SleepDatabase::class.java) + // Allowing main thread queries, just for testing. + .allowMainThreadQueries() + .build() + sleepDao = db.sleepDatabaseDao + } + + @After + @Throws(IOException::class) + fun closeDb() { + db.close() + } + + @Test + @Throws(Exception::class) + fun insertAndGetNight() { + val night = SleepNight() + sleepDao.insert(night) + val tonight = sleepDao.getTonight() + assertEquals(tonight?.sleepQuality, -1) + } +} \ No newline at end of file diff --git a/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabase.kt b/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabase.kt index f29a7d352..474fc6010 100755 --- a/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabase.kt +++ b/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabase.kt @@ -15,3 +15,35 @@ */ package com.example.android.trackmysleepquality.database + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase + +@Database(entities = [SleepNight::class], version = 1, exportSchema = false) +abstract class SleepDatabase : RoomDatabase() { + abstract val sleepDatabaseDao: SleepDatabaseDao + + companion object { + @Volatile + private var INSTANCE: SleepDatabase? = null + + fun getInstance(context: Context): SleepDatabase { + synchronized(this) { + var instance = INSTANCE + + if (instance == null) { + instance = Room.databaseBuilder( + context.applicationContext, + SleepDatabase::class.java, + "sleep_history_database" + ).fallbackToDestructiveMigration() + .build() + INSTANCE = instance + } + return instance + } + } + } +} diff --git a/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt b/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt index 6410761f3..ba3d29a2e 100755 --- a/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt +++ b/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabaseDao.kt @@ -16,7 +16,29 @@ package com.example.android.trackmysleepquality.database +import androidx.lifecycle.LiveData import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query +import androidx.room.Update @Dao -interface SleepDatabaseDao +interface SleepDatabaseDao { + @Insert + fun insert(night: SleepNight) + + @Update + fun update(night: SleepNight) + + @Query("SELECT * from daily_sleep_quality_table WHERE nightId = :key") + fun get(key: Long): SleepNight? + + @Query("DELETE FROM daily_sleep_quality_table") + fun clear() + + @Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC LIMIT 1") + fun getTonight(): SleepNight? + + @Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC") + fun getAllNights(): LiveData> +} diff --git a/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/database/SleepNight.kt b/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/database/SleepNight.kt index e80e4b70b..fa06e0a50 100755 --- a/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/database/SleepNight.kt +++ b/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/database/SleepNight.kt @@ -16,3 +16,18 @@ package com.example.android.trackmysleepquality.database +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "daily_sleep_quality_table") +data class SleepNight( + @PrimaryKey(autoGenerate = true) + var nightId: Long = 0L, + @ColumnInfo(name = "start_time_milli") + val startTimeMilli: Long = System.currentTimeMillis(), + @ColumnInfo(name = "end_time_milli") + var endTimeMilli: Long = startTimeMilli, + @ColumnInfo(name = "quality_rating") + var sleepQuality: Int = -1 +) \ No newline at end of file From 00865ae1a76b7a8490589849d9e0d2202ffa7607 Mon Sep 17 00:00:00 2001 From: impactrudia Date: Fri, 25 Feb 2022 05:01:05 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat=20:=20viewmodel=20dao=20=EB=B6=99?= =?UTF-8?q?=EC=9D=B4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TrackMySleepQuality-Starter/app/build.gradle | 5 ++ .../database/SleepDatabase.kt | 4 +- .../sleepquality/SleepQualityViewModel.kt | 5 ++ .../SleepQualityViewModelFactory.kt | 13 ++++ .../sleeptracker/SleepTrackerFragment.kt | 25 ++++++- .../sleeptracker/SleepTrackerViewModel.kt | 71 ++++++++++++++++++- .../res/layout/fragment_sleep_tracker.xml | 37 ++++++++-- 7 files changed, 147 insertions(+), 13 deletions(-) diff --git a/TrackMySleepQuality-Starter/app/build.gradle b/TrackMySleepQuality-Starter/app/build.gradle index 25ed8d70e..1dacb3cae 100755 --- a/TrackMySleepQuality-Starter/app/build.gradle +++ b/TrackMySleepQuality-Starter/app/build.gradle @@ -80,5 +80,10 @@ dependencies { testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' + + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" + + // Kotlin Extensions and Coroutines support for Room + implementation "androidx.room:room-ktx:$room_version" } diff --git a/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabase.kt b/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabase.kt index 474fc6010..3be0ce071 100755 --- a/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabase.kt +++ b/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/database/SleepDatabase.kt @@ -38,7 +38,9 @@ abstract class SleepDatabase : RoomDatabase() { context.applicationContext, SleepDatabase::class.java, "sleep_history_database" - ).fallbackToDestructiveMigration() + ) + .fallbackToDestructiveMigration() + .allowMainThreadQueries() .build() INSTANCE = instance } diff --git a/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModel.kt b/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModel.kt index e66449235..f44abb05f 100755 --- a/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModel.kt +++ b/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModel.kt @@ -15,3 +15,8 @@ */ package com.example.android.trackmysleepquality.sleepquality + +import androidx.lifecycle.ViewModel + +class SleepQualityViewModel : ViewModel() { +} \ No newline at end of file diff --git a/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModelFactory.kt b/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModelFactory.kt index e66449235..44d027b83 100755 --- a/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModelFactory.kt +++ b/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleepquality/SleepQualityViewModelFactory.kt @@ -15,3 +15,16 @@ */ package com.example.android.trackmysleepquality.sleepquality + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider + +class SleepQualityViewModelFactory : ViewModelProvider.Factory { + + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(SleepQualityViewModel::class.java)) { + return SleepQualityViewModel() as T + } + throw IllegalArgumentException("Unknown viewModel class") + } +} \ No newline at end of file diff --git a/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerFragment.kt b/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerFragment.kt index d395f78e9..7e548b8ba 100755 --- a/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerFragment.kt +++ b/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerFragment.kt @@ -22,7 +22,9 @@ import android.view.View import android.view.ViewGroup import androidx.databinding.DataBindingUtil import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider import com.example.android.trackmysleepquality.R +import com.example.android.trackmysleepquality.database.SleepDatabase import com.example.android.trackmysleepquality.databinding.FragmentSleepTrackerBinding /** @@ -37,12 +39,29 @@ class SleepTrackerFragment : Fragment() { * * This function uses DataBindingUtil to inflate R.layout.fragment_sleep_quality. */ - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View? { + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { // Get a reference to the binding object and inflate the fragment views. val binding: FragmentSleepTrackerBinding = DataBindingUtil.inflate( - inflater, R.layout.fragment_sleep_tracker, container, false) + inflater, R.layout.fragment_sleep_tracker, container, false + ) + + val application = requireNotNull(this.activity).application + + val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao + + val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application) + + val sleepTrackerViewModel = + ViewModelProvider( + this, viewModelFactory + ).get(SleepTrackerViewModel::class.java) + + binding.lifecycleOwner = this + binding.sleepTrackerViewModel = sleepTrackerViewModel return binding.root } diff --git a/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt b/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt index 77fe6e337..3d59770a5 100755 --- a/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt +++ b/TrackMySleepQuality-Starter/app/src/main/java/com/example/android/trackmysleepquality/sleeptracker/SleepTrackerViewModel.kt @@ -18,13 +18,80 @@ package com.example.android.trackmysleepquality.sleeptracker import android.app.Application import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Transformations +import androidx.lifecycle.viewModelScope import com.example.android.trackmysleepquality.database.SleepDatabaseDao +import com.example.android.trackmysleepquality.database.SleepNight +import kotlinx.coroutines.launch /** * ViewModel for SleepTrackerFragment. */ class SleepTrackerViewModel( - val database: SleepDatabaseDao, - application: Application) : AndroidViewModel(application) { + val database: SleepDatabaseDao, + application: Application +) : AndroidViewModel(application) { + + private val nights = database.getAllNights() + + val nightsString = Transformations.map(nights) { nights -> + "${nights.toString()}"// formatNights(nights, application.resources) + } + + private var tonight = MutableLiveData() + + init { + initializeTonight() + } + + private fun initializeTonight() { + viewModelScope.launch { +// tonight.value = getTonightFromDatabase() + } + } + + private suspend fun getTonightFromDatabase(): SleepNight? { + var night: SleepNight? = database.getTonight() ?: return null + if (night?.endTimeMilli != night?.startTimeMilli) { + night = null + } + return night + } + + fun onStartTracking() { + viewModelScope.launch { + val newNight = SleepNight() + insert(newNight) + tonight.value = getTonightFromDatabase() + } + } + + private suspend fun insert(night: SleepNight) { + database.insert(night) + } + + fun onStopTracking() { + viewModelScope.launch { + val oldNight = tonight.value ?: return@launch + oldNight.endTimeMilli = System.currentTimeMillis() + update(oldNight) + } + } + + private suspend fun update(night: SleepNight) { + database.update(night) + } + + fun onClear() { + viewModelScope.launch { + clear() + tonight.value = null + } + } + + suspend fun clear() { + database.clear() + } } diff --git a/TrackMySleepQuality-Starter/app/src/main/res/layout/fragment_sleep_tracker.xml b/TrackMySleepQuality-Starter/app/src/main/res/layout/fragment_sleep_tracker.xml index a54e99e5d..e5515fe56 100755 --- a/TrackMySleepQuality-Starter/app/src/main/res/layout/fragment_sleep_tracker.xml +++ b/TrackMySleepQuality-Starter/app/src/main/res/layout/fragment_sleep_tracker.xml @@ -24,6 +24,9 @@ click handlers, and state variables. --> + @@ -49,14 +52,30 @@ which keeps it displayed and updated in the TextView whenever it changes. --> - + android:orientation="vertical"> + + + + + + + + + + - - + android:scaleType="centerCrop" + app:imageUrl="@{property.imgSrcUrl}"/> + + \ No newline at end of file From 21d9f32784b9c5f9d3f5c7d8d8c3a687de2812c1 Mon Sep 17 00:00:00 2001 From: impactrudia Date: Mon, 7 Mar 2022 22:35:51 +0900 Subject: [PATCH 08/10] feat : Filter the results --- .../marsrealestate/network/MarsApiService.kt | 9 ++++- .../marsrealestate/network/MarsProperty.kt | 5 ++- .../overview/OverviewFragment.kt | 12 ++++++ .../overview/OverviewViewModel.kt | 10 +++-- .../src/main/res/layout/grid_view_item.xml | 40 ++++++++++++++----- 5 files changed, 62 insertions(+), 14 deletions(-) diff --git a/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/network/MarsApiService.kt b/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/network/MarsApiService.kt index 2de84d061..77fbfc704 100644 --- a/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/network/MarsApiService.kt +++ b/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/network/MarsApiService.kt @@ -23,6 +23,7 @@ import retrofit2.Call import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory import retrofit2.http.GET +import retrofit2.http.Query private const val BASE_URL = "https://android-kotlin-fun-mars-server.appspot.com" @@ -37,11 +38,17 @@ private val retrofit = Retrofit.Builder() interface MarsApiService { @GET("/realestate") - suspend fun getProperties(): List + suspend fun getProperties(@Query("filter") type: String): List } object MarsApi { val retrofitService: MarsApiService by lazy { retrofit.create(MarsApiService::class.java) } +} + +enum class MarsApiFilter(val value: String) { + SHOW_RENT("rent"), + SHOW_BUY("buy"), + SHOW_ALL("all") } \ No newline at end of file diff --git a/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/network/MarsProperty.kt b/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/network/MarsProperty.kt index 324c7ea4a..b652c7eb0 100644 --- a/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/network/MarsProperty.kt +++ b/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/network/MarsProperty.kt @@ -24,4 +24,7 @@ data class MarsProperty( @Json(name = "img_src") val imgSrcUrl: String, val type: String, val price: Double -) +){ + val isRental + get() = type == "rent" +} diff --git a/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/overview/OverviewFragment.kt b/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/overview/OverviewFragment.kt index 6afee7d74..57ee1cbe0 100644 --- a/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/overview/OverviewFragment.kt +++ b/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/overview/OverviewFragment.kt @@ -24,6 +24,7 @@ import androidx.lifecycle.ViewModelProvider import com.example.android.marsrealestate.R import com.example.android.marsrealestate.databinding.FragmentOverviewBinding import com.example.android.marsrealestate.databinding.GridViewItemBinding +import com.example.android.marsrealestate.network.MarsApiFilter /** * This fragment shows the the status of the Mars real-estate web services transaction. @@ -63,4 +64,15 @@ class OverviewFragment : Fragment() { inflater.inflate(R.menu.overflow_menu, menu) super.onCreateOptionsMenu(menu, inflater) } + + override fun onOptionsItemSelected(item: MenuItem?): Boolean { + viewModel.updateFilter( + when(item?.itemId){ + R.id.show_rent_menu -> MarsApiFilter.SHOW_RENT + R.id.show_buy_menu -> MarsApiFilter.SHOW_BUY + else -> MarsApiFilter.SHOW_ALL + } + ) + return super.onOptionsItemSelected(item) + } } diff --git a/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/overview/OverviewViewModel.kt b/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/overview/OverviewViewModel.kt index d34fc10d7..6c8f14984 100644 --- a/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/overview/OverviewViewModel.kt +++ b/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/overview/OverviewViewModel.kt @@ -22,6 +22,7 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.example.android.marsrealestate.network.MarsApi +import com.example.android.marsrealestate.network.MarsApiFilter import com.example.android.marsrealestate.network.MarsProperty import kotlinx.coroutines.launch import retrofit2.Call @@ -56,17 +57,17 @@ class OverviewViewModel : ViewModel() { * Call getMarsRealEstateProperties() on init so we can display status immediately. */ init { - getMarsRealEstateProperties() + getMarsRealEstateProperties(MarsApiFilter.SHOW_ALL) } /** * Sets the value of the status LiveData to the Mars API status. */ - private fun getMarsRealEstateProperties() { + private fun getMarsRealEstateProperties(filter: MarsApiFilter) { viewModelScope.launch { _status.value = MarsApiStatus.LOADING try { - val listResult = MarsApi.retrofitService.getProperties() + val listResult = MarsApi.retrofitService.getProperties(filter.value) if (listResult.isNotEmpty()) { _properties.value = listResult _status.value = MarsApiStatus.DONE @@ -78,4 +79,7 @@ class OverviewViewModel : ViewModel() { } } + fun updateFilter(filter: MarsApiFilter){ + getMarsRealEstateProperties(filter) + } } diff --git a/MarsRealEstate-Starter/app/src/main/res/layout/grid_view_item.xml b/MarsRealEstate-Starter/app/src/main/res/layout/grid_view_item.xml index e2039801d..acca4eb63 100644 --- a/MarsRealEstate-Starter/app/src/main/res/layout/grid_view_item.xml +++ b/MarsRealEstate-Starter/app/src/main/res/layout/grid_view_item.xml @@ -1,19 +1,41 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> - + + + type="com.example.android.marsrealestate.network.MarsProperty" /> - + + + + + + + \ No newline at end of file From 48d286b6bf03b4cd0ae32a0b70df625b02f999ba Mon Sep 17 00:00:00 2001 From: impactrudia Date: Mon, 7 Mar 2022 23:06:56 +0900 Subject: [PATCH 09/10] feat : Create a detail page and set up navigation --- .../marsrealestate/detail/DetailFragment.kt | 15 +++++++++++++-- .../marsrealestate/detail/DetailViewModel.kt | 12 +++++++++++- .../marsrealestate/network/MarsProperty.kt | 5 ++++- .../marsrealestate/overview/OverviewFragment.kt | 13 ++++++++++++- .../marsrealestate/overview/OverviewViewModel.kt | 12 ++++++++++++ .../marsrealestate/overview/PhotoGridAdapter.kt | 9 ++++++++- .../app/src/main/res/layout/fragment_detail.xml | 8 ++++++++ .../app/src/main/res/navigation/nav_graph.xml | 4 ++++ 8 files changed, 72 insertions(+), 6 deletions(-) diff --git a/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/detail/DetailFragment.kt b/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/detail/DetailFragment.kt index fd1a972e2..00c518c65 100644 --- a/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/detail/DetailFragment.kt +++ b/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/detail/DetailFragment.kt @@ -21,19 +21,30 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider import com.example.android.marsrealestate.databinding.FragmentDetailBinding /** * This [Fragment] will show the detailed information about a selected piece of Mars real estate. */ class DetailFragment : Fragment() { - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View? { + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { @Suppress("UNUSED_VARIABLE") val application = requireNotNull(activity).application val binding = FragmentDetailBinding.inflate(inflater) binding.lifecycleOwner = this + + val marsProperty = DetailFragmentArgs.fromBundle(arguments!!).selectedProperty + val viewModelFactory = DetailViewModelFactory(marsProperty, application) + binding.viewModel = ViewModelProvider( + this, viewModelFactory + ).get(DetailViewModel::class.java) + return binding.root } } \ No newline at end of file diff --git a/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/detail/DetailViewModel.kt b/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/detail/DetailViewModel.kt index 0f6020bab..94175b963 100644 --- a/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/detail/DetailViewModel.kt +++ b/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/detail/DetailViewModel.kt @@ -18,11 +18,21 @@ package com.example.android.marsrealestate.detail import android.app.Application import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.example.android.marsrealestate.network.MarsProperty /** * The [ViewModel] that is associated with the [DetailFragment]. */ -class DetailViewModel(@Suppress("UNUSED_PARAMETER")marsProperty: MarsProperty, app: Application) : AndroidViewModel(app) { +class DetailViewModel(@Suppress("UNUSED_PARAMETER") marsProperty: MarsProperty, app: Application) : + AndroidViewModel(app) { + private val _selectedProperty = MutableLiveData() + val selectedProperty: LiveData + get() = _selectedProperty + + init { + _selectedProperty.value = marsProperty + } } diff --git a/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/network/MarsProperty.kt b/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/network/MarsProperty.kt index b652c7eb0..8ea83ea3b 100644 --- a/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/network/MarsProperty.kt +++ b/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/network/MarsProperty.kt @@ -17,14 +17,17 @@ package com.example.android.marsrealestate.network +import android.os.Parcelable import com.squareup.moshi.Json +import kotlinx.android.parcel.Parcelize +@Parcelize data class MarsProperty( val id: String, @Json(name = "img_src") val imgSrcUrl: String, val type: String, val price: Double -){ +): Parcelable { val isRental get() = type == "rent" } diff --git a/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/overview/OverviewFragment.kt b/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/overview/OverviewFragment.kt index 57ee1cbe0..ef86548ad 100644 --- a/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/overview/OverviewFragment.kt +++ b/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/overview/OverviewFragment.kt @@ -20,7 +20,9 @@ package com.example.android.marsrealestate.overview import android.os.Bundle import android.view.* import androidx.fragment.app.Fragment +import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider +import androidx.navigation.fragment.findNavController import com.example.android.marsrealestate.R import com.example.android.marsrealestate.databinding.FragmentOverviewBinding import com.example.android.marsrealestate.databinding.GridViewItemBinding @@ -51,8 +53,17 @@ class OverviewFragment : Fragment() { // Giving the binding access to the OverviewViewModel binding.viewModel = viewModel - binding.photosGrid.adapter = PhotoGridAdapter() + binding.photosGrid.adapter = PhotoGridAdapter(PhotoGridAdapter.OnClickListener{ + viewModel.displayPropertyDetails(it) + }) + viewModel.navigateToSelectedProperty.observe(this, Observer { + if ( null != it ) { + this.findNavController().navigate( + OverviewFragmentDirections.actionShowDetail(it)) + viewModel.displayPropertyDetailsComplete() + } + }) setHasOptionsMenu(true) return binding.root } diff --git a/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/overview/OverviewViewModel.kt b/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/overview/OverviewViewModel.kt index 6c8f14984..ea995583c 100644 --- a/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/overview/OverviewViewModel.kt +++ b/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/overview/OverviewViewModel.kt @@ -53,6 +53,10 @@ class OverviewViewModel : ViewModel() { val status: LiveData get() = _status + private val _navigateToSelectedProperty = MutableLiveData() + val navigateToSelectedProperty: LiveData + get() = _navigateToSelectedProperty + /** * Call getMarsRealEstateProperties() on init so we can display status immediately. */ @@ -60,6 +64,14 @@ class OverviewViewModel : ViewModel() { getMarsRealEstateProperties(MarsApiFilter.SHOW_ALL) } + fun displayPropertyDetails(marsProperty: MarsProperty){ + _navigateToSelectedProperty.value = marsProperty + } + + fun displayPropertyDetailsComplete(){ + _navigateToSelectedProperty.value = null + } + /** * Sets the value of the status LiveData to the Mars API status. */ diff --git a/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/overview/PhotoGridAdapter.kt b/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/overview/PhotoGridAdapter.kt index 88217eab4..c52ec702a 100644 --- a/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/overview/PhotoGridAdapter.kt +++ b/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/overview/PhotoGridAdapter.kt @@ -25,7 +25,7 @@ import androidx.recyclerview.widget.RecyclerView import com.example.android.marsrealestate.databinding.GridViewItemBinding import com.example.android.marsrealestate.network.MarsProperty -class PhotoGridAdapter : ListAdapter(DiffCallback) { override fun onCreateViewHolder( @@ -41,6 +41,9 @@ class PhotoGridAdapter : ListAdapter Unit) { + fun onClick(marsProperty: MarsProperty) = clickListener(marsProperty) + } } \ No newline at end of file diff --git a/MarsRealEstate-Starter/app/src/main/res/layout/fragment_detail.xml b/MarsRealEstate-Starter/app/src/main/res/layout/fragment_detail.xml index 002f892ac..a74c5107a 100644 --- a/MarsRealEstate-Starter/app/src/main/res/layout/fragment_detail.xml +++ b/MarsRealEstate-Starter/app/src/main/res/layout/fragment_detail.xml @@ -21,6 +21,13 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> + + + + + + From f8dae2a7b384700c2428ce77786abafe2d16dc4f Mon Sep 17 00:00:00 2001 From: impactrudia Date: Mon, 7 Mar 2022 23:14:14 +0900 Subject: [PATCH 10/10] feat : Task: Create a more useful detail page --- .../marsrealestate/detail/DetailViewModel.kt | 24 +++++++++++++++---- .../src/main/res/layout/fragment_detail.xml | 2 ++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/detail/DetailViewModel.kt b/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/detail/DetailViewModel.kt index 94175b963..cab6274f7 100644 --- a/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/detail/DetailViewModel.kt +++ b/MarsRealEstate-Starter/app/src/main/java/com/example/android/marsrealestate/detail/DetailViewModel.kt @@ -17,21 +17,37 @@ package com.example.android.marsrealestate.detail import android.app.Application -import androidx.lifecycle.AndroidViewModel -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel +import androidx.lifecycle.* import com.example.android.marsrealestate.network.MarsProperty +import com.example.android.marsrealestate.R /** * The [ViewModel] that is associated with the [DetailFragment]. */ class DetailViewModel(@Suppress("UNUSED_PARAMETER") marsProperty: MarsProperty, app: Application) : AndroidViewModel(app) { + private val _selectedProperty = MutableLiveData() val selectedProperty: LiveData get() = _selectedProperty + val displayPropertyPrice = Transformations.map(selectedProperty) { + app.applicationContext.getString( + when (it.isRental) { + true -> R.string.display_price_monthly_rental + false -> R.string.display_price + }, it.price) + } + + val displayPropertyType = Transformations.map(selectedProperty) { + app.applicationContext.getString(R.string.display_type, + app.applicationContext.getString( + when (it.isRental) { + true -> R.string.type_rent + false -> R.string.type_sale + })) + } + init { _selectedProperty.value = marsProperty } diff --git a/MarsRealEstate-Starter/app/src/main/res/layout/fragment_detail.xml b/MarsRealEstate-Starter/app/src/main/res/layout/fragment_detail.xml index a74c5107a..815395157 100644 --- a/MarsRealEstate-Starter/app/src/main/res/layout/fragment_detail.xml +++ b/MarsRealEstate-Starter/app/src/main/res/layout/fragment_detail.xml @@ -54,6 +54,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" + android:text="@{viewModel.displayPropertyType}" android:textColor="#de000000" android:textSize="39sp" app:layout_constraintStart_toStartOf="parent" @@ -65,6 +66,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" + android:text="@{viewModel.displayPropertyPrice}" android:textColor="#de000000" android:textSize="20sp" app:layout_constraintStart_toStartOf="parent"