├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── gradle.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── androiddata │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ └── monster_data.json │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── androiddata │ │ │ ├── Global.kt │ │ │ ├── MainActivity.kt │ │ │ ├── SettingsActivity.kt │ │ │ ├── data │ │ │ ├── Monster.kt │ │ │ ├── MonsterDao.kt │ │ │ ├── MonsterDatabase.kt │ │ │ ├── MonsterRepository.kt │ │ │ └── MonsterService.kt │ │ │ ├── ui │ │ │ ├── detail │ │ │ │ └── DetailFragment.kt │ │ │ ├── main │ │ │ │ ├── MainFragment.kt │ │ │ │ └── MainRecyclerAdapter.kt │ │ │ ├── shared │ │ │ │ └── SharedViewModel.kt │ │ │ └── splash │ │ │ │ └── SplashFragment.kt │ │ │ └── utilities │ │ │ ├── BindingAdapters.kt │ │ │ ├── FileHelper.kt │ │ │ └── PrefsHelper.kt │ └── res │ │ ├── anim │ │ ├── explode_in.xml │ │ └── explode_out.xml │ │ ├── drawable-mdpi │ │ ├── monster01.webp │ │ ├── monster01_tn.webp │ │ ├── monster02.webp │ │ ├── monster02_tn.webp │ │ ├── monster03.webp │ │ ├── monster03_tn.webp │ │ ├── monster04.webp │ │ ├── monster04_tn.webp │ │ ├── monster05.webp │ │ ├── monster05_tn.webp │ │ ├── monster06.webp │ │ ├── monster06_tn.webp │ │ ├── monster07.webp │ │ ├── monster07_tn.webp │ │ ├── monster08.webp │ │ ├── monster08_tn.webp │ │ ├── monster09.webp │ │ ├── monster09_tn.webp │ │ ├── monster10.webp │ │ ├── monster10_tn.webp │ │ ├── monster11.webp │ │ ├── monster11_tn.webp │ │ ├── monster12.webp │ │ └── monster12_tn.webp │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── background.xml │ │ ├── background_selected.xml │ │ ├── grid_item_background.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_view_grid.xml │ │ └── ic_view_list.xml │ │ ├── layout │ │ ├── fragment_detail.xml │ │ ├── fragment_splash.xml │ │ ├── main_activity.xml │ │ ├── main_fragment.xml │ │ ├── monster_grid_item.xml │ │ ├── monster_list_item.xml │ │ └── settings_activity.xml │ │ ├── menu │ │ └── options_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── navigation │ │ └── nav_graph.xml │ │ ├── raw │ │ └── monster_data.json │ │ ├── values │ │ ├── arrays.xml │ │ ├── colors.xml │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ └── root_preferences.xml │ └── test │ └── java │ └── com │ └── example │ └── androiddata │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.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 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AndroidDev2019Data 2 | Exercise files for Android Development Essential Training: Manage Data (2019) 3 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /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/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/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/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/assets/monster_data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "imageFile": "monster01", 4 | "monsterName": "Mingle", 5 | "caption": "Double Trouble", 6 | "description": "Mingle excels at doing twice the work in half the time, with pinpoint accuracy. These skills serve her well in her role as Senior Data Analyst for an international cloud computing company. She's also got a penchant for ballroom dance, line dancing, and pretty much any kind of activity that lets her groove to music.", 7 | "price": 0.29, 8 | "scariness": 2 9 | }, 10 | { 11 | "imageFile": "monster02", 12 | "monsterName": "Yodel", 13 | "caption": "Yodelay Hee Hoo!", 14 | "description": "Yodel grew up in a family of singers and loud talkers and could never get a word in edgewise. Then his vast talent for yodeling emerged. Now he performs to adoring fans throughout the world, and always has the loudest voice at the dinner table. Incidentally, he's also a loud proponent of net neutrality, and spends countless hours moderating an internet forum devoted solely to that subject.", 15 | "price": 0.19, 16 | "scariness": 4 17 | }, 18 | { 19 | "imageFile": "monster03", 20 | "monsterName": "Squido", 21 | "caption": "An Eye for Design", 22 | "description": "Squido's got his hands or rather tentacles, in everything. First and foremost, he's a web designer with an eye for visual aesthetics, but he's also rather keen on UX design and making sure what he creates translates optimally to the end user. In his off-hours he's an avid nature photographer and bowler.", 23 | "price": 0.29, 24 | "scariness": 3 25 | }, 26 | { 27 | "imageFile": "monster04", 28 | "monsterName": "Spook", 29 | "caption": "Safe and Sound", 30 | "description": "Cracking code and battling hackers is Spook's forte. She holds a prominent position as Head of Cyber Security for the Department of Monster Defense, where she thwarts attacks on government computer systems as often as she blinks. When not at work, Spook delights in serving up a fright at haunted mansions and ghost walks.", 31 | "price": 0.29, 32 | "scariness": 5 33 | }, 34 | { 35 | "imageFile": "monster05", 36 | "monsterName": "Melville", 37 | "caption": "Networking Guru", 38 | "description": "Setting up computer networks has always come easily to Melville. In his role as Senior Network Engineer for Landon Hotel, Melville builds complex blueprints for communication networks, a task that requires enormous attention to detail and patience. When not at work, Melville chooses less taxing mental activities, like hiking in the hills near his Silicon Valley home.", 39 | "price": 0.19, 40 | "scariness": 2 41 | }, 42 | { 43 | "imageFile": "monster06", 44 | "monsterName": "Filo", 45 | "caption": "Baker by Day, Techie by Night", 46 | "description": "Filo was named after the wonderful, buttery pastry crust that monsters adore. She’s a prominent baker and pastry chef in monster circles and is always exploring and sharing new dessert trends. In her off time, however, she's quite the techie and dabbles in web and mobile app development. She even built a custom cake ordering app for her pastry business.", 47 | "price": 0.29, 48 | "scariness": 3 49 | }, 50 | { 51 | "imageFile": "monster07", 52 | "monsterName": "Blade", 53 | "caption": "Monster APPetite", 54 | "description": "Blade freelances as a mobile app developer and has built some of the most popular Android and iOS apps used in modern monster society, including the award-winning Monster APPetite, which tracks calorie consumption and activity for the health-conscious monster. In his spare time, he competes in national agility contests with his border collie Winston.", 55 | "price": 0.29, 56 | "scariness": 5 57 | }, 58 | { 59 | "imageFile": "monster08", 60 | "monsterName": "Timber", 61 | "caption": "Database Expert", 62 | "description": "A natural-born problem-solver, Timber's especially excited to solve complex business problems using databases. As a database administrator for Globe Bank International, he's able to flex his mental muscles using his certifications in Oracle, Microsoft SQL Server, and MySQL. When not behind the computer, Timber can often be found biking, surfing, or lounging around with a good detective novel.", 63 | "price": 0.19, 64 | "scariness": 2 65 | }, 66 | { 67 | "imageFile": "monster09", 68 | "monsterName": "Skedaddle", 69 | "caption": "Game of Life", 70 | "description": "When Skedaddle was a teenager, his parents couldn't keep him away from his Game Boy. In fact, they seriously worried that he might not find a suitable career. Now as a prominent game developer for Red30 Tech, he's found his true calling…and put his family's worries to rest. You probably could have guessed this, but in his spare time Skedaddle loves to pay computer games.", 71 | "price": 0.29, 72 | "scariness": 4 73 | }, 74 | { 75 | "imageFile": "monster10", 76 | "monsterName": "Smiley", 77 | "caption": "Don’t Worry, Be Happy!", 78 | "description": "With the bad rap they get from movies, monsters have it pretty tough. Perhaps no monster has done more to squash stereotypes than Smiley, who can take anyone’s frown and turn it upside down. That's why Smiley has a reputation as the best computer support specialist on her IT team, three years running.", 79 | "price": 0.29, 80 | "scariness": 1 81 | }, 82 | { 83 | "imageFile": "monster11", 84 | "monsterName": "Frex", 85 | "caption": "Born Leader", 86 | "description": "Frex has always had a knack for leadership, starting from his days of being a Monster Scout. After studying computer science in college, and working as an IT specialist at several large companies, he naturally followed the management path. Now, as an IT manager for a Fortune 500 company, he gets to put his technical know-how to work, while also leading a team of talented engineers. Frex's hobbies include golf, billiards, and community service.", 87 | "price": 0.19, 88 | "scariness": 3 89 | }, 90 | { 91 | "imageFile": "monster12", 92 | "monsterName": "Drift", 93 | "caption": "In the Clouds", 94 | "description": "After years of everyone saying her head was in the clouds, Drift found her calling as a software engineer developing a well-known cloud solution for the computing giant, Red30 Tech. After work, she prefers to unwind by catching wind in her sail and paragliding high in the sky.", 95 | "price": 0.29, 96 | "scariness": 4 97 | } 98 | ] -------------------------------------------------------------------------------- /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/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/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/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/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 | } -------------------------------------------------------------------------------- /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/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/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/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/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/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 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | } -------------------------------------------------------------------------------- /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/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/anim/explode_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/anim/explode_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster01.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/f7b8f056d3264059fc2f36328a0577bea9697137/app/src/main/res/drawable-mdpi/monster01.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster01_tn.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/f7b8f056d3264059fc2f36328a0577bea9697137/app/src/main/res/drawable-mdpi/monster01_tn.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster02.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/f7b8f056d3264059fc2f36328a0577bea9697137/app/src/main/res/drawable-mdpi/monster02.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster02_tn.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/f7b8f056d3264059fc2f36328a0577bea9697137/app/src/main/res/drawable-mdpi/monster02_tn.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster03.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/f7b8f056d3264059fc2f36328a0577bea9697137/app/src/main/res/drawable-mdpi/monster03.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster03_tn.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/f7b8f056d3264059fc2f36328a0577bea9697137/app/src/main/res/drawable-mdpi/monster03_tn.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster04.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/f7b8f056d3264059fc2f36328a0577bea9697137/app/src/main/res/drawable-mdpi/monster04.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster04_tn.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/f7b8f056d3264059fc2f36328a0577bea9697137/app/src/main/res/drawable-mdpi/monster04_tn.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster05.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/f7b8f056d3264059fc2f36328a0577bea9697137/app/src/main/res/drawable-mdpi/monster05.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster05_tn.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/f7b8f056d3264059fc2f36328a0577bea9697137/app/src/main/res/drawable-mdpi/monster05_tn.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster06.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/f7b8f056d3264059fc2f36328a0577bea9697137/app/src/main/res/drawable-mdpi/monster06.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster06_tn.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/f7b8f056d3264059fc2f36328a0577bea9697137/app/src/main/res/drawable-mdpi/monster06_tn.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster07.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/f7b8f056d3264059fc2f36328a0577bea9697137/app/src/main/res/drawable-mdpi/monster07.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster07_tn.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/f7b8f056d3264059fc2f36328a0577bea9697137/app/src/main/res/drawable-mdpi/monster07_tn.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster08.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/f7b8f056d3264059fc2f36328a0577bea9697137/app/src/main/res/drawable-mdpi/monster08.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster08_tn.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/f7b8f056d3264059fc2f36328a0577bea9697137/app/src/main/res/drawable-mdpi/monster08_tn.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster09.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/f7b8f056d3264059fc2f36328a0577bea9697137/app/src/main/res/drawable-mdpi/monster09.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster09_tn.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/f7b8f056d3264059fc2f36328a0577bea9697137/app/src/main/res/drawable-mdpi/monster09_tn.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster10.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/f7b8f056d3264059fc2f36328a0577bea9697137/app/src/main/res/drawable-mdpi/monster10.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster10_tn.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/f7b8f056d3264059fc2f36328a0577bea9697137/app/src/main/res/drawable-mdpi/monster10_tn.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster11.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/f7b8f056d3264059fc2f36328a0577bea9697137/app/src/main/res/drawable-mdpi/monster11.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster11_tn.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/f7b8f056d3264059fc2f36328a0577bea9697137/app/src/main/res/drawable-mdpi/monster11_tn.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster12.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/f7b8f056d3264059fc2f36328a0577bea9697137/app/src/main/res/drawable-mdpi/monster12.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/monster12_tn.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidgassner/AndroidDev2019Data/f7b8f056d3264059fc2f36328a0577bea9697137/app/src/main/res/drawable-mdpi/monster12_tn.webp -------------------------------------------------------------------------------- /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/drawable/background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_selected.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/grid_item_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 16 | 21 | 26 | 31 | 36 | 41 | 46 | 51 | 56 | 61 | 66 | 71 | 76 | 81 | 86 | 91 | 96 | 101 | 106 | 111 | 116 | 121 | 126 | 131 | 136 | 141 | 146 | 151 | 156 | 161 | 166 | 171 | 172 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_view_grid.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_view_list.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_detail.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 19 | 20 | 31 | 32 | 41 | 42 |