RecyclerView apps added.
Change-Id: If081193ea949419364bdb2dad5b3375493c1592a
55
RecyclerViewClickHandler/README.md
Executable 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
@ -0,0 +1 @@
|
||||
/build
|
||||
80
RecyclerViewClickHandler/app/build.gradle
Executable 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'
|
||||
}
|
||||
|
||||
21
RecyclerViewClickHandler/app/proguard-rules.pro
vendored
Executable 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
|
||||
24
RecyclerViewClickHandler/app/src/main/AndroidManifest.xml
Executable 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>
|
||||
BIN
RecyclerViewClickHandler/app/src/main/ic_launcher_sleep_tracker-web.png
Executable file
|
After Width: | Height: | Size: 29 KiB |
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
}
|
||||
@ -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)
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@ -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")
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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")
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
@ -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")
|
||||
}
|
||||
}
|
||||
25
RecyclerViewClickHandler/app/src/main/res/anim/slide_in_right.xml
Executable 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>
|
||||
@ -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>
|
||||
@ -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>
|
||||
28
RecyclerViewClickHandler/app/src/main/res/drawable/ic_sleep_0.xml
Executable 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>
|
||||
30
RecyclerViewClickHandler/app/src/main/res/drawable/ic_sleep_1.xml
Executable 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>
|
||||
30
RecyclerViewClickHandler/app/src/main/res/drawable/ic_sleep_2.xml
Executable 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>
|
||||
30
RecyclerViewClickHandler/app/src/main/res/drawable/ic_sleep_3.xml
Executable 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>
|
||||
30
RecyclerViewClickHandler/app/src/main/res/drawable/ic_sleep_4.xml
Executable 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>
|
||||
28
RecyclerViewClickHandler/app/src/main/res/drawable/ic_sleep_5.xml
Executable 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>
|
||||
77
RecyclerViewClickHandler/app/src/main/res/drawable/ic_sleep_active.xml
Executable 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>
|
||||
7
RecyclerViewClickHandler/app/src/main/res/font/roboto.xml
Executable 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>
|
||||
32
RecyclerViewClickHandler/app/src/main/res/layout/activity_main.xml
Executable 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>
|
||||
@ -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>
|
||||
130
RecyclerViewClickHandler/app/src/main/res/layout/fragment_sleep_quality.xml
Executable 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>
|
||||
98
RecyclerViewClickHandler/app/src/main/res/layout/fragment_sleep_tracker.xml
Executable 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>
|
||||
65
RecyclerViewClickHandler/app/src/main/res/layout/list_item_sleep_night.xml
Executable 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>
|
||||
@ -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>
|
||||
@ -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" />
|
||||
21
RecyclerViewClickHandler/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Executable 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>
|
||||
@ -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>
|
||||
@ -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>
|
||||
@ -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>
|
||||
BIN
RecyclerViewClickHandler/app/src/main/res/mipmap-hdpi/ic_launcher.png
Executable file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
RecyclerViewClickHandler/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Executable file
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
BIN
RecyclerViewClickHandler/app/src/main/res/mipmap-mdpi/ic_launcher.png
Executable file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
RecyclerViewClickHandler/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Executable file
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
BIN
RecyclerViewClickHandler/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Executable file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
RecyclerViewClickHandler/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Executable file
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
BIN
RecyclerViewClickHandler/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Executable file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
RecyclerViewClickHandler/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Executable file
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
BIN
RecyclerViewClickHandler/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Executable file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
RecyclerViewClickHandler/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Executable file
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 13 KiB |
75
RecyclerViewClickHandler/app/src/main/res/navigation/navigation.xml
Executable 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>
|
||||
26
RecyclerViewClickHandler/app/src/main/res/values/colors.xml
Executable 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>
|
||||
22
RecyclerViewClickHandler/app/src/main/res/values/dimens.xml
Executable 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>
|
||||
32
RecyclerViewClickHandler/app/src/main/res/values/font_certs.xml
Executable 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>
|
||||
21
RecyclerViewClickHandler/app/src/main/res/values/preloaded_fonts.xml
Executable 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>
|
||||
61
RecyclerViewClickHandler/app/src/main/res/values/strings.xml
Executable 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>
|
||||
34
RecyclerViewClickHandler/app/src/main/res/values/styles.xml
Executable 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>
|
||||
55
RecyclerViewClickHandler/build.gradle
Executable 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
|
||||
}
|
||||
15
RecyclerViewClickHandler/gradle.properties
Executable 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
|
||||
BIN
RecyclerViewClickHandler/gradle/wrapper/gradle-wrapper.jar
vendored
Executable file
6
RecyclerViewClickHandler/gradle/wrapper/gradle-wrapper.properties
vendored
Executable 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
@ -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
@ -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
|
||||
1
RecyclerViewClickHandler/settings.gradle
Executable file
@ -0,0 +1 @@
|
||||
include ':app'
|
||||
55
RecyclerViewDiffUtilDataBinding/README.md
Executable 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.
|
||||
|
||||
1
RecyclerViewDiffUtilDataBinding/app/.gitignore
vendored
Executable file
@ -0,0 +1 @@
|
||||
/build
|
||||
80
RecyclerViewDiffUtilDataBinding/app/build.gradle
Executable 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'
|
||||
}
|
||||
|
||||
21
RecyclerViewDiffUtilDataBinding/app/proguard-rules.pro
vendored
Executable 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
|
||||
24
RecyclerViewDiffUtilDataBinding/app/src/main/AndroidManifest.xml
Executable 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>
|
||||
BIN
RecyclerViewDiffUtilDataBinding/app/src/main/ic_launcher_sleep_tracker-web.png
Executable file
|
After Width: | Height: | Size: 29 KiB |
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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?
|
||||
}
|
||||
@ -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)
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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")
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
})
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
@ -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")
|
||||
}
|
||||
}
|
||||
25
RecyclerViewDiffUtilDataBinding/app/src/main/res/anim/slide_in_right.xml
Executable 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>
|
||||
@ -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>
|
||||
@ -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>
|
||||