RecyclerView apps added.

Change-Id: If081193ea949419364bdb2dad5b3375493c1592a
This commit is contained in:
Aleks Haecky 2019-05-15 11:22:27 -07:00
parent 55560b6b49
commit c685d8f3f1
376 changed files with 14930 additions and 0 deletions

View File

@ -0,0 +1,55 @@
TrackMySleepQuality with RecyclerView - Solution Code for 7.4
=============================================================
Solution code for Android Kotlin Fundamentals Codelab 7.4 Interacting with RecyclerView items
Introduction
------------
TrackMySleepQuality is an app for recording sleep data for each night.
You can record a start and stop time, assign a quality rating, and clear the database.
Learn how to make items in the RecyclerView clickable.
Implement a click listener and navigate on click in your Android Kotlin app.
Pre-requisites
--------------
You should be familiar with:
* Building a basic user interface (UI) using an activity, fragments, and views.
* Navigating between fragments, and using safeArgs to pass data between fragments.
* Using view models, view model factories, transformations, and LiveData and their observers.
* Creating a Room database, creating a DAO, and defining entities.
* Using coroutines for database tasks and other long-running tasks.
* How to implement a basic RecyclerView with an Adapter, ViewHolder, and item layout.
* How to implement data binding for RecyclerView.
* How to create and use binding adapters to transform data.
* How to use GridLayoutManager.
Getting Started
---------------
1. Download and run the app.
License
-------
Copyright 2019 Google, Inc.
Licensed to the Apache Software Foundation (ASF) under one or more contributor
license agreements. See the NOTICE file distributed with this work for
additional information regarding copyright ownership. The ASF licenses this
file to you under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy of
the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations under
the License.

1
RecyclerViewClickHandler/app/.gitignore vendored Executable file
View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,80 @@
/*
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: 'androidx.navigation.safeargs'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.example.android.trackmysleepqualityrecyclerview"
minSdkVersion 19
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
// Enables data binding.
dataBinding {
enabled = true
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
// Support libraries
implementation "androidx.appcompat:appcompat:1.0.0"
implementation "androidx.fragment:fragment:1.0.0"
implementation "androidx.constraintlayout:constraintlayout:2.0.0-alpha2"
// Android KTX
implementation 'androidx.core:core-ktx:1.0.1'
// Room and Lifecycle dependencies
implementation "androidx.room:room-runtime:$room_version"
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.lifecycle:lifecycle-extensions:2.0.0"
// Coroutines
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"
// Navigation
implementation "android.arch.navigation:navigation-fragment-ktx:$navigationVersion"
implementation "android.arch.navigation:navigation-ui-ktx:$navigationVersion"
// Testing
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.0-alpha4'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-alpha4'
}

View File

@ -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

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.trackmysleepquality">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher_sleep_tracker"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_sleep_tracker_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<meta-data
android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts" />
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -0,0 +1,30 @@
/*
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.trackmysleepquality
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}

View File

@ -0,0 +1,156 @@
/*
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.trackmysleepquality
import android.annotation.SuppressLint
import android.content.res.Resources
import android.os.Build
import android.text.Html
import android.text.Spanned
import android.view.View
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.android.trackmysleepquality.database.SleepNight
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.TimeUnit
/**
* These functions create a formatted string that can be set in a TextView.
*/
private val ONE_MINUTE_MILLIS = TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES)
private val ONE_HOUR_MILLIS = TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS)
/**
* Convert a duration to a formatted string for display.
*
* Examples:
*
* 6 seconds on Wednesday
* 2 minutes on Monday
* 40 hours on Thursday
*
* @param startTimeMilli the start of the interval
* @param endTimeMilli the end of the interval
* @param res resources used to load formatted strings
*/
fun convertDurationToFormatted(startTimeMilli: Long, endTimeMilli: Long, res: Resources): String {
val durationMilli = endTimeMilli - startTimeMilli
val weekdayString = SimpleDateFormat("EEEE", Locale.getDefault()).format(startTimeMilli)
return when {
durationMilli < ONE_MINUTE_MILLIS -> {
val seconds = TimeUnit.SECONDS.convert(durationMilli, TimeUnit.MILLISECONDS)
res.getString(R.string.seconds_length, seconds, weekdayString)
}
durationMilli < ONE_HOUR_MILLIS -> {
val minutes = TimeUnit.MINUTES.convert(durationMilli, TimeUnit.MILLISECONDS)
res.getString(R.string.minutes_length, minutes, weekdayString)
}
else -> {
val hours = TimeUnit.HOURS.convert(durationMilli, TimeUnit.MILLISECONDS)
res.getString(R.string.hours_length, hours, weekdayString)
}
}
}
/**
* Returns a string representing the numeric quality rating.
*/
fun convertNumericQualityToString(quality: Int, resources: Resources): String {
var qualityString = resources.getString(R.string.three_ok)
when (quality) {
-1 -> qualityString = "--"
0 -> qualityString = resources.getString(R.string.zero_very_bad)
1 -> qualityString = resources.getString(R.string.one_poor)
2 -> qualityString = resources.getString(R.string.two_soso)
4 -> qualityString = resources.getString(R.string.four_pretty_good)
5 -> qualityString = resources.getString(R.string.five_excellent)
}
return qualityString
}
/**
* Take the Long milliseconds returned by the system and stored in Room,
* and convert it to a nicely formatted string for display.
*
* EEEE - Display the long letter version of the weekday
* MMM - Display the letter abbreviation of the nmotny
* dd-yyyy - day in month and full year numerically
* HH:mm - Hours and minutes in 24hr format
*/
@SuppressLint("SimpleDateFormat")
fun convertLongToDateString(systemTime: Long): String {
return SimpleDateFormat("EEEE MMM-dd-yyyy' Time: 'HH:mm")
.format(systemTime).toString()
}
/**
* Takes a list of SleepNights and converts and formats it into one string for display.
*
* For display in a TextView, we have to supply one string, and styles are per TextView, not
* applicable per word. So, we build a formatted string using HTML. This is handy, but we will
* learn a better way of displaying this data in a future lesson.
*
* @param nights - List of all SleepNights in the database.
* @param resources - Resources object for all the resources defined for our app.
*
* @return Spanned - An interface for text that has formatting attached to it.
* See: https://developer.android.com/reference/android/text/Spanned
*/
fun formatNights(nights: List<SleepNight>, resources: Resources): Spanned {
val sb = StringBuilder()
sb.apply {
append(resources.getString(R.string.title))
nights.forEach {
append("<br>")
append(resources.getString(R.string.start_time))
append("\t${convertLongToDateString(it.startTimeMilli)}<br>")
if (it.endTimeMilli != it.startTimeMilli) {
append(resources.getString(R.string.end_time))
append("\t${convertLongToDateString(it.endTimeMilli)}<br>")
append(resources.getString(R.string.quality))
append("\t${convertNumericQualityToString(it.sleepQuality, resources)}<br>")
append(resources.getString(R.string.hours_slept))
// Hours
append("\t ${it.endTimeMilli.minus(it.startTimeMilli) / 1000 / 60 / 60}:")
// Minutes
append("${it.endTimeMilli.minus(it.startTimeMilli) / 1000 / 60}:")
// Seconds
append("${it.endTimeMilli.minus(it.startTimeMilli) / 1000}<br><br>")
}
}
}
// fromHtml is deprecated for target API without a flag, but since our minSDK is 19, we
// can't use the newer version, which requires minSDK of 24
//https://developer.android.com/reference/android/text/Html#fromHtml(java.lang.String,%20int)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return Html.fromHtml(sb.toString(), Html.FROM_HTML_MODE_LEGACY)
} else {
@Suppress("DEPRECATION")
return Html.fromHtml(sb.toString())
}
}
/**
* ViewHolder that holds a single [TextView].
*
* A ViewHolder holds a view for the [RecyclerView] as well as providing additional information
* to the RecyclerView such as where on the screen it was last drawn during scrolling.
*/
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)

View File

@ -0,0 +1,107 @@
/*
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.trackmysleepquality.database
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
/**
* A database that stores SleepNight information.
* And a global method to get access to the database.
*
* This pattern is pretty much the same for any database,
* so you can reuse it.
*/
@Database(entities = [SleepNight::class], version = 1, exportSchema = false)
abstract class SleepDatabase : RoomDatabase() {
/**
* Connects the database to the DAO.
*/
abstract val sleepDatabaseDao: SleepDatabaseDao
/**
* Define a companion object, this allows us to add functions on the SleepDatabase class.
*
* For example, clients can call `SleepDatabase.getInstance(context)` to instantiate
* a new SleepDatabase.
*/
companion object {
/**
* INSTANCE will keep a reference to any database returned via getInstance.
*
* This will help us avoid repeatedly initializing the database, which is expensive.
*
* The value of a volatile variable will never be cached, and all writes and
* reads will be done to and from the main memory. It means that changes made by one
* thread to shared data are visible to other threads.
*/
@Volatile
private var INSTANCE: SleepDatabase? = null
/**
* Helper function to get the database.
*
* If a database has already been retrieved, the previous database will be returned.
* Otherwise, create a new database.
*
* This function is threadsafe, and callers should cache the result for multiple database
* calls to avoid overhead.
*
* This is an example of a simple Singleton pattern that takes another Singleton as an
* argument in Kotlin.
*
* To learn more about Singleton read the wikipedia article:
* https://en.wikipedia.org/wiki/Singleton_pattern
*
* @param context The application context Singleton, used to get access to the filesystem.
*/
fun getInstance(context: Context): SleepDatabase {
// Multiple threads can ask for the database at the same time, ensure we only initialize
// it once by using synchronized. Only one thread may enter a synchronized block at a
// time.
synchronized(this) {
// Copy the current value of INSTANCE to a local variable so Kotlin can smart cast.
// Smart cast is only available to local variables.
var instance = INSTANCE
// If instance is `null` make a new database instance.
if (instance == null) {
instance = Room.databaseBuilder(
context.applicationContext,
SleepDatabase::class.java,
"sleep_history_database"
)
// Wipes and rebuilds instead of migrating if no Migration object.
// Migration is not part of this lesson. You can learn more about
// migration with Room in this blog post:
// https://medium.com/androiddevelopers/understanding-migrations-with-room-f01e04b07929
.fallbackToDestructiveMigration()
.build()
// Assign INSTANCE to the newly created database.
INSTANCE = instance
}
// Return instance; smart cast to be non-null.
return instance
}
}
}
}

View File

@ -0,0 +1,80 @@
/*
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.trackmysleepquality.database
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
/**
* Defines methods for using the SleepNight class with Room.
*/
@Dao
interface SleepDatabaseDao {
@Insert
fun insert(night: SleepNight)
/**
* When updating a row with a value already set in a column,
* replaces the old value with the new one.
*
* @param night new value to write
*/
@Update
fun update(night: SleepNight)
/**
* Selects and returns the row that matches the supplied start time, which is our key.
*
* @param key startTimeMilli to match
*/
@Query("SELECT * from daily_sleep_quality_table WHERE nightId = :key")
fun get(key: Long): SleepNight
/**
* Deletes all values from the table.
*
* This does not delete the table, only its contents.
*/
@Query("DELETE FROM daily_sleep_quality_table")
fun clear()
/**
* Selects and returns all rows in the table,
*
* sorted by start time in descending order.
*/
@Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC")
fun getAllNights(): LiveData<List<SleepNight>>
/**
* Selects and returns the latest night.
*/
@Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC LIMIT 1")
fun getTonight(): SleepNight?
/**
* Selects and returns the night with given nightId.
*/
@Query("SELECT * from daily_sleep_quality_table WHERE nightId = :key")
fun getNightWithId(key: Long): LiveData<SleepNight>
}

View File

@ -0,0 +1,38 @@
/*
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.trackmysleepquality.database
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
/**
* Represents one night's sleep through start, end times, and the sleep quality.
*/
@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)

View File

@ -0,0 +1,84 @@
/*
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.trackmysleepquality.sleepdetail
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.navigation.fragment.findNavController
import com.example.android.trackmysleepquality.R
import com.example.android.trackmysleepquality.database.SleepDatabase
import com.example.android.trackmysleepquality.databinding.FragmentSleepDetailBinding
/**
* A simple [Fragment] subclass.
* Activities that contain this fragment must implement the
* [SleepDetailFragment.OnFragmentInteractionListener] interface
* to handle interaction events.
* Use the [SleepDetailFragment.newInstance] factory method to
* create an instance of this fragment.
*
*/
class SleepDetailFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Get a reference to the binding object and inflate the fragment views.
val binding: FragmentSleepDetailBinding = DataBindingUtil.inflate(
inflater, R.layout.fragment_sleep_detail, container, false)
val application = requireNotNull(this.activity).application
val arguments = SleepDetailFragmentArgs.fromBundle(arguments)
// Create an instance of the ViewModel Factory.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
val viewModelFactory = SleepDetailViewModelFactory(arguments.sleepNightKey, dataSource)
// Get a reference to the ViewModel associated with this fragment.
val sleepDetailViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(SleepDetailViewModel::class.java)
// To use the View Model with data binding, you have to explicitly
// give the binding object a reference to it.
binding.sleepDetailViewModel = sleepDetailViewModel
binding.setLifecycleOwner(this)
// Add an Observer to the state variable for Navigating when a Quality icon is tapped.
sleepDetailViewModel.navigateToSleepTracker.observe(this, Observer {
if (it == true) { // Observed state is true.
this.findNavController().navigate(
SleepDetailFragmentDirections.actionSleepDetailFragmentToSleepTrackerFragment())
// Reset state to make sure we only navigate once, even if the device
// has a configuration change.
sleepDetailViewModel.doneNavigating()
}
})
return binding.root
}
}

View File

