├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── drawable-mdpi │ │ │ │ ├── monster01.webp │ │ │ │ ├── monster02.webp │ │ │ │ ├── monster03.webp │ │ │ │ ├── monster04.webp │ │ │ │ ├── monster05.webp │ │ │ │ ├── monster06.webp │ │ │ │ ├── monster07.webp │ │ │ │ ├── monster08.webp │ │ │ │ ├── monster09.webp │ │ │ │ ├── monster10.webp │ │ │ │ ├── monster11.webp │ │ │ │ ├── monster12.webp │ │ │ │ ├── monster01_tn.webp │ │ │ │ ├── monster02_tn.webp │ │ │ │ ├── monster03_tn.webp │ │ │ │ ├── monster04_tn.webp │ │ │ │ ├── monster05_tn.webp │ │ │ │ ├── monster06_tn.webp │ │ │ │ ├── monster07_tn.webp │ │ │ │ ├── monster08_tn.webp │ │ │ │ ├── monster09_tn.webp │ │ │ │ ├── monster10_tn.webp │ │ │ │ ├── monster11_tn.webp │ │ │ │ └── monster12_tn.webp │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── values │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── arrays.xml │ │ │ │ ├── styles.xml │ │ │ │ └── strings.xml │ │ │ ├── drawable │ │ │ │ ├── background.xml │ │ │ │ ├── background_selected.xml │ │ │ │ ├── grid_item_background.xml │ │ │ │ ├── ic_view_list.xml │ │ │ │ ├── ic_view_grid.xml │ │ │ │ └── ic_launcher_background.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── layout │ │ │ │ ├── settings_activity.xml │ │ │ │ ├── fragment_splash.xml │ │ │ │ ├── main_activity.xml │ │ │ │ ├── main_fragment.xml │ │ │ │ ├── monster_grid_item.xml │ │ │ │ ├── monster_list_item.xml │ │ │ │ └── fragment_detail.xml │ │ │ ├── anim │ │ │ │ ├── explode_in.xml │ │ │ │ └── explode_out.xml │ │ │ ├── menu │ │ │ │ └── options_main.xml │ │ │ ├── navigation │ │ │ │ └── nav_graph.xml │ │ │ ├── xml │ │ │ │ └── root_preferences.xml │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ └── raw │ │ │ │ └── monster_data.json │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── androiddata │ │ │ │ ├── Global.kt │ │ │ │ ├── data │ │ │ │ ├── MonsterService.kt │ │ │ │ ├── MonsterDao.kt │ │ │ │ ├── Monster.kt │ │ │ │ ├── MonsterDatabase.kt │ │ │ │ └── MonsterRepository.kt │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── utilities │ │ │ │ ├── BindingAdapters.kt │ │ │ │ ├── PrefsHelper.kt │ │ │ │ └── FileHelper.kt │ │ │ │ ├── SettingsActivity.kt │ │ │ │ └── ui │ │ │ │ ├── shared │ │ │ │ └── SharedViewModel.kt │ │ │ │ ├── detail │ │ │ │ └── DetailFragment.kt │ │ │ │ ├── splash │ │ │ │ └── SplashFragment.kt │ │ │ │ └── main │ │ │ │ ├── MainRecyclerAdapter.kt │ │ │ │ └── MainFragment.kt │ │ ├── AndroidManifest.xml │ │ └── assets │ │ │ └── monster_data.json │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ └── androiddata │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── example │ │ └── androiddata │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── README.md ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .idea ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml ├── vcs.xml ├── misc.xml ├── runConfigurations.xml └── gradle.xml ├── .gitignore ├── gradle.properties ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | rootProject.name='AndroidDev2019Data' 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AndroidDev2019Data 2 | Exercise files for Android Development Essential Training: Manage Data (2019) 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster01.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/drawable-mdpi/monster01.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster02.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/drawable-mdpi/monster02.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster03.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/drawable-mdpi/monster03.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster04.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/drawable-mdpi/monster04.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster05.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/drawable-mdpi/monster05.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster06.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/drawable-mdpi/monster06.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster07.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/drawable-mdpi/monster07.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster08.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/drawable-mdpi/monster08.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster09.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/drawable-mdpi/monster09.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster10.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/drawable-mdpi/monster10.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster11.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/drawable-mdpi/monster11.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster12.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/drawable-mdpi/monster12.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster01_tn.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/drawable-mdpi/monster01_tn.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster02_tn.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/drawable-mdpi/monster02_tn.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster03_tn.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/drawable-mdpi/monster03_tn.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster04_tn.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/drawable-mdpi/monster04_tn.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster05_tn.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/drawable-mdpi/monster05_tn.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster06_tn.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/drawable-mdpi/monster06_tn.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster07_tn.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/drawable-mdpi/monster07_tn.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster08_tn.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/drawable-mdpi/monster08_tn.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster09_tn.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/drawable-mdpi/monster09_tn.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster10_tn.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/drawable-mdpi/monster10_tn.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster11_tn.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/drawable-mdpi/monster11_tn.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster12_tn.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/drawable-mdpi/monster12_tn.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFF02 4 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/androiddata/Global.kt: -------------------------------------------------------------------------------- 1 | package com.example.androiddata 2 | 3 | const val LOG_TAG = "monsterLogging" 4 | 5 | const val WEB_SERVICE_URL = "https://774906.youcanlearnit.net" 6 | const val IMAGE_BASE_URL = "$WEB_SERVICE_URL/images" -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 21 12:24:03 PDT 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/androiddata/data/MonsterService.kt: -------------------------------------------------------------------------------- 1 | package com.example.androiddata.data 2 | 3 | import retrofit2.Response 4 | import retrofit2.http.GET 5 | 6 | interface MonsterService { 7 | @GET("/feed/monster_data.json") 8 | suspend fun getMonsterData(): Response> 9 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_selected.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/grid_item_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Reply 5 | Reply to all 6 | 7 | 8 | 9 | reply 10 | reply_all 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/settings_activity.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/anim/explode_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/anim/explode_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_view_list.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/androiddata/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.androiddata 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import com.example.androiddata.ui.main.MainFragment 6 | 7 | class MainActivity : AppCompatActivity() { 8 | 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | setContentView(R.layout.main_activity) 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /app/src/test/java/com/example/androiddata/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.androiddata 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | /** 7 | * Example local unit test, which will execute on the development machine (host). 8 | * 9 | * See [testing documentation](http://d.android.com/tools/testing). 10 | */ 11 | class ExampleUnitTest { 12 | @Test 13 | fun addition_isCorrect() { 14 | assertEquals(4, 2 + 2) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/androiddata/data/MonsterDao.kt: -------------------------------------------------------------------------------- 1 | package com.example.androiddata.data 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.Query 6 | 7 | @Dao 8 | interface MonsterDao { 9 | 10 | @Query("SELECT * from monsters") 11 | fun getAll(): List 12 | 13 | @Insert 14 | suspend fun insertMonster(monster: Monster) 15 | 16 | @Insert 17 | suspend fun insertMonsters(monsters: List) 18 | 19 | @Query("DELETE from monsters") 20 | suspend fun deleteAll() 21 | 22 | } -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_view_grid.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/androiddata/data/Monster.kt: -------------------------------------------------------------------------------- 1 | package com.example.androiddata.data 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | import com.example.androiddata.IMAGE_BASE_URL 6 | 7 | @Entity(tableName = "monsters") 8 | data class Monster ( 9 | @PrimaryKey(autoGenerate = true) 10 | val monsterId: Int, 11 | val monsterName: String, 12 | val imageFile: String, 13 | val caption: String, 14 | val description: String, 15 | val price: Double, 16 | val scariness: Int 17 | ) { 18 | val imageUrl 19 | get() = "$IMAGE_BASE_URL/$imageFile.webp" 20 | val thumbnailUrl 21 | get() = "$IMAGE_BASE_URL/${imageFile}_tn.webp" 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/androiddata/utilities/BindingAdapters.kt: -------------------------------------------------------------------------------- 1 | package com.example.androiddata.utilities 2 | 3 | import android.widget.ImageView 4 | import android.widget.TextView 5 | import androidx.databinding.BindingAdapter 6 | import com.bumptech.glide.Glide 7 | import java.text.NumberFormat 8 | 9 | @BindingAdapter("imageUrl") 10 | fun loadImage(view: ImageView, imageUrl: String) { 11 | Glide.with(view.context) 12 | .load(imageUrl) 13 | .into(view) 14 | } 15 | 16 | @BindingAdapter("price") 17 | fun itemPrice(view: TextView, value: Double) { 18 | val formatter = NumberFormat.getCurrencyInstance() 19 | val text = "${formatter.format(value)} / each" 20 | view.text = text 21 | } -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/androiddata/utilities/PrefsHelper.kt: -------------------------------------------------------------------------------- 1 | package com.example.androiddata.utilities 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | 6 | const val ITEM_TYPE_KEY = "item_type_key" 7 | 8 | class PrefsHelper { 9 | 10 | companion object { 11 | private fun preferences(context: Context): SharedPreferences = 12 | context.getSharedPreferences("default", 0) 13 | 14 | fun setItemType(context: Context, type: String) { 15 | preferences(context).edit() 16 | .putString(ITEM_TYPE_KEY, type) 17 | .apply() 18 | } 19 | 20 | fun getItemType(context: Context): String = 21 | preferences(context).getString(ITEM_TYPE_KEY, "list")!! 22 | 23 | 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/res/menu/options_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | 16 | 17 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/main_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/androiddata/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.androiddata 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("com.example.androiddata", appContext.packageName) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/androiddata/SettingsActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.androiddata 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.preference.PreferenceFragmentCompat 6 | 7 | class SettingsActivity : AppCompatActivity() { 8 | 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | setContentView(R.layout.settings_activity) 12 | supportFragmentManager 13 | .beginTransaction() 14 | .replace(R.id.settings, SettingsFragment()) 15 | .commit() 16 | supportActionBar?.setDisplayHomeAsUpEnabled(true) 17 | } 18 | 19 | class SettingsFragment : PreferenceFragmentCompat() { 20 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { 21 | setPreferencesFromResource(R.xml.root_preferences, rootKey) 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/androiddata/data/MonsterDatabase.kt: -------------------------------------------------------------------------------- 1 | package com.example.androiddata.data 2 | 3 | import android.content.Context 4 | import androidx.room.Database 5 | import androidx.room.Room 6 | import androidx.room.RoomDatabase 7 | 8 | @Database(entities = [Monster::class], version = 1, exportSchema = false) 9 | abstract class MonsterDatabase: RoomDatabase() { 10 | 11 | abstract fun monsterDao(): MonsterDao 12 | 13 | companion object { 14 | @Volatile 15 | private var INSTANCE: MonsterDatabase? = null 16 | 17 | fun getDatabase(context: Context): MonsterDatabase { 18 | if (INSTANCE == null) { 19 | synchronized(this) { 20 | INSTANCE = Room.databaseBuilder( 21 | context.applicationContext, 22 | MonsterDatabase::class.java, 23 | "monsters.db" 24 | ).build() 25 | } 26 | } 27 | return INSTANCE!! 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/androiddata/ui/shared/SharedViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.example.androiddata.ui.shared 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.AndroidViewModel 5 | import androidx.lifecycle.MutableLiveData 6 | import androidx.preference.PreferenceManager 7 | import com.example.androiddata.data.Monster 8 | import com.example.androiddata.data.MonsterRepository 9 | 10 | class SharedViewModel(val app: Application) : AndroidViewModel(app) { 11 | private val dataRepo = MonsterRepository(app) 12 | val monsterData = dataRepo.monsterData 13 | 14 | val selectedMonster = MutableLiveData() 15 | val activityTitle = MutableLiveData() 16 | 17 | init { 18 | updateActivityTitle() 19 | } 20 | 21 | fun refreshData() { 22 | dataRepo.refreshDataFromWeb() 23 | } 24 | 25 | fun updateActivityTitle() { 26 | val signature = 27 | PreferenceManager.getDefaultSharedPreferences(app) 28 | .getString("signature", "Monster fan") 29 | activityTitle.value = "Stickers for $signature" 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Monster Stickers 3 | Stickers 4 | 5 | 6 | Hello blank fragment 7 | Scariness Rating 8 | List 9 | Grid 10 | Settings 11 | Settings 12 | 13 | 14 | Messages 15 | Sync 16 | 17 | 18 | Your signature 19 | Default reply action 20 | 21 | 22 | Sync email periodically 23 | Download incoming attachments 24 | Automatically download attachments for incoming emails 25 | Only download attachments when manually requested 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/androiddata/utilities/FileHelper.kt: -------------------------------------------------------------------------------- 1 | package com.example.androiddata.utilities 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import java.io.File 6 | 7 | class FileHelper { 8 | companion object { 9 | fun getTextFromResources(context: Context, resourceId: Int): String { 10 | return context.resources.openRawResource(resourceId).use { 11 | it.bufferedReader().use { 12 | it.readText() 13 | } 14 | } 15 | } 16 | 17 | fun getTextFromAssets(context: Context, fileName: String): String { 18 | return context.assets.open(fileName).use { 19 | it.bufferedReader().use { 20 | it.readText() 21 | } 22 | } 23 | } 24 | 25 | fun saveTextToFile(app: Application, json: String?) { 26 | val file = File(app.getExternalFilesDir("monsters"), "monsters.json") 27 | file.writeText(json ?: "", Charsets.UTF_8) 28 | } 29 | 30 | fun readTextFile(app: Application): String? { 31 | val file = File(app.getExternalFilesDir("monsters"), "monsters.json") 32 | return if (file.exists()) { 33 | file.readText() 34 | } else null 35 | } 36 | 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/main_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 19 | 20 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/navigation/nav_graph.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 16 | 17 | 18 | 23 | 28 | 31 | 32 | 36 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 19 | 23 | 26 | 27 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/androiddata/ui/detail/DetailFragment.kt: -------------------------------------------------------------------------------- 1 | package com.example.androiddata.ui.detail 2 | 3 | 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.MenuItem 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import androidx.appcompat.app.AppCompatActivity 10 | import androidx.fragment.app.Fragment 11 | import androidx.lifecycle.ViewModelProviders 12 | import androidx.navigation.NavController 13 | import androidx.navigation.Navigation 14 | import com.example.androiddata.R 15 | import com.example.androiddata.databinding.FragmentDetailBinding 16 | import com.example.androiddata.ui.shared.SharedViewModel 17 | 18 | /** 19 | * A simple [Fragment] subclass. 20 | */ 21 | class DetailFragment : Fragment() { 22 | 23 | private lateinit var viewModel: SharedViewModel 24 | private lateinit var navController: NavController 25 | 26 | override fun onCreateView( 27 | inflater: LayoutInflater, container: ViewGroup?, 28 | savedInstanceState: Bundle? 29 | ): View? { 30 | // Inflate the layout for this fragment 31 | (requireActivity() as AppCompatActivity).run { 32 | supportActionBar?.setDisplayHomeAsUpEnabled(true) 33 | } 34 | setHasOptionsMenu(true) 35 | navController = Navigation.findNavController( 36 | requireActivity(), R.id.nav_host 37 | ) 38 | 39 | viewModel = ViewModelProviders.of(requireActivity()).get(SharedViewModel::class.java) 40 | val binding = FragmentDetailBinding.inflate( 41 | inflater, container, false 42 | ) 43 | binding.lifecycleOwner = this 44 | binding.viewModel = viewModel 45 | 46 | return binding.root 47 | 48 | } 49 | 50 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 51 | if (item.itemId == android.R.id.home) { 52 | navController.navigateUp() 53 | } 54 | return super.onOptionsItemSelected(item) 55 | } 56 | 57 | 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/res/xml/root_preferences.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 19 | 20 | 22 | 23 | 27 | 28 | 35 | 36 | 37 | 38 | 40 | 41 | 44 | 45 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/monster_grid_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 24 | 25 | 39 | 40 | 50 | 51 | -------------------------------------------------------------------------------- /app/src/main/res/layout/monster_list_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 26 | 27 | 40 | 41 | 50 | 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/androiddata/ui/splash/SplashFragment.kt: -------------------------------------------------------------------------------- 1 | package com.example.androiddata.ui.splash 2 | 3 | 4 | import android.Manifest 5 | import android.content.pm.PackageManager 6 | import android.os.Bundle 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import android.widget.Toast 11 | import androidx.core.content.ContextCompat 12 | import androidx.fragment.app.Fragment 13 | import androidx.navigation.NavOptions 14 | import androidx.navigation.Navigation 15 | import com.example.androiddata.R 16 | 17 | const val PERMISSION_REQUEST_CODE = 1001 18 | 19 | class SplashFragment : Fragment() { 20 | 21 | override fun onCreateView( 22 | inflater: LayoutInflater, container: ViewGroup?, 23 | savedInstanceState: Bundle? 24 | ): View? { 25 | 26 | if (ContextCompat.checkSelfPermission( 27 | requireContext(), 28 | Manifest.permission.WRITE_EXTERNAL_STORAGE 29 | ) 30 | == PackageManager.PERMISSION_GRANTED 31 | ) { 32 | displayMainFragment() 33 | } else { 34 | requestPermissions( 35 | arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 36 | PERMISSION_REQUEST_CODE 37 | ) 38 | } 39 | 40 | // Inflate the layout for this fragment 41 | return inflater.inflate(R.layout.fragment_splash, container, false) 42 | } 43 | 44 | override fun onRequestPermissionsResult( 45 | requestCode: Int, 46 | permissions: Array, grantResults: IntArray 47 | ) { 48 | if (requestCode == PERMISSION_REQUEST_CODE) { 49 | if (grantResults.isNotEmpty() && grantResults[0] 50 | == PackageManager.PERMISSION_GRANTED 51 | ) { 52 | displayMainFragment() 53 | } else { 54 | Toast.makeText(requireContext(), "Permission denied", Toast.LENGTH_LONG) 55 | .show() 56 | } 57 | } 58 | 59 | } 60 | 61 | private fun displayMainFragment() { 62 | val navController = Navigation.findNavController( 63 | requireActivity(), R.id.nav_host 64 | ) 65 | navController.navigate( 66 | R.id.action_nav_main, null, 67 | NavOptions.Builder() 68 | .setPopUpTo(R.id.splashFragment, true) 69 | .build() 70 | ) 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/androiddata/ui/main/MainRecyclerAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.example.androiddata.ui.main 2 | 3 | import android.content.Context 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.ImageView 8 | import android.widget.RatingBar 9 | import android.widget.TextView 10 | import androidx.recyclerview.widget.RecyclerView 11 | import com.bumptech.glide.Glide 12 | import com.example.androiddata.R 13 | import com.example.androiddata.data.Monster 14 | import com.example.androiddata.utilities.PrefsHelper 15 | 16 | class MainRecyclerAdapter(val context: Context, 17 | val monsters: List, 18 | val itemListener: MonsterItemListener): 19 | RecyclerView.Adapter() 20 | 21 | { 22 | override fun getItemCount() = monsters.size 23 | 24 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 25 | val inflater = LayoutInflater.from(parent.context) 26 | val layoutStyle = PrefsHelper.getItemType(parent.context) 27 | val layoutId = if (layoutStyle == "grid") { 28 | R.layout.monster_grid_item 29 | } else { 30 | R.layout.monster_list_item 31 | } 32 | val view = inflater.inflate(layoutId, parent, false) 33 | return ViewHolder(view) 34 | } 35 | 36 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 37 | val monster = monsters[position] 38 | with(holder) { 39 | nameText?.let { 40 | it.text = monster.monsterName 41 | it.contentDescription = monster.monsterName 42 | } 43 | ratingBar?.rating = monster.scariness.toFloat() 44 | Glide.with(context) 45 | .load(monster.thumbnailUrl) 46 | .into(monsterImage) 47 | holder.itemView.setOnClickListener{ 48 | itemListener.onMonsterItemClick(monster) 49 | } 50 | } 51 | } 52 | 53 | inner class ViewHolder(itemView: View) : 54 | RecyclerView.ViewHolder(itemView) { 55 | val nameText = itemView.findViewById(R.id.nameText) 56 | val monsterImage = itemView.findViewById(R.id.monsterImage) 57 | val ratingBar = itemView.findViewById(R.id.ratingBar) 58 | } 59 | 60 | interface MonsterItemListener { 61 | fun onMonsterItemClick(monster: Monster) 62 | } 63 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | apply plugin: 'kotlin-kapt' 8 | 9 | android { 10 | compileSdkVersion 29 11 | buildToolsVersion "29.0.0" 12 | defaultConfig { 13 | applicationId "com.example.androiddata" 14 | minSdkVersion 18 15 | targetSdkVersion 29 16 | versionCode 1 17 | versionName "1.0" 18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 19 | } 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | 27 | dataBinding { 28 | enabled = true 29 | } 30 | } 31 | 32 | dependencies { 33 | implementation fileTree(dir: 'libs', include: ['*.jar']) 34 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 35 | implementation 'androidx.appcompat:appcompat:1.0.2' 36 | implementation 'androidx.core:core-ktx:1.0.2' 37 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 38 | implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0' 39 | implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.0.0' 40 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 41 | implementation 'androidx.preference:preference:1.1.0-rc01' 42 | testImplementation 'junit:junit:4.12' 43 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 44 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 45 | implementation 'androidx.navigation:navigation-fragment-ktx:2.0.0' 46 | implementation 'androidx.navigation:navigation-ui-ktx:2.0.0' 47 | 48 | implementation "com.squareup.moshi:moshi-kotlin:1.8.0" 49 | 50 | def retrofit2_version = "2.6.0" 51 | implementation "com.squareup.retrofit2:retrofit:$retrofit2_version" 52 | implementation "com.squareup.retrofit2:converter-moshi:$retrofit2_version" 53 | 54 | def coroutines_version = "1.2.1" 55 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" 56 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" 57 | 58 | def glide_version = "4.9.0" 59 | implementation "com.github.bumptech.glide:glide:$glide_version" 60 | annotationProcessor "com.github.bumptech.glide:compiler:$glide_version" 61 | 62 | def room_version = "2.1.0" 63 | implementation "androidx.room:room-runtime:$room_version" 64 | implementation "androidx.room:room-ktx:$room_version" 65 | kapt "androidx.room:room-compiler:$room_version" 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/androiddata/data/MonsterRepository.kt: -------------------------------------------------------------------------------- 1 | package com.example.androiddata.data 2 | 3 | import android.Manifest 4 | import android.app.Application 5 | import android.content.Context 6 | import android.content.pm.PackageManager 7 | import android.net.ConnectivityManager 8 | import android.util.Log 9 | import android.widget.Toast 10 | import androidx.annotation.WorkerThread 11 | import androidx.core.content.ContextCompat 12 | import androidx.lifecycle.MutableLiveData 13 | import com.example.androiddata.LOG_TAG 14 | import com.example.androiddata.WEB_SERVICE_URL 15 | import com.example.androiddata.utilities.FileHelper 16 | import com.squareup.moshi.JsonAdapter 17 | import com.squareup.moshi.Moshi 18 | import com.squareup.moshi.Types 19 | import kotlinx.coroutines.CoroutineScope 20 | import kotlinx.coroutines.Dispatchers 21 | import kotlinx.coroutines.launch 22 | import kotlinx.coroutines.withContext 23 | import retrofit2.Retrofit 24 | import retrofit2.converter.moshi.MoshiConverterFactory 25 | 26 | class MonsterRepository(val app: Application) { 27 | 28 | val monsterData = MutableLiveData>() 29 | private val monsterDao = MonsterDatabase.getDatabase(app) 30 | .monsterDao() 31 | 32 | init { 33 | CoroutineScope(Dispatchers.IO).launch { 34 | val data = monsterDao.getAll() 35 | if (data.isEmpty()) { 36 | callWebService() 37 | } else { 38 | monsterData.postValue(data) 39 | withContext(Dispatchers.Main) { 40 | Toast.makeText(app, "Using local data", Toast.LENGTH_LONG).show() 41 | } 42 | } 43 | } 44 | } 45 | 46 | @WorkerThread 47 | suspend fun callWebService() { 48 | if (networkAvailable()) { 49 | withContext(Dispatchers.Main) { 50 | Toast.makeText(app, "Using remote data", Toast.LENGTH_LONG).show() 51 | } 52 | Log.i(LOG_TAG, "Calling web service") 53 | val retrofit = Retrofit.Builder() 54 | .baseUrl(WEB_SERVICE_URL) 55 | .addConverterFactory(MoshiConverterFactory.create()) 56 | .build() 57 | val service = retrofit.create(MonsterService::class.java) 58 | val serviceData = service.getMonsterData().body() ?: emptyList() 59 | monsterData.postValue(serviceData) 60 | monsterDao.deleteAll() 61 | monsterDao.insertMonsters(serviceData) 62 | } 63 | } 64 | 65 | @Suppress("DEPRECATION") 66 | private fun networkAvailable(): Boolean { 67 | val connectivityManager = app.getSystemService(Context.CONNECTIVITY_SERVICE) 68 | as ConnectivityManager 69 | val networkInfo = connectivityManager.activeNetworkInfo 70 | return networkInfo?.isConnectedOrConnecting ?: false 71 | } 72 | 73 | fun refreshDataFromWeb() { 74 | CoroutineScope(Dispatchers.IO).launch { 75 | callWebService() 76 | } 77 | } 78 | 79 | private fun saveDataToCache(monsterData: List) { 80 | if (ContextCompat.checkSelfPermission( 81 | app, 82 | Manifest.permission.WRITE_EXTERNAL_STORAGE 83 | ) 84 | == PackageManager.PERMISSION_GRANTED 85 | ) { 86 | val moshi = Moshi.Builder().build() 87 | val listType = Types.newParameterizedType(List::class.java, Monster::class.java) 88 | val adapter: JsonAdapter> = moshi.adapter(listType) 89 | val json = adapter.toJson(monsterData) 90 | FileHelper.saveTextToFile(app, json) 91 | } 92 | } 93 | 94 | private fun readDataFromCache(): List { 95 | val json = FileHelper.readTextFile(app) 96 | if (json == null) { 97 | return emptyList() 98 | } 99 | val moshi = Moshi.Builder().build() 100 | val listType = Types.newParameterizedType(List::class.java, Monster::class.java) 101 | val adapter: JsonAdapter> = moshi.adapter(listType) 102 | return adapter.fromJson(json) ?: emptyList() 103 | } 104 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/androiddata/ui/main/MainFragment.kt: -------------------------------------------------------------------------------- 1 | package com.example.androiddata.ui.main 2 | 3 | import android.os.Bundle 4 | import android.util.Log 5 | import android.view.* 6 | import androidx.appcompat.app.AppCompatActivity 7 | import androidx.fragment.app.Fragment 8 | import androidx.lifecycle.Observer 9 | import androidx.lifecycle.ViewModelProviders 10 | import androidx.navigation.NavController 11 | import androidx.navigation.Navigation 12 | import androidx.recyclerview.widget.GridLayoutManager 13 | import androidx.recyclerview.widget.LinearLayoutManager 14 | import androidx.recyclerview.widget.RecyclerView 15 | import androidx.swiperefreshlayout.widget.SwipeRefreshLayout 16 | import com.example.androiddata.LOG_TAG 17 | import com.example.androiddata.R 18 | import com.example.androiddata.data.Monster 19 | import com.example.androiddata.ui.shared.SharedViewModel 20 | import com.example.androiddata.utilities.PrefsHelper 21 | 22 | class MainFragment : Fragment(), 23 | MainRecyclerAdapter.MonsterItemListener { 24 | 25 | private lateinit var viewModel: SharedViewModel 26 | private lateinit var recyclerView: RecyclerView 27 | private lateinit var swipeLayout: SwipeRefreshLayout 28 | private lateinit var navController: NavController 29 | private lateinit var adapter: MainRecyclerAdapter 30 | 31 | override fun onCreateView( 32 | inflater: LayoutInflater, container: ViewGroup?, 33 | savedInstanceState: Bundle? 34 | ): View { 35 | 36 | (requireActivity() as AppCompatActivity).run { 37 | supportActionBar?.setDisplayHomeAsUpEnabled(false) 38 | } 39 | setHasOptionsMenu(true) 40 | 41 | val view = inflater.inflate(R.layout.main_fragment, container, false) 42 | recyclerView = view.findViewById(R.id.recyclerView) 43 | val layoutStyle = PrefsHelper.getItemType(requireContext()) 44 | recyclerView.layoutManager = 45 | if (layoutStyle == "grid") { 46 | GridLayoutManager(requireContext(), 2) 47 | } else { 48 | LinearLayoutManager(requireContext()) 49 | } 50 | 51 | navController = Navigation.findNavController( 52 | requireActivity(), R.id.nav_host 53 | ) 54 | 55 | swipeLayout = view.findViewById(R.id.swipeLayout) 56 | swipeLayout.setOnRefreshListener { 57 | viewModel.refreshData() 58 | } 59 | 60 | viewModel = ViewModelProviders.of(requireActivity()).get(SharedViewModel::class.java) 61 | viewModel.monsterData.observe(this, Observer 62 | { 63 | adapter = MainRecyclerAdapter(requireContext(), it, this) 64 | recyclerView.adapter = adapter 65 | swipeLayout.isRefreshing = false 66 | }) 67 | viewModel.activityTitle.observe(this, Observer { 68 | requireActivity().title = it 69 | }) 70 | 71 | return view 72 | } 73 | 74 | override fun onMonsterItemClick(monster: Monster) { 75 | Log.i(LOG_TAG, "Selected monster: ${monster.monsterName}") 76 | viewModel.selectedMonster.value = monster 77 | navController.navigate(R.id.action_nav_detail) 78 | } 79 | 80 | override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { 81 | inflater.inflate(R.menu.options_main, menu) 82 | super.onCreateOptionsMenu(menu, inflater) 83 | } 84 | 85 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 86 | when (item.itemId) { 87 | R.id.action_view_grid -> { 88 | PrefsHelper.setItemType(requireContext(), "grid") 89 | recyclerView.layoutManager = 90 | GridLayoutManager(requireContext(), 2) 91 | recyclerView.adapter = adapter 92 | } 93 | R.id.action_view_list -> { 94 | PrefsHelper.setItemType(requireContext(), "list") 95 | recyclerView.layoutManager = 96 | LinearLayoutManager(requireContext()) 97 | recyclerView.adapter = adapter 98 | } 99 | R.id.action_settings -> { 100 | navController.navigate(R.id.settingsActivity) 101 | } 102 | } 103 | return true 104 | } 105 | 106 | override fun onResume() { 107 | super.onResume() 108 | viewModel.updateActivityTitle() 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_detail.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 19 | 20 | 31 | 32 | 41 | 42 |