From 8b5a334d2a72681bf35388a8af9eb4a5149e6360 Mon Sep 17 00:00:00 2001 From: Pablo Bergez Date: Sat, 20 Apr 2019 10:49:48 -0300 Subject: [PATCH] Complete the code test A) Describe the strategy used to consume the API endpoints and the data management. On the first screen I request the cocktail list. When a cocktail is selected I request the cocktail id endpoint. B) Explain which library was used for the routing and why. Would you use the same for a consumer facing app targeting thousands of users? Why? I'm using the navigation component provide from android C) Have you used any strategy to optimize the performance of the list generated for the first feature? No, but an optimization that could be done here is pagination if the API provides this feature. D) Would you like to add any further comments or observations? Yes, another optimitazion to the app could be done be spliting the api data model and the app data model. The api data model has a different fields for each ingredient and measure, so, those could be represented by a Ingredients object that containd the list of ingredient whit the measure. --- MyApplication/.gitignore | 13 ++ MyApplication/.idea/codeStyles/Project.xml | 122 +++++++++++++ .../.idea/codeStyles/codeStyleConfig.xml | 5 + MyApplication/.idea/encodings.xml | 4 + MyApplication/.idea/gradle.xml | 18 ++ MyApplication/.idea/misc.xml | 14 ++ MyApplication/.idea/runConfigurations.xml | 12 ++ MyApplication/.idea/vcs.xml | 6 + MyApplication/app/.gitignore | 1 + MyApplication/app/build.gradle | 66 +++++++ MyApplication/app/proguard-rules.pro | 21 +++ .../buoy/codetest/ExampleInstrumentedTest.kt | 24 +++ .../app/src/main/AndroidManifest.xml | 27 +++ .../com/buoy/codetest/CodeTestApplication.kt | 23 +++ .../buoy/codetest/model/api/CocktailApi.kt | 21 +++ .../com/buoy/codetest/model/domain/Drink.kt | 40 ++++ .../buoy/codetest/model/domain/DrinksList.kt | 3 + .../model/repository/CocktailRepository.kt | 13 ++ .../repository/CocktailRepositoryImpl.kt | 28 +++ .../codetest/system/extentions/Nullability.kt | 5 + .../codetest/system/extentions/Properties.kt | 10 + .../codetest/system/injection/GlobalModule.kt | 52 ++++++ .../java/com/buoy/codetest/ui/MainActivity.kt | 37 ++++ .../cocktaildetail/CocktailDetailFragment.kt | 92 ++++++++++ .../cocktaildetail/CocktailDetailViewModel.kt | 39 ++++ .../buoy/codetest/ui/common/BaseFragment.kt | 42 +++++ .../buoy/codetest/ui/common/BaseViewModel.kt | 23 +++ .../buoy/codetest/ui/home/CocktailsAdapter.kt | 67 +++++++ .../com/buoy/codetest/ui/home/HomeFragment.kt | 97 ++++++++++ .../buoy/codetest/ui/home/HomeViewModel.kt | 72 ++++++++ .../app/src/main/res/anim/slide_in_left.xml | 7 + .../app/src/main/res/anim/slide_in_right.xml | 7 + .../app/src/main/res/anim/slide_out_left.xml | 7 + .../app/src/main/res/anim/slide_out_right.xml | 7 + .../drawable-v24/ic_launcher_foreground.xml | 34 ++++ .../main/res/drawable-xhdpi/placeholder.png | Bin 0 -> 5114 bytes .../res/drawable/ic_launcher_background.xml | 74 ++++++++ .../app/src/main/res/layout/activity_main.xml | 22 +++ .../res/layout/fragment_cocktail_detail.xml | 100 ++++++++++ .../app/src/main/res/layout/fragment_home.xml | 29 +++ .../src/main/res/layout/listitem_cocktail.xml | 56 ++++++ .../src/main/res/layout/view_load_helper.xml | 43 +++++ .../app/src/main/res/menu/menu_home.xml | 11 ++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 2963 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 4905 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2060 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2783 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4490 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 6895 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 6387 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10413 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 9128 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 15132 bytes .../main/res/navigation/main_nav_graph.xml | 25 +++ .../res/transition/change_image_transform.xml | 4 + .../app/src/main/res/values/colors.xml | 7 + .../app/src/main/res/values/strings.xml | 10 + .../app/src/main/res/values/styles.xml | 30 +++ .../java/com/buoy/codetest/ExampleUnitTest.kt | 17 ++ MyApplication/build.gradle | 28 +++ MyApplication/gradle.properties | 17 ++ .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + MyApplication/gradlew | 172 ++++++++++++++++++ MyApplication/gradlew.bat | 84 +++++++++ MyApplication/settings.gradle | 1 + 68 files changed, 1805 insertions(+) create mode 100644 MyApplication/.gitignore create mode 100644 MyApplication/.idea/codeStyles/Project.xml create mode 100644 MyApplication/.idea/codeStyles/codeStyleConfig.xml create mode 100644 MyApplication/.idea/encodings.xml create mode 100644 MyApplication/.idea/gradle.xml create mode 100644 MyApplication/.idea/misc.xml create mode 100644 MyApplication/.idea/runConfigurations.xml create mode 100644 MyApplication/.idea/vcs.xml create mode 100644 MyApplication/app/.gitignore create mode 100644 MyApplication/app/build.gradle create mode 100644 MyApplication/app/proguard-rules.pro create mode 100644 MyApplication/app/src/androidTest/java/com/buoy/codetest/ExampleInstrumentedTest.kt create mode 100644 MyApplication/app/src/main/AndroidManifest.xml create mode 100644 MyApplication/app/src/main/java/com/buoy/codetest/CodeTestApplication.kt create mode 100644 MyApplication/app/src/main/java/com/buoy/codetest/model/api/CocktailApi.kt create mode 100644 MyApplication/app/src/main/java/com/buoy/codetest/model/domain/Drink.kt create mode 100644 MyApplication/app/src/main/java/com/buoy/codetest/model/domain/DrinksList.kt create mode 100644 MyApplication/app/src/main/java/com/buoy/codetest/model/repository/CocktailRepository.kt create mode 100644 MyApplication/app/src/main/java/com/buoy/codetest/model/repository/CocktailRepositoryImpl.kt create mode 100644 MyApplication/app/src/main/java/com/buoy/codetest/system/extentions/Nullability.kt create mode 100644 MyApplication/app/src/main/java/com/buoy/codetest/system/extentions/Properties.kt create mode 100644 MyApplication/app/src/main/java/com/buoy/codetest/system/injection/GlobalModule.kt create mode 100644 MyApplication/app/src/main/java/com/buoy/codetest/ui/MainActivity.kt create mode 100644 MyApplication/app/src/main/java/com/buoy/codetest/ui/cocktaildetail/CocktailDetailFragment.kt create mode 100644 MyApplication/app/src/main/java/com/buoy/codetest/ui/cocktaildetail/CocktailDetailViewModel.kt create mode 100644 MyApplication/app/src/main/java/com/buoy/codetest/ui/common/BaseFragment.kt create mode 100644 MyApplication/app/src/main/java/com/buoy/codetest/ui/common/BaseViewModel.kt create mode 100644 MyApplication/app/src/main/java/com/buoy/codetest/ui/home/CocktailsAdapter.kt create mode 100644 MyApplication/app/src/main/java/com/buoy/codetest/ui/home/HomeFragment.kt create mode 100644 MyApplication/app/src/main/java/com/buoy/codetest/ui/home/HomeViewModel.kt create mode 100644 MyApplication/app/src/main/res/anim/slide_in_left.xml create mode 100644 MyApplication/app/src/main/res/anim/slide_in_right.xml create mode 100644 MyApplication/app/src/main/res/anim/slide_out_left.xml create mode 100644 MyApplication/app/src/main/res/anim/slide_out_right.xml create mode 100644 MyApplication/app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 MyApplication/app/src/main/res/drawable-xhdpi/placeholder.png create mode 100644 MyApplication/app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 MyApplication/app/src/main/res/layout/activity_main.xml create mode 100644 MyApplication/app/src/main/res/layout/fragment_cocktail_detail.xml create mode 100644 MyApplication/app/src/main/res/layout/fragment_home.xml create mode 100644 MyApplication/app/src/main/res/layout/listitem_cocktail.xml create mode 100644 MyApplication/app/src/main/res/layout/view_load_helper.xml create mode 100644 MyApplication/app/src/main/res/menu/menu_home.xml create mode 100644 MyApplication/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 MyApplication/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 MyApplication/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 MyApplication/app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 MyApplication/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 MyApplication/app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 MyApplication/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 MyApplication/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 MyApplication/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 MyApplication/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 MyApplication/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 MyApplication/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 MyApplication/app/src/main/res/navigation/main_nav_graph.xml create mode 100644 MyApplication/app/src/main/res/transition/change_image_transform.xml create mode 100644 MyApplication/app/src/main/res/values/colors.xml create mode 100644 MyApplication/app/src/main/res/values/strings.xml create mode 100644 MyApplication/app/src/main/res/values/styles.xml create mode 100644 MyApplication/app/src/test/java/com/buoy/codetest/ExampleUnitTest.kt create mode 100644 MyApplication/build.gradle create mode 100644 MyApplication/gradle.properties create mode 100644 MyApplication/gradle/wrapper/gradle-wrapper.jar create mode 100644 MyApplication/gradle/wrapper/gradle-wrapper.properties create mode 100755 MyApplication/gradlew create mode 100644 MyApplication/gradlew.bat create mode 100644 MyApplication/settings.gradle diff --git a/MyApplication/.gitignore b/MyApplication/.gitignore new file mode 100644 index 000000000..2b75303ac --- /dev/null +++ b/MyApplication/.gitignore @@ -0,0 +1,13 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/MyApplication/.idea/codeStyles/Project.xml b/MyApplication/.idea/codeStyles/Project.xml new file mode 100644 index 000000000..cb22ebb76 --- /dev/null +++ b/MyApplication/.idea/codeStyles/Project.xml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MyApplication/.idea/codeStyles/codeStyleConfig.xml b/MyApplication/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 000000000..79ee123c2 --- /dev/null +++ b/MyApplication/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/MyApplication/.idea/encodings.xml b/MyApplication/.idea/encodings.xml new file mode 100644 index 000000000..15a15b218 --- /dev/null +++ b/MyApplication/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/MyApplication/.idea/gradle.xml b/MyApplication/.idea/gradle.xml new file mode 100644 index 000000000..7ac24c777 --- /dev/null +++ b/MyApplication/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/MyApplication/.idea/misc.xml b/MyApplication/.idea/misc.xml new file mode 100644 index 000000000..af0bbdde1 --- /dev/null +++ b/MyApplication/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/MyApplication/.idea/runConfigurations.xml b/MyApplication/.idea/runConfigurations.xml new file mode 100644 index 000000000..7f68460d8 --- /dev/null +++ b/MyApplication/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/MyApplication/.idea/vcs.xml b/MyApplication/.idea/vcs.xml new file mode 100644 index 000000000..6c0b86358 --- /dev/null +++ b/MyApplication/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/MyApplication/app/.gitignore b/MyApplication/app/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/MyApplication/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/MyApplication/app/build.gradle b/MyApplication/app/build.gradle new file mode 100644 index 000000000..7daeda822 --- /dev/null +++ b/MyApplication/app/build.gradle @@ -0,0 +1,66 @@ +apply plugin: 'com.android.application' + +apply plugin: 'kotlin-android' + +apply plugin: 'kotlin-android-extensions' + +androidExtensions { + experimental = true +} + +android { + compileSdkVersion 28 + defaultConfig { + applicationId "com.buoy.codetest" + minSdkVersion 21 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + + //implementation 'android.arch.navigation:navigation-fragment:1.0.0' + //implementation 'android.arch.navigation:navigation-ui:1.0.0' + + // Koin for Android + implementation 'org.koin:koin-android:2.0.0-rc-2' + // or Koin for Lifecycle scoping + implementation 'org.koin:koin-androidx-scope:2.0.0-rc-2' + // or Koin for Android Architecture ViewModel + implementation 'org.koin:koin-androidx-viewmodel:2.0.0-rc-2' + + implementation 'com.squareup.retrofit2:retrofit:2.5.0' + implementation 'com.squareup.retrofit2:converter-gson:2.5.0' + implementation 'com.squareup.retrofit2:adapter-rxjava2:2.5.0' + + implementation 'com.google.code.gson:gson:2.8.5' + + // rxandroid + implementation "io.reactivex.rxjava2:rxandroid:2.0.2" + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + + implementation ("com.github.bumptech.glide:glide:4.9.0") { + exclude group: "com.android.support" + } + implementation "com.android.support:support-fragment:28.0.0" + + implementation 'androidx.navigation:navigation-fragment-ktx:2.0.0' + implementation 'androidx.navigation:navigation-ui-ktx:2.0.0' + +} diff --git a/MyApplication/app/proguard-rules.pro b/MyApplication/app/proguard-rules.pro new file mode 100644 index 000000000..f1b424510 --- /dev/null +++ b/MyApplication/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/MyApplication/app/src/androidTest/java/com/buoy/codetest/ExampleInstrumentedTest.kt b/MyApplication/app/src/androidTest/java/com/buoy/codetest/ExampleInstrumentedTest.kt new file mode 100644 index 000000000..d395c041a --- /dev/null +++ b/MyApplication/app/src/androidTest/java/com/buoy/codetest/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.buoy.codetest + +import android.support.test.InstrumentationRegistry +import android.support.test.runner.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getTargetContext() + assertEquals("com.buoy.codetest", appContext.packageName) + } +} diff --git a/MyApplication/app/src/main/AndroidManifest.xml b/MyApplication/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..fac25922d --- /dev/null +++ b/MyApplication/app/src/main/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MyApplication/app/src/main/java/com/buoy/codetest/CodeTestApplication.kt b/MyApplication/app/src/main/java/com/buoy/codetest/CodeTestApplication.kt new file mode 100644 index 000000000..7909d7302 --- /dev/null +++ b/MyApplication/app/src/main/java/com/buoy/codetest/CodeTestApplication.kt @@ -0,0 +1,23 @@ +package com.buoy.codetest + +import android.app.Application +import com.buoy.codetest.system.injection.networkModule +import com.buoy.codetest.system.injection.repositoryModule +import com.buoy.codetest.system.injection.viewModelModule +import org.koin.android.ext.koin.androidContext +import org.koin.android.ext.koin.androidLogger +import org.koin.core.context.startKoin + +class CodeTestApplication: Application() { + + override fun onCreate() { + super.onCreate() + + startKoin{ + androidLogger() + androidContext(this@CodeTestApplication) + modules(listOf(repositoryModule, networkModule, viewModelModule)) + } + } + +} \ No newline at end of file diff --git a/MyApplication/app/src/main/java/com/buoy/codetest/model/api/CocktailApi.kt b/MyApplication/app/src/main/java/com/buoy/codetest/model/api/CocktailApi.kt new file mode 100644 index 000000000..71c328ee4 --- /dev/null +++ b/MyApplication/app/src/main/java/com/buoy/codetest/model/api/CocktailApi.kt @@ -0,0 +1,21 @@ +package com.buoy.codetest.model.api + +import com.buoy.codetest.model.domain.DrinksList +import io.reactivex.Single + +import retrofit2.http.GET +import retrofit2.http.Path +import retrofit2.http.Query + +interface CocktailApi { + + @GET("api/json/v1/1/filter.php?g=Cocktail_glass") + fun getAllCocktails(): Single + + @GET("api/json/v1/1/lookup.php") + fun getCocktailDetail(@Query("i") id: String): Single + + companion object { + const val URL = "http://www.thecocktaildb.com" + } +} \ No newline at end of file diff --git a/MyApplication/app/src/main/java/com/buoy/codetest/model/domain/Drink.kt b/MyApplication/app/src/main/java/com/buoy/codetest/model/domain/Drink.kt new file mode 100644 index 000000000..b325d9fb3 --- /dev/null +++ b/MyApplication/app/src/main/java/com/buoy/codetest/model/domain/Drink.kt @@ -0,0 +1,40 @@ +package com.buoy.codetest.model.domain + +import android.os.Parcelable +import kotlinx.android.parcel.Parcelize + +@Parcelize +data class Drink(val idDrink: String?, + val strDrink: String?, + val strDrinkThumb: String?, + val strInstructions: String?, + val strIngredient1: String?, + val strIngredient2: String?, + val strIngredient3: String?, + val strIngredient4: String?, + val strIngredient5: String?, + val strIngredient6: String?, + val strIngredient7: String?, + val strIngredient8: String?, + val strIngredient9: String?, + val strIngredient10: String?, + val strIngredient11: String?, + val strIngredient12: String?, + val strIngredient13: String?, + val strIngredient14: String?, + val strIngredient15: String?, + val strMeasure1: String?, + val strMeasure2: String?, + val strMeasure3: String?, + val strMeasure4: String?, + val strMeasure5: String?, + val strMeasure6: String?, + val strMeasure7: String?, + val strMeasure8: String?, + val strMeasure9: String?, + val strMeasure10: String?, + val strMeasure11: String?, + val strMeasure12: String?, + val strMeasure13: String?, + val strMeasure14: String?, + val strMeasure15: String?): Parcelable \ No newline at end of file diff --git a/MyApplication/app/src/main/java/com/buoy/codetest/model/domain/DrinksList.kt b/MyApplication/app/src/main/java/com/buoy/codetest/model/domain/DrinksList.kt new file mode 100644 index 000000000..566c500a1 --- /dev/null +++ b/MyApplication/app/src/main/java/com/buoy/codetest/model/domain/DrinksList.kt @@ -0,0 +1,3 @@ +package com.buoy.codetest.model.domain + +data class DrinksList(val drinks: List?) \ No newline at end of file diff --git a/MyApplication/app/src/main/java/com/buoy/codetest/model/repository/CocktailRepository.kt b/MyApplication/app/src/main/java/com/buoy/codetest/model/repository/CocktailRepository.kt new file mode 100644 index 000000000..8ea33d575 --- /dev/null +++ b/MyApplication/app/src/main/java/com/buoy/codetest/model/repository/CocktailRepository.kt @@ -0,0 +1,13 @@ +package com.buoy.codetest.model.repository + +import com.buoy.codetest.model.domain.Drink +import io.reactivex.Single + + +interface CocktailRepository { + + fun getAllCocktails(): Single> + + fun getCocktailDetail(id: String): Single + +} \ No newline at end of file diff --git a/MyApplication/app/src/main/java/com/buoy/codetest/model/repository/CocktailRepositoryImpl.kt b/MyApplication/app/src/main/java/com/buoy/codetest/model/repository/CocktailRepositoryImpl.kt new file mode 100644 index 000000000..015ac8d35 --- /dev/null +++ b/MyApplication/app/src/main/java/com/buoy/codetest/model/repository/CocktailRepositoryImpl.kt @@ -0,0 +1,28 @@ +package com.buoy.codetest.model.repository + +import com.buoy.codetest.model.api.CocktailApi +import com.buoy.codetest.model.domain.Drink +import io.reactivex.Single + +class CocktailRepositoryImpl(val cocktailApi: CocktailApi): CocktailRepository { + + override fun getCocktailDetail(id: String): Single { + return cocktailApi.getCocktailDetail(id).map { drinkList -> + var drink: Drink? = null + drinkList.drinks?.let { drinks -> + if (drinks.isNotEmpty()) { + drink = drinks[0] + } + } + drink + } + + } + + override fun getAllCocktails(): Single> { + return cocktailApi.getAllCocktails().map { drinksList -> + drinksList.drinks + } + } + +} \ No newline at end of file diff --git a/MyApplication/app/src/main/java/com/buoy/codetest/system/extentions/Nullability.kt b/MyApplication/app/src/main/java/com/buoy/codetest/system/extentions/Nullability.kt new file mode 100644 index 000000000..1dd0fbde1 --- /dev/null +++ b/MyApplication/app/src/main/java/com/buoy/codetest/system/extentions/Nullability.kt @@ -0,0 +1,5 @@ +package com.buoy.codetest.system.extentions + +fun notEmptyStrings(p1: T1?, p2: T2?, block: (T1, T2)->R?): R? { + return if (p1 != null && p1.isNotEmpty() && p2 != null && p2.isNotEmpty()) block(p1, p2) else null +} \ No newline at end of file diff --git a/MyApplication/app/src/main/java/com/buoy/codetest/system/extentions/Properties.kt b/MyApplication/app/src/main/java/com/buoy/codetest/system/extentions/Properties.kt new file mode 100644 index 000000000..b1d336592 --- /dev/null +++ b/MyApplication/app/src/main/java/com/buoy/codetest/system/extentions/Properties.kt @@ -0,0 +1,10 @@ +package com.buoy.codetest.system.extentions + +inline fun Any.getThroughReflection(propertyName: String): T? { + val getterName = "get" + propertyName.capitalize() + return try { + javaClass.getMethod(getterName).invoke(this) as? T + } catch (e: NoSuchMethodException) { + null + } +} \ No newline at end of file diff --git a/MyApplication/app/src/main/java/com/buoy/codetest/system/injection/GlobalModule.kt b/MyApplication/app/src/main/java/com/buoy/codetest/system/injection/GlobalModule.kt new file mode 100644 index 000000000..df050eb4a --- /dev/null +++ b/MyApplication/app/src/main/java/com/buoy/codetest/system/injection/GlobalModule.kt @@ -0,0 +1,52 @@ +package com.buoy.codetest.system.injection + +import com.buoy.codetest.model.api.CocktailApi +import com.buoy.codetest.model.repository.CocktailRepository +import com.buoy.codetest.model.repository.CocktailRepositoryImpl +import com.buoy.codetest.ui.cocktaildetail.CocktailDetailViewModel +import com.buoy.codetest.ui.home.HomeViewModel +import com.google.gson.GsonBuilder +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module +import retrofit2.Retrofit +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory +import retrofit2.converter.gson.GsonConverterFactory +import okhttp3.OkHttpClient + + + +private val retrofit: Retrofit = createNetworkClient() +private val cocktailApi: CocktailApi = retrofit.create(CocktailApi::class.java) + +const val DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss" + + +val viewModelModule = module { + viewModel { CocktailDetailViewModel(get()) } + viewModel { HomeViewModel(get()) } +} + +val repositoryModule = module { + single { CocktailRepositoryImpl(cocktailApi = get()) as CocktailRepository } +} + +val networkModule = module { + single { cocktailApi } +} + + +fun createNetworkClient(): Retrofit { + val gson = GsonBuilder() + .setDateFormat(DATE_FORMAT) + .create() + + val okHttpClient = OkHttpClient().newBuilder().build() + + return Retrofit.Builder() + .baseUrl(CocktailApi.URL) + .client(okHttpClient) + .addConverterFactory(GsonConverterFactory.create(gson)) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .build() +} + diff --git a/MyApplication/app/src/main/java/com/buoy/codetest/ui/MainActivity.kt b/MyApplication/app/src/main/java/com/buoy/codetest/ui/MainActivity.kt new file mode 100644 index 000000000..d4dcf6199 --- /dev/null +++ b/MyApplication/app/src/main/java/com/buoy/codetest/ui/MainActivity.kt @@ -0,0 +1,37 @@ +package com.buoy.codetest.ui + +import android.os.Bundle +import android.view.Window +import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.NavUtils +import androidx.navigation.Navigation.findNavController +import androidx.navigation.fragment.NavHostFragment +import androidx.navigation.fragment.findNavController +import com.buoy.codetest.R +import kotlinx.android.synthetic.main.activity_main.* + +class MainActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + with(window) { + requestFeature(Window.FEATURE_CONTENT_TRANSITIONS) + } + + setContentView(R.layout.activity_main) + + } + + override fun onBackPressed() { + when(NavHostFragment.findNavController(nav_host_fragment).currentDestination?.id) { + R.id.cocktailDetailFragment -> { + NavHostFragment.findNavController(nav_host_fragment).popBackStack() + } + else -> { + super.onBackPressed() + } + } + } + +} diff --git a/MyApplication/app/src/main/java/com/buoy/codetest/ui/cocktaildetail/CocktailDetailFragment.kt b/MyApplication/app/src/main/java/com/buoy/codetest/ui/cocktaildetail/CocktailDetailFragment.kt new file mode 100644 index 000000000..03041d210 --- /dev/null +++ b/MyApplication/app/src/main/java/com/buoy/codetest/ui/cocktaildetail/CocktailDetailFragment.kt @@ -0,0 +1,92 @@ +package com.buoy.codetest.ui.cocktaildetail + +import android.os.Bundle +import android.transition.TransitionInflater +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Observer +import androidx.transition.ChangeBounds +import com.bumptech.glide.Glide +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.request.RequestOptions +import com.buoy.codetest.R +import com.buoy.codetest.model.domain.Drink +import com.buoy.codetest.system.extentions.getThroughReflection +import com.buoy.codetest.system.extentions.notEmptyStrings +import com.buoy.codetest.ui.common.BaseFragment +import kotlinx.android.synthetic.main.fragment_cocktail_detail.* +import org.koin.androidx.viewmodel.ext.android.viewModel + +class CocktailDetailFragment: BaseFragment() { + + val model: CocktailDetailViewModel by viewModel() + + override fun getLayoutResource(): Int = R.layout.fragment_cocktail_detail + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setHasOptionsMenu(true) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + sharedElementEnterTransition = TransitionInflater.from(context).inflateTransition(android.R.transition.move) + return super.onCreateView(inflater, container, savedInstanceState) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + (activity as AppCompatActivity).setSupportActionBar(toolbar) + + arguments?.let { args -> + if (args.containsKey(EXTRA_DRINK)) { + val drink: Drink = args.getParcelable(EXTRA_DRINK) + model.initialize(drink) + setCocktailImage(drink.strDrinkThumb) + } + } + + model.getDrink().observe(viewLifecycleOwner, Observer { drink -> onDrinkUpdate(drink) }) + } + + private fun setCocktailImage(strDrinkThumb: String?) { + Glide.with(this) + .load(strDrinkThumb) + .fitCenter() + .placeholder(R.drawable.placeholder) + .apply(RequestOptions.bitmapTransform(RoundedCorners(16))) + .into(image) + } + + private fun onDrinkUpdate(drink: Drink?) { + drink?.let { drink -> + setToolbarTitle(drink.strDrink, true) + setCocktailImage(drink.strDrinkThumb) + + ingredients.text = getIngredients(drink) + instructions.text = drink.strInstructions?.let { it } + } + } + + private fun getIngredients(drink: Drink): String { + var ingredients = "" + + for (i in 1..15) { + val ingredient = drink.getThroughReflection("strIngredient$i") + val measure = drink.getThroughReflection("strMeasure$i") + + notEmptyStrings(ingredient, measure) { ingredient, measure -> + ingredients += "$ingredient $measure \n" + } + + } + + return ingredients + } + + companion object { + const val EXTRA_DRINK = "extra_drink" + } +} \ No newline at end of file diff --git a/MyApplication/app/src/main/java/com/buoy/codetest/ui/cocktaildetail/CocktailDetailViewModel.kt b/MyApplication/app/src/main/java/com/buoy/codetest/ui/cocktaildetail/CocktailDetailViewModel.kt new file mode 100644 index 000000000..882f0e940 --- /dev/null +++ b/MyApplication/app/src/main/java/com/buoy/codetest/ui/cocktaildetail/CocktailDetailViewModel.kt @@ -0,0 +1,39 @@ +package com.buoy.codetest.ui.cocktaildetail + +import android.util.Log +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import com.buoy.codetest.model.domain.Drink +import com.buoy.codetest.model.repository.CocktailRepository +import com.buoy.codetest.ui.common.BaseViewModel +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers + +class CocktailDetailViewModel(val cocktailRepository: CocktailRepository): BaseViewModel() { + + private val drink = MutableLiveData() + fun getDrink(): LiveData = drink + + fun initialize(drink: Drink) { + this.drink.value = drink + requestDrinkDetail(drink.idDrink) + } + + private fun requestDrinkDetail(cocktailId: String?) { + cocktailId?.let { id -> + setState(State.LOADING) + cocktailRepository.getCocktailDetail(id) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe ({ result -> + drink.value = result + setState(State.SUCCESS) + }, { error -> + Log.e("HomeViewModel", "requestDrinks:", error) + setState(State.ERROR) + }) + + } + + } +} \ No newline at end of file diff --git a/MyApplication/app/src/main/java/com/buoy/codetest/ui/common/BaseFragment.kt b/MyApplication/app/src/main/java/com/buoy/codetest/ui/common/BaseFragment.kt new file mode 100644 index 000000000..355620d1c --- /dev/null +++ b/MyApplication/app/src/main/java/com/buoy/codetest/ui/common/BaseFragment.kt @@ -0,0 +1,42 @@ +package com.buoy.codetest.ui.common + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.NavUtils +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController +import kotlinx.android.synthetic.main.view_load_helper.* + +abstract class BaseFragment: Fragment() { + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(getLayoutResource(), container, false) + } + + abstract fun getLayoutResource(): Int + + fun setToolbarTitle(title: String?, backButton: Boolean) { + (activity as AppCompatActivity)?.let { + it.supportActionBar?.title = title + it.supportActionBar?.setHomeButtonEnabled(backButton) + it.supportActionBar?.setDisplayHomeAsUpEnabled(backButton) + } + } + + override fun onOptionsItemSelected(item: MenuItem?): Boolean { + when(item?.itemId) { + android.R.id.home -> { + activity?.let { it.onBackPressed() } + return true + } + } + + return super.onOptionsItemSelected(item) + + } + +} \ No newline at end of file diff --git a/MyApplication/app/src/main/java/com/buoy/codetest/ui/common/BaseViewModel.kt b/MyApplication/app/src/main/java/com/buoy/codetest/ui/common/BaseViewModel.kt new file mode 100644 index 000000000..da206116a --- /dev/null +++ b/MyApplication/app/src/main/java/com/buoy/codetest/ui/common/BaseViewModel.kt @@ -0,0 +1,23 @@ +package com.buoy.codetest.ui.common + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel + +open class BaseViewModel: ViewModel() { + + enum class State { + LOADING, + SUCCESS, + ERROR + } + + private val state = MutableLiveData() + fun getState(): LiveData = state + + + protected fun setState(state: State) { + this.state.value = state + } + +} \ No newline at end of file diff --git a/MyApplication/app/src/main/java/com/buoy/codetest/ui/home/CocktailsAdapter.kt b/MyApplication/app/src/main/java/com/buoy/codetest/ui/home/CocktailsAdapter.kt new file mode 100644 index 000000000..3d0b93e0e --- /dev/null +++ b/MyApplication/app/src/main/java/com/buoy/codetest/ui/home/CocktailsAdapter.kt @@ -0,0 +1,67 @@ +package com.buoy.codetest.ui.home + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.navigation.Navigation.findNavController +import androidx.navigation.fragment.FragmentNavigatorExtras +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.request.RequestOptions +import com.buoy.codetest.R +import com.buoy.codetest.model.domain.Drink +import com.buoy.codetest.ui.cocktaildetail.CocktailDetailFragment +import kotlinx.android.synthetic.main.listitem_cocktail.view.* + +class CocktailsAdapter: RecyclerView.Adapter() { + + private val drinks: MutableList = ArrayList() + + fun setDrinks(drinks: List) { + this.drinks.clear() + this.drinks.addAll(drinks) + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CocktailItemViewHolder { + return CocktailItemViewHolder(LayoutInflater.from(parent.context) + .inflate(R.layout.listitem_cocktail, parent, false)) + } + + override fun getItemCount(): Int = drinks.size + + override fun onBindViewHolder(holder: CocktailItemViewHolder, position: Int) { + holder.bind(drinks[position]) + } + + inner class CocktailItemViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) { + + fun bind(drink: Drink) = with(itemView) { + name.text = drink.strDrink + + Glide.with(context) + .load(drink.strDrinkThumb) + .placeholder(R.drawable.placeholder) + .fitCenter() + .apply(RequestOptions.bitmapTransform(RoundedCorners(16))) + .into(image) + + setOnClickListener { onDrinkSelected(drink, this) } + } + + private fun onDrinkSelected(drink: Drink, view: View) { + val bundle = Bundle() + bundle.putParcelable(CocktailDetailFragment.EXTRA_DRINK, drink) + + val extras = FragmentNavigatorExtras( + view.image to "cocktailTransitionName_to" + ) + + findNavController(view).navigate(R.id.action_homeFragment_to_cocktailDetailFragment, bundle, null, extras) + } + } + +} + diff --git a/MyApplication/app/src/main/java/com/buoy/codetest/ui/home/HomeFragment.kt b/MyApplication/app/src/main/java/com/buoy/codetest/ui/home/HomeFragment.kt new file mode 100644 index 000000000..81714523b --- /dev/null +++ b/MyApplication/app/src/main/java/com/buoy/codetest/ui/home/HomeFragment.kt @@ -0,0 +1,97 @@ +package com.buoy.codetest.ui.home + +import android.os.Bundle +import android.view.Menu +import android.view.MenuInflater +import android.view.View +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.SearchView +import androidx.appcompat.widget.SearchView.OnQueryTextListener +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.buoy.codetest.R +import com.buoy.codetest.ui.common.BaseFragment +import com.buoy.codetest.ui.common.BaseViewModel +import kotlinx.android.synthetic.main.fragment_home.* +import kotlinx.android.synthetic.main.view_load_helper.* +import org.koin.androidx.viewmodel.ext.android.viewModel + + +class HomeFragment: BaseFragment() { + + private val model: HomeViewModel by viewModel() + private val adapter = CocktailsAdapter() + + override fun getLayoutResource(): Int = R.layout.fragment_home + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setHasOptionsMenu(true) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + (activity as AppCompatActivity).setSupportActionBar(toolbar) + + cocktail_list.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false) + cocktail_list.adapter = adapter + + error_retry.setOnClickListener { onRetryClicked() } + + model.getDrinks().observe(viewLifecycleOwner, Observer { drinks -> + adapter.setDrinks(drinks) + }) + + model.getState().observe(viewLifecycleOwner, Observer { state -> + onStateChanged(state) + }) + } + + override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) { + super.onCreateOptionsMenu(menu, inflater) + inflater?.inflate(R.menu.menu_home, menu) + val menuItem = menu?.findItem(R.id.search) + (menuItem?.actionView as SearchView).setOnQueryTextListener( onQueryListener ) + } + + private fun onStateChanged(state: BaseViewModel.State?) { + + when(state) { + BaseViewModel.State.LOADING -> { + progress.visibility = View.VISIBLE + error_view.visibility = View.GONE + } + + BaseViewModel.State.SUCCESS -> { + progress.visibility = View.GONE + error_view.visibility = View.GONE + } + + BaseViewModel.State.ERROR -> { + progress.visibility = View.GONE + error_view.visibility = View.VISIBLE + } + } + } + + + private val onQueryListener = object: OnQueryTextListener { + override fun onQueryTextSubmit(query: String?): Boolean { + return false + } + + override fun onQueryTextChange(newText: String?): Boolean { + model.filterList(newText) + return true + } + + } + + private fun onRetryClicked() { + model.requestDrinks() + } + + +} \ No newline at end of file diff --git a/MyApplication/app/src/main/java/com/buoy/codetest/ui/home/HomeViewModel.kt b/MyApplication/app/src/main/java/com/buoy/codetest/ui/home/HomeViewModel.kt new file mode 100644 index 000000000..5a7dcea53 --- /dev/null +++ b/MyApplication/app/src/main/java/com/buoy/codetest/ui/home/HomeViewModel.kt @@ -0,0 +1,72 @@ +package com.buoy.codetest.ui.home + +import android.util.Log +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import com.buoy.codetest.model.domain.Drink +import com.buoy.codetest.model.repository.CocktailRepository +import com.buoy.codetest.ui.common.BaseViewModel +import io.reactivex.Single +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers + +class HomeViewModel(val cocktailRepository: CocktailRepository): BaseViewModel() { + + + private var drinks: List? = null + private val filterDrinks = MutableLiveData>() + + private var filterText: String? = null + + fun getDrinks(): LiveData> = filterDrinks + fun getFilter(): String? = filterText + + init { + requestDrinks() + } + + fun requestDrinks() { + setState(State.LOADING) + + cocktailRepository.getAllCocktails() + .flatMap { drinks -> + this.drinks = drinks + executeFilter() + } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe ({ + setState(State.SUCCESS) + }, { error -> + Log.e("HomeViewModel", "requestDrinks:", error) + setState(State.ERROR) + }) + + + } + + fun filterList(newText: String?) { + filterText = newText + executeFilter() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe ({}, {}) + } + + private fun executeFilter(): Single { + return Single.create { emitter -> + drinks?.let { drinks -> + + val filter = filterText?.let { it.toLowerCase() } ?: "" + if (filter.isNotEmpty()) { + val filterList = drinks.filter { drink -> drink.strDrink?.toLowerCase()?.contains(filter) ?: false } + filterDrinks.postValue(filterList) + } else { + filterDrinks.postValue(drinks) + } + } + + emitter.onSuccess(true) + } + } +} \ No newline at end of file diff --git a/MyApplication/app/src/main/res/anim/slide_in_left.xml b/MyApplication/app/src/main/res/anim/slide_in_left.xml new file mode 100644 index 000000000..96995fa63 --- /dev/null +++ b/MyApplication/app/src/main/res/anim/slide_in_left.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/MyApplication/app/src/main/res/anim/slide_in_right.xml b/MyApplication/app/src/main/res/anim/slide_in_right.xml new file mode 100644 index 000000000..6fb52a3b7 --- /dev/null +++ b/MyApplication/app/src/main/res/anim/slide_in_right.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/MyApplication/app/src/main/res/anim/slide_out_left.xml b/MyApplication/app/src/main/res/anim/slide_out_left.xml new file mode 100644 index 000000000..020a1bee1 --- /dev/null +++ b/MyApplication/app/src/main/res/anim/slide_out_left.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/MyApplication/app/src/main/res/anim/slide_out_right.xml b/MyApplication/app/src/main/res/anim/slide_out_right.xml new file mode 100644 index 000000000..c4de80064 --- /dev/null +++ b/MyApplication/app/src/main/res/anim/slide_out_right.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/MyApplication/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/MyApplication/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 000000000..6348baae3 --- /dev/null +++ b/MyApplication/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/MyApplication/app/src/main/res/drawable-xhdpi/placeholder.png b/MyApplication/app/src/main/res/drawable-xhdpi/placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..b2c6afe474c9a3eed0002adc6d539c032e29554f GIT binary patch literal 5114 zcmeHLc~Dc=8owbLPzlD05TQISkyctH3nT$jf`P=HARDmG{$1S$#SB~}O}@DjNo zRN^lYh_`OJA@P79ti3E?jb;tshb2kc;tr!ys5++SpK|)Bfh{lHc6_{C|MLrL_`ER!iB6*g;Hql?(V1!XJ;pXa8hqY zG=fMcL_KHJ0#BkAsborxOo70tMS)NereTvnq{Au5mG8|W>X9;mf>9y`N(zlkMN=9D z^7;Q8Dwn@Ut2O?Tv3MsmtPa?!lu-O7YDJhz1RA$t4(dwD;;JM9jY1WmP;4HpqMuZu zQK+Q~CCpvPgdO+-kqkivUl=s_eAW^~tq~w1$r2u$1ZP@39 zaQEg>y?G3Vn+w&8Nu_gnqq;nWC`>LvG^4uWF>Y`+jD}iU8L?sKCh!?69a@deG zOEy6)bS|Ch%3yjkTYnzFXmWKoQ3*bsD5%)>&k6$I5o zP%;kU&>^S*0}=3}mslwTjYE6@96@{l95FR!aXexopC|&j9uFLeYFu!CK#U7+%-Kj5 ze*g|A_;>h*R{vdn1L^-i@~r%?QFza`;fWc?HM^q?8?_PPy}iBP#>1Xsm#m7n-L+3@ z(o>DMBKlSJrv&`& zVoJrko$iF((A#;bp4+5m$+*Qo4D|HW)-F@S{t8QrppzUr=aZBj@ykA`dfCv?gvDap zKqxxs zZ@Wb&WT=jbO%63wR>s-ER{5f|v^3MW;s+0!>63U^O?7YYdAr@n+?pcGIac{Id$*mM zB)f3tjKCxrS9)?|$wm_}c54U9&xuSReM9IZo**XX9WD@WX6705+qEaX?cQzCLxZO% z>yguf%fYwB&bY;yIWo9zXz16y!hFX(kF94*i!}-O<-Wc?)9axnW%m~`HA_Fwkcl^y z1?FnC);6F@U3=svnx@B(L&~y)f`c-x3oyt_+0cwO;1n1-S%psF?dJ;O4FyCA*Wkgr{BeZhDA4Ywlp<1BK_R`RsgY6CtySQK!g;o8lVDcAzc-H?(sgKv5| z)^%RX!PG-r94)3gH7#lm7FphM1qqPOubtP}(PJMG3K|{zny#{oQ=7g!b*5~(t!JhO zYy~^(yzTmPqX*vfbamb70v77qf3d-3KkKd>y0QF`MP`t+POhX8io{ zVaFTV_e(#cOakiC?1)!{BjGl-Hd~#*Q!lkG3HWIQMY!&s`DC{ljzhny{r-9>v%Yxt z-Ab@+yv|6}wwc!(ZnX!yUa~z@36p^#&cMJotv25s@xT;duBpSLESl|dbM@MOZBCB) zs#V_&CQ-o%t+2RWePlU%{*`X7E8wkkg(-hs|8exmo>|HCbl)ATgu!OC(Oh7<@O1%v{^`>IZbaGYGs?}e0V~_lUy+1E6ZTmco1^0GLlYmTi`Ye3e)aIGD z=$wahaY+BCJF44KZ3~Dm_YYX-Kgp5Bo@eLg$_FzY=q2U82T*y+fiPDGFx#<0sUE**(V`TvnB^1KYPbi9HxGY@t{eHp z=tg`BW-&)QgL4QqSyoo|)x(FuMvsf!??E#xxfw!mSad*F-4qOJ2f1G*o1^6 z&LzGtf3{Hph7}SW{fMymLRQvnzzL2q*jgS1cdqfTWw?PUzngtQfsZSW`mOsgx}B># zn;t!?uV0~tw->I`!DCFZM$7Q?QjW;bXj0p*8uS{reZ@+pqHg2RR6ST(g2Jxpz z0j3K~2TH7w3re$br$xZ=4~Ym69{@*8jaeLz_)CW$GW8?*f(7{_h5Ja^V5@P}9jn$y z%zXI5I#Sm0)g60l9J4qc@t3YYG~uf|n&6*eKWy~QA_OYFmA;Z|xu1moH*U!SUtW=S HNc?{Q=(#I9 literal 0 HcmV?d00001 diff --git a/MyApplication/app/src/main/res/drawable/ic_launcher_background.xml b/MyApplication/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 000000000..a0ad202f9 --- /dev/null +++ b/MyApplication/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MyApplication/app/src/main/res/layout/activity_main.xml b/MyApplication/app/src/main/res/layout/activity_main.xml new file mode 100644 index 000000000..03fccc4c6 --- /dev/null +++ b/MyApplication/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,22 @@ + + + + + + \ No newline at end of file diff --git a/MyApplication/app/src/main/res/layout/fragment_cocktail_detail.xml b/MyApplication/app/src/main/res/layout/fragment_cocktail_detail.xml new file mode 100644 index 000000000..eaa79dff5 --- /dev/null +++ b/MyApplication/app/src/main/res/layout/fragment_cocktail_detail.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MyApplication/app/src/main/res/layout/fragment_home.xml b/MyApplication/app/src/main/res/layout/fragment_home.xml new file mode 100644 index 000000000..365b8e7ff --- /dev/null +++ b/MyApplication/app/src/main/res/layout/fragment_home.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/MyApplication/app/src/main/res/layout/listitem_cocktail.xml b/MyApplication/app/src/main/res/layout/listitem_cocktail.xml new file mode 100644 index 000000000..885e37c4b --- /dev/null +++ b/MyApplication/app/src/main/res/layout/listitem_cocktail.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MyApplication/app/src/main/res/layout/view_load_helper.xml b/MyApplication/app/src/main/res/layout/view_load_helper.xml new file mode 100644 index 000000000..b173b542a --- /dev/null +++ b/MyApplication/app/src/main/res/layout/view_load_helper.xml @@ -0,0 +1,43 @@ + + + + + + + +