@ -0,0 +1,97 @@
/*
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.trackmysleepquality.sleepdetail
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.example.android.trackmysleepquality.database.SleepDatabaseDao
import com.example.android.trackmysleepquality.database.SleepNight
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/**
* ViewModel for SleepQualityFragment.
*
* @param sleepNightKey The key of the current night we are working on.
*/
class SleepDetailViewModel(
private val sleepNightKey: Long = 0L,
dataSource: SleepDatabaseDao) : ViewModel() {
/**
* Hold a reference to SleepDatabase via its SleepDatabaseDao.
*/
val database = dataSource
/** Coroutine setup variables */
/**
* viewModelJob allows us to cancel all coroutines started by this ViewModel.
*/
private val viewModelJob = Job()
private val night: LiveData<SleepNight>
fun getNight() = night
init {
night=database.getNightWithId(sleepNightKey)
}
/**
* Variable that tells the fragment whether it should navigate to [SleepTrackerFragment].
*
* This is `private` because we don't want to expose the ability to set [MutableLiveData] to
* the [Fragment]
*/
private val _navigateToSleepTracker = MutableLiveData<Boolean?>()
/**
* When true immediately navigate back to the [SleepTrackerFragment]
*/
val navigateToSleepTracker: LiveData<Boolean?>
get() = _navigateToSleepTracker
/**
* Cancels all coroutines when the ViewModel is cleared, to cleanup any pending work.
*
* onCleared() gets called when the ViewModel is destroyed.
*/
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
/**
* Call this immediately after navigating to [SleepTrackerFragment]
*/
fun doneNavigating() {
_navigateToSleepTracker.value = null
}
fun onClose() {
_navigateToSleepTracker.value = true
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.trackmysleepquality.sleepdetail
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.example.android.trackmysleepquality.database.SleepDatabaseDao
/**
* This is pretty much boiler plate code for a ViewModel Factory.
*
* Provides the key for the night and the SleepDatabaseDao to the ViewModel.
*/
class SleepDetailViewModelFactory(
private val sleepNightKey: Long,
private val dataSource: SleepDatabaseDao) : ViewModelProvider.Factory {
@Suppress("unchecked_cast")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(SleepDetailViewModel::class.java)) {
return SleepDetailViewModel(sleepNightKey, dataSource) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}

View File

@ -0,0 +1,85 @@
/*
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.trackmysleepquality.sleepquality
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.navigation.fragment.findNavController
import com.example.android.trackmysleepquality.R
import com.example.android.trackmysleepquality.database.SleepDatabase
import com.example.android.trackmysleepquality.databinding.FragmentSleepQualityBinding
/**
* Fragment that displays a list of clickable icons,
* each representing a sleep quality rating.
* Once the user taps an icon, the quality is set in the current sleepNight
* and the database is updated.
*/
class SleepQualityFragment : Fragment() {
/**
* Called when the Fragment is ready to display content to the screen.
*
* This function uses DataBindingUtil to inflate R.layout.fragment_sleep_quality.
*
* It is also responsible for passing the [SleepQualityViewModel] to the
* [FragmentSleepQualityBinding] generated by DataBinding. This will allow DataBinding
* to use the [LiveData] on our ViewModel.
*/
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Get a reference to the binding object and inflate the fragment views.
val binding: FragmentSleepQualityBinding = DataBindingUtil.inflate(
inflater, R.layout.fragment_sleep_quality, container, false)
val application = requireNotNull(this.activity).application
val arguments = SleepQualityFragmentArgs.fromBundle(arguments)
// Create an instance of the ViewModel Factory.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
val viewModelFactory = SleepQualityViewModelFactory(arguments.sleepNightKey, dataSource)
// Get a reference to the ViewModel associated with this fragment.
val sleepQualityViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(SleepQualityViewModel::class.java)
// To use the View Model with data binding, you have to explicitly
// give the binding object a reference to it.
binding.sleepQualityViewModel = sleepQualityViewModel
// Add an Observer to the state variable for Navigating when a Quality icon is tapped.
sleepQualityViewModel.navigateToSleepTracker.observe(this, Observer {
if (it == true) { // Observed state is true.
this.findNavController().navigate(
SleepQualityFragmentDirections.actionSleepQualityFragmentToSleepTrackerFragment())
// Reset state to make sure we only navigate once, even if the device
// has a configuration change.
sleepQualityViewModel.doneNavigating()
}
})
return binding.root
}
}

View File

@ -0,0 +1,112 @@
/*
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.trackmysleepquality.sleepquality
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.example.android.trackmysleepquality.database.SleepDatabaseDao
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/**
* ViewModel for SleepQualityFragment.
*
* @param sleepNightKey The key of the current night we are working on.
*/
class SleepQualityViewModel(
private val sleepNightKey: Long = 0L,
dataSource: SleepDatabaseDao) : ViewModel() {
/**
* Hold a reference to SleepDatabase via its SleepDatabaseDao.
*/
val database = dataSource
/** Coroutine setup variables */
/**
* viewModelJob allows us to cancel all coroutines started by this ViewModel.
*/
private val viewModelJob = Job()
/**
* A [CoroutineScope] keeps track of all coroutines started by this ViewModel.
*
* Because we pass it [viewModelJob], any coroutine started in this scope can be cancelled
* by calling `viewModelJob.cancel()`
*
* By default, all coroutines started in uiScope will launch in [Dispatchers.Main] which is
* the main thread on Android. This is a sensible default because most coroutines started by
* a [ViewModel] update the UI after performing some processing.
*/
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
/**
* Variable that tells the fragment whether it should navigate to [SleepTrackerFragment].
*
* This is `private` because we don't want to expose the ability to set [MutableLiveData] to
* the [Fragment]
*/
private val _navigateToSleepTracker = MutableLiveData<Boolean?>()
/**
* When true immediately navigate back to the [SleepTrackerFragment]
*/
val navigateToSleepTracker: LiveData<Boolean?>
get() = _navigateToSleepTracker
/**
* Cancels all coroutines when the ViewModel is cleared, to cleanup any pending work.
*
* onCleared() gets called when the ViewModel is destroyed.
*/
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
/**
* Call this immediately after navigating to [SleepTrackerFragment]
*/
fun doneNavigating() {
_navigateToSleepTracker.value = null
}
/**
* Sets the sleep quality and updates the database.
*
* Then navigates back to the SleepTrackerFragment.
*/
fun onSetSleepQuality(quality: Int) {
uiScope.launch {
// IO is a thread pool for running operations that access the disk, such as
// our Room database.
withContext(Dispatchers.IO) {
val tonight = database.get(sleepNightKey)
tonight.sleepQuality = quality
database.update(tonight)
}
// Setting this state variable to true will alert the observer and trigger navigation.
_navigateToSleepTracker.value = true
}
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.trackmysleepquality.sleepquality
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.example.android.trackmysleepquality.database.SleepDatabaseDao
/**
* This is pretty much boiler plate code for a ViewModel Factory.
*
* Provides the key for the night and the SleepDatabaseDao to the ViewModel.
*/
class SleepQualityViewModelFactory(
private val sleepNightKey: Long,
private val dataSource: SleepDatabaseDao) : ViewModelProvider.Factory {
@Suppress("unchecked_cast")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(SleepQualityViewModel::class.java)) {
return SleepQualityViewModel(sleepNightKey, dataSource) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.trackmysleepquality.sleeptracker
import android.widget.ImageView
import android.widget.TextView
import androidx.databinding.BindingAdapter
import com.example.android.trackmysleepquality.R
import com.example.android.trackmysleepquality.convertDurationToFormatted
import com.example.android.trackmysleepquality.convertNumericQualityToString
import com.example.android.trackmysleepquality.database.SleepNight
@BindingAdapter("sleepDurationFormatted")
fun TextView.setSleepDurationFormatted(item: SleepNight?) {
item?.let {
text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, context.resources)
}
}
@BindingAdapter("sleepQualityString")
fun TextView.setSleepQualityString(item: SleepNight?) {
item?.let {
text = convertNumericQualityToString(item.sleepQuality, context.resources)
}
}
@BindingAdapter("sleepImage")
fun ImageView.setSleepImage(item: SleepNight?) {
item?.let {
setImageResource(when (item.sleepQuality) {
0 -> R.drawable.ic_sleep_0
1 -> R.drawable.ic_sleep_1
2 -> R.drawable.ic_sleep_2
3 -> R.drawable.ic_sleep_3
4 -> R.drawable.ic_sleep_4
5 -> R.drawable.ic_sleep_5
else -> R.drawable.ic_sleep_active
})
}
}

View File

@ -0,0 +1,70 @@
/*
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.trackmysleepquality.sleeptracker
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.android.trackmysleepquality.database.SleepNight
import com.example.android.trackmysleepquality.databinding.ListItemSleepNightBinding
class SleepNightAdapter(val clickListener: SleepNightListener) : ListAdapter<SleepNight, SleepNightAdapter.ViewHolder>(SleepNightDiffCallback()) {
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position)!!, clickListener)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder.from(parent)
}
class ViewHolder private constructor(val binding: ListItemSleepNightBinding) : RecyclerView.ViewHolder(binding.root){
fun bind(item: SleepNight, clickListener: SleepNightListener) {
binding.sleep = item
binding.clickListener = clickListener
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ListItemSleepNightBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding)
}
}
}
}
class SleepNightDiffCallback : DiffUtil.ItemCallback<SleepNight>() {
override fun areItemsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
return oldItem.nightId == newItem.nightId
}
override fun areContentsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
return oldItem == newItem
}
}
class SleepNightListener(val clickListener: (sleepId: Long) -> Unit) {
fun onClick(night: SleepNight) = clickListener(night.nightId)
}

View File

@ -0,0 +1,140 @@
/*
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.trackmysleepquality.sleeptracker
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.GridLayoutManager
import com.example.android.trackmysleepquality.R
import com.example.android.trackmysleepquality.database.SleepDatabase
import com.example.android.trackmysleepquality.databinding.FragmentSleepTrackerBinding
import com.google.android.material.snackbar.Snackbar
/**
* A fragment with buttons to record start and end times for sleep, which are saved in
* a database. Cumulative data is displayed in a simple scrollable TextView.
* (Because we have not learned about RecyclerView yet.)
* The Clear button will clear all data from the database.
*/
class SleepTrackerFragment : Fragment() {
/**
* Called when the Fragment is ready to display content to the screen.
*
* This function uses DataBindingUtil to inflate R.layout.fragment_sleep_quality.
*
* It is also responsible for passing the [SleepTrackerViewModel] to the
* [FragmentSleepTrackerBinding] generated by DataBinding. This will allow DataBinding
* to use the [LiveData] on our ViewModel.
*/
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)
val application = requireNotNull(this.activity).application
// Create an instance of the ViewModel Factory.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
// Get a reference to the ViewModel associated with this fragment.
val sleepTrackerViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(SleepTrackerViewModel::class.java)
// To use the View Model with data binding, you have to explicitly
// give the binding object a reference to it.
binding.sleepTrackerViewModel = sleepTrackerViewModel
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
//Toast.makeText(context, "${nightId}", Toast.LENGTH_LONG).show()
sleepTrackerViewModel.onSleepNightClicked(nightId)
})
binding.sleepList.adapter = adapter
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.submitList(it)
}
})
// Specify the current activity as the lifecycle owner of the binding.
// This is necessary so that the binding can observe LiveData updates.
binding.setLifecycleOwner(this)
// Add an Observer on the state variable for showing a Snackbar message
// when the CLEAR button is pressed.
sleepTrackerViewModel.showSnackBarEvent.observe(this, Observer {
if (it == true) { // Observed state is true.
Snackbar.make(
activity!!.findViewById(android.R.id.content),
getString(R.string.cleared_message),
Snackbar.LENGTH_SHORT // How long to display the message.
).show()
// Reset state to make sure the toast is only shown once, even if the device
// has a configuration change.
sleepTrackerViewModel.doneShowingSnackbar()
}
})
// Add an Observer on the state variable for Navigating when STOP button is pressed.
sleepTrackerViewModel.navigateToSleepQuality.observe(this, Observer { night ->
night?.let {
// We need to get the navController from this, because button is not ready, and it
// just has to be a view. For some reason, this only matters if we hit stop again
// after using the back button, not if we hit stop and choose a quality.
// Also, in the Navigation Editor, for Quality -> Tracker, check "Inclusive" for
// popping the stack to get the correct behavior if we press stop multiple times
// followed by back.
// Also: https://stackoverflow.com/questions/28929637/difference-and-uses-of-oncreate-oncreateview-and-onactivitycreated-in-fra
this.findNavController().navigate(
SleepTrackerFragmentDirections
.actionSleepTrackerFragmentToSleepQualityFragment(night.nightId))
// Reset state to make sure we only navigate once, even if the device
// has a configuration change.
sleepTrackerViewModel.doneNavigating()
}
})
// Add an Observer on the state variable for Navigating when and item is clicked.
sleepTrackerViewModel.navigateToSleepDetail.observe(this, Observer { night ->
night?.let {
this.findNavController().navigate(
SleepTrackerFragmentDirections
.actionSleepTrackerFragmentToSleepDetailFragment(night))
sleepTrackerViewModel.onSleepDetailNavigated()
}
})
val manager = GridLayoutManager(activity, 3)
binding.sleepList.layoutManager = manager
return binding.root
}
}

View File

@ -0,0 +1,263 @@
/*
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.trackmysleepquality.sleeptracker
import android.app.Application
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel
import com.example.android.trackmysleepquality.database.SleepDatabaseDao
import com.example.android.trackmysleepquality.database.SleepNight
import com.example.android.trackmysleepquality.formatNights
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/**
* ViewModel for SleepTrackerFragment.
*/
class SleepTrackerViewModel(
dataSource: SleepDatabaseDao,
application: Application) : ViewModel() {
/**
* Hold a reference to SleepDatabase via SleepDatabaseDao.
*/
val database = dataSource
/** Coroutine variables */
/**
* viewModelJob allows us to cancel all coroutines started by this ViewModel.
*/
private var viewModelJob = Job()
/**
* A [CoroutineScope] keeps track of all coroutines started by this ViewModel.
*
* Because we pass it [viewModelJob], any coroutine started in this uiScope can be cancelled
* by calling `viewModelJob.cancel()`
*
* By default, all coroutines started in uiScope will launch in [Dispatchers.Main] which is
* the main thread on Android. This is a sensible default because most coroutines started by
* a [ViewModel] update the UI after performing some processing.
*/
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
private var tonight = MutableLiveData<SleepNight?>()
val nights = database.getAllNights()
/**
* Converted nights to Spanned for displaying.
*/
val nightsString = Transformations.map(nights) { nights ->
formatNights(nights, application.resources)
}
/**
* If tonight has not been set, then the START button should be visible.
*/
val startButtonVisible = Transformations.map(tonight) {
null == it
}
/**
* If tonight has been set, then the STOP button should be visible.
*/
val stopButtonVisible = Transformations.map(tonight) {
null != it
}
/**
* If there are any nights in the database, show the CLEAR button.
*/
val clearButtonVisible = Transformations.map(nights) {
it?.isNotEmpty()
}
/**
* Request a toast by setting this value to true.
*
* This is private because we don't want to expose setting this value to the Fragment.
*/
private var _showSnackbarEvent = MutableLiveData<Boolean?>()
/**
* If this is true, immediately `show()` a toast and call `doneShowingSnackbar()`.
*/
val showSnackBarEvent: LiveData<Boolean?>
get() = _showSnackbarEvent
/**
* Variable that tells the Fragment to navigate to a specific [SleepQualityFragment]
*
* This is private because we don't want to expose setting this value to the Fragment.
*/
private val _navigateToSleepQuality = MutableLiveData<SleepNight>()
/**
* If this is non-null, immediately navigate to [SleepQualityFragment] and call [doneNavigating]
*/
val navigateToSleepQuality: LiveData<SleepNight>
get() = _navigateToSleepQuality
/**
* Call this immediately after calling `show()` on a toast.
*
* It will clear the toast request, so if the user rotates their phone it won't show a duplicate
* toast.
*/
fun doneShowingSnackbar() {
_showSnackbarEvent.value = null
}
/**
* Call this immediately after navigating to [SleepQualityFragment]
*
* It will clear the navigation request, so if the user rotates their phone it won't navigate
* twice.
*/
fun doneNavigating() {
_navigateToSleepQuality.value = null
}
/**
* Navigation for the SleepDetail fragment.
*/
private val _navigateToSleepDetail = MutableLiveData<Long>()
val navigateToSleepDetail
get() = _navigateToSleepDetail
fun onSleepNightClicked(id: Long) {
_navigateToSleepDetail.value = id
}
fun onSleepDetailNavigated() {
_navigateToSleepDetail.value = null
}
init {
initializeTonight()
}
private fun initializeTonight() {
uiScope.launch {
tonight.value = getTonightFromDatabase()
}
}
/**
* Handling the case of the stopped app or forgotten recording,
* the start and end times will be the same.j
*
* If the start time and end time are not the same, then we do not have an unfinished
* recording.
*/
private suspend fun getTonightFromDatabase(): SleepNight? {
return withContext(Dispatchers.IO) {
var night = database.getTonight()
if (night?.endTimeMilli != night?.startTimeMilli) {
night = null
}
night
}
}
private suspend fun insert(night: SleepNight) {
withContext(Dispatchers.IO) {
database.insert(night)
}
}
private suspend fun update(night: SleepNight) {
withContext(Dispatchers.IO) {
database.update(night)
}
}
private suspend fun clear() {
withContext(Dispatchers.IO) {
database.clear()
}
}
/**
* Executes when the START button is clicked.
*/
fun onStart() {
uiScope.launch {
// Create a new night, which captures the current time,
// and insert it into the database.
val newNight = SleepNight()
insert(newNight)
tonight.value = getTonightFromDatabase()
}
}
/**
* Executes when the STOP button is clicked.
*/
fun onStop() {
uiScope.launch {
// In Kotlin, the return@label syntax is used for specifying which function among
// several nested ones this statement returns from.
// In this case, we are specifying to return from launch().
val oldNight = tonight.value ?: return@launch
// Update the night in the database to add the end time.
oldNight.endTimeMilli = System.currentTimeMillis()
update(oldNight)
// Set state to navigate to the SleepQualityFragment.
_navigateToSleepQuality.value = oldNight
}
}
/**
* Executes when the CLEAR button is clicked.
*/
fun onClear() {
uiScope.launch {
// Clear the database table.
clear()
// And clear tonight since it's no longer in the database
tonight.value = null
// Show a snackbar message, because it's friendly.
_showSnackbarEvent.value = true
}
}
/**
* Called when the ViewModel is dismantled.
* At this point, we want to cancel all coroutines;
* otherwise we end up with processes that have nowhere to return to
* using memory and resources.
*/
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.trackmysleepquality.sleeptracker
import android.app.Application
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.example.android.trackmysleepquality.database.SleepDatabaseDao
/**
* This is pretty much boiler plate code for a ViewModel Factory.
*
* Provides the SleepDatabaseDao and context to the ViewModel.
*/
class SleepTrackerViewModelFactory(
private val dataSource: SleepDatabaseDao,
private val application: Application) : ViewModelProvider.Factory {
@Suppress("unchecked_cast")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(SleepTrackerViewModel::class.java)) {
return SleepTrackerViewModel(dataSource, application) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2019, The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="700"
android:fromXDelta="100%"
android:fromYDelta="0%"
android:toXDelta="0%"
android:toYDelta="0%" />
</set>

View File

@ -0,0 +1,55 @@
<!--
~ Copyright 2019, The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:pathData="M0,369.07h512v142.93h-512z"
android:fillColor="#e0e0e0"/>
<path
android:pathData="M228.55,271.42l15.7,-30.1L225,244.55l-1,-5.78 26.34,-4.41 1,6.22 -15.6,30.07L256,267.26l1,5.79 -27.42,4.59Z"
android:fillColor="#757575"/>
<path
android:pathData="M271.75,215.33l41.52,-29.2 -27.86,-9 2.68,-8.35 38,12.23 -2.89,9 -41.37,29.25 29.28,9.41L308.44,237l-39.58,-12.73Z"
android:fillColor="#bdbdbd"/>
<path
android:pathData="M350,187.18l39.94,-79.32L339.54,117l-2.75,-15.11 68.74,-12.52 3,16.23 -39.67,79.27 52.94,-9.64 2.75,15.1 -71.57,13Z"
android:fillColor="#e0e0e0"/>
<group>
<clip-path android:pathData="M584.94,-624.15h512v512h-512z M 0,0"/>
<path
android:pathData="M448.55,-187.1,709.34,73.69a8.72,8.72,0,0,0,12.32,0l325.45,-325.45a8.72,8.72,0,0,0,0,-12.32L778,-533.19a18.84,18.84,0,0,0,-26.65,0L442,-241.74l-2.36,5.16A44.05,44.05,0,0,0,448.55,-187.1Z"
android:fillColor="#43a047"/>
<path
android:pathData="M440.25,-208.98l275.52002,275.53l335.0,-335.0l-278.15002,-278.14996l-334.26,328.40997l1.8900146,9.210007z"
android:fillColor="#c0ca33"/>
<path
android:pathData="M544.72,-487.03h393.36v473.76h-393.36z"
android:fillColor="#e6ee9c"/>
<path
android:pathData="M548.74,-506.95H930.26a8.41,8.41,0,0,1,8.41,8.41V-37.68a8.41,8.41,0,0,1,-8.41,8.41H548.74a18.19,18.19,0,0,1,-18.19,-18.19V-488.75A18.19,18.19,0,0,1,548.74,-506.95Z"
android:fillColor="#6ab343"/>
</group>
<group>
<clip-path android:pathData="M0,-624.15h512v512h-512z M 0,0"/>
<path
android:pathData="M246.59,-272.23l39.17,-11.46a3.9,3.9,0,0,0,2.67,-4.84,3.9,3.9,0,0,0,-4.84,-2.63L244,-279.58a134.83,134.83,0,0,0,-30.74,-47.09,134.89,134.89,0,0,0,-47,-30.69l11.49,-39.52a3.86,3.86,0,0,0,-2.63,-4.83,3.85,3.85,0,0,0,-4.83,2.64l-11.42,39.16c-41.3,-13.2,-86.47,-4.85,-117.17,25.89l178.9,178.87c30.72,-30.71,39,-75.79,25.93,-117.08m-211,-54.15L-93.27,-197.48a29.94,29.94,0,0,0,0,42.38l14.43,14.44,-44,44a28.1,28.1,0,0,0,0,39.7,28.12,28.12,0,0,0,39.74,0l44,-44,26.81,26.8,-44,44a28.09,28.09,0,0,0,0,39.7,28.11,28.11,0,0,0,39.74,0l44,-44L41.91,-20A30,30,0,0,0,84.3,-20L213.16,-148.87Zm180.59,90.91L203.59,-248a1.7,1.7,0,0,1,0,-2.4l1.59,-1.6a1.71,1.71,0,0,1,2.41,0l12.58,12.59a1.7,1.7,0,0,1,0,2.4l-1.59,1.59A1.7,1.7,0,0,1,216.17,-235.47Zm-81.43,-81.42,-12.58,-12.59a1.7,1.7,0,0,1,0,-2.4l1.59,-1.59a1.7,1.7,0,0,1,2.41,0l12.58,12.58a1.68,1.68,0,0,1,0,2.4l-1.59,1.6A1.71,1.71,0,0,1,134.74,-316.89Zm-99.16,-9.49L-93.27,-197.48a29.94,29.94,0,0,0,0,42.38l14.43,14.44,-44,44a28.1,28.1,0,0,0,0,39.7,28.12,28.12,0,0,0,39.74,0l44,-44,26.81,26.8,-44,44a28.09,28.09,0,0,0,0,39.7,28.11,28.11,0,0,0,39.74,0l44,-44L41.91,-20A30,30,0,0,0,84.3,-20L213.16,-148.87Z"
android:fillColor="#6ab343"/>
</group>
</vector>

View File

@ -0,0 +1,77 @@
<!--
~ Copyright 2019, The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:pathData="M35.58,297.76l6.18,-7.64l98.76,-8.5l100.14,101.33l-20,86.04l-7.5,6.29l-126.86,-7.63l-50.72,-169.89z"
android:fillColor="#fff"/>
<path
android:pathData="M389,302.51q-28.14,0 -47.68,19.54T321.8,369.73q0,28.15 19.55,48.08T389,437.74q28.14,0 48.08,-19.93T457,369.73q0,-28.14 -19.93,-47.68T389,302.51Z"
android:fillColor="#6ab343"/>
<path
android:pathData="M389.17,326.32a43.23,43.23 0,0 0,-43.55 43.55A42.84,42.84 0,0 0,358.28 401a41.6,41.6 0,0 0,30.89 12.92,44.09 44.09,0 0,0 44.05,-44.06A41.61,41.61 0,0 0,420.31 339,42.91 42.91,0 0,0 389.17,326.32Z"
android:fillColor="#f5f5f5"/>
<path
android:pathData="M389.4,363.79L389.4,340.35"
android:strokeLineJoin="round"
android:strokeWidth="10"
android:fillColor="#00000000"
android:strokeColor="#6ab343"
android:strokeLineCap="round"/>
<path
android:pathData="M394.5,373.69L412.12,384.46"
android:strokeLineJoin="round"
android:strokeWidth="10"
android:fillColor="#00000000"
android:strokeColor="#6ab343"
android:strokeLineCap="round"/>
<path
android:pathData="M389.39,363.79a6.25,6.25 0,0 0,-6.3 6.3,6.2 6.2,0 0,0 1.83,4.5 6,6 0,0 0,4.47 1.87,6.37 6.37,0 0,0 6.36,-6.37 6,6 0,0 0,-1.86 -4.47A6.21,6.21 0,0 0,389.39 363.79Z"
android:strokeWidth="7"
android:fillColor="#f5f5f5"
android:strokeColor="#6ab343"/>
<group>
<clip-path android:pathData="M584.94,0h512v512h-512z M 0,0"/>
<path
android:pathData="M448.55,437.05,709.34,697.84a8.72,8.72,0,0,0,12.32,0l325.45,-325.45a8.72,8.72,0,0,0,0,-12.32L778,91a18.84,18.84,0,0,0,-26.65,0L442,382.4l-2.36,5.16A44.07,44.07,0,0,0,448.55,437.05Z"
android:fillColor="#43a047"/>
<path
android:pathData="M440.25,415.17l275.52002,275.52l335.0,-334.99l-278.15002,-278.15002l-334.26,328.40002l1.8900146,9.220001z"
android:fillColor="#c0ca33"/>
<path
android:pathData="M544.72,137.12h393.36v473.76h-393.36z"
android:fillColor="#e6ee9c"/>
<path
android:pathData="M548.74,117.2H930.26a8.41,8.41,0,0,1,8.41,8.41V586.47a8.41,8.41,0,0,1,-8.41,8.41H548.74a18.19,18.19,0,0,1,-18.19,-18.19V135.4a18.19,18.19,0,0,1,18.19,-18.19Z"
android:fillColor="#6ab343"/>
<path
android:pathData="M601.64,-8.95h13.65v477.68h-13.65z"
android:fillColor="#43a047"/>
<path
android:pathData="M612.29,-1.71h6.82v477.68h-6.82z"
android:fillColor="#66bb6a"/>
</group>
<group>
<clip-path android:pathData="M0,0h512v512h-512z M 0,0"/>
<path
android:pathData="M246.59,351.91l39.17,-11.45a3.89,3.89,0,1,0,-2.17,-7.47L244,344.56a136.82,136.82,0,0,0,-77.69,-77.77l11.49,-39.53a3.89,3.89,0,1,0,-7.46,-2.19l-11.42,39.16c-41.3,-13.19,-86.47,-4.84,-117.17,25.89L220.66,469c30.72,-30.71,39,-75.78,25.93,-117.08m-211,-54.15L-93.27,426.67a29.94,29.94,0,0,0,0,42.38l14.43,14.43,-44,44a28.11,28.11,0,0,0,0,39.71,28.12,28.12,0,0,0,39.74,0l44,-44L-12.31,550l-44,44a28.1,28.1,0,0,0,0,39.71,28.1,28.1,0,0,0,39.74,0l44,-44,14.49,14.47a30,30,0,0,0,42.39,0l128.86,-128.9Zm180.59,90.92L203.59,376.1a1.71,1.71,0,0,1,0,-2.41l1.59,-1.59a1.71,1.71,0,0,1,2.41,0l12.58,12.58a1.71,1.71,0,0,1,0,2.41l-1.59,1.59A1.7,1.7,0,0,1,216.17,388.68Zm-81.43,-81.43,-12.58,-12.58a1.7,1.7,0,0,1,0,-2.4l1.59,-1.6a1.71,1.71,0,0,1,2.41,0l12.58,12.59a1.68,1.68,0,0,1,0,2.4l-1.59,1.59A1.7,1.7,0,0,1,134.74,307.25Zm-99.16,-9.49L-93.27,426.67a29.94,29.94,0,0,0,0,42.38l14.43,14.43,-44,44a28.11,28.11,0,0,0,0,39.71,28.12,28.12,0,0,0,39.74,0l44,-44L-12.31,550l-44,44a28.1,28.1,0,0,0,0,39.71,28.1,28.1,0,0,0,39.74,0l44,-44,14.49,14.47a30,30,0,0,0,42.39,0l128.86,-128.9Z"
android:fillColor="#6ab343"/>
</group>
</vector>

View File

@ -0,0 +1,28 @@
<!--
~ Copyright 2019, The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<vector android:height="24dp" android:viewportHeight="500"
android:viewportWidth="500" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#6ab343" android:pathData="M181.16,316.18m-181.7,0a181.7,181.7 0,1 1,363.4 0a181.7,181.7 0,1 1,-363.4 0"/>
<path android:fillColor="#fff" android:pathData="M81.158,261.465L145.387,272.79A5.49,5.49 55,0 1,149.84 279.15L149.832,279.199A5.49,5.49 55,0 1,143.472 283.652L79.243,272.327A5.49,5.49 55,0 1,74.789 265.967L74.798,265.918A5.49,5.49 55,0 1,81.158 261.465z"/>
<path android:fillColor="#fff" android:pathData="M214.909,272.293L278.825,261.206A5.461,5.461 125.159,0 1,285.138 265.652L285.147,265.701A5.461,5.461 125.159,0 1,280.7 272.015L216.784,283.102A5.461,5.461 125.159,0 1,210.471 278.655L210.462,278.606A5.461,5.461 125.159,0 1,214.909 272.293z"/>
<path android:fillColor="#fff" android:pathData="M87.58,410.05a9.55,9.55 0,0 1,-9.41 -11.11c7.36,-44.12 57.39,-78.27 103,-78.27s95.63,34.15 103,78.27a9.55,9.55 0,0 1,-9.41 11.11Z"/>
<path android:fillColor="#eee" android:pathData="M398.31,101.69m-101.69,0a101.69,101.69 0,1 1,203.38 0a101.69,101.69 0,1 1,-203.38 0"/>
<path android:fillColor="#eee" android:pathData="M267.86,57.09m-24.76,0a24.76,24.76 0,1 1,49.52 0a24.76,24.76 0,1 1,-49.52 0"/>
<path android:fillColor="#eee" android:pathData="M224.93,85.36m-16.33,0a16.33,16.33 0,1 1,32.66 0a16.33,16.33 0,1 1,-32.66 0"/>
<path android:fillColor="#eee" android:pathData="M217.77,120.19m-9.17,0a9.17,9.17 0,1 1,18.34 0a9.17,9.17 0,1 1,-18.34 0"/>
<path android:fillColor="#6ab343" android:pathData="M377.25,147.51a45.14,45.14 0,0 1,-15.08 -19.2,68.28 68.28,0 0,1 -5.32,-27.53 68.23,68.23 0,0 1,5.32 -27.52,45.14 45.14,0 0,1 15.08,-19.2 40.24,40.24 0,0 1,45.3 0,45.12 45.12,0 0,1 15.07,19.2A68.23,68.23 0,0 1,443 100.78a68.28,68.28 0,0 1,-5.33 27.53,45.12 45.12,0 0,1 -15.07,19.2 40.29,40.29 0,0 1,-45.3 0ZM412.42,131.51a28.4,28.4 0,0 0,8.1 -12.6,55.08 55.08,0 0,0 2.78,-18.08q0,-15.59 -6.3,-25.42t-17.1,-9.83q-10.65,0 -16.95,9.9t-6.3,25.35a55.09,55.09 0,0 0,2.77 18.08,28.4 28.4,0 0,0 8.1,12.6 19.24,19.24 0,0 0,24.9 0Z"/>
</vector>

View File

@ -0,0 +1,30 @@
<!--
~ Copyright 2019, The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<vector android:height="24dp" android:viewportHeight="500"
android:viewportWidth="500" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#6ab343" android:pathData="M181.16,316.18m-181.7,0a181.7,181.7 0,1 1,363.4 0a181.7,181.7 0,1 1,-363.4 0"/>
<path android:fillColor="#fff" android:pathData="M80.313,264.221L145.284,269.905A5.49,5.49 50,0 1,150.275 275.853L150.271,275.903A5.49,5.49 50,0 1,144.323 280.893L79.351,275.209A5.49,5.49 50,0 1,74.361 269.261L74.365,269.212A5.49,5.49 50,0 1,80.313 264.221z"/>
<path android:fillColor="#fff" android:pathData="M217.037,269.907L282.009,264.223A5.49,5.49 130,0 1,287.956 269.213L287.961,269.263A5.49,5.49 130,0 1,282.97 275.211L217.998,280.895A5.49,5.49 130,0 1,212.051 275.904L212.046,275.855A5.49,5.49 130,0 1,217.037 269.907z"/>
<path android:fillColor="#00000000"
android:pathData="M92.91,395.3c19.86,-26.3 54.92,-43.94 87.69,-43.94 33.08,0 68.5,18 88.26,44.69"
android:strokeColor="#fff" android:strokeLineCap="round" android:strokeWidth="20"/>
<path android:fillColor="#eee" android:pathData="M398.31,101.69m-101.69,0a101.69,101.69 0,1 1,203.38 0a101.69,101.69 0,1 1,-203.38 0"/>
<path android:fillColor="#eee" android:pathData="M267.86,57.09m-24.76,0a24.76,24.76 0,1 1,49.52 0a24.76,24.76 0,1 1,-49.52 0"/>
<path android:fillColor="#eee" android:pathData="M224.93,85.36m-16.33,0a16.33,16.33 0,1 1,32.66 0a16.33,16.33 0,1 1,-32.66 0"/>
<path android:fillColor="#eee" android:pathData="M217.77,120.19m-9.17,0a9.17,9.17 0,1 1,18.34 0a9.17,9.17 0,1 1,-18.34 0"/>
<path android:fillColor="#6ab343" android:pathData="M400.67,75.13 L385.22,86.38l-9.9,-15.15 30.15,-21.75h14.85v102.6H400.67Z"/>
</vector>

View File

@ -0,0 +1,30 @@
<!--
~ Copyright 2019, The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<vector android:height="24dp" android:viewportHeight="500"
android:viewportWidth="500" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#6ab343" android:pathData="M181.16,316.18m-181.7,0a181.7,181.7 0,1 1,363.4 0a181.7,181.7 0,1 1,-363.4 0"/>
<path android:fillColor="#fff" android:pathData="M79.71,267.04L144.93,267.04A5.49,5.49 0,0 1,150.42 272.53L150.42,272.58A5.49,5.49 0,0 1,144.93 278.07L79.71,278.07A5.49,5.49 0,0 1,74.22 272.58L74.22,272.53A5.49,5.49 0,0 1,79.71 267.04z"/>
<path android:fillColor="#fff" android:pathData="M217.39,267.04L282.61,267.04A5.49,5.49 0,0 1,288.1 272.53L288.1,272.58A5.49,5.49 0,0 1,282.61 278.07L217.39,278.07A5.49,5.49 0,0 1,211.9 272.58L211.9,272.53A5.49,5.49 0,0 1,217.39 267.04z"/>
<path android:fillColor="#00000000"
android:pathData="M268.86,383.11L92.91,382.36"
android:strokeColor="#fff" android:strokeLineCap="round" android:strokeWidth="20"/>
<path android:fillColor="#eee" android:pathData="M398.31,101.69m-101.69,0a101.69,101.69 0,1 1,203.38 0a101.69,101.69 0,1 1,-203.38 0"/>
<path android:fillColor="#eee" android:pathData="M267.86,57.09m-24.76,0a24.76,24.76 0,1 1,49.52 0a24.76,24.76 0,1 1,-49.52 0"/>
<path android:fillColor="#eee" android:pathData="M224.93,85.36m-16.33,0a16.33,16.33 0,1 1,32.66 0a16.33,16.33 0,1 1,-32.66 0"/>
<path android:fillColor="#eee" android:pathData="M217.77,120.19m-9.17,0a9.17,9.17 0,1 1,18.34 0a9.17,9.17 0,1 1,-18.34 0"/>
<path android:fillColor="#6ab343" android:pathData="M366.07,134.23l19.13,-19.12q14.18,-14.17 18.22,-18.53a48.84,48.84 0,0 0,7.43 -9.75,18.89 18.89,0 0,0 2,-8.85 11.59,11.59 0,0 0,-3.9 -8.85q-3.9,-3.6 -10.35,-3.6a14.44,14.44 0,0 0,-10.2 3.6,19.9 19.9,0 0,0 -5.55,9l-17.7,-7.35a31.77,31.77 0,0 1,6.08 -11.32,33.4 33.4,0 0,1 11.47,-8.93 36.54,36.54 0,0 1,16.2 -3.45,38.19 38.19,0 0,1 17.85,4 29.55,29.55 0,0 1,11.85 10.8,28.48 28.48,0 0,1 4.2,15.22q0,16.65 -16.2,32.55 -6.6,6.47 -24.45,24.3l0.45,0.9h41.55v17.25h-68.1Z"/>
</vector>

View File

@ -0,0 +1,30 @@
<!--
~ Copyright 2019, The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<vector android:height="24dp" android:viewportHeight="500"
android:viewportWidth="500" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#6ab343" android:pathData="M181.16,316.18m-181.7,0a181.7,181.7 0,1 1,363.4 0a181.7,181.7 0,1 1,-363.4 0"/>
<path android:fillColor="#eee" android:pathData="M398.31,101.69m-101.69,0a101.69,101.69 0,1 1,203.38 0a101.69,101.69 0,1 1,-203.38 0"/>
<path android:fillColor="#eee" android:pathData="M267.86,57.09m-24.76,0a24.76,24.76 0,1 1,49.52 0a24.76,24.76 0,1 1,-49.52 0"/>
<path android:fillColor="#eee" android:pathData="M224.93,85.36m-16.33,0a16.33,16.33 0,1 1,32.66 0a16.33,16.33 0,1 1,-32.66 0"/>
<path android:fillColor="#eee" android:pathData="M217.77,120.19m-9.17,0a9.17,9.17 0,1 1,18.34 0a9.17,9.17 0,1 1,-18.34 0"/>
<path android:fillColor="#6ab343" android:pathData="M377,147.58q-10,-6.89 -13.73,-20.1l18.6,-7.35q1.8,7.37 6.45,11.33a17.37,17.37 0,0 0,11.7 4,17 17,0 0,0 11.18,-3.9 12.88,12.88 0,0 0,-0.3 -20.17q-5,-3.83 -13.58,-3.83h-8.85V89.68h8.1a18.54,18.54 0,0 0,11.18 -3.3c3.05,-2.19 4.57,-5.4 4.57,-9.6a10.72,10.72 0,0 0,-3.75 -8.4,14 14,0 0,0 -9.6,-3.3 14.49,14.49 0,0 0,-9.9 3.3,17.57 17.57,0 0,0 -5.25,8.25l-17.85,-7.35a33.92,33.92 0,0 1,11.63 -15.6q8.47,-6.59 21.52,-6.6a39.56,39.56 0,0 1,17.18 3.6A28.64,28.64 0,0 1,428 60.51a24.51,24.51 0,0 1,4.2 14,25.17 25.17,0 0,1 -3.82,14.1 23.8,23.8 0,0 1,-9.23 8.55v1.2a27.18,27.18 0,0 1,12.15 9.45,26 26,0 0,1 4.65,15.6 28.87,28.87 0,0 1,-4.5 15.9,30.82 30.82,0 0,1 -12.67,11.1 41.9,41.9 0,0 1,-18.83 4A39.46,39.46 0,0 1,377 147.58Z"/>
<path android:fillColor="#fff" android:pathData="M79.71,267.04L144.93,267.04A5.49,5.49 0,0 1,150.42 272.53L150.42,272.58A5.49,5.49 0,0 1,144.93 278.07L79.71,278.07A5.49,5.49 0,0 1,74.22 272.58L74.22,272.53A5.49,5.49 0,0 1,79.71 267.04z"/>
<path android:fillColor="#fff" android:pathData="M217.39,267.04L282.61,267.04A5.49,5.49 0,0 1,288.1 272.53L288.1,272.58A5.49,5.49 0,0 1,282.61 278.07L217.39,278.07A5.49,5.49 0,0 1,211.9 272.58L211.9,272.53A5.49,5.49 0,0 1,217.39 267.04z"/>
<path android:fillColor="#00000000"
android:pathData="M268.86,377.57c-19.87,8.69 -54.93,14.52 -87.7,14.52 -33.08,0 -68.49,-5.94 -88.25,-14.77"
android:strokeColor="#fff" android:strokeLineCap="round" android:strokeWidth="20"/>
</vector>

View File

@ -0,0 +1,30 @@
<!--
~ Copyright 2019, The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<vector android:height="24dp" android:viewportHeight="500"
android:viewportWidth="500" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#6ab343" android:pathData="M181.16,316.18m-181.7,0a181.7,181.7 0,1 1,363.4 0a181.7,181.7 0,1 1,-363.4 0"/>
<path android:fillColor="#fff" android:pathData="M79.351,269.907L144.323,264.222A5.49,5.49 130,0 1,150.27 269.213L150.275,269.263A5.49,5.49 130,0 1,145.284 275.21L80.312,280.895A5.49,5.49 130,0 1,74.364 275.904L74.36,275.854A5.49,5.49 130,0 1,79.351 269.907z"/>
<path android:fillColor="#fff" android:pathData="M218.066,265.855L283.286,271.725A5.512,5.512 50.143,0 1,288.282 277.709L288.278,277.759A5.512,5.512 50.143,0 1,282.294 282.755L217.074,276.885A5.512,5.512 50.143,0 1,212.078 270.901L212.082,270.851A5.512,5.512 50.143,0 1,218.066 265.855z"/>
<path android:fillColor="#00000000"
android:pathData="M268.86,363.11c-19.87,26.3 -54.93,43.94 -87.7,43.94 -33.08,0 -68.49,-18 -88.25,-44.69"
android:strokeColor="#fff" android:strokeLineCap="round" android:strokeWidth="20"/>
<path android:fillColor="#eee" android:pathData="M398.31,101.69m-101.69,0a101.69,101.69 0,1 1,203.38 0a101.69,101.69 0,1 1,-203.38 0"/>
<path android:fillColor="#eee" android:pathData="M267.86,57.09m-24.76,0a24.76,24.76 0,1 1,49.52 0a24.76,24.76 0,1 1,-49.52 0"/>
<path android:fillColor="#eee" android:pathData="M224.93,85.36m-16.33,0a16.33,16.33 0,1 1,32.66 0a16.33,16.33 0,1 1,-32.66 0"/>
<path android:fillColor="#eee" android:pathData="M217.77,120.19m-9.17,0a9.17,9.17 0,1 1,18.34 0a9.17,9.17 0,1 1,-18.34 0"/>
<path android:fillColor="#6ab343" android:pathData="M402.17,133.18L354.92,133.18v-15.9l45.6,-67.8h21.3v65.4h12.75v18.3L421.82,133.18v18.9L402.17,152.08ZM402.17,114.88L402.17,78.73L401,78.73l-24.3,36.15Z"/>
</vector>

View File

@ -0,0 +1,28 @@
<!--
~ Copyright 2019, The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<vector android:height="24dp" android:viewportHeight="500"
android:viewportWidth="500" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#6ab343" android:pathData="M181.16,316.18m-181.7,0a181.7,181.7 0,1 1,363.4 0a181.7,181.7 0,1 1,-363.4 0"/>
<path android:fillColor="#fff" android:pathData="M79.25,272.792L143.479,261.466A5.49,5.49 125,0 1,149.839 265.919L149.848,265.969A5.49,5.49 125,0 1,145.395 272.329L81.165,283.654A5.49,5.49 125,0 1,74.805 279.201L74.797,279.151A5.49,5.49 125,0 1,79.25 272.792z"/>
<path android:fillColor="#fff" android:pathData="M218.846,261.462L283.076,272.788A5.49,5.49 55,0 1,287.529 279.148L287.52,279.197A5.49,5.49 55,0 1,281.16 283.65L216.931,272.325A5.49,5.49 55,0 1,212.478 265.965L212.486,265.916A5.49,5.49 55,0 1,218.846 261.462z"/>
<path android:fillColor="#fff" android:pathData="M274.74,337.67a9.55,9.55 0,0 1,9.41 11.11c-7.36,44.12 -57.39,78.27 -103,78.27s-95.63,-34.15 -103,-78.27a9.55,9.55 0,0 1,9.41 -11.11Z"/>
<path android:fillColor="#eee" android:pathData="M398.31,101.69m-101.69,0a101.69,101.69 0,1 1,203.38 0a101.69,101.69 0,1 1,-203.38 0"/>
<path android:fillColor="#eee" android:pathData="M267.86,57.09m-24.76,0a24.76,24.76 0,1 1,49.52 0a24.76,24.76 0,1 1,-49.52 0"/>
<path android:fillColor="#eee" android:pathData="M224.93,85.36m-16.33,0a16.33,16.33 0,1 1,32.66 0a16.33,16.33 0,1 1,-32.66 0"/>
<path android:fillColor="#eee" android:pathData="M217.77,120.19m-9.17,0a9.17,9.17 0,1 1,18.34 0a9.17,9.17 0,1 1,-18.34 0"/>
<path android:fillColor="#6ab343" android:pathData="M383.77,151.41a35.84,35.84 0,0 1,-13.12 -9.3,33.67 33.67,0 0,1 -7.73,-15.38l17.7,-6.9a23.14,23.14 0,0 0,6.6 12.08,17 17,0 0,0 12,4.42 16.24,16.24 0,0 0,12 -4.8,16.43 16.43,0 0,0 4.8,-12.15 16.66,16.66 0,0 0,-4.72 -12.07,16.1 16.1,0 0,0 -12.08,-4.88 17.71,17.71 0,0 0,-8.17 1.88,19.05 19.05,0 0,0 -6.23,5l-19.05,-8.55 5.85,-51.3h57.9V66.73H388l-3.75,24 1.2,0.3a27.32,27.32 0,0 1,17.85 -6.15,31.18 31.18,0 0,1 16,4.35 32.65,32.65 0,0 1,11.92 12.23,35.49 35.49,0 0,1 4.5,17.92A35.22,35.22 0,0 1,431 137.53,32.81 32.81,0 0,1 418.12,150a38.51,38.51 0,0 1,-18.75 4.5A40.73,40.73 0,0 1,383.77 151.41Z"/>
</vector>

View File

@ -0,0 +1,77 @@
<!--
~ Copyright 2019, The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:pathData="M35.58,297.76l6.18,-7.64l98.76,-8.5l100.14,101.33l-20,86.04l-7.5,6.29l-126.86,-7.63l-50.72,-169.89z"
android:fillColor="#fff"/>
<path
android:pathData="M389,302.51q-28.14,0 -47.68,19.54T321.8,369.73q0,28.15 19.55,48.08T389,437.74q28.14,0 48.08,-19.93T457,369.73q0,-28.14 -19.93,-47.68T389,302.51Z"
android:fillColor="#6ab343"/>
<path
android:pathData="M389.17,326.32a43.23,43.23 0,0 0,-43.55 43.55A42.84,42.84 0,0 0,358.28 401a41.6,41.6 0,0 0,30.89 12.92,44.09 44.09,0 0,0 44.05,-44.06A41.61,41.61 0,0 0,420.31 339,42.91 42.91,0 0,0 389.17,326.32Z"
android:fillColor="#f5f5f5"/>
<path
android:pathData="M389.4,363.79L389.4,340.35"
android:strokeLineJoin="round"
android:strokeWidth="10"
android:fillColor="#00000000"
android:strokeColor="#6ab343"
android:strokeLineCap="round"/>
<path
android:pathData="M394.5,373.69L412.12,384.46"
android:strokeLineJoin="round"
android:strokeWidth="10"
android:fillColor="#00000000"
android:strokeColor="#6ab343"
android:strokeLineCap="round"/>
<path
android:pathData="M389.39,363.79a6.25,6.25 0,0 0,-6.3 6.3,6.2 6.2,0 0,0 1.83,4.5 6,6 0,0 0,4.47 1.87,6.37 6.37,0 0,0 6.36,-6.37 6,6 0,0 0,-1.86 -4.47A6.21,6.21 0,0 0,389.39 363.79Z"
android:strokeWidth="7"
android:fillColor="#f5f5f5"
android:strokeColor="#6ab343"/>
<group>
<clip-path android:pathData="M584.94,0h512v512h-512z M 0,0"/>
<path
android:pathData="M448.55,437.05,709.34,697.84a8.72,8.72,0,0,0,12.32,0l325.45,-325.45a8.72,8.72,0,0,0,0,-12.32L778,91a18.84,18.84,0,0,0,-26.65,0L442,382.4l-2.36,5.16A44.07,44.07,0,0,0,448.55,437.05Z"
android:fillColor="#43a047"/>
<path
android:pathData="M440.25,415.17l275.52002,275.52l335.0,-334.99l-278.15002,-278.15002l-334.26,328.40002l1.8900146,9.220001z"
android:fillColor="#c0ca33"/>
<path
android:pathData="M544.72,137.12h393.36v473.76h-393.36z"
android:fillColor="#e6ee9c"/>
<path
android:pathData="M548.74,117.2H930.26a8.41,8.41,0,0,1,8.41,8.41V586.47a8.41,8.41,0,0,1,-8.41,8.41H548.74a18.19,18.19,0,0,1,-18.19,-18.19V135.4a18.19,18.19,0,0,1,18.19,-18.19Z"
android:fillColor="#6ab343"/>
<path
android:pathData="M601.64,-8.95h13.65v477.68h-13.65z"
android:fillColor="#43a047"/>
<path
android:pathData="M612.29,-1.71h6.82v477.68h-6.82z"
android:fillColor="#66bb6a"/>
</group>
<group>
<clip-path android:pathData="M0,0h512v512h-512z M 0,0"/>
<path
android:pathData="M246.59,351.91l39.17,-11.45a3.89,3.89,0,1,0,-2.17,-7.47L244,344.56a136.82,136.82,0,0,0,-77.69,-77.77l11.49,-39.53a3.89,3.89,0,1,0,-7.46,-2.19l-11.42,39.16c-41.3,-13.19,-86.47,-4.84,-117.17,25.89L220.66,469c30.72,-30.71,39,-75.78,25.93,-117.08m-211,-54.15L-93.27,426.67a29.94,29.94,0,0,0,0,42.38l14.43,14.43,-44,44a28.11,28.11,0,0,0,0,39.71,28.12,28.12,0,0,0,39.74,0l44,-44L-12.31,550l-44,44a28.1,28.1,0,0,0,0,39.71,28.1,28.1,0,0,0,39.74,0l44,-44,14.49,14.47a30,30,0,0,0,42.39,0l128.86,-128.9Zm180.59,90.92L203.59,376.1a1.71,1.71,0,0,1,0,-2.41l1.59,-1.59a1.71,1.71,0,0,1,2.41,0l12.58,12.58a1.71,1.71,0,0,1,0,2.41l-1.59,1.59A1.7,1.7,0,0,1,216.17,388.68Zm-81.43,-81.43,-12.58,-12.58a1.7,1.7,0,0,1,0,-2.4l1.59,-1.6a1.71,1.71,0,0,1,2.41,0l12.58,12.59a1.68,1.68,0,0,1,0,2.4l-1.59,1.59A1.7,1.7,0,0,1,134.74,307.25Zm-99.16,-9.49L-93.27,426.67a29.94,29.94,0,0,0,0,42.38l14.43,14.43,-44,44a28.11,28.11,0,0,0,0,39.71,28.12,28.12,0,0,0,39.74,0l44,-44L-12.31,550l-44,44a28.1,28.1,0,0,0,0,39.71,28.1,28.1,0,0,0,39.74,0l44,-44,14.49,14.47a30,30,0,0,0,42.39,0l128.86,-128.9Z"
android:fillColor="#6ab343"/>
</group>
</vector>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
app:fontProviderAuthority="com.google.android.gms.fonts"
app:fontProviderPackage="com.google.android.gms"
app:fontProviderQuery="Roboto"
app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
</font-family>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2019, The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<!-- The merge tag can be used to eliminate redundant layouts when
including layouts, and it's a good idea to use it.
See: https://developer.android.com/training/improving-layouts/reusing-layouts -->
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/navigation" />
</merge>

View File

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2019, The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<!-- Data to make available to the XML via data binding. In this case,
the whole ViewModel, so that we can access the LiveData,
click handlers, and state variables. -->
<data>
<variable
name="sleepDetailViewModel"
type="com.example.android.trackmysleepquality.sleepdetail.SleepDetailViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".sleepdetail.SleepDetailFragment">
<ImageView
android:id="@+id/quality_image"
android:layout_width="@dimen/icon_size"
android:layout_height="@dimen/icon_size"
android:layout_marginStart="8dp"
android:layout_marginTop="56dp"
android:layout_marginEnd="8dp"
app:sleepImage="@{sleepDetailViewModel.night}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/quality_string"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:layout_marginStart="8dp"
android:layout_marginTop="100dp"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/quality_image"
app:sleepQualityString="@{sleepDetailViewModel.night}" />
<TextView
android:id="@+id/sleep_length"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:layout_marginStart="8dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/quality_string"
app:sleepDurationFormatted="@{sleepDetailViewModel.night}" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="32dp"
android:onClick="@{() -> sleepDetailViewModel.onClose()}"
android:text="@string/close"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@ -0,0 +1,130 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2019, The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<!-- Wrapping the layout into /layout to make it available with data binding. -->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<!-- Data to make available to the XML via data binding. In this case,
the whole ViewModel, so that we can access the LiveData,
click handlers, and state variables. -->
<data>
<variable
name="sleepQualityViewModel"
type="com.example.android.trackmysleepquality.sleepquality.SleepQualityViewModel" />
</data>
<!-- Start of the visible fragment layout using ConstraintLayout -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".sleepquality.SleepQualityFragment">
<TextView
android:id="@+id/title_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin"
android:layout_marginTop="@dimen/margin"
android:layout_marginBottom="@dimen/triple_margin"
android:fontFamily="@font/roboto"
android:text="@string/how_was_hour_sleep"
android:textSize="@dimen/title_text_size"
app:layout_constraintBottom_toTopOf="@+id/quality_zero_image"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/quality_zero_image"
android:layout_width="@dimen/icon_size"
android:layout_height="@dimen/icon_size"
android:layout_marginStart="@dimen/margin"
android:layout_marginTop="@dimen/margin"
android:contentDescription="@string/quality_0"
android:onClick="@{() -> sleepQualityViewModel.onSetSleepQuality(0)}"
app:layout_constraintEnd_toStartOf="@+id/quality_one_image"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title_text"
app:srcCompat="@drawable/ic_sleep_0" />
<ImageView
android:id="@+id/quality_one_image"
android:layout_width="@dimen/icon_size"
android:layout_height="@dimen/icon_size"
android:contentDescription="@string/quality_1"
android:onClick="@{() -> sleepQualityViewModel.onSetSleepQuality(1)}"
app:layout_constraintBottom_toBottomOf="@+id/quality_zero_image"
app:layout_constraintEnd_toStartOf="@+id/quality_two_image"
app:layout_constraintStart_toEndOf="@+id/quality_zero_image"
app:srcCompat="@drawable/ic_sleep_1" />
<ImageView
android:id="@+id/quality_two_image"
android:layout_width="@dimen/icon_size"
android:layout_height="@dimen/icon_size"
android:layout_marginEnd="@dimen/margin"
android:contentDescription="@string/quality_2"
android:onClick="@{() -> sleepQualityViewModel.onSetSleepQuality(2)}"
app:layout_constraintBottom_toBottomOf="@+id/quality_one_image"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/quality_one_image"
app:srcCompat="@drawable/ic_sleep_2" />
<ImageView
android:id="@+id/quality_three_image"
android:layout_width="@dimen/icon_size"
android:layout_height="@dimen/icon_size"
android:layout_marginStart="@dimen/margin"
android:layout_marginTop="@dimen/margin"
android:layout_marginBottom="@dimen/margin"
android:contentDescription="@string/quality_3"
android:onClick="@{() -> sleepQualityViewModel.onSetSleepQuality(3)}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/quality_four_image"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/quality_zero_image"
app:layout_constraintVertical_bias="0.0"
app:srcCompat="@drawable/ic_sleep_3" />
<ImageView
android:id="@+id/quality_four_image"
android:layout_width="@dimen/icon_size"
android:layout_height="@dimen/icon_size"
android:contentDescription="@string/quality_4"
android:onClick="@{() -> sleepQualityViewModel.onSetSleepQuality(4)}"
app:layout_constraintBottom_toBottomOf="@+id/quality_three_image"
app:layout_constraintEnd_toStartOf="@+id/quality_five_image"
app:layout_constraintStart_toEndOf="@+id/quality_three_image"
app:layout_constraintTop_toTopOf="@+id/quality_three_image"
app:srcCompat="@drawable/ic_sleep_4" />
<ImageView
android:id="@+id/quality_five_image"
android:layout_width="@dimen/icon_size"
android:layout_height="@dimen/icon_size"
android:layout_marginEnd="@dimen/margin"
android:contentDescription="@string/quality_5"
android:onClick="@{() -> sleepQualityViewModel.onSetSleepQuality(5)}"
app:layout_constraintBottom_toBottomOf="@+id/quality_four_image"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/quality_four_image"
app:layout_constraintTop_toTopOf="@+id/quality_four_image"
app:srcCompat="@drawable/ic_sleep_5" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2019, The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<!-- Wrapping the layout into /layout to make it available with data binding. -->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<!-- Data to make available to the XML via data binding. In this case,
the whole ViewModel, so that we can access the LiveData,
click handlers, and state variables. -->
<data>
<variable
name="sleepTrackerViewModel"
type="com.example.android.trackmysleepquality.sleeptracker.SleepTrackerViewModel" />
</data>
<!-- Start of the visible fragment layout using ConstraintLayout -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".sleeptracker.SleepTrackerFragment">
<!-- This lesson we will switch to RecyclerView to properly
display the sleep data-->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/sleep_list"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/clear_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/stop_button"/>
<!-- With data binding and LiveData, we can track the buttons' visibility states
from the ViewModel. The click handler is in the ViewModel as well, and
you can set it for the Views using this lambda pattern. -->
<Button
android:id="@+id/start_button"
style="@style/SleepButtons"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin"
android:enabled="@{sleepTrackerViewModel.startButtonVisible}"
android:onClick="@{() -> sleepTrackerViewModel.onStart()}"
android:text="@string/start"
app:layout_constraintBaseline_toBaselineOf="@id/stop_button"
app:layout_constraintEnd_toStartOf="@+id/stop_button"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/stop_button"
style="@style/SleepButtons"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin"
android:layout_marginEnd="@dimen/margin"
android:enabled="@{sleepTrackerViewModel.stopButtonVisible}"
android:onClick="@{() -> sleepTrackerViewModel.onStop()}"
android:text="@string/stop"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/start_button"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/clear_button"
style="@style/SleepButtons"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin"
android:layout_marginEnd="@dimen/margin"
android:layout_marginBottom="@dimen/margin"
android:enabled="@{sleepTrackerViewModel.clearButtonVisible}"
android:onClick="@{() -> sleepTrackerViewModel.onClear()}"
android:text="@string/clear"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2019, The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="sleep"
type="com.example.android.trackmysleepquality.database.SleepNight"/>
<variable
name="clickListener"
type="com.example.android.trackmysleepquality.sleeptracker.SleepNightListener" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{() -> clickListener.onClick(sleep)}">
<ImageView
android:id="@+id/quality_image"
android:layout_width="@dimen/icon_size"
android:layout_height="60dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@drawable/ic_sleep_5"
app:sleepImage="@{sleep}"/>
<TextView
android:id="@+id/quality_string"
android:layout_width="0dp"
android:layout_height="20dp"
android:textAlignment="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/quality_image"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="@+id/quality_image"
app:layout_constraintTop_toBottomOf="@+id/quality_image"
tools:text="Excellent!!!"
app:sleepQualityString="@{sleep}" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2019, The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="sleep"
type="com.example.android.trackmysleepquality.database.SleepNight"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/quality_image"
android:layout_width="@dimen/icon_size"
android:layout_height="60dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@drawable/ic_sleep_5"
app:sleepImage="@{sleep}"/>
<TextView
android:id="@+id/sleep_length"
android:layout_width="0dp"
android:layout_height="20dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/quality_image"
app:layout_constraintTop_toTopOf="@+id/quality_image"
tools:text="Wednesday"
app:sleepDurationFormatted="@{sleep}"/>
<TextView
android:id="@+id/quality_string"
android:layout_width="0dp"
android:layout_height="20dp"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="@+id/sleep_length"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="@+id/sleep_length"
app:layout_constraintTop_toBottomOf="@+id/sleep_length"
tools:text="Excellent!!!"
app:sleepQualityString="@{sleep}"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2019, The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:textSize="24sp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:layout_width="match_parent" android:layout_height="wrap_content" />

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019, The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_sleep_tracker_background" />
<foreground android:drawable="@drawable/ic_launcher_sleep_tracker_foreground" />
</adaptive-icon>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019, The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_sleep_tracker_background" />
<foreground android:drawable="@drawable/ic_launcher_sleep_tracker_foreground" />
</adaptive-icon>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019, The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_sleep_tracker_background"/>
<foreground android:drawable="@drawable/ic_launcher_sleep_tracker_foreground"/>
</adaptive-icon>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019, The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_sleep_tracker_background"/>
<foreground android:drawable="@drawable/ic_launcher_sleep_tracker_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2019, The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<!-- Note that the animations may show red in Android Studio;
however, they are present and working perfectly fine. -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/navigation"
app:startDestination="@id/sleep_tracker_fragment">
<fragment
android:id="@+id/sleep_tracker_fragment"
android:name="com.example.android.trackmysleepquality.sleeptracker.SleepTrackerFragment"
android:label="@string/sleep_tracker_fragment"
tools:layout="@layout/fragment_sleep_tracker">
<action
android:id="@+id/action_sleepTrackerFragment_to_sleepQualityFragment"
app:destination="@id/sleep_quality_fragment"
app:enterAnim="@anim/slide_in_right"
app:popEnterAnim="@anim/slide_in_right" />
<action
android:id="@+id/action_sleep_tracker_fragment_to_sleepDetailFragment"
app:destination="@id/sleep_detail_fragment"
app:enterAnim="@anim/slide_in_right"
app:popEnterAnim="@anim/slide_in_right" />
</fragment>
<fragment
android:id="@+id/sleep_quality_fragment"
android:name="com.example.android.trackmysleepquality.sleepquality.SleepQualityFragment"
android:label="@string/sleep_quality_fragment"
tools:layout="@layout/fragment_sleep_quality">
<argument
android:name="sleepNightKey"
app:argType="long" />
<action
android:id="@+id/action_sleepQualityFragment_to_sleepTrackerFragment"
app:destination="@id/sleep_tracker_fragment"
app:launchSingleTop="false"
app:popUpTo="@+id/sleep_tracker_fragment"
app:popUpToInclusive="true" />
</fragment>
<fragment
android:id="@+id/sleep_detail_fragment"
android:name="com.example.android.trackmysleepquality.sleepdetail.SleepDetailFragment"
android:label="fragment_sleep_detail"
tools:layout="@layout/fragment_sleep_detail" >
<argument
android:name="sleepNightKey"
app:argType="long" />
<action
android:id="@+id/action_sleepDetailFragment_to_sleep_tracker_fragment"
app:destination="@id/sleep_tracker_fragment"
app:launchSingleTop="false"
app:popUpTo="@+id/sleep_tracker_fragment"
app:popUpToInclusive="true" />
</fragment>
</navigation>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2019, The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<resources>
<color name="colorPrimary">#6ab343</color>
<color name="colorPrimaryDark">#388310</color>
<color name="colorAccent">#6ab343</color>
<color name="green_color">#388310</color>
<color name="white_text_color">#f0f0f0</color>
</resources>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2019, The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<resources>
<dimen name="margin">16dp</dimen>
<dimen name="icon_size">64dp</dimen>
<dimen name="title_text_size">20sp</dimen>
<dimen name="triple_margin">48dp</dimen>
</resources>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2019, The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<resources>
<array name="com_google_android_gms_fonts_certs">
<item>@array/com_google_android_gms_fonts_certs_dev</item>
<item>@array/com_google_android_gms_fonts_certs_prod</item>
</array>
<string-array name="com_google_android_gms_fonts_certs_dev">
<item>
MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAeFw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVyxW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8XW8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexAcKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkwHQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0cxb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrPzgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXclaXjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05aIskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+aayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUWEv9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs=
</item>
</string-array>
<string-array name="com_google_android_gms_fonts_certs_prod">
<item>
MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK
</item>
</string-array>
</resources>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2019, The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<resources>
<array name="preloaded_fonts" translatable="false">
<item>@font/roboto</item>
</array>
</resources>

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2019, The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<resources>
<!-- App title and fragment titles -->
<string name="app_name">Track My Sleep Quality</string>
<string name="sleep_tracker_fragment">SleepTrackerFragment</string>
<string name="sleep_quality_fragment">SleepQualityFragment</string>
<!-- Button labels -->
<string name="start">Start</string>
<string name="stop">Stop</string>
<string name="clear">Clear</string>
<!-- contentDescription strings for quality icons -->
<string name="quality_0">Sleep Quality 0</string>
<string name="quality_1">Sleep Quality 1</string>
<string name="quality_2">Sleep Quality 2</string>
<string name="quality_3">Sleep Quality 3</string>
<string name="quality_4">Sleep Quality 4</string>
<string name="quality_5">Sleep Quality 5</string>
<!-- Output to TextView styled with a little HTML -->
<string name="title"><![CDATA[<h3>HERE IS YOUR SLEEP DATA</h3>]]></string>
<string name="start_time"><![CDATA[<b>Start:</b>]]></string>
<string name="end_time"><![CDATA[<b>End:</b>]]></string>
<string name="quality"><![CDATA[<b>Quality:</b>]]></string>
<string name="hours_slept"><![CDATA[<b>Hours:Minutes:Seconds</b>]]></string>
<!-- Sleep quality strings -->
<string name="how_was_hour_sleep">How was your sleep?</string>
<string name="zero_very_bad">Very bad</string>
<string name="one_poor">Poor</string>
<string name="two_soso">So-so</string>
<string name="three_ok">OK</string>
<string name="four_pretty_good">Pretty good</string>
<string name="five_excellent">Excellent!</string>
<!-- Toast to play after Clear button has been pressed -->
<string name="cleared_message">All your data is gone forever.</string>
<string name="header_text">Sleep Results</string>
<string name="close">Close</string>
<string name="minutes_length">%d minutes on %s</string>
<string name="hours_length">%d hours on %s</string>
<string name="seconds_length">%d seconds on %s</string>
</resources>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2019, The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<!-- Basic styling for the buttons. -->
<style name="SleepButtons" parent="Widget.AppCompat.Button.Colored">
<item name="android:textColor">@color/white_text_color</item>
<item name="colorAccent">@color/green_color</item>
<item name="colorControlHighlight">@color/green_color</item>
<item name="android:textStyle">bold</item>
</style>
</resources>

View File

@ -0,0 +1,55 @@
/*
* Copyright 2018, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext {
kotlin_version = '1.3.10'
archLifecycleVersion = '1.1.1'
room_version = '2.0.0'
coroutine_version = '1.0.0'
gradleVersion = '3.4.0'
navigationVersion = '1.0.0-alpha07'
dataBindingCompilerVersion = gradleVersion // Always need to be the same.
}
repositories {
google()
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:$gradleVersion"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:$navigationVersion"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View File

@ -0,0 +1,15 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
android.useAndroidX=true
android.enableJetifier=true

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Tue May 14 17:22:57 PDT 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip

172
RecyclerViewClickHandler/gradlew vendored Executable file
View File

@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

84
RecyclerViewClickHandler/gradlew.bat vendored Executable file
View File

@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -0,0 +1 @@
include ':app'

View File

@ -0,0 +1,55 @@
TrackMySleepQuality with RecyclerView - Solution Code for 7.2
============================================================
Solution code for Android Kotlin Fundamentals Codelab 7.2 DiffUtil and data binding with RecyclerView
Introduction
------------
TrackMySleepQuality is an app for recording sleep data for each night.
You can record a start and stop time, assign a quality rating, and clear the database.
Learn techniques that make RecyclerView more efficient for large lists.
You will also learn techniques to make your code easier to maintain and extend for complex
lists and grids in your Android Kotlin apps.
Pre-requisites
--------------
You should be familiar with:
* Building a basic user interface (UI) using an activity, fragments, and views.
* Navigating between fragments, and using safeArgs to pass data between fragments.
* Using view models, view model factories, transformations, and LiveData and their observers.
* Creating a Room database, creating a DAO, and defining entities.
* Using coroutines for database tasks and other long-running tasks.
* How to implement a basic RecyclerView with an Adapter, ViewHolder, and item layout.
Getting Started
---------------
1. Download and run the app.
License
-------
Copyright 2019 Google, Inc.
Licensed to the Apache Software Foundation (ASF) under one or more contributor
license agreements. See the NOTICE file distributed with this work for
additional information regarding copyright ownership. The ASF licenses this
file to you under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy of
the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations under
the License.

View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,80 @@
/*
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: 'androidx.navigation.safeargs'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.example.android.trackmysleepqualityrecyclerview"
minSdkVersion 19
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
// Enables data binding.
dataBinding {
enabled = true
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
// Support libraries
implementation "androidx.appcompat:appcompat:1.0.0"
implementation "androidx.fragment:fragment:1.0.0"
implementation "androidx.constraintlayout:constraintlayout:2.0.0-alpha2"
// Android KTX
implementation 'androidx.core:core-ktx:1.0.1'
// Room and Lifecycle dependencies
implementation "androidx.room:room-runtime:$room_version"
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.lifecycle:lifecycle-extensions:2.0.0"
// Coroutines
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"
// Navigation
implementation "android.arch.navigation:navigation-fragment-ktx:$navigationVersion"
implementation "android.arch.navigation:navigation-ui-ktx:$navigationVersion"
// Testing
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.0-alpha4'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-alpha4'
}

View File

@ -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

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.trackmysleepquality">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher_sleep_tracker"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_sleep_tracker_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<meta-data
android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts" />
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -0,0 +1,30 @@
/*
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.trackmysleepquality
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}

View File

@ -0,0 +1,156 @@
/*
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.trackmysleepquality
import android.annotation.SuppressLint
import android.content.res.Resources
import android.os.Build
import android.text.Html
import android.text.Spanned
import android.view.View
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.android.trackmysleepquality.database.SleepNight
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.TimeUnit
/**
* These functions create a formatted string that can be set in a TextView.
*/
private val ONE_MINUTE_MILLIS = TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES)
private val ONE_HOUR_MILLIS = TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS)
/**
* Convert a duration to a formatted string for display.
*
* Examples:
*
* 6 seconds on Wednesday
* 2 minutes on Monday
* 40 hours on Thursday
*
* @param startTimeMilli the start of the interval
* @param endTimeMilli the end of the interval
* @param res resources used to load formatted strings
*/
fun convertDurationToFormatted(startTimeMilli: Long, endTimeMilli: Long, res: Resources): String {
val durationMilli = endTimeMilli - startTimeMilli
val weekdayString = SimpleDateFormat("EEEE", Locale.getDefault()).format(startTimeMilli)
return when {
durationMilli < ONE_MINUTE_MILLIS -> {
val seconds = TimeUnit.SECONDS.convert(durationMilli, TimeUnit.MILLISECONDS)
res.getString(R.string.seconds_length, seconds, weekdayString)
}
durationMilli < ONE_HOUR_MILLIS -> {
val minutes = TimeUnit.MINUTES.convert(durationMilli, TimeUnit.MILLISECONDS)
res.getString(R.string.minutes_length, minutes, weekdayString)
}
else -> {
val hours = TimeUnit.HOURS.convert(durationMilli, TimeUnit.MILLISECONDS)
res.getString(R.string.hours_length, hours, weekdayString)
}
}
}
/**
* Returns a string representing the numeric quality rating.
*/
fun convertNumericQualityToString(quality: Int, resources: Resources): String {
var qualityString = resources.getString(R.string.three_ok)
when (quality) {
-1 -> qualityString = "--"
0 -> qualityString = resources.getString(R.string.zero_very_bad)
1 -> qualityString = resources.getString(R.string.one_poor)
2 -> qualityString = resources.getString(R.string.two_soso)
4 -> qualityString = resources.getString(R.string.four_pretty_good)
5 -> qualityString = resources.getString(R.string.five_excellent)
}
return qualityString
}
/**
* Take the Long milliseconds returned by the system and stored in Room,
* and convert it to a nicely formatted string for display.
*
* EEEE - Display the long letter version of the weekday
* MMM - Display the letter abbreviation of the nmotny
* dd-yyyy - day in month and full year numerically
* HH:mm - Hours and minutes in 24hr format
*/
@SuppressLint("SimpleDateFormat")
fun convertLongToDateString(systemTime: Long): String {
return SimpleDateFormat("EEEE MMM-dd-yyyy' Time: 'HH:mm")
.format(systemTime).toString()
}
/**
* Takes a list of SleepNights and converts and formats it into one string for display.
*
* For display in a TextView, we have to supply one string, and styles are per TextView, not
* applicable per word. So, we build a formatted string using HTML. This is handy, but we will
* learn a better way of displaying this data in a future lesson.
*
* @param nights - List of all SleepNights in the database.
* @param resources - Resources object for all the resources defined for our app.
*
* @return Spanned - An interface for text that has formatting attached to it.
* See: https://developer.android.com/reference/android/text/Spanned
*/
fun formatNights(nights: List<SleepNight>, resources: Resources): Spanned {
val sb = StringBuilder()
sb.apply {
append(resources.getString(R.string.title))
nights.forEach {
append("<br>")
append(resources.getString(R.string.start_time))
append("\t${convertLongToDateString(it.startTimeMilli)}<br>")
if (it.endTimeMilli != it.startTimeMilli) {
append(resources.getString(R.string.end_time))
append("\t${convertLongToDateString(it.endTimeMilli)}<br>")
append(resources.getString(R.string.quality))
append("\t${convertNumericQualityToString(it.sleepQuality, resources)}<br>")
append(resources.getString(R.string.hours_slept))
// Hours
append("\t ${it.endTimeMilli.minus(it.startTimeMilli) / 1000 / 60 / 60}:")
// Minutes
append("${it.endTimeMilli.minus(it.startTimeMilli) / 1000 / 60}:")
// Seconds
append("${it.endTimeMilli.minus(it.startTimeMilli) / 1000}<br><br>")
}
}
}
// fromHtml is deprecated for target API without a flag, but since our minSDK is 19, we
// can't use the newer version, which requires minSDK of 24
//https://developer.android.com/reference/android/text/Html#fromHtml(java.lang.String,%20int)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return Html.fromHtml(sb.toString(), Html.FROM_HTML_MODE_LEGACY)
} else {
@Suppress("DEPRECATION")
return Html.fromHtml(sb.toString())
}
}
/**
* ViewHolder that holds a single [TextView].
*
* A ViewHolder holds a view for the [RecyclerView] as well as providing additional information
* to the RecyclerView such as where on the screen it was last drawn during scrolling.
*/
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)

View File

@ -0,0 +1,107 @@
/*
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.trackmysleepquality.database
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
/**
* A database that stores SleepNight information.
* And a global method to get access to the database.
*
* This pattern is pretty much the same for any database,
* so you can reuse it.
*/
@Database(entities = [SleepNight::class], version = 1, exportSchema = false)
abstract class SleepDatabase : RoomDatabase() {
/**
* Connects the database to the DAO.
*/
abstract val sleepDatabaseDao: SleepDatabaseDao
/**
* Define a companion object, this allows us to add functions on the SleepDatabase class.
*
* For example, clients can call `SleepDatabase.getInstance(context)` to instantiate
* a new SleepDatabase.
*/
companion object {
/**
* INSTANCE will keep a reference to any database returned via getInstance.
*
* This will help us avoid repeatedly initializing the database, which is expensive.
*
* The value of a volatile variable will never be cached, and all writes and
* reads will be done to and from the main memory. It means that changes made by one
* thread to shared data are visible to other threads.
*/
@Volatile
private var INSTANCE: SleepDatabase? = null
/**
* Helper function to get the database.
*
* If a database has already been retrieved, the previous database will be returned.
* Otherwise, create a new database.
*
* This function is threadsafe, and callers should cache the result for multiple database
* calls to avoid overhead.
*
* This is an example of a simple Singleton pattern that takes another Singleton as an
* argument in Kotlin.
*
* To learn more about Singleton read the wikipedia article:
* https://en.wikipedia.org/wiki/Singleton_pattern
*
* @param context The application context Singleton, used to get access to the filesystem.
*/
fun getInstance(context: Context): SleepDatabase {
// Multiple threads can ask for the database at the same time, ensure we only initialize
// it once by using synchronized. Only one thread may enter a synchronized block at a
// time.
synchronized(this) {
// Copy the current value of INSTANCE to a local variable so Kotlin can smart cast.
// Smart cast is only available to local variables.
var instance = INSTANCE
// If instance is `null` make a new database instance.
if (instance == null) {
instance = Room.databaseBuilder(
context.applicationContext,
SleepDatabase::class.java,
"sleep_history_database"
)
// Wipes and rebuilds instead of migrating if no Migration object.
// Migration is not part of this lesson. You can learn more about
// migration with Room in this blog post:
// https://medium.com/androiddevelopers/understanding-migrations-with-room-f01e04b07929
.fallbackToDestructiveMigration()
.build()
// Assign INSTANCE to the newly created database.
INSTANCE = instance
}
// Return instance; smart cast to be non-null.
return instance
}
}
}
}

View File

@ -0,0 +1,74 @@
/*
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.trackmysleepquality.database
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
/**
* Defines methods for using the SleepNight class with Room.
*/
@Dao
interface SleepDatabaseDao {
@Insert
fun insert(night: SleepNight)
/**
* When updating a row with a value already set in a column,
* replaces the old value with the new one.
*
* @param night new value to write
*/
@Update
fun update(night: SleepNight)
/**
* Selects and returns the row that matches the supplied start time, which is our key.
*
* @param key startTimeMilli to match
*/
@Query("SELECT * from daily_sleep_quality_table WHERE nightId = :key")
fun get(key: Long): SleepNight
/**
* Deletes all values from the table.
*
* This does not delete the table, only its contents.
*/
@Query("DELETE FROM daily_sleep_quality_table")
fun clear()
/**
* Selects and returns all rows in the table,
*
* sorted by start time in descending order.
*/
@Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC")
fun getAllNights(): LiveData<List<SleepNight>>
/**
* Selects and returns the latest night.
*/
@Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC LIMIT 1")
fun getTonight(): SleepNight?
}

View File

@ -0,0 +1,38 @@
/*
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.trackmysleepquality.database
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
/**
* Represents one night's sleep through start, end times, and the sleep quality.
*/
@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)

View File

@ -0,0 +1,85 @@
/*
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.trackmysleepquality.sleepquality
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.navigation.fragment.findNavController
import com.example.android.trackmysleepquality.R
import com.example.android.trackmysleepquality.database.SleepDatabase
import com.example.android.trackmysleepquality.databinding.FragmentSleepQualityBinding
/**
* Fragment that displays a list of clickable icons,
* each representing a sleep quality rating.
* Once the user taps an icon, the quality is set in the current sleepNight
* and the database is updated.
*/
class SleepQualityFragment : Fragment() {
/**
* Called when the Fragment is ready to display content to the screen.
*
* This function uses DataBindingUtil to inflate R.layout.fragment_sleep_quality.
*
* It is also responsible for passing the [SleepQualityViewModel] to the
* [FragmentSleepQualityBinding] generated by DataBinding. This will allow DataBinding
* to use the [LiveData] on our ViewModel.
*/
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Get a reference to the binding object and inflate the fragment views.
val binding: FragmentSleepQualityBinding = DataBindingUtil.inflate(
inflater, R.layout.fragment_sleep_quality, container, false)
val application = requireNotNull(this.activity).application
val arguments = SleepQualityFragmentArgs.fromBundle(arguments)
// Create an instance of the ViewModel Factory.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
val viewModelFactory = SleepQualityViewModelFactory(arguments.sleepNightKey, dataSource)
// Get a reference to the ViewModel associated with this fragment.
val sleepQualityViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(SleepQualityViewModel::class.java)
// To use the View Model with data binding, you have to explicitly
// give the binding object a reference to it.
binding.sleepQualityViewModel = sleepQualityViewModel
// Add an Observer to the state variable for Navigating when a Quality icon is tapped.
sleepQualityViewModel.navigateToSleepTracker.observe(this, Observer {
if (it == true) { // Observed state is true.
this.findNavController().navigate(
SleepQualityFragmentDirections.actionSleepQualityFragmentToSleepTrackerFragment())
// Reset state to make sure we only navigate once, even if the device
// has a configuration change.
sleepQualityViewModel.doneNavigating()
}
})
return binding.root
}
}

View File

@ -0,0 +1,113 @@
/*
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.trackmysleepquality.sleepquality
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.example.android.trackmysleepquality.database.SleepDatabaseDao
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/**
* ViewModel for SleepQualityFragment.
*
* @param sleepNightKey The key of the current night we are working on.
*/
class SleepQualityViewModel(
private val sleepNightKey: Long = 0L,
dataSource: SleepDatabaseDao) : ViewModel() {
/**
* Hold a reference to SleepDatabase via its SleepDatabaseDao.
*/
val database = dataSource
/** Coroutine setup variables */
/**
* viewModelJob allows us to cancel all coroutines started by this ViewModel.
*/
private val viewModelJob = Job()
/**
* A [CoroutineScope] keeps track of all coroutines started by this ViewModel.
*
* Because we pass it [viewModelJob], any coroutine started in this scope can be cancelled
* by calling `viewModelJob.cancel()`
*
* By default, all coroutines started in uiScope will launch in [Dispatchers.Main] which is
* the main thread on Android. This is a sensible default because most coroutines started by
* a [ViewModel] update the UI after performing some processing.
*/
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
/**
* Variable that tells the fragment whether it should navigate to [SleepTrackerFragment].
*
* This is `private` because we don't want to expose the ability to set [MutableLiveData] to
* the [Fragment]
*/
private val _navigateToSleepTracker = MutableLiveData<Boolean?>()
/**
* When true immediately navigate back to the [SleepTrackerFragment]
*/
val navigateToSleepTracker: LiveData<Boolean?>
get() = _navigateToSleepTracker
/**
* Cancels all coroutines when the ViewModel is cleared, to cleanup any pending work.
*
* onCleared() gets called when the ViewModel is destroyed.
*/
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
/**
* Call this immediately after navigating to [SleepTrackerFragment]
*/
fun doneNavigating() {
_navigateToSleepTracker.value = null
}
/**
* Sets the sleep quality and updates the database.
*
* Then navigates back to the SleepTrackerFragment.
*/
fun onSetSleepQuality(quality: Int) {
uiScope.launch {
// IO is a thread pool for running operations that access the disk, such as
// our Room database.
withContext(Dispatchers.IO) {
val tonight = database.get(sleepNightKey)
tonight.sleepQuality = quality
database.update(tonight)
}
// Setting this state variable to true will alert the observer and trigger navigation.
_navigateToSleepTracker.value = true
}
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.trackmysleepquality.sleepquality
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.example.android.trackmysleepquality.database.SleepDatabaseDao
/**
* This is pretty much boiler plate code for a ViewModel Factory.
*
* Provides the key for the night and the SleepDatabaseDao to the ViewModel.
*/
class SleepQualityViewModelFactory(
private val sleepNightKey: Long,
private val dataSource: SleepDatabaseDao) : ViewModelProvider.Factory {
@Suppress("unchecked_cast")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(SleepQualityViewModel::class.java)) {
return SleepQualityViewModel(sleepNightKey, dataSource) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.trackmysleepquality.sleeptracker
import android.widget.ImageView
import android.widget.TextView
import androidx.databinding.BindingAdapter
import com.example.android.trackmysleepquality.R
import com.example.android.trackmysleepquality.convertDurationToFormatted
import com.example.android.trackmysleepquality.convertNumericQualityToString
import com.example.android.trackmysleepquality.database.SleepNight
@BindingAdapter("sleepDurationFormatted")
fun TextView.setSleepDurationFormatted(item: SleepNight) {
text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, context.resources)
}
@BindingAdapter("sleepQualityString")
fun TextView.setSleepQualityString(item: SleepNight) {
text = convertNumericQualityToString(item.sleepQuality, context.resources)
}
@BindingAdapter("sleepImage")
fun ImageView.setSleepImage(item: SleepNight) {
setImageResource(when (item.sleepQuality) {
0 -> R.drawable.ic_sleep_0
1 -> R.drawable.ic_sleep_1
2 -> R.drawable.ic_sleep_2
3 -> R.drawable.ic_sleep_3
4 -> R.drawable.ic_sleep_4
5 -> R.drawable.ic_sleep_5
else -> R.drawable.ic_sleep_active
})
}

View File

@ -0,0 +1,71 @@
/*
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.trackmysleepquality.sleeptracker
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.android.trackmysleepquality.R
import com.example.android.trackmysleepquality.convertDurationToFormatted
import com.example.android.trackmysleepquality.convertNumericQualityToString
import com.example.android.trackmysleepquality.database.SleepNight
import com.example.android.trackmysleepquality.databinding.ListItemSleepNightBinding
class SleepNightAdapter : ListAdapter<SleepNight, SleepNightAdapter.ViewHolder>(SleepNightDiffCallback()) {
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)
holder.bind(item)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder.from(parent)
}
class ViewHolder private constructor(val binding: ListItemSleepNightBinding) : RecyclerView.ViewHolder(binding.root){
fun bind(item: SleepNight) {
binding.sleep = item
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ListItemSleepNightBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding)
}
}
}
}
class SleepNightDiffCallback : DiffUtil.ItemCallback<SleepNight>() {
override fun areItemsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
return oldItem.nightId == newItem.nightId
}
override fun areContentsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
return oldItem == newItem
}
}

View File

@ -0,0 +1,120 @@
/*
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.trackmysleepquality.sleeptracker
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.navigation.fragment.findNavController
import com.example.android.trackmysleepquality.R
import com.example.android.trackmysleepquality.database.SleepDatabase
import com.example.android.trackmysleepquality.databinding.FragmentSleepTrackerBinding
import com.google.android.material.snackbar.Snackbar
/**
* A fragment with buttons to record start and end times for sleep, which are saved in
* a database. Cumulative data is displayed in a simple scrollable TextView.
* (Because we have not learned about RecyclerView yet.)
* The Clear button will clear all data from the database.
*/
class SleepTrackerFragment : Fragment() {
/**
* Called when the Fragment is ready to display content to the screen.
*
* This function uses DataBindingUtil to inflate R.layout.fragment_sleep_quality.
*
* It is also responsible for passing the [SleepTrackerViewModel] to the
* [FragmentSleepTrackerBinding] generated by DataBinding. This will allow DataBinding
* to use the [LiveData] on our ViewModel.
*/
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)
val application = requireNotNull(this.activity).application
// Create an instance of the ViewModel Factory.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
// Get a reference to the ViewModel associated with this fragment.
val sleepTrackerViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(SleepTrackerViewModel::class.java)
// To use the View Model with data binding, you have to explicitly
// give the binding object a reference to it.
binding.sleepTrackerViewModel = sleepTrackerViewModel
val adapter = SleepNightAdapter()
binding.sleepList.adapter = adapter
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.submitList(it)
}
})
// Specify the current activity as the lifecycle owner of the binding.
// This is necessary so that the binding can observe LiveData updates.
binding.setLifecycleOwner(this)
// Add an Observer on the state variable for showing a Snackbar message
// when the CLEAR button is pressed.
sleepTrackerViewModel.showSnackBarEvent.observe(this, Observer {
if (it == true) { // Observed state is true.
Snackbar.make(
activity!!.findViewById(android.R.id.content),
getString(R.string.cleared_message),
Snackbar.LENGTH_SHORT // How long to display the message.
).show()
// Reset state to make sure the toast is only shown once, even if the device
// has a configuration change.
sleepTrackerViewModel.doneShowingSnackbar()
}
})
// Add an Observer on the state variable for Navigating when STOP button is pressed.
sleepTrackerViewModel.navigateToSleepQuality.observe(this, Observer { night ->
night?.let {
// We need to get the navController from this, because button is not ready, and it
// just has to be a view. For some reason, this only matters if we hit stop again
// after using the back button, not if we hit stop and choose a quality.
// Also, in the Navigation Editor, for Quality -> Tracker, check "Inclusive" for
// popping the stack to get the correct behavior if we press stop multiple times
// followed by back.
// Also: https://stackoverflow.com/questions/28929637/difference-and-uses-of-oncreate-oncreateview-and-onactivitycreated-in-fra
this.findNavController().navigate(
SleepTrackerFragmentDirections
.actionSleepTrackerFragmentToSleepQualityFragment(night.nightId))
// Reset state to make sure we only navigate once, even if the device
// has a configuration change.
sleepTrackerViewModel.doneNavigating()
}
})
return binding.root
}
}

View File

@ -0,0 +1,249 @@
/*
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.trackmysleepquality.sleeptracker
import android.app.Application
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel
import com.example.android.trackmysleepquality.database.SleepDatabaseDao
import com.example.android.trackmysleepquality.database.SleepNight
import com.example.android.trackmysleepquality.formatNights
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/**
* ViewModel for SleepTrackerFragment.
*/
class SleepTrackerViewModel(
dataSource: SleepDatabaseDao,
application: Application) : ViewModel() {
/**
* Hold a reference to SleepDatabase via SleepDatabaseDao.
*/
val database = dataSource
/** Coroutine variables */
/**
* viewModelJob allows us to cancel all coroutines started by this ViewModel.
*/
private var viewModelJob = Job()
/**
* A [CoroutineScope] keeps track of all coroutines started by this ViewModel.
*
* Because we pass it [viewModelJob], any coroutine started in this uiScope can be cancelled
* by calling `viewModelJob.cancel()`
*
* By default, all coroutines started in uiScope will launch in [Dispatchers.Main] which is
* the main thread on Android. This is a sensible default because most coroutines started by
* a [ViewModel] update the UI after performing some processing.
*/
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
private var tonight = MutableLiveData<SleepNight?>()
val nights = database.getAllNights()
/**
* Converted nights to Spanned for displaying.
*/
val nightsString = Transformations.map(nights) { nights ->
formatNights(nights, application.resources)
}
/**
* If tonight has not been set, then the START button should be visible.
*/
val startButtonVisible = Transformations.map(tonight) {
null == it
}
/**
* If tonight has been set, then the STOP button should be visible.
*/
val stopButtonVisible = Transformations.map(tonight) {
null != it
}
/**
* If there are any nights in the database, show the CLEAR button.
*/
val clearButtonVisible = Transformations.map(nights) {
it?.isNotEmpty()
}
/**
* Request a toast by setting this value to true.
*
* This is private because we don't want to expose setting this value to the Fragment.
*/
private var _showSnackbarEvent = MutableLiveData<Boolean?>()
/**
* If this is true, immediately `show()` a toast and call `doneShowingSnackbar()`.
*/
val showSnackBarEvent: LiveData<Boolean?>
get() = _showSnackbarEvent
/**
* Variable that tells the Fragment to navigate to a specific [SleepQualityFragment]
*
* This is private because we don't want to expose setting this value to the Fragment.
*/
private val _navigateToSleepQuality = MutableLiveData<SleepNight>()
/**
* If this is non-null, immediately navigate to [SleepQualityFragment] and call [doneNavigating]
*/
val navigateToSleepQuality: LiveData<SleepNight>
get() = _navigateToSleepQuality
/**
* Call this immediately after calling `show()` on a toast.
*
* It will clear the toast request, so if the user rotates their phone it won't show a duplicate
* toast.
*/
fun doneShowingSnackbar() {
_showSnackbarEvent.value = null
}
/**
* Call this immediately after navigating to [SleepQualityFragment]
*
* It will clear the navigation request, so if the user rotates their phone it won't navigate
* twice.
*/
fun doneNavigating() {
_navigateToSleepQuality.value = null
}
init {
initializeTonight()
}
private fun initializeTonight() {
uiScope.launch {
tonight.value = getTonightFromDatabase()
}
}
/**
* Handling the case of the stopped app or forgotten recording,
* the start and end times will be the same.j
*
* If the start time and end time are not the same, then we do not have an unfinished
* recording.
*/
private suspend fun getTonightFromDatabase(): SleepNight? {
return withContext(Dispatchers.IO) {
var night = database.getTonight()
if (night?.endTimeMilli != night?.startTimeMilli) {
night = null
}
night
}
}
private suspend fun insert(night: SleepNight) {
withContext(Dispatchers.IO) {
database.insert(night)
}
}
private suspend fun update(night: SleepNight) {
withContext(Dispatchers.IO) {
database.update(night)
}
}
private suspend fun clear() {
withContext(Dispatchers.IO) {
database.clear()
}
}
/**
* Executes when the START button is clicked.
*/
fun onStart() {
uiScope.launch {
// Create a new night, which captures the current time,
// and insert it into the database.
val newNight = SleepNight()
insert(newNight)
tonight.value = getTonightFromDatabase()
}
}
/**
* Executes when the STOP button is clicked.
*/
fun onStop() {
uiScope.launch {
// In Kotlin, the return@label syntax is used for specifying which function among
// several nested ones this statement returns from.
// In this case, we are specifying to return from launch().
val oldNight = tonight.value ?: return@launch
// Update the night in the database to add the end time.
oldNight.endTimeMilli = System.currentTimeMillis()
update(oldNight)
// Set state to navigate to the SleepQualityFragment.
_navigateToSleepQuality.value = oldNight
}
}
/**
* Executes when the CLEAR button is clicked.
*/
fun onClear() {
uiScope.launch {
// Clear the database table.
clear()
// And clear tonight since it's no longer in the database
tonight.value = null
// Show a snackbar message, because it's friendly.
_showSnackbarEvent.value = true
}
}
/**
* Called when the ViewModel is dismantled.
* At this point, we want to cancel all coroutines;
* otherwise we end up with processes that have nowhere to return to
* using memory and resources.
*/
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.trackmysleepquality.sleeptracker
import android.app.Application
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.example.android.trackmysleepquality.database.SleepDatabaseDao
/**
* This is pretty much boiler plate code for a ViewModel Factory.
*
* Provides the SleepDatabaseDao and context to the ViewModel.
*/
class SleepTrackerViewModelFactory(
private val dataSource: SleepDatabaseDao,
private val application: Application) : ViewModelProvider.Factory {
@Suppress("unchecked_cast")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(SleepTrackerViewModel::class.java)) {
return SleepTrackerViewModel(dataSource, application) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2019, The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="700"
android:fromXDelta="100%"
android:fromYDelta="0%"
android:toXDelta="0%"
android:toYDelta="0%" />
</set>

View File

@ -0,0 +1,55 @@
<!--
~ Copyright 2019, The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:pathData="M0,369.07h512v142.93h-512z"
android:fillColor="#e0e0e0"/>
<path
android:pathData="M228.55,271.42l15.7,-30.1L225,244.55l-1,-5.78 26.34,-4.41 1,6.22 -15.6,30.07L256,267.26l1,5.79 -27.42,4.59Z"
android:fillColor="#757575"/>
<path
android:pathData="M271.75,215.33l41.52,-29.2 -27.86,-9 2.68,-8.35 38,12.23 -2.89,9 -41.37,29.25 29.28,9.41L308.44,237l-39.58,-12.73Z"
android:fillColor="#bdbdbd"/>
<path
android:pathData="M350,187.18l39.94,-79.32L339.54,117l-2.75,-15.11 68.74,-12.52 3,16.23 -39.67,79.27 52.94,-9.64 2.75,15.1 -71.57,13Z"
android:fillColor="#e0e0e0"/>
<group>
<clip-path android:pathData="M584.94,-624.15h512v512h-512z M 0,0"/>
<path
android:pathData="M448.55,-187.1,709.34,73.69a8.72,8.72,0,0,0,12.32,0l325.45,-325.45a8.72,8.72,0,0,0,0,-12.32L778,-533.19a18.84,18.84,0,0,0,-26.65,0L442,-241.74l-2.36,5.16A44.05,44.05,0,0,0,448.55,-187.1Z"
android:fillColor="#43a047"/>
<path
android:pathData="M440.25,-208.98l275.52002,275.53l335.0,-335.0l-278.15002,-278.14996l-334.26,328.40997l1.8900146,9.210007z"
android:fillColor="#c0ca33"/>
<path
android:pathData="M544.72,-487.03h393.36v473.76h-393.36z"
android:fillColor="#e6ee9c"/>
<path
android:pathData="M548.74,-506.95H930.26a8.41,8.41,0,0,1,8.41,8.41V-37.68a8.41,8.41,0,0,1,-8.41,8.41H548.74a18.19,18.19,0,0,1,-18.19,-18.19V-488.75A18.19,18.19,0,0,1,548.74,-506.95Z"
android:fillColor="#6ab343"/>
</group>
<group>
<clip-path android:pathData="M0,-624.15h512v512h-512z M 0,0"/>
<path
android:pathData="M246.59,-272.23l39.17,-11.46a3.9,3.9,0,0,0,2.67,-4.84,3.9,3.9,0,0,0,-4.84,-2.63L244,-279.58a134.83,134.83,0,0,0,-30.74,-47.09,134.89,134.89,0,0,0,-47,-30.69l11.49,-39.52a3.86,3.86,0,0,0,-2.63,-4.83,3.85,3.85,0,0,0,-4.83,2.64l-11.42,39.16c-41.3,-13.2,-86.47,-4.85,-117.17,25.89l178.9,178.87c30.72,-30.71,39,-75.79,25.93,-117.08m-211,-54.15L-93.27,-197.48a29.94,29.94,0,0,0,0,42.38l14.43,14.44,-44,44a28.1,28.1,0,0,0,0,39.7,28.12,28.12,0,0,0,39.74,0l44,-44,26.81,26.8,-44,44a28.09,28.09,0,0,0,0,39.7,28.11,28.11,0,0,0,39.74,0l44,-44L41.91,-20A30,30,0,0,0,84.3,-20L213.16,-148.87Zm180.59,90.91L203.59,-248a1.7,1.7,0,0,1,0,-2.4l1.59,-1.6a1.71,1.71,0,0,1,2.41,0l12.58,12.59a1.7,1.7,0,0,1,0,2.4l-1.59,1.59A1.7,1.7,0,0,1,216.17,-235.47Zm-81.43,-81.42,-12.58,-12.59a1.7,1.7,0,0,1,0,-2.4l1.59,-1.59a1.7,1.7,0,0,1,2.41,0l12.58,12.58a1.68,1.68,0,0,1,0,2.4l-1.59,1.6A1.71,1.71,0,0,1,134.74,-316.89Zm-99.16,-9.49L-93.27,-197.48a29.94,29.94,0,0,0,0,42.38l14.43,14.44,-44,44a28.1,28.1,0,0,0,0,39.7,28.12,28.12,0,0,0,39.74,0l44,-44,26.81,26.8,-44,44a28.09,28.09,0,0,0,0,39.7,28.11,28.11,0,0,0,39.74,0l44,-44L41.91,-20A30,30,0,0,0,84.3,-20L213.16,-148.87Z"
android:fillColor="#6ab343"/>
</group>
</vector>

View File

@ -0,0 +1,77 @@
<!--
~ Copyright 2019, The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:pathData="M35.58,297.76l6.18,-7.64l98.76,-8.5l100.14,101.33l-20,86.04l-7.5,6.29l-126.86,-7.63l-50.72,-169.89z"
android:fillColor="#fff"/>
<path
android:pathData="M389,302.51q-28.14,0 -47.68,19.54T321.8,369.73q0,28.15 19.55,48.08T389,437.74q28.14,0 48.08,-19.93T457,369.73q0,-28.14 -19.93,-47.68T389,302.51Z"
android:fillColor="#6ab343"/>
<path
android:pathData="M389.17,326.32a43.23,43.23 0,0 0,-43.55 43.55A42.84,42.84 0,0 0,358.28 401a41.6,41.6 0,0 0,30.89 12.92,44.09 44.09,0 0,0 44.05,-44.06A41.61,41.61 0,0 0,420.31 339,42.91 42.91,0 0,0 389.17,326.32Z"
android:fillColor="#f5f5f5"/>
<path
android:pathData="M389.4,363.79L389.4,340.35"
android:strokeLineJoin="round"
android:strokeWidth="10"
android:fillColor="#00000000"
android:strokeColor="#6ab343"
android:strokeLineCap="round"/>
<path
android:pathData="M394.5,373.69L412.12,384.46"
android:strokeLineJoin="round"
android:strokeWidth="10"
android:fillColor="#00000000"
android:strokeColor="#6ab343"
android:strokeLineCap="round"/>
<path
android:pathData="M389.39,363.79a6.25,6.25 0,0 0,-6.3 6.3,6.2 6.2,0 0,0 1.83,4.5 6,6 0,0 0,4.47 1.87,6.37 6.37,0 0,0 6.36,-6.37 6,6 0,0 0,-1.86 -4.47A6.21,6.21 0,0 0,389.39 363.79Z"
android:strokeWidth="7"
android:fillColor="#f5f5f5"
android:strokeColor="#6ab343"/>
<group>
<clip-path android:pathData="M584.94,0h512v512h-512z M 0,0"/>
<path
android:pathData="M448.55,437.05,709.34,697.84a8.72,8.72,0,0,0,12.32,0l325.45,-325.45a8.72,8.72,0,0,0,0,-12.32L778,91a18.84,18.84,0,0,0,-26.65,0L442,382.4l-2.36,5.16A44.07,44.07,0,0,0,448.55,437.05Z"
android:fillColor="#43a047"/>
<path
android:pathData="M440.25,415.17l275.52002,275.52l335.0,-334.99l-278.15002,-278.15002l-334.26,328.40002l1.8900146,9.220001z"
android:fillColor="#c0ca33"/>
<path
android:pathData="M544.72,137.12h393.36v473.76h-393.36z"
android:fillColor="#e6ee9c"/>
<path
android:pathData="M548.74,117.2H930.26a8.41,8.41,0,0,1,8.41,8.41V586.47a8.41,8.41,0,0,1,-8.41,8.41H548.74a18.19,18.19,0,0,1,-18.19,-18.19V135.4a18.19,18.19,0,0,1,18.19,-18.19Z"
android:fillColor="#6ab343"/>
<path
android:pathData="M601.64,-8.95h13.65v477.68h-13.65z"
android:fillColor="#43a047"/>
<path
android:pathData="M612.29,-1.71h6.82v477.68h-6.82z"
android:fillColor="#66bb6a"/>
</group>
<group>
<clip-path android:pathData="M0,0h512v512h-512z M 0,0"/>
<path
android:pathData="M246.59,351.91l39.17,-11.45a3.89,3.89,0,1,0,-2.17,-7.47L244,344.56a136.82,136.82,0,0,0,-77.69,-77.77l11.49,-39.53a3.89,3.89,0,1,0,-7.46,-2.19l-11.42,39.16c-41.3,-13.19,-86.47,-4.84,-117.17,25.89L220.66,469c30.72,-30.71,39,-75.78,25.93,-117.08m-211,-54.15L-93.27,426.67a29.94,29.94,0,0,0,0,42.38l14.43,14.43,-44,44a28.11,28.11,0,0,0,0,39.71,28.12,28.12,0,0,0,39.74,0l44,-44L-12.31,550l-44,44a28.1,28.1,0,0,0,0,39.71,28.1,28.1,0,0,0,39.74,0l44,-44,14.49,14.47a30,30,0,0,0,42.39,0l128.86,-128.9Zm180.59,90.92L203.59,376.1a1.71,1.71,0,0,1,0,-2.41l1.59,-1.59a1.71,1.71,0,0,1,2.41,0l12.58,12.58a1.71,1.71,0,0,1,0,2.41l-1.59,1.59A1.7,1.7,0,0,1,216.17,388.68Zm-81.43,-81.43,-12.58,-12.58a1.7,1.7,0,0,1,0,-2.4l1.59,-1.6a1.71,1.71,0,0,1,2.41,0l12.58,12.59a1.68,1.68,0,0,1,0,2.4l-1.59,1.59A1.7,1.7,0,0,1,134.74,307.25Zm-99.16,-9.49L-93.27,426.67a29.94,29.94,0,0,0,0,42.38l14.43,14.43,-44,44a28.11,28.11,0,0,0,0,39.71,28.12,28.12,0,0,0,39.74,0l44,-44L-12.31,550l-44,44a28.1,28.1,0,0,0,0,39.71,28.1,28.1,0,0,0,39.74,0l44,-44,14.49,14.47a30,30,0,0,0,42.39,0l128.86,-128.9Z"
android:fillColor="#6ab343"/>
</group>
</vector>

Some files were not shown because too many files have changed in this diff Show More