From 33dc4a4ab52b07be83b9d4ced0014c9bb4183098 Mon Sep 17 00:00:00 2001 From: Dionisis Karatzas Date: Wed, 7 Mar 2018 17:55:32 +0200 Subject: [PATCH] Completed exercise! --- .gitignore | 146 +++++++++ LICENSE | 201 +++++++++++++ README.md | 40 +++ app/.gitignore | 1 + app/build.gradle | 65 ++++ app/proguard-rules.pro | 21 ++ .../bakingrecipes/BakingRecipesTests.java | 115 ++++++++ .../bakingrecipes/utils/BaseTest.java | 51 ++++ .../bakingrecipes/utils/Navigation.java | 36 +++ app/src/main/AndroidManifest.xml | 65 ++++ app/src/main/ic_launcher-web.png | Bin 0 -> 16083 bytes .../eu/dkaratzas/bakingrecipes/Constants.java | 21 ++ .../bakingrecipes/GlobalApplication.java | 73 +++++ .../IdlingRecource/RecipesIdlingResource.java | 52 ++++ .../eu/dkaratzas/bakingrecipes/Prefs.java | 40 +++ .../bakingrecipes/adapters/RecipeAdapter.java | 100 +++++++ .../adapters/RecipesAdapter.java | 74 +++++ .../adapters/StepsFragmentPagerAdapter.java | 64 ++++ .../bakingrecipes/api/RecipesApiCallback.java | 24 ++ .../bakingrecipes/api/RecipesApiManager.java | 82 ++++++ .../bakingrecipes/api/RecipesApiService.java | 30 ++ .../holders/IngredientsViewHolder.java | 38 +++ .../holders/RecipeViewHolder.java | 40 +++ .../bakingrecipes/holders/StepViewHolder.java | 40 +++ .../bakingrecipes/models/Ingredients.java | 91 ++++++ .../bakingrecipes/models/Recipe.java | 152 ++++++++++ .../dkaratzas/bakingrecipes/models/Step.java | 111 +++++++ .../dkaratzas/bakingrecipes/ui/Listeners.java | 24 ++ .../ui/activities/MainActivity.java | 42 +++ .../ui/activities/RecipeInfoActivity.java | 157 ++++++++++ .../activities/RecipeStepDetailActivity.java | 116 ++++++++ .../fragments/RecipeStepDetailFragment.java | 181 ++++++++++++ .../ui/fragments/RecipesFragment.java | 277 ++++++++++++++++++ .../dkaratzas/bakingrecipes/utils/Misc.java | 31 ++ .../utils/RecipesAppGlideModule.java | 24 ++ .../utils/SpacingItemDecoration.java | 92 ++++++ .../bakingrecipes/widget/AppWidget.java | 84 ++++++ .../widget/AppWidgetService.java | 45 +++ .../widget/ListRemoteViewsFactory.java | 84 ++++++ .../res/drawable-nodpi/appwidget_preview.png | Bin 0 -> 46174 bytes .../res/drawable/ic_arrow_point_to_right.xml | 9 + app/src/main/res/drawable/ic_dinner.xml | 30 ++ app/src/main/res/drawable/ic_recipe.xml | 48 +++ app/src/main/res/drawable/round_tv.xml | 28 ++ .../main/res/drawable/widget_background.xml | 21 ++ .../res/layout-land/recipe_step_detail.xml | 89 ++++++ .../layout-w900dp-land/recipe_step_detail.xml | 86 ++++++ .../res/layout-w900dp/recipe_step_list.xml | 52 ++++ app/src/main/res/layout/activity_main.xml | 21 ++ .../main/res/layout/activity_recipe_info.xml | 48 +++ .../layout/activity_recipe_step_detail.xml | 63 ++++ .../res/layout/baking_recipes_app_widget.xml | 42 +++ .../baking_recipes_app_widget_list_item.xml | 25 ++ app/src/main/res/layout/fragment_recipes.xml | 37 +++ app/src/main/res/layout/no_data_layout.xml | 62 ++++ .../layout/recipe_ingredient_list_item.xml | 72 +++++ app/src/main/res/layout/recipe_list_item.xml | 65 ++++ .../main/res/layout/recipe_step_detail.xml | 85 ++++++ app/src/main/res/layout/recipe_step_list.xml | 28 ++ .../main/res/layout/recipe_step_list_item.xml | 75 +++++ app/src/main/res/menu/recipe_info.xml | 24 ++ app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 4020 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2594 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 5405 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 8037 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 10907 bytes app/src/main/res/values-sw600dp/attr.xml | 19 ++ app/src/main/res/values-w900dp/attr.xml | 19 ++ app/src/main/res/values/attr.xml | 20 ++ app/src/main/res/values/colors.xml | 25 ++ app/src/main/res/values/dimens.xml | 33 +++ app/src/main/res/values/strings.xml | 31 ++ app/src/main/res/values/styles.xml | 41 +++ .../xml/baking_recipes_app_widget_info.xml | 23 ++ .../bakingrecipes/ExampleUnitTest.java | 33 +++ art/logo.png | Bin 0 -> 1354327 bytes build.gradle | 28 ++ gradle.properties | 13 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53636 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 160 ++++++++++ gradlew.bat | 90 ++++++ settings.gradle | 1 + 83 files changed, 4482 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/eu/dkaratzas/bakingrecipes/BakingRecipesTests.java create mode 100644 app/src/androidTest/java/eu/dkaratzas/bakingrecipes/utils/BaseTest.java create mode 100644 app/src/androidTest/java/eu/dkaratzas/bakingrecipes/utils/Navigation.java create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/ic_launcher-web.png create mode 100644 app/src/main/java/eu/dkaratzas/bakingrecipes/Constants.java create mode 100644 app/src/main/java/eu/dkaratzas/bakingrecipes/GlobalApplication.java create mode 100644 app/src/main/java/eu/dkaratzas/bakingrecipes/IdlingRecource/RecipesIdlingResource.java create mode 100644 app/src/main/java/eu/dkaratzas/bakingrecipes/Prefs.java create mode 100644 app/src/main/java/eu/dkaratzas/bakingrecipes/adapters/RecipeAdapter.java create mode 100644 app/src/main/java/eu/dkaratzas/bakingrecipes/adapters/RecipesAdapter.java create mode 100644 app/src/main/java/eu/dkaratzas/bakingrecipes/adapters/StepsFragmentPagerAdapter.java create mode 100644 app/src/main/java/eu/dkaratzas/bakingrecipes/api/RecipesApiCallback.java create mode 100644 app/src/main/java/eu/dkaratzas/bakingrecipes/api/RecipesApiManager.java create mode 100644 app/src/main/java/eu/dkaratzas/bakingrecipes/api/RecipesApiService.java create mode 100644 app/src/main/java/eu/dkaratzas/bakingrecipes/holders/IngredientsViewHolder.java create mode 100644 app/src/main/java/eu/dkaratzas/bakingrecipes/holders/RecipeViewHolder.java create mode 100644 app/src/main/java/eu/dkaratzas/bakingrecipes/holders/StepViewHolder.java create mode 100644 app/src/main/java/eu/dkaratzas/bakingrecipes/models/Ingredients.java create mode 100644 app/src/main/java/eu/dkaratzas/bakingrecipes/models/Recipe.java create mode 100644 app/src/main/java/eu/dkaratzas/bakingrecipes/models/Step.java create mode 100644 app/src/main/java/eu/dkaratzas/bakingrecipes/ui/Listeners.java create mode 100644 app/src/main/java/eu/dkaratzas/bakingrecipes/ui/activities/MainActivity.java create mode 100644 app/src/main/java/eu/dkaratzas/bakingrecipes/ui/activities/RecipeInfoActivity.java create mode 100644 app/src/main/java/eu/dkaratzas/bakingrecipes/ui/activities/RecipeStepDetailActivity.java create mode 100644 app/src/main/java/eu/dkaratzas/bakingrecipes/ui/fragments/RecipeStepDetailFragment.java create mode 100644 app/src/main/java/eu/dkaratzas/bakingrecipes/ui/fragments/RecipesFragment.java create mode 100644 app/src/main/java/eu/dkaratzas/bakingrecipes/utils/Misc.java create mode 100644 app/src/main/java/eu/dkaratzas/bakingrecipes/utils/RecipesAppGlideModule.java create mode 100644 app/src/main/java/eu/dkaratzas/bakingrecipes/utils/SpacingItemDecoration.java create mode 100644 app/src/main/java/eu/dkaratzas/bakingrecipes/widget/AppWidget.java create mode 100644 app/src/main/java/eu/dkaratzas/bakingrecipes/widget/AppWidgetService.java create mode 100644 app/src/main/java/eu/dkaratzas/bakingrecipes/widget/ListRemoteViewsFactory.java create mode 100644 app/src/main/res/drawable-nodpi/appwidget_preview.png create mode 100644 app/src/main/res/drawable/ic_arrow_point_to_right.xml create mode 100644 app/src/main/res/drawable/ic_dinner.xml create mode 100644 app/src/main/res/drawable/ic_recipe.xml create mode 100644 app/src/main/res/drawable/round_tv.xml create mode 100644 app/src/main/res/drawable/widget_background.xml create mode 100644 app/src/main/res/layout-land/recipe_step_detail.xml create mode 100644 app/src/main/res/layout-w900dp-land/recipe_step_detail.xml create mode 100644 app/src/main/res/layout-w900dp/recipe_step_list.xml create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/layout/activity_recipe_info.xml create mode 100644 app/src/main/res/layout/activity_recipe_step_detail.xml create mode 100644 app/src/main/res/layout/baking_recipes_app_widget.xml create mode 100644 app/src/main/res/layout/baking_recipes_app_widget_list_item.xml create mode 100644 app/src/main/res/layout/fragment_recipes.xml create mode 100644 app/src/main/res/layout/no_data_layout.xml create mode 100644 app/src/main/res/layout/recipe_ingredient_list_item.xml create mode 100644 app/src/main/res/layout/recipe_list_item.xml create mode 100644 app/src/main/res/layout/recipe_step_detail.xml create mode 100644 app/src/main/res/layout/recipe_step_list.xml create mode 100644 app/src/main/res/layout/recipe_step_list_item.xml create mode 100644 app/src/main/res/menu/recipe_info.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/values-sw600dp/attr.xml create mode 100644 app/src/main/res/values-w900dp/attr.xml create mode 100644 app/src/main/res/values/attr.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/dimens.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 app/src/main/res/xml/baking_recipes_app_widget_info.xml create mode 100644 app/src/test/java/eu/dkaratzas/bakingrecipes/ExampleUnitTest.java create mode 100644 art/logo.png create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ce2bdd6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,146 @@ + +# Created by https://www.gitignore.io/api/android,androidstudio + +### Android ### +# Built application files +app/release/ +*.apk +*.ap_ + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# Intellij +*.iml +.idea/ + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +### Android Patch ### +gen-external-apklibs + +### AndroidStudio ### +# Covers files to be ignored for android development using Android Studio. + +# Built application files + +# Files for the ART/Dalvik VM + +# Java class files + +# Generated files + +# Gradle files +.gradle + +# Signing files +.signing/ + +# Local configuration file (sdk path, etc) + +# Proguard folder generated by Eclipse + +# Log Files + +# Android Studio +/*/build/ +/*/local.properties +/*/out +/*/*/build +/*/*/production +*.ipr +*~ +*.swp + +# Android Patch + +# External native build folder generated in Android Studio 2.2 and later + +# NDK +obj/ + +# IntelliJ IDEA +*.iws +/out/ + +# OS-specific files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Legacy Eclipse project files +.classpath +.project +.cproject +.settings/ + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.war +*.ear + +# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) +hs_err_pid* + +## Plugin-specific files: + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Mongo Explorer plugin +.idea/mongoSettings.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### AndroidStudio Patch ### + +!/gradle/wrapper/gradle-wrapper.jar + + +# End of https://www.gitignore.io/api/android,androidstudio \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e64105d --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +![screen](../master/art/logo.png) + +The Baking Recipes Android app, was made as part of Udacity's [Android Developer Nanodegree Program](https://www.udacity.com/course/android-developer-nanodegree-by-google--nd801). +It allow Udacity’s resident baker-in-chief, Miriam, to share her recipes with the world. The user can select a recipe and see video-guided steps for how to complete it. +This app has adaptive UI for phone and tablet devices. +Also the user has the ability to add a recipe as a home screen widget. + +**Download:** + +You can download an APK build [on releases page](https://github.com/dnKaratzas/udacity-baking-recipes/releases/). + +Libraries +--------- +* [ExoPlayer](https://github.com/google/ExoPlayer) +* [Jackson](https://github.com/FasterXML/jacksont) +* [Retrofit](https://github.com/square/retrofit) +* [Butter Knife](https://github.com/JakeWharton/butterknife) +* [Logger](https://github.com/orhanobut/logger) +* [Glide](https://github.com/bumptech/glide) + +Icon credits +--------- +* Recipe by [Freepik](http://www.freepik.com) +* Dinner by [Freepik](http://www.freepik.com) + +License +------- +Copyright 2018 Dionysios Karatzas + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..4345233 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,65 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 27 + defaultConfig { + applicationId "eu.dkaratzas.bakingrecipes" + minSdkVersion 16 + targetSdkVersion 27 + versionCode 1 + versionName "1.0" + android.defaultConfig.vectorDrawables.useSupportLibrary = true + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support:appcompat-v7:27.1.0' + implementation 'com.android.support:recyclerview-v7:27.1.0' + implementation 'com.android.support:cardview-v7:27.1.0' + implementation 'com.android.support:design:27.1.0' + implementation 'com.android.support:support-v4:27.1.0' + implementation 'com.android.support.constraint:constraint-layout:1.0.2' + + implementation 'com.orhanobut:logger:2.1.1' + + // ExoPlayer + implementation 'com.google.android.exoplayer:exoplayer:2.6.1' + + // Jackson + implementation 'com.fasterxml.jackson.core:jackson-core:2.9.4' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.4' + + // Retrofit + implementation 'com.squareup.retrofit2:retrofit:2.3.0' + implementation 'com.squareup.retrofit2:converter-jackson:2.3.0' + + // Butter Knife + implementation 'com.jakewharton:butterknife:8.8.1' + annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' + + // Glide + implementation 'com.github.bumptech.glide:glide:4.6.1' + annotationProcessor 'com.github.bumptech.glide:compiler:4.6.1' + + // Testing + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.1' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' + androidTestImplementation 'com.android.support.test.espresso:espresso-intents:3.0.1' + androidTestImplementation('com.android.support.test.espresso:espresso-contrib:3.0.1') { + + exclude group: 'com.android.support', module: 'appcompat' + exclude group: 'com.android.support', module: 'support-v4' + exclude group: 'com.android.support', module: 'support-annotations' + exclude module: 'recyclerview-v7' + } + implementation 'com.android.support.test.espresso:espresso-idling-resource:3.0.1' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/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/app/src/androidTest/java/eu/dkaratzas/bakingrecipes/BakingRecipesTests.java b/app/src/androidTest/java/eu/dkaratzas/bakingrecipes/BakingRecipesTests.java new file mode 100644 index 0000000..7fb367f --- /dev/null +++ b/app/src/androidTest/java/eu/dkaratzas/bakingrecipes/BakingRecipesTests.java @@ -0,0 +1,115 @@ +/* + * Copyright 2018 Dionysios Karatzas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.dkaratzas.bakingrecipes; + +import android.content.Context; +import android.support.test.espresso.intent.Intents; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import eu.dkaratzas.bakingrecipes.models.Recipe; +import eu.dkaratzas.bakingrecipes.ui.activities.RecipeInfoActivity; +import eu.dkaratzas.bakingrecipes.ui.activities.RecipeStepDetailActivity; +import eu.dkaratzas.bakingrecipes.utils.BaseTest; +import eu.dkaratzas.bakingrecipes.utils.Navigation; + +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.intent.Intents.intended; +import static android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent; +import static android.support.test.espresso.intent.matcher.IntentMatchers.hasExtraWithKey; +import static android.support.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static junit.framework.Assert.assertNotNull; + +@RunWith(AndroidJUnit4.class) +public class BakingRecipesTests extends BaseTest { + + @Test + public void clickRecyclerViewItemHasIntentWithAKey() { + //Checks if the key is present + Intents.init(); + + Navigation.getMeToRecipeInfo(0); + intended(hasExtraWithKey(RecipeInfoActivity.RECIPE_KEY)); + + Intents.release(); + + } + + @Test + public void clickOnRecyclerViewItem_opensRecipeInfoActivity() { + + Navigation.getMeToRecipeInfo(0); + + onView(withId(R.id.ingredients_text)) + .check(matches(isDisplayed())); + + onView(withId(R.id.recipe_step_list)) + .check(matches(isDisplayed())); + } + + @Test + public void clickOnRecyclerViewStepItem_opensRecipeStepActivity_orFragment() { + Navigation.getMeToRecipeInfo(0); + + boolean twoPaneMode = globalApplication.getResources().getBoolean(R.bool.twoPaneMode); + if (!twoPaneMode) { + // Checks if the keys are present and the intent launched is RecipeStepDetailActivity + Intents.init(); + Navigation.selectRecipeStep(1); + intended(hasComponent(RecipeStepDetailActivity.class.getName())); + intended(hasExtraWithKey(RecipeStepDetailActivity.RECIPE_KEY)); + intended(hasExtraWithKey(RecipeStepDetailActivity.STEP_SELECTED_KEY)); + Intents.release(); + + // Check TabLayout + onView(withId(R.id.recipe_step_tab_layout)) + .check(matches(isCompletelyDisplayed())); + } else { + Navigation.selectRecipeStep(1); + + onView(withId(R.id.exo_player_view)) + .check(matches(isDisplayed())); + } + } + + @Test + public void checkAddWidgetButtonFunctionality() { + // Clear the preferences values + globalApplication.getSharedPreferences(Prefs.PREFS_NAME, Context.MODE_PRIVATE).edit() + .clear() + .commit(); + + Navigation.getMeToRecipeInfo(0); + + onView(withId(R.id.action_add_to_widget)) + .check(matches(isDisplayed())) + .perform(click()); + + // Get the recipe base64 string from the sharedPrefs + Recipe recipe = Prefs.loadRecipe(globalApplication); + + // Assert recipe is not null + assertNotNull(recipe); + } + +} diff --git a/app/src/androidTest/java/eu/dkaratzas/bakingrecipes/utils/BaseTest.java b/app/src/androidTest/java/eu/dkaratzas/bakingrecipes/utils/BaseTest.java new file mode 100644 index 0000000..165ed42 --- /dev/null +++ b/app/src/androidTest/java/eu/dkaratzas/bakingrecipes/utils/BaseTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2018 Dionysios Karatzas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.dkaratzas.bakingrecipes.utils; + +import android.support.test.espresso.IdlingRegistry; +import android.support.test.espresso.IdlingResource; +import android.support.test.rule.ActivityTestRule; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; + +import eu.dkaratzas.bakingrecipes.GlobalApplication; +import eu.dkaratzas.bakingrecipes.ui.activities.MainActivity; + +public abstract class BaseTest { + protected GlobalApplication globalApplication; + protected IdlingResource mIdlingResource; + + @Rule + public ActivityTestRule activityTestRule = new ActivityTestRule<>(MainActivity.class); + + @Before + public void registerIdlingResource() { + globalApplication = (GlobalApplication) activityTestRule.getActivity().getApplicationContext(); + mIdlingResource = globalApplication.getIdlingResource(); + // Register Idling Resources + IdlingRegistry.getInstance().register(mIdlingResource); + } + + @After + public void unregisterIdlingResource() { + if (mIdlingResource != null) { + IdlingRegistry.getInstance().unregister(mIdlingResource); + } + } +} diff --git a/app/src/androidTest/java/eu/dkaratzas/bakingrecipes/utils/Navigation.java b/app/src/androidTest/java/eu/dkaratzas/bakingrecipes/utils/Navigation.java new file mode 100644 index 0000000..3141086 --- /dev/null +++ b/app/src/androidTest/java/eu/dkaratzas/bakingrecipes/utils/Navigation.java @@ -0,0 +1,36 @@ +/* + * Copyright 2018 Dionysios Karatzas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package eu.dkaratzas.bakingrecipes.utils; + +import android.support.test.espresso.contrib.RecyclerViewActions; + +import eu.dkaratzas.bakingrecipes.R; + +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.matcher.ViewMatchers.withId; + +public class Navigation { + public static void getMeToRecipeInfo(int recipePosition) { + onView(withId(R.id.recipes_recycler_view)) + .perform(RecyclerViewActions.actionOnItemAtPosition(recipePosition, click())); + } + + public static void selectRecipeStep(int recipeStep) { + onView(withId(R.id.recipe_step_list)) + .perform(RecyclerViewActions.actionOnItemAtPosition(recipeStep, click())); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..66d7579 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/ic_launcher-web.png b/app/src/main/ic_launcher-web.png new file mode 100644 index 0000000000000000000000000000000000000000..1dc6c66c7f871f0ad8d20d4afd79bbf6582e91f4 GIT binary patch literal 16083 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 f8@>5Kq?J`%P-`$9wQ}2HaHFQ7clhdG`MSq}9GK literal 0 HcmV?d00001 diff --git a/app/src/main/java/eu/dkaratzas/bakingrecipes/Constants.java b/app/src/main/java/eu/dkaratzas/bakingrecipes/Constants.java new file mode 100644 index 0000000..9364623 --- /dev/null +++ b/app/src/main/java/eu/dkaratzas/bakingrecipes/Constants.java @@ -0,0 +1,21 @@ +/* + * Copyright 2018 Dionysios Karatzas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.dkaratzas.bakingrecipes; + +public class Constants { + public static final String RECIPES_API_URL = "https://d17h27t6h515a5.cloudfront.net/"; +} diff --git a/app/src/main/java/eu/dkaratzas/bakingrecipes/GlobalApplication.java b/app/src/main/java/eu/dkaratzas/bakingrecipes/GlobalApplication.java new file mode 100644 index 0000000..0ea358b --- /dev/null +++ b/app/src/main/java/eu/dkaratzas/bakingrecipes/GlobalApplication.java @@ -0,0 +1,73 @@ +/* + * Copyright 2018 Dionysios Karatzas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.dkaratzas.bakingrecipes; + +import android.app.Application; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; +import android.support.test.espresso.IdlingResource; + +import com.orhanobut.logger.AndroidLogAdapter; +import com.orhanobut.logger.Logger; + +import eu.dkaratzas.bakingrecipes.IdlingRecource.RecipesIdlingResource; + +public class GlobalApplication extends Application { + // The Idling Resource which will be null in production. + @Nullable + private RecipesIdlingResource mIdlingResource; + + /** + * Only called from test, creates and returns a new {@link RecipesIdlingResource}. + */ + @VisibleForTesting + @NonNull + private IdlingResource initializeIdlingResource() { + if (mIdlingResource == null) { + mIdlingResource = new RecipesIdlingResource(); + } + return mIdlingResource; + } + + public GlobalApplication() { + + // The IdlingResource will be null in production. + if (BuildConfig.DEBUG) { + initializeIdlingResource(); + } + + Logger.addLogAdapter(new AndroidLogAdapter() { + @Override + public boolean isLoggable(int priority, String tag) { + // Enable logging only on debug + return BuildConfig.DEBUG; + } + }); + + } + + public void setIdleState(boolean state) { + if (mIdlingResource != null) + mIdlingResource.setIdleState(state); + } + + @Nullable + public RecipesIdlingResource getIdlingResource() { + return mIdlingResource; + } +} diff --git a/app/src/main/java/eu/dkaratzas/bakingrecipes/IdlingRecource/RecipesIdlingResource.java b/app/src/main/java/eu/dkaratzas/bakingrecipes/IdlingRecource/RecipesIdlingResource.java new file mode 100644 index 0000000..648642e --- /dev/null +++ b/app/src/main/java/eu/dkaratzas/bakingrecipes/IdlingRecource/RecipesIdlingResource.java @@ -0,0 +1,52 @@ +/* + * Copyright 2018 Dionysios Karatzas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.dkaratzas.bakingrecipes.IdlingRecource; + +import android.support.annotation.Nullable; +import android.support.test.espresso.IdlingResource; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class RecipesIdlingResource implements IdlingResource { + + @Nullable + private volatile ResourceCallback callback; + + private AtomicBoolean isIdleNow = new AtomicBoolean(true); + + @Override + public String getName() { + return this.getClass().getName(); + } + + @Override + public boolean isIdleNow() { + return isIdleNow.get(); + } + + @Override + public void registerIdleTransitionCallback(ResourceCallback callback) { + this.callback = callback; + } + + public void setIdleState(boolean idleState) { + isIdleNow.set(idleState); + if (idleState && callback != null) { + callback.onTransitionToIdle(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/dkaratzas/bakingrecipes/Prefs.java b/app/src/main/java/eu/dkaratzas/bakingrecipes/Prefs.java new file mode 100644 index 0000000..9d8409a --- /dev/null +++ b/app/src/main/java/eu/dkaratzas/bakingrecipes/Prefs.java @@ -0,0 +1,40 @@ +/* + * Copyright 2018 Dionysios Karatzas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.dkaratzas.bakingrecipes; + +import android.content.Context; +import android.content.SharedPreferences; + +import eu.dkaratzas.bakingrecipes.models.Recipe; + +public class Prefs { + public static final String PREFS_NAME = "prefs"; + + public static void saveRecipe(Context context, Recipe recipe) { + SharedPreferences.Editor prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit(); + prefs.putString(context.getString(R.string.widget_recipe_key), Recipe.toBase64String(recipe)); + + prefs.commit(); + } + + public static Recipe loadRecipe(Context context) { + SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); + String recipeBase64 = prefs.getString(context.getString(R.string.widget_recipe_key), ""); + + return "".equals(recipeBase64) ? null : Recipe.fromBase64(prefs.getString(context.getString(R.string.widget_recipe_key), "")); + } +} diff --git a/app/src/main/java/eu/dkaratzas/bakingrecipes/adapters/RecipeAdapter.java b/app/src/main/java/eu/dkaratzas/bakingrecipes/adapters/RecipeAdapter.java new file mode 100644 index 0000000..7eef5a6 --- /dev/null +++ b/app/src/main/java/eu/dkaratzas/bakingrecipes/adapters/RecipeAdapter.java @@ -0,0 +1,100 @@ +/* + * Copyright 2018 Dionysios Karatzas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.dkaratzas.bakingrecipes.adapters; + +import android.annotation.SuppressLint; +import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import java.util.Locale; + +import eu.dkaratzas.bakingrecipes.R; +import eu.dkaratzas.bakingrecipes.holders.IngredientsViewHolder; +import eu.dkaratzas.bakingrecipes.holders.StepViewHolder; +import eu.dkaratzas.bakingrecipes.models.Ingredients; +import eu.dkaratzas.bakingrecipes.models.Recipe; +import eu.dkaratzas.bakingrecipes.ui.Listeners; + +public class RecipeAdapter extends RecyclerView.Adapter { + private Recipe mRecipe; + private Listeners.OnItemClickListener mOnItemClickListener; + + public RecipeAdapter(Recipe recipe, Listeners.OnItemClickListener onItemClickListener) { + this.mRecipe = recipe; + this.mOnItemClickListener = onItemClickListener; + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + if (viewType == 0) { // Ingredients + return new IngredientsViewHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.recipe_ingredient_list_item, parent, false)); + } else { // Steps + return new StepViewHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.recipe_step_list_item, parent, false)); + } + + } + + @SuppressLint("RecyclerView") + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, final int position) { + if (holder instanceof IngredientsViewHolder) { + IngredientsViewHolder viewHolder = (IngredientsViewHolder) holder; + + StringBuilder ingValue = new StringBuilder(); + for (int i = 0; i < mRecipe.getIngredients().size(); i++) { + Ingredients ingredients = mRecipe.getIngredients().get(i); + ingValue.append(String.format(Locale.getDefault(), "• %s (%d %s)", ingredients.getIngredient(), ingredients.getQuantity(), ingredients.getMeasure())); + if (i != mRecipe.getIngredients().size() - 1) + ingValue.append("\n"); + } + + viewHolder.mTvIngredients.setText(ingValue.toString()); + } else if (holder instanceof StepViewHolder) { + StepViewHolder viewHolder = (StepViewHolder) holder; + viewHolder.mTvStepOrder.setText(String.valueOf(position - 1) + "."); + viewHolder.mTvStepName.setText(mRecipe.getSteps().get(position - 1).getShortDescription()); + + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mOnItemClickListener != null) + mOnItemClickListener.onItemClick(position - 1); + } + }); + } + } + + + @Override + public int getItemViewType(int position) { + if (position == 0) + return 0; + else + return 1; + } + + @Override + public int getItemCount() { + return mRecipe.getSteps().size() + 1; + } +} diff --git a/app/src/main/java/eu/dkaratzas/bakingrecipes/adapters/RecipesAdapter.java b/app/src/main/java/eu/dkaratzas/bakingrecipes/adapters/RecipesAdapter.java new file mode 100644 index 0000000..35e6a8d --- /dev/null +++ b/app/src/main/java/eu/dkaratzas/bakingrecipes/adapters/RecipesAdapter.java @@ -0,0 +1,74 @@ +/* + * Copyright 2018 Dionysios Karatzas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.dkaratzas.bakingrecipes.adapters; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import java.util.List; + +import eu.dkaratzas.bakingrecipes.R; +import eu.dkaratzas.bakingrecipes.holders.RecipeViewHolder; +import eu.dkaratzas.bakingrecipes.models.Recipe; +import eu.dkaratzas.bakingrecipes.ui.Listeners; + +public class RecipesAdapter extends RecyclerView.Adapter { + private Context mContext; + private List mRecipes; + private Listeners.OnItemClickListener mOnItemClickListener; + + public RecipesAdapter(Context context, List recipes, Listeners.OnItemClickListener onItemClickListener) { + this.mContext = context; + this.mRecipes = recipes; + this.mOnItemClickListener = onItemClickListener; + } + + @NonNull + @Override + public RecipeViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.recipe_list_item, parent, false); + + return new RecipeViewHolder(view); + } + + @SuppressLint("RecyclerView") + @Override + public void onBindViewHolder(@NonNull RecipeViewHolder holder, final int position) { + holder.mTvRecipeName.setText(mRecipes.get(position).getName()); + holder.mTvServings.setText(mContext.getString(R.string.servings, mRecipes.get(position).getServings())); + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mOnItemClickListener != null) + mOnItemClickListener.onItemClick(position); + } + }); + } + + @Override + public int getItemCount() { + return mRecipes.size(); + } + + +} diff --git a/app/src/main/java/eu/dkaratzas/bakingrecipes/adapters/StepsFragmentPagerAdapter.java b/app/src/main/java/eu/dkaratzas/bakingrecipes/adapters/StepsFragmentPagerAdapter.java new file mode 100644 index 0000000..7a9d0f0 --- /dev/null +++ b/app/src/main/java/eu/dkaratzas/bakingrecipes/adapters/StepsFragmentPagerAdapter.java @@ -0,0 +1,64 @@ +/* + * Copyright 2018 Dionysios Karatzas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.dkaratzas.bakingrecipes.adapters; + +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentPagerAdapter; + +import java.util.List; + +import eu.dkaratzas.bakingrecipes.R; +import eu.dkaratzas.bakingrecipes.models.Step; +import eu.dkaratzas.bakingrecipes.ui.fragments.RecipeStepDetailFragment; + +public class StepsFragmentPagerAdapter extends FragmentPagerAdapter { + private Context mContext; + private List mSteps; + + public StepsFragmentPagerAdapter(Context context, List steps, FragmentManager fm) { + super(fm); + this.mContext = context; + this.mSteps = steps; + } + + @Override + public Fragment getItem(int position) { + Bundle arguments = new Bundle(); + arguments.putParcelable(RecipeStepDetailFragment.STEP_KEY, mSteps.get(position)); + RecipeStepDetailFragment fragment = new RecipeStepDetailFragment(); + fragment.setArguments(arguments); + + return fragment; + } + + @Nullable + @Override + public CharSequence getPageTitle(int position) { + return String.format(mContext.getString(R.string.step), position); + } + + @Override + public int getCount() { + return mSteps.size(); + } + + +} diff --git a/app/src/main/java/eu/dkaratzas/bakingrecipes/api/RecipesApiCallback.java b/app/src/main/java/eu/dkaratzas/bakingrecipes/api/RecipesApiCallback.java new file mode 100644 index 0000000..b96bc1e --- /dev/null +++ b/app/src/main/java/eu/dkaratzas/bakingrecipes/api/RecipesApiCallback.java @@ -0,0 +1,24 @@ +/* + * Copyright 2018 Dionysios Karatzas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.dkaratzas.bakingrecipes.api; + + +public interface RecipesApiCallback { + void onResponse(T result); + + void onCancel(); +} diff --git a/app/src/main/java/eu/dkaratzas/bakingrecipes/api/RecipesApiManager.java b/app/src/main/java/eu/dkaratzas/bakingrecipes/api/RecipesApiManager.java new file mode 100644 index 0000000..83447ec --- /dev/null +++ b/app/src/main/java/eu/dkaratzas/bakingrecipes/api/RecipesApiManager.java @@ -0,0 +1,82 @@ +/* + * Copyright 2018 Dionysios Karatzas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.dkaratzas.bakingrecipes.api; + +import com.orhanobut.logger.Logger; + +import java.io.Serializable; +import java.util.List; + +import eu.dkaratzas.bakingrecipes.Constants; +import eu.dkaratzas.bakingrecipes.models.Recipe; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +import retrofit2.Retrofit; +import retrofit2.converter.jackson.JacksonConverterFactory; + +public final class RecipesApiManager implements Serializable { + + private static volatile RecipesApiManager sharedInstance = new RecipesApiManager(); + private RecipesApiService recipesApiService; + + private RecipesApiManager() { + //Prevent from the reflection api. + if (sharedInstance != null) { + throw new RuntimeException("Use getInstance() method to get the single instance of this class."); + } + + Retrofit retrofit = new Retrofit.Builder() + .baseUrl(Constants.RECIPES_API_URL) + .addConverterFactory(JacksonConverterFactory.create()) + .build(); + + recipesApiService = retrofit.create(RecipesApiService.class); + } + + public static RecipesApiManager getInstance() { + if (sharedInstance == null) { + synchronized (RecipesApiManager.class) { + if (sharedInstance == null) sharedInstance = new RecipesApiManager(); + } + } + + return sharedInstance; + } + + public void getRecipes(final RecipesApiCallback> recipesApiCallback) { + recipesApiService.getRecipes().enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + recipesApiCallback.onResponse(response.body()); + } + + @Override + public void onFailure(Call> call, Throwable t) { + if (call.isCanceled()) { + Logger.e("Request was cancelled"); + recipesApiCallback.onCancel(); + } else { + Logger.e(t.getMessage()); + recipesApiCallback.onResponse(null); + } + } + }); + } + +} + diff --git a/app/src/main/java/eu/dkaratzas/bakingrecipes/api/RecipesApiService.java b/app/src/main/java/eu/dkaratzas/bakingrecipes/api/RecipesApiService.java new file mode 100644 index 0000000..86f880c --- /dev/null +++ b/app/src/main/java/eu/dkaratzas/bakingrecipes/api/RecipesApiService.java @@ -0,0 +1,30 @@ +/* + * Copyright 2018 Dionysios Karatzas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.dkaratzas.bakingrecipes.api; + +import java.util.List; + +import eu.dkaratzas.bakingrecipes.models.Recipe; +import retrofit2.Call; +import retrofit2.http.GET; + +interface RecipesApiService { + + @GET("topher/2017/May/59121517_baking/baking.json") + Call> getRecipes(); + +} diff --git a/app/src/main/java/eu/dkaratzas/bakingrecipes/holders/IngredientsViewHolder.java b/app/src/main/java/eu/dkaratzas/bakingrecipes/holders/IngredientsViewHolder.java new file mode 100644 index 0000000..7e215c3 --- /dev/null +++ b/app/src/main/java/eu/dkaratzas/bakingrecipes/holders/IngredientsViewHolder.java @@ -0,0 +1,38 @@ +/* + * Copyright 2018 Dionysios Karatzas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.dkaratzas.bakingrecipes.holders; + +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.TextView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import eu.dkaratzas.bakingrecipes.R; + +public class IngredientsViewHolder extends RecyclerView.ViewHolder { + @BindView(R.id.ingredients_text) + public TextView mTvIngredients; + + public IngredientsViewHolder(View itemView) { + super(itemView); + + ButterKnife.bind(this, itemView); + + } + +} diff --git a/app/src/main/java/eu/dkaratzas/bakingrecipes/holders/RecipeViewHolder.java b/app/src/main/java/eu/dkaratzas/bakingrecipes/holders/RecipeViewHolder.java new file mode 100644 index 0000000..b30e9d9 --- /dev/null +++ b/app/src/main/java/eu/dkaratzas/bakingrecipes/holders/RecipeViewHolder.java @@ -0,0 +1,40 @@ +/* + * Copyright 2018 Dionysios Karatzas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.dkaratzas.bakingrecipes.holders; + +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.TextView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import eu.dkaratzas.bakingrecipes.R; + +public class RecipeViewHolder extends RecyclerView.ViewHolder { + @BindView(R.id.recipe_name_text) + public TextView mTvRecipeName; + + @BindView(R.id.servings_text) + public TextView mTvServings; + + public RecipeViewHolder(View itemView) { + super(itemView); + + ButterKnife.bind(this, itemView); + } + +} diff --git a/app/src/main/java/eu/dkaratzas/bakingrecipes/holders/StepViewHolder.java b/app/src/main/java/eu/dkaratzas/bakingrecipes/holders/StepViewHolder.java new file mode 100644 index 0000000..7636eb1 --- /dev/null +++ b/app/src/main/java/eu/dkaratzas/bakingrecipes/holders/StepViewHolder.java @@ -0,0 +1,40 @@ +/* + * Copyright 2018 Dionysios Karatzas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.dkaratzas.bakingrecipes.holders; + +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.TextView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import eu.dkaratzas.bakingrecipes.R; + +public class StepViewHolder extends RecyclerView.ViewHolder { + @BindView(R.id.step_order_text) + public TextView mTvStepOrder; + + @BindView(R.id.step_name_text) + public TextView mTvStepName; + + public StepViewHolder(View itemView) { + super(itemView); + + ButterKnife.bind(this, itemView); + } + +} diff --git a/app/src/main/java/eu/dkaratzas/bakingrecipes/models/Ingredients.java b/app/src/main/java/eu/dkaratzas/bakingrecipes/models/Ingredients.java new file mode 100644 index 0000000..f9b2056 --- /dev/null +++ b/app/src/main/java/eu/dkaratzas/bakingrecipes/models/Ingredients.java @@ -0,0 +1,91 @@ +/* + * Copyright 2018 Dionysios Karatzas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.dkaratzas.bakingrecipes.models; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.fasterxml.jackson.annotation.JsonProperty; + + +public class Ingredients implements Parcelable { + @JsonProperty("quantity") + private int quantity; + @JsonProperty("measure") + private String measure; + @JsonProperty("ingredient") + private String ingredient; + + public Ingredients() { + this.quantity = 0; + this.measure = ""; + this.ingredient = ""; + } + + // Parcelable + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(this.quantity); + dest.writeString(this.measure); + dest.writeString(this.ingredient); + } + + protected Ingredients(Parcel in) { + this.quantity = in.readInt(); + this.measure = in.readString(); + this.ingredient = in.readString(); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public Ingredients createFromParcel(Parcel source) { + return new Ingredients(source); + } + + @Override + public Ingredients[] newArray(int size) { + return new Ingredients[size]; + } + }; + + // Getters + public int getQuantity() { + return quantity; + } + + public String getMeasure() { + return measure; + } + + public String getIngredient() { + return ingredient; + } + + @Override + public String toString() { + return "Ingredients{" + + "quantity=" + quantity + + ", measure='" + measure + '\'' + + ", ingredient='" + ingredient + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/dkaratzas/bakingrecipes/models/Recipe.java b/app/src/main/java/eu/dkaratzas/bakingrecipes/models/Recipe.java new file mode 100644 index 0000000..78dd640 --- /dev/null +++ b/app/src/main/java/eu/dkaratzas/bakingrecipes/models/Recipe.java @@ -0,0 +1,152 @@ +/* + * Copyright 2018 Dionysios Karatzas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.dkaratzas.bakingrecipes.models; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Base64; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.orhanobut.logger.Logger; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class Recipe implements Parcelable { + @JsonProperty("image") + private String image; + @JsonProperty("servings") + private int servings; + @JsonProperty("name") + private String name; + @JsonProperty("ingredients") + private List ingredients; + @JsonProperty("id") + private int id; + @JsonProperty("steps") + private List steps; + + public Recipe() { + this.image = ""; + this.servings = 0; + this.name = ""; + this.ingredients = new ArrayList<>(); + this.id = 0; + this.steps = new ArrayList<>(); + } + + // Parcelable + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.image); + dest.writeInt(this.servings); + dest.writeString(this.name); + dest.writeList(this.ingredients); + dest.writeInt(this.id); + dest.writeList(this.steps); + } + + protected Recipe(Parcel in) { + this.image = in.readString(); + this.servings = in.readInt(); + this.name = in.readString(); + this.ingredients = new ArrayList<>(); + in.readList(this.ingredients, Ingredients.class.getClassLoader()); + this.id = in.readInt(); + this.steps = new ArrayList<>(); + in.readList(this.steps, Step.class.getClassLoader()); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public Recipe createFromParcel(Parcel source) { + return new Recipe(source); + } + + @Override + public Recipe[] newArray(int size) { + return new Recipe[size]; + } + }; + + // Getters + public String getImage() { + return image; + } + + public int getServings() { + return servings; + } + + public String getName() { + return name; + } + + public List getIngredients() { + return ingredients; + } + + public int getId() { + return id; + } + + public List getSteps() { + return steps; + } + + public static String toBase64String(Recipe recipe) { + ObjectMapper mapper = new ObjectMapper(); + try { + return Base64.encodeToString(mapper.writeValueAsBytes(recipe), 0); + } catch (JsonProcessingException e) { + Logger.e(e.getMessage()); + } + return null; + } + + public static Recipe fromBase64(String encoded) { + if (!"".equals(encoded)) { + ObjectMapper mapper = new ObjectMapper(); + try { + return mapper.readValue(Base64.decode(encoded, 0), Recipe.class); + } catch (IOException e) { + Logger.e(e.getMessage()); + } + } + return null; + } + + @Override + public String toString() { + return "Recipe{" + + "image='" + image + '\'' + + ", servings=" + servings + + ", name='" + name + '\'' + + ", ingredients=" + ingredients + + ", id=" + id + + ", steps=" + steps + + '}'; + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/dkaratzas/bakingrecipes/models/Step.java b/app/src/main/java/eu/dkaratzas/bakingrecipes/models/Step.java new file mode 100644 index 0000000..bce0d8b --- /dev/null +++ b/app/src/main/java/eu/dkaratzas/bakingrecipes/models/Step.java @@ -0,0 +1,111 @@ +/* + * Copyright 2018 Dionysios Karatzas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.dkaratzas.bakingrecipes.models; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.fasterxml.jackson.annotation.JsonProperty; + + +public class Step implements Parcelable { + @JsonProperty("videoURL") + private String videoURL; + @JsonProperty("description") + private String description; + @JsonProperty("id") + private int id; + @JsonProperty("shortDescription") + private String shortDescription; + @JsonProperty("thumbnailURL") + private String thumbnailURL; + + public Step() { + this.videoURL = ""; + this.description = ""; + this.id = 0; + this.shortDescription = ""; + this.thumbnailURL = ""; + } + + // Parcelable + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.videoURL); + dest.writeString(this.description); + dest.writeInt(this.id); + dest.writeString(this.shortDescription); + dest.writeString(this.thumbnailURL); + } + + protected Step(Parcel in) { + this.videoURL = in.readString(); + this.description = in.readString(); + this.id = in.readInt(); + this.shortDescription = in.readString(); + this.thumbnailURL = in.readString(); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public Step createFromParcel(Parcel source) { + return new Step(source); + } + + @Override + public Step[] newArray(int size) { + return new Step[size]; + } + }; + + // Getters + public String getVideoURL() { + return videoURL; + } + + public String getDescription() { + return description; + } + + public int getId() { + return id; + } + + public String getShortDescription() { + return shortDescription; + } + + public String getThumbnailURL() { + return thumbnailURL; + } + + @Override + public String toString() { + return "Step{" + + "videoURL='" + videoURL + '\'' + + ", description='" + description + '\'' + + ", id=" + id + + ", shortDescription='" + shortDescription + '\'' + + ", thumbnailURL='" + thumbnailURL + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/dkaratzas/bakingrecipes/ui/Listeners.java b/app/src/main/java/eu/dkaratzas/bakingrecipes/ui/Listeners.java new file mode 100644 index 0000000..e6aac78 --- /dev/null +++ b/app/src/main/java/eu/dkaratzas/bakingrecipes/ui/Listeners.java @@ -0,0 +1,24 @@ +/* + * Copyright 2018 Dionysios Karatzas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.dkaratzas.bakingrecipes.ui; + + +public final class Listeners { + public interface OnItemClickListener { + void onItemClick(int position); + } +} diff --git a/app/src/main/java/eu/dkaratzas/bakingrecipes/ui/activities/MainActivity.java b/app/src/main/java/eu/dkaratzas/bakingrecipes/ui/activities/MainActivity.java new file mode 100644 index 0000000..a1664fb --- /dev/null +++ b/app/src/main/java/eu/dkaratzas/bakingrecipes/ui/activities/MainActivity.java @@ -0,0 +1,42 @@ +/* + * Copyright 2018 Dionysios Karatzas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.dkaratzas.bakingrecipes.ui.activities; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; + +import eu.dkaratzas.bakingrecipes.R; +import eu.dkaratzas.bakingrecipes.models.Recipe; +import eu.dkaratzas.bakingrecipes.ui.fragments.RecipesFragment; + +public class MainActivity extends AppCompatActivity implements RecipesFragment.OnRecipeClickListener { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + } + + @Override + public void onRecipeSelected(Recipe recipe) { + Intent intent = new Intent(this, RecipeInfoActivity.class); + intent.putExtra(RecipeInfoActivity.RECIPE_KEY, recipe); + startActivity(intent); + } + +} diff --git a/app/src/main/java/eu/dkaratzas/bakingrecipes/ui/activities/RecipeInfoActivity.java b/app/src/main/java/eu/dkaratzas/bakingrecipes/ui/activities/RecipeInfoActivity.java new file mode 100644 index 0000000..3347782 --- /dev/null +++ b/app/src/main/java/eu/dkaratzas/bakingrecipes/ui/activities/RecipeInfoActivity.java @@ -0,0 +1,157 @@ +/* + * Copyright 2018 Dionysios Karatzas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.dkaratzas.bakingrecipes.ui.activities; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.Toolbar; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.widget.Toast; + +import com.orhanobut.logger.Logger; + +import butterknife.BindView; +import butterknife.ButterKnife; +import eu.dkaratzas.bakingrecipes.R; +import eu.dkaratzas.bakingrecipes.adapters.RecipeAdapter; +import eu.dkaratzas.bakingrecipes.models.Recipe; +import eu.dkaratzas.bakingrecipes.ui.Listeners; +import eu.dkaratzas.bakingrecipes.ui.fragments.RecipeStepDetailFragment; +import eu.dkaratzas.bakingrecipes.utils.SpacingItemDecoration; +import eu.dkaratzas.bakingrecipes.widget.AppWidgetService; + +/** + * An activity representing a list of RecipeSteps. This activity + * has different presentations for handset and tablet-size devices. On + * handsets, the activity presents a list of items, which when touched, + * lead to a {@link RecipeStepDetailActivity} representing + * item details. On tablets, the activity presents the list of items and + * item details side-by-side using two vertical panes. + */ +public class RecipeInfoActivity extends AppCompatActivity { + public static final String RECIPE_KEY = "recipe_k"; + + @BindView(R.id.recipe_step_list) + RecyclerView mRecyclerView; + + private boolean mTwoPane; + + private Recipe mRecipe; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Bundle bundle = getIntent().getExtras(); + if (bundle != null && bundle.containsKey(RECIPE_KEY)) { + mRecipe = bundle.getParcelable(RECIPE_KEY); + } else { + Toast.makeText(getApplicationContext(), R.string.failed_to_load_recipe, Toast.LENGTH_SHORT).show(); + finish(); + } + + setContentView(R.layout.activity_recipe_info); + ButterKnife.bind(this); + + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + + // Show the Up button in the action bar and set recipes name as title. + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setTitle(mRecipe.getName()); + actionBar.setDisplayHomeAsUpEnabled(true); + } + + if (findViewById(R.id.recipe_step_detail_container) != null) { + // The detail container view will be present only in the + // large-screen layouts (res/values-w900dp). + // If this view is present, then the + // activity should be in two-pane mode. + mTwoPane = true; + + // If there is no fragment state and the recipe contains steps, show the 1st one + if (savedInstanceState == null && !mRecipe.getSteps().isEmpty()) { + showStep(0); + } + } + + setupRecyclerView(); + } + + @Override + public boolean onSupportNavigateUp() { + onBackPressed(); + return true; + } + + @Override + protected void onDestroy() { + super.onDestroy(); + Logger.d("onDestroy"); + } + + private void setupRecyclerView() { + mRecyclerView.addItemDecoration(new SpacingItemDecoration((int) getResources().getDimension(R.dimen.margin_medium))); + mRecyclerView.setAdapter(new RecipeAdapter(mRecipe, new Listeners.OnItemClickListener() { + @Override + public void onItemClick(int position) { + showStep(position); + } + })); + } + + private void showStep(int position) { + if (mTwoPane) { + Bundle arguments = new Bundle(); + arguments.putParcelable(RecipeStepDetailFragment.STEP_KEY, mRecipe.getSteps().get(position)); + RecipeStepDetailFragment fragment = new RecipeStepDetailFragment(); + fragment.setArguments(arguments); + getSupportFragmentManager().beginTransaction() + .replace(R.id.recipe_step_detail_container, fragment) + .commit(); + } else { + Intent intent = new Intent(this, RecipeStepDetailActivity.class); + intent.putExtra(RecipeStepDetailActivity.RECIPE_KEY, mRecipe); + intent.putExtra(RecipeStepDetailActivity.STEP_SELECTED_KEY, position); + startActivity(intent); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.recipe_info, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.action_add_to_widget) { + AppWidgetService.updateWidget(this, mRecipe); + Toast.makeText(this, String.format(getString(R.string.added_to_widget), mRecipe.getName()), Toast.LENGTH_SHORT).show(); + return true; + } else + return super.onOptionsItemSelected(item); + } +} diff --git a/app/src/main/java/eu/dkaratzas/bakingrecipes/ui/activities/RecipeStepDetailActivity.java b/app/src/main/java/eu/dkaratzas/bakingrecipes/ui/activities/RecipeStepDetailActivity.java new file mode 100644 index 0000000..a02b0cc --- /dev/null +++ b/app/src/main/java/eu/dkaratzas/bakingrecipes/ui/activities/RecipeStepDetailActivity.java @@ -0,0 +1,116 @@ +/* + * Copyright 2018 Dionysios Karatzas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.dkaratzas.bakingrecipes.ui.activities; + +import android.os.Bundle; +import android.support.design.widget.TabLayout; +import android.support.v4.view.ViewPager; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.widget.Toast; + +import com.orhanobut.logger.Logger; + +import butterknife.BindView; +import butterknife.ButterKnife; +import eu.dkaratzas.bakingrecipes.R; +import eu.dkaratzas.bakingrecipes.adapters.StepsFragmentPagerAdapter; +import eu.dkaratzas.bakingrecipes.models.Recipe; + +/** + * An activity representing a single RecipeStep detail screen. This + * activity is only used on narrow width devices. On tablet-size devices, + * item details are presented side-by-side with a list of items + * in a {@link RecipeInfoActivity}. + */ +public class RecipeStepDetailActivity extends AppCompatActivity { + @BindView(R.id.recipe_step_tab_layout) + TabLayout mTlRecipeStep; + @BindView(R.id.recipe_step_viewpager) + ViewPager mVpRecipeStep; + + private Recipe mRecipe; + private int mStepSelectedPosition; + + public static final String RECIPE_KEY = "recipe_k"; + public static final String STEP_SELECTED_KEY = "step_k"; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_recipe_step_detail); + + ButterKnife.bind(this); + + Toolbar toolbar = findViewById(R.id.detail_toolbar); + setSupportActionBar(toolbar); + + Bundle bundle = getIntent().getExtras(); + if (bundle != null && bundle.containsKey(RECIPE_KEY) && bundle.containsKey(STEP_SELECTED_KEY)) { + mRecipe = bundle.getParcelable(RECIPE_KEY); + mStepSelectedPosition = bundle.getInt(STEP_SELECTED_KEY); + } else { + Toast.makeText(getApplicationContext(), R.string.failed_to_load_recipe, Toast.LENGTH_SHORT).show(); + finish(); + } + + // Show the Up button in the action bar. + final ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setTitle(mRecipe.getName()); + actionBar.setDisplayHomeAsUpEnabled(true); + } + + StepsFragmentPagerAdapter adapter = new StepsFragmentPagerAdapter(getApplicationContext(), mRecipe.getSteps(), getSupportFragmentManager()); + mVpRecipeStep.setAdapter(adapter); + mTlRecipeStep.setupWithViewPager(mVpRecipeStep); + mVpRecipeStep.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + + } + + @Override + public void onPageSelected(int position) { + if (actionBar != null) { + actionBar.setTitle(mRecipe.getSteps().get(position).getShortDescription()); + } + } + + @Override + public void onPageScrollStateChanged(int state) { + + } + }); + mVpRecipeStep.setCurrentItem(mStepSelectedPosition); + } + + @Override + public boolean onSupportNavigateUp() { + onBackPressed(); + return true; + } + + @Override + protected void onDestroy() { + super.onDestroy(); + Logger.d("onDestroy"); + } + +} diff --git a/app/src/main/java/eu/dkaratzas/bakingrecipes/ui/fragments/RecipeStepDetailFragment.java b/app/src/main/java/eu/dkaratzas/bakingrecipes/ui/fragments/RecipeStepDetailFragment.java new file mode 100644 index 0000000..f1c45b9 --- /dev/null +++ b/app/src/main/java/eu/dkaratzas/bakingrecipes/ui/fragments/RecipeStepDetailFragment.java @@ -0,0 +1,181 @@ +/* + * Copyright 2018 Dionysios Karatzas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.dkaratzas.bakingrecipes.ui.fragments; + +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.widget.NestedScrollView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.google.android.exoplayer2.ExoPlayerFactory; +import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.source.ExtractorMediaSource; +import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection; +import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; +import com.google.android.exoplayer2.trackselection.TrackSelection; +import com.google.android.exoplayer2.trackselection.TrackSelector; +import com.google.android.exoplayer2.ui.SimpleExoPlayerView; +import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; +import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; +import com.google.android.exoplayer2.util.Util; +import com.orhanobut.logger.Logger; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; +import eu.dkaratzas.bakingrecipes.R; +import eu.dkaratzas.bakingrecipes.models.Step; +import eu.dkaratzas.bakingrecipes.ui.activities.RecipeInfoActivity; +import eu.dkaratzas.bakingrecipes.ui.activities.RecipeStepDetailActivity; +import eu.dkaratzas.bakingrecipes.utils.GlideApp; + +/** + * A fragment representing a single RecipeStep detail screen. + * This fragment is either contained in a {@link RecipeInfoActivity} + * in two-pane mode (on tablets) or a {@link RecipeStepDetailActivity} + * on handsets. + */ +public class RecipeStepDetailFragment extends Fragment { + public static final String STEP_KEY = "step_k"; + private static final String POSITION_KEY = "pos_k"; + + @BindView(R.id.instructions_container) + NestedScrollView mInstructionsContainer; + + + @BindView(R.id.exo_player_view) + SimpleExoPlayerView mExoPlayerView; + @BindView(R.id.step_thumbnail_image) + ImageView mIvThumbnail; + @BindView(R.id.instruction_text) + TextView mTvInstructions; + + private SimpleExoPlayer mExoPlayer; + private Step mStep; + private Unbinder unbinder; + private long currentPosition = 0; + + public RecipeStepDetailFragment() { + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (getArguments() != null && getArguments().containsKey(STEP_KEY)) { + mStep = getArguments().getParcelable(STEP_KEY); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.recipe_step_detail, container, false); + + if (savedInstanceState != null && savedInstanceState.containsKey(POSITION_KEY)) { + currentPosition = savedInstanceState.getLong(POSITION_KEY); + } + + unbinder = ButterKnife.bind(this, rootView); + + mTvInstructions.setText(mStep.getDescription()); + + // Show thumbnail if url exists + if (!mStep.getThumbnailURL().isEmpty()) { + GlideApp.with(this) + .load(mStep.getThumbnailURL()) + .diskCacheStrategy(DiskCacheStrategy.ALL) + .into(mIvThumbnail); + mIvThumbnail.setVisibility(View.VISIBLE); + } + + if (!mStep.getVideoURL().isEmpty()) { + initializePlayer(Uri.parse(mStep.getVideoURL())); + } else { + // Un- hide InstructionsContainer because in case of phone landscape is hidden + mInstructionsContainer.setVisibility(View.VISIBLE); + } + + return rootView; + } + + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + releasePlayer(); + Logger.d("onDestroyView"); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + if (mExoPlayer != null) { + outState.putLong(POSITION_KEY, mExoPlayer.getCurrentPosition()); + } + } + + private void initializePlayer(Uri mediaUri) { + if (mExoPlayer == null) { + // Create a default TrackSelector + DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); + TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter); + TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory); + + // Create the player + mExoPlayer = ExoPlayerFactory.newSimpleInstance(getContext(), trackSelector); + + // Bind the player to the view. + mExoPlayerView.setPlayer(mExoPlayer); + // Measures bandwidth during playback. Can be null if not required. + // Produces DataSource instances through which media data is loaded. + DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(getContext(), Util.getUserAgent(getContext(), getString(R.string.app_name)), bandwidthMeter); + // This is the MediaSource representing the media to be played. + MediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(mediaUri); + // Prepare the player with the source. + mExoPlayer.prepare(videoSource); + + // onRestore + if (currentPosition != 0) + mExoPlayer.seekTo(currentPosition); + + mExoPlayer.setPlayWhenReady(true); + mExoPlayerView.setVisibility(View.VISIBLE); + } + } + + /** + * Release ExoPlayer. + */ + private void releasePlayer() { + if (mExoPlayer != null) { + mExoPlayer.stop(); + mExoPlayer.release(); + mExoPlayer = null; + } + } +} diff --git a/app/src/main/java/eu/dkaratzas/bakingrecipes/ui/fragments/RecipesFragment.java b/app/src/main/java/eu/dkaratzas/bakingrecipes/ui/fragments/RecipesFragment.java new file mode 100644 index 0000000..342fe2b --- /dev/null +++ b/app/src/main/java/eu/dkaratzas/bakingrecipes/ui/fragments/RecipesFragment.java @@ -0,0 +1,277 @@ +/* + * Copyright 2018 Dionysios Karatzas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.dkaratzas.bakingrecipes.ui.fragments; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.os.Bundle; +import android.os.Parcelable; +import android.support.annotation.NonNull; +import android.support.constraint.ConstraintLayout; +import android.support.design.widget.Snackbar; +import android.support.v4.app.Fragment; +import android.support.v4.content.ContextCompat; +import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.orhanobut.logger.Logger; + +import java.util.ArrayList; +import java.util.List; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; +import eu.dkaratzas.bakingrecipes.GlobalApplication; +import eu.dkaratzas.bakingrecipes.Prefs; +import eu.dkaratzas.bakingrecipes.R; +import eu.dkaratzas.bakingrecipes.adapters.RecipesAdapter; +import eu.dkaratzas.bakingrecipes.api.RecipesApiCallback; +import eu.dkaratzas.bakingrecipes.api.RecipesApiManager; +import eu.dkaratzas.bakingrecipes.models.Recipe; +import eu.dkaratzas.bakingrecipes.ui.Listeners; +import eu.dkaratzas.bakingrecipes.utils.Misc; +import eu.dkaratzas.bakingrecipes.utils.SpacingItemDecoration; +import eu.dkaratzas.bakingrecipes.widget.AppWidgetService; + +/** + * A simple {@link Fragment} subclass. + * Activities that contain this fragment must implement the + * {@link OnRecipeClickListener} interface + * to handle interaction events. + */ +public class RecipesFragment extends Fragment { + @BindView(R.id.recipes_recycler_view) + RecyclerView mRecipesRecyclerView; + @BindView(R.id.pull_to_refresh) + SwipeRefreshLayout mPullToRefresh; + @BindView(R.id.noDataContainer) + ConstraintLayout mNoDataContainer; + + private static String RECIPES_KEY = "recipes"; + + private OnRecipeClickListener mListener; + private Unbinder unbinder; + private List mRecipes; + private GlobalApplication globalApplication; + + /** + * Will load the movies when the app launch, or if the app will launch without an internet connection + * and then reconnects, will load them without the need for user to perform a (pull to refresh) + */ + private final BroadcastReceiver networkChangeReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (mRecipes == null) { + loadRecipes(); + } + } + }; + + public RecipesFragment() { + // Required empty public constructor + } + + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment bind view to butter knife + View viewRoot = inflater.inflate(R.layout.fragment_recipes, container, false); + unbinder = ButterKnife.bind(this, viewRoot); + + mPullToRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { + @Override + public void onRefresh() { + loadRecipes(); + } + }); + + mNoDataContainer.setVisibility(View.VISIBLE); + setupRecyclerView(); + + // Get the IdlingResource instance + globalApplication = (GlobalApplication) getActivity().getApplicationContext(); + + globalApplication.setIdleState(false); + + + if (savedInstanceState != null && savedInstanceState.containsKey(RECIPES_KEY)) { + mRecipes = savedInstanceState.getParcelableArrayList(RECIPES_KEY); + + mRecipesRecyclerView.setAdapter(new RecipesAdapter(getActivity().getApplicationContext(), mRecipes, new Listeners.OnItemClickListener() { + @Override + public void onItemClick(int position) { + mListener.onRecipeSelected(mRecipes.get(position)); + } + })); + dataLoadedTakeCareLayout(); + } + return viewRoot; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + if (context instanceof OnRecipeClickListener) { + mListener = (OnRecipeClickListener) context; + } else { + throw new RuntimeException(context.toString() + + " must implement OnRecipeClickListener"); + } + } + + @Override + public void onDetach() { + super.onDetach(); + mListener = null; + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + Logger.d("onDestroyView"); + } + + @Override + public void onResume() { + super.onResume(); + + getActivity().registerReceiver(networkChangeReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); + } + + @Override + public void onPause() { + super.onPause(); + + getActivity().unregisterReceiver(networkChangeReceiver); + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + outState.putParcelableArrayList(RECIPES_KEY, (ArrayList) mRecipes); + } + + private void setupRecyclerView() { + mRecipesRecyclerView.setVisibility(View.GONE); + mRecipesRecyclerView.setHasFixedSize(true); + + boolean tabletSize = getResources().getBoolean(R.bool.isTablet); + if (tabletSize) { + mRecipesRecyclerView.setLayoutManager(new GridLayoutManager(getActivity().getApplicationContext(), 3)); + } else { + mRecipesRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity().getApplicationContext(), LinearLayoutManager.VERTICAL, false)); + } + + mRecipesRecyclerView.addItemDecoration(new SpacingItemDecoration((int) getResources().getDimension(R.dimen.margin_medium))); + mRecipesRecyclerView.addOnItemTouchListener(new RecyclerView.SimpleOnItemTouchListener()); + } + + private void loadRecipes() { + // Set SwipeRefreshLayout that refreshing in case that loadRecipes get called by the networkChangeReceiver + if (Misc.isNetworkAvailable(getActivity().getApplicationContext())) { + mPullToRefresh.setRefreshing(true); + + RecipesApiManager.getInstance().getRecipes(new RecipesApiCallback>() { + @Override + public void onResponse(final List result) { + if (result != null) { + mRecipes = result; + mRecipesRecyclerView.setAdapter(new RecipesAdapter(getActivity().getApplicationContext(), mRecipes, new Listeners.OnItemClickListener() { + @Override + public void onItemClick(int position) { + mListener.onRecipeSelected(mRecipes.get(position)); + } + })); + // Set the default recipe for the widget + if (Prefs.loadRecipe(getActivity().getApplicationContext()) == null) { + AppWidgetService.updateWidget(getActivity(), mRecipes.get(0)); + } + + } else { + showMessage(getString(R.string.failed_to_load_data), true); + } + + dataLoadedTakeCareLayout(); + } + + @Override + public void onCancel() { + dataLoadedTakeCareLayout(); + } + + }); + } else { + showMessage(getString(R.string.no_internet), true); + } + } + + + /** + * Check if data is loaded and show/hide Recipes RecyclerView & NoDataContainer regarding the recipes data state + */ + private void dataLoadedTakeCareLayout() { + boolean loaded = mRecipes != null && mRecipes.size() > 0; + mPullToRefresh.setRefreshing(false); + + mRecipesRecyclerView.setVisibility(loaded ? View.VISIBLE : View.GONE); + mNoDataContainer.setVisibility(loaded ? View.GONE : View.VISIBLE); + + globalApplication.setIdleState(true); + + } + + /** + * Create Snackbar to show a message and set its background color regarding message type (Error - Info) + */ + private void showMessage(String message, boolean error) { + Snackbar snackbar; + snackbar = Snackbar.make(getView(), message, Snackbar.LENGTH_LONG); + View snackBarView = snackbar.getView(); + snackBarView.setBackgroundColor(ContextCompat.getColor(getActivity().getApplicationContext(), error ? R.color.colorError : R.color.colorInfo)); + snackbar.show(); + } + + /** + * This interface must be implemented by activities that contain this + * fragment to allow an interaction in this fragment to be communicated + * to the activity and potentially other fragments contained in that + * activity. + *

+ * See the Android Training lesson Communicating with Other Fragments for more information. + */ + public interface OnRecipeClickListener { + void onRecipeSelected(Recipe recipe); + } +} diff --git a/app/src/main/java/eu/dkaratzas/bakingrecipes/utils/Misc.java b/app/src/main/java/eu/dkaratzas/bakingrecipes/utils/Misc.java new file mode 100644 index 0000000..489d6ed --- /dev/null +++ b/app/src/main/java/eu/dkaratzas/bakingrecipes/utils/Misc.java @@ -0,0 +1,31 @@ +/* + * Copyright 2018 Dionysios Karatzas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.dkaratzas.bakingrecipes.utils; + +import android.content.Context; +import android.net.ConnectivityManager; + +public class Misc { + + public static boolean isNetworkAvailable(Context context) { + + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + + return cm.getActiveNetworkInfo() != null && cm.getActiveNetworkInfo().isConnected(); + } + +} diff --git a/app/src/main/java/eu/dkaratzas/bakingrecipes/utils/RecipesAppGlideModule.java b/app/src/main/java/eu/dkaratzas/bakingrecipes/utils/RecipesAppGlideModule.java new file mode 100644 index 0000000..04c3849 --- /dev/null +++ b/app/src/main/java/eu/dkaratzas/bakingrecipes/utils/RecipesAppGlideModule.java @@ -0,0 +1,24 @@ +/* + * Copyright 2018 Dionysios Karatzas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.dkaratzas.bakingrecipes.utils; + +import com.bumptech.glide.annotation.GlideModule; +import com.bumptech.glide.module.AppGlideModule; + +@GlideModule +public final class RecipesAppGlideModule extends AppGlideModule { +} diff --git a/app/src/main/java/eu/dkaratzas/bakingrecipes/utils/SpacingItemDecoration.java b/app/src/main/java/eu/dkaratzas/bakingrecipes/utils/SpacingItemDecoration.java new file mode 100644 index 0000000..7f9be79 --- /dev/null +++ b/app/src/main/java/eu/dkaratzas/bakingrecipes/utils/SpacingItemDecoration.java @@ -0,0 +1,92 @@ +/* + * Copyright 2018 Dionysios Karatzas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.dkaratzas.bakingrecipes.utils; + +import android.graphics.Rect; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +public class SpacingItemDecoration extends RecyclerView.ItemDecoration { + private final int spacing; + private int displayMode; + + public static final int HORIZONTAL = 0; + public static final int VERTICAL = 1; + public static final int GRID = 2; + + public SpacingItemDecoration(int spacing) { + this(spacing, -1); + } + + public SpacingItemDecoration(int spacing, int displayMode) { + this.spacing = spacing; + this.displayMode = displayMode; + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + int position = parent.getChildViewHolder(view).getAdapterPosition(); + int itemCount = state.getItemCount(); + RecyclerView.LayoutManager layoutManager = parent.getLayoutManager(); + setSpacingForDirection(outRect, layoutManager, position, itemCount); + } + + private void setSpacingForDirection(Rect outRect, + RecyclerView.LayoutManager layoutManager, + int position, + int itemCount) { + + // Resolve display mode automatically + if (displayMode == -1) { + displayMode = resolveDisplayMode(layoutManager); + } + + switch (displayMode) { + case HORIZONTAL: + outRect.left = spacing; + outRect.right = position == itemCount - 1 ? spacing : 0; + outRect.top = spacing; + outRect.bottom = spacing; + break; + case VERTICAL: + outRect.left = spacing; + outRect.right = spacing; + outRect.top = spacing; + outRect.bottom = position == itemCount - 1 ? spacing : 0; + break; + case GRID: + if (layoutManager instanceof GridLayoutManager) { + GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager; + int cols = gridLayoutManager.getSpanCount(); + int rows = itemCount / cols; + + outRect.left = spacing; + outRect.right = position % cols == cols - 1 ? spacing : 0; + outRect.top = spacing; + outRect.bottom = position / cols == rows - 1 ? spacing : 0; + } + break; + } + } + + private int resolveDisplayMode(RecyclerView.LayoutManager layoutManager) { + if (layoutManager instanceof GridLayoutManager) return GRID; + if (layoutManager.canScrollHorizontally()) return HORIZONTAL; + return VERTICAL; + } +} diff --git a/app/src/main/java/eu/dkaratzas/bakingrecipes/widget/AppWidget.java b/app/src/main/java/eu/dkaratzas/bakingrecipes/widget/AppWidget.java new file mode 100644 index 0000000..06b4c10 --- /dev/null +++ b/app/src/main/java/eu/dkaratzas/bakingrecipes/widget/AppWidget.java @@ -0,0 +1,84 @@ +/* + * Copyright 2018 Dionysios Karatzas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.dkaratzas.bakingrecipes.widget; + +import android.app.PendingIntent; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProvider; +import android.content.Context; +import android.content.Intent; +import android.widget.RemoteViews; + +import eu.dkaratzas.bakingrecipes.Prefs; +import eu.dkaratzas.bakingrecipes.R; +import eu.dkaratzas.bakingrecipes.models.Recipe; +import eu.dkaratzas.bakingrecipes.ui.activities.MainActivity; + +/** + * Implementation of App Widget functionality. + */ +public class AppWidget extends AppWidgetProvider { + + public static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, + int appWidgetId) { + + Recipe recipe = Prefs.loadRecipe(context); + if (recipe != null) { + PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, new Intent(context, MainActivity.class), 0); + // Construct the RemoteViews object + RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.baking_recipes_app_widget); + + views.setTextViewText(R.id.recipe_widget_name_text, recipe.getName()); + // Widgets allow click handlers to only launch pending intents + views.setOnClickPendingIntent(R.id.recipe_widget_name_text, pendingIntent); + + // Initialize the list view + Intent intent = new Intent(context, AppWidgetService.class); + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); + // Bind the remote adapter + views.setRemoteAdapter(R.id.recipe_widget_listview, intent); + // Instruct the widget manager to update the widget + appWidgetManager.updateAppWidget(appWidgetId, views); + appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.recipe_widget_listview); + } + } + + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + // There may be multiple widgets active, so update all of them + for (int appWidgetId : appWidgetIds) { + updateAppWidget(context, appWidgetManager, appWidgetId); + } + } + + public static void updateAppWidgets(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + for (int appWidgetId : appWidgetIds) { + updateAppWidget(context, appWidgetManager, appWidgetId); + } + } + + @Override + public void onEnabled(Context context) { + // Enter relevant functionality for when the first widget is created + } + + @Override + public void onDisabled(Context context) { + // Enter relevant functionality for when the last widget is disabled + } +} + diff --git a/app/src/main/java/eu/dkaratzas/bakingrecipes/widget/AppWidgetService.java b/app/src/main/java/eu/dkaratzas/bakingrecipes/widget/AppWidgetService.java new file mode 100644 index 0000000..439fe51 --- /dev/null +++ b/app/src/main/java/eu/dkaratzas/bakingrecipes/widget/AppWidgetService.java @@ -0,0 +1,45 @@ +/* + * Copyright 2018 Dionysios Karatzas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.dkaratzas.bakingrecipes.widget; + +import android.appwidget.AppWidgetManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.widget.RemoteViewsService; + +import eu.dkaratzas.bakingrecipes.Prefs; +import eu.dkaratzas.bakingrecipes.models.Recipe; + +public class AppWidgetService extends RemoteViewsService { + + public static void updateWidget(Context context, Recipe recipe) { + Prefs.saveRecipe(context, recipe); + + AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); + int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, AppWidget.class)); + AppWidget.updateAppWidgets(context, appWidgetManager, appWidgetIds); + } + + @Override + public RemoteViewsService.RemoteViewsFactory onGetViewFactory(Intent intent) { + intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); + + return new ListRemoteViewsFactory(getApplicationContext()); + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/dkaratzas/bakingrecipes/widget/ListRemoteViewsFactory.java b/app/src/main/java/eu/dkaratzas/bakingrecipes/widget/ListRemoteViewsFactory.java new file mode 100644 index 0000000..211f5ec --- /dev/null +++ b/app/src/main/java/eu/dkaratzas/bakingrecipes/widget/ListRemoteViewsFactory.java @@ -0,0 +1,84 @@ +/* + * Copyright 2018 Dionysios Karatzas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.dkaratzas.bakingrecipes.widget; + +import android.content.Context; +import android.widget.RemoteViews; +import android.widget.RemoteViewsService; + +import eu.dkaratzas.bakingrecipes.Prefs; +import eu.dkaratzas.bakingrecipes.R; +import eu.dkaratzas.bakingrecipes.models.Recipe; + + +public class ListRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory { + private Context mContext; + private Recipe recipe; + + public ListRemoteViewsFactory(Context context) { + this.mContext = context; + } + + @Override + public void onCreate() { + + } + + @Override + public void onDataSetChanged() { + recipe = Prefs.loadRecipe(mContext); + } + + @Override + public void onDestroy() { + + } + + @Override + public int getCount() { + return recipe.getIngredients().size(); + } + + @Override + public RemoteViews getViewAt(int position) { + RemoteViews row = new RemoteViews(mContext.getPackageName(), R.layout.baking_recipes_app_widget_list_item); + + row.setTextViewText(R.id.ingredient_item_text, recipe.getIngredients().get(position).getIngredient()); + + return row; + } + + @Override + public RemoteViews getLoadingView() { + return null; + } + + @Override + public int getViewTypeCount() { + return 1; + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public boolean hasStableIds() { + return true; + } +} diff --git a/app/src/main/res/drawable-nodpi/appwidget_preview.png b/app/src/main/res/drawable-nodpi/appwidget_preview.png new file mode 100644 index 0000000000000000000000000000000000000000..99268761e708d875f1a647c3620c2f3c3a726f31 GIT binary patch literal 46174 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 Bc1!>O literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_arrow_point_to_right.xml b/app/src/main/res/drawable/ic_arrow_point_to_right.xml new file mode 100644 index 0000000..8c363b9 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_point_to_right.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_dinner.xml b/app/src/main/res/drawable/ic_dinner.xml new file mode 100644 index 0000000..447e910 --- /dev/null +++ b/app/src/main/res/drawable/ic_dinner.xml @@ -0,0 +1,30 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_recipe.xml b/app/src/main/res/drawable/ic_recipe.xml new file mode 100644 index 0000000..84c5f2c --- /dev/null +++ b/app/src/main/res/drawable/ic_recipe.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/round_tv.xml b/app/src/main/res/drawable/round_tv.xml new file mode 100644 index 0000000..cc0caf1 --- /dev/null +++ b/app/src/main/res/drawable/round_tv.xml @@ -0,0 +1,28 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/widget_background.xml b/app/src/main/res/drawable/widget_background.xml new file mode 100644 index 0000000..d00d1f8 --- /dev/null +++ b/app/src/main/res/drawable/widget_background.xml @@ -0,0 +1,21 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-land/recipe_step_detail.xml b/app/src/main/res/layout-land/recipe_step_detail.xml new file mode 100644 index 0000000..1a4f8db --- /dev/null +++ b/app/src/main/res/layout-land/recipe_step_detail.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout-w900dp-land/recipe_step_detail.xml b/app/src/main/res/layout-w900dp-land/recipe_step_detail.xml new file mode 100644 index 0000000..c122e07 --- /dev/null +++ b/app/src/main/res/layout-w900dp-land/recipe_step_detail.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout-w900dp/recipe_step_list.xml b/app/src/main/res/layout-w900dp/recipe_step_list.xml new file mode 100644 index 0000000..47423a6 --- /dev/null +++ b/app/src/main/res/layout-w900dp/recipe_step_list.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..86f4341 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,21 @@ + + + diff --git a/app/src/main/res/layout/activity_recipe_info.xml b/app/src/main/res/layout/activity_recipe_info.xml new file mode 100644 index 0000000..ff323ff --- /dev/null +++ b/app/src/main/res/layout/activity_recipe_info.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_recipe_step_detail.xml b/app/src/main/res/layout/activity_recipe_step_detail.xml new file mode 100644 index 0000000..2782b43 --- /dev/null +++ b/app/src/main/res/layout/activity_recipe_step_detail.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/baking_recipes_app_widget.xml b/app/src/main/res/layout/baking_recipes_app_widget.xml new file mode 100644 index 0000000..8decc8c --- /dev/null +++ b/app/src/main/res/layout/baking_recipes_app_widget.xml @@ -0,0 +1,42 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/baking_recipes_app_widget_list_item.xml b/app/src/main/res/layout/baking_recipes_app_widget_list_item.xml new file mode 100644 index 0000000..3d7d078 --- /dev/null +++ b/app/src/main/res/layout/baking_recipes_app_widget_list_item.xml @@ -0,0 +1,25 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_recipes.xml b/app/src/main/res/layout/fragment_recipes.xml new file mode 100644 index 0000000..b2f278e --- /dev/null +++ b/app/src/main/res/layout/fragment_recipes.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/no_data_layout.xml b/app/src/main/res/layout/no_data_layout.xml new file mode 100644 index 0000000..f3029c9 --- /dev/null +++ b/app/src/main/res/layout/no_data_layout.xml @@ -0,0 +1,62 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/recipe_ingredient_list_item.xml b/app/src/main/res/layout/recipe_ingredient_list_item.xml new file mode 100644 index 0000000..40f5d85 --- /dev/null +++ b/app/src/main/res/layout/recipe_ingredient_list_item.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/recipe_list_item.xml b/app/src/main/res/layout/recipe_list_item.xml new file mode 100644 index 0000000..49e2d68 --- /dev/null +++ b/app/src/main/res/layout/recipe_list_item.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/recipe_step_detail.xml b/app/src/main/res/layout/recipe_step_detail.xml new file mode 100644 index 0000000..dd31da6 --- /dev/null +++ b/app/src/main/res/layout/recipe_step_detail.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/recipe_step_list.xml b/app/src/main/res/layout/recipe_step_list.xml new file mode 100644 index 0000000..a8f813d --- /dev/null +++ b/app/src/main/res/layout/recipe_step_list.xml @@ -0,0 +1,28 @@ + + + diff --git a/app/src/main/res/layout/recipe_step_list_item.xml b/app/src/main/res/layout/recipe_step_list_item.xml new file mode 100644 index 0000000..bef265a --- /dev/null +++ b/app/src/main/res/layout/recipe_step_list_item.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/recipe_info.xml b/app/src/main/res/menu/recipe_info.xml new file mode 100644 index 0000000..2ff4a84 --- /dev/null +++ b/app/src/main/res/menu/recipe_info.xml @@ -0,0 +1,24 @@ + + +

+ + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..0353ffda0a941aafc0492acae81c8046668c2718 GIT binary patch literal 4020 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 Ef<06MKmY&$ literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..90e35dcff327089c7cffd1ca6493372ebcd226a0 GIT binary patch literal 5405 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..25f577a72b3408c49a31a9281f56ecdd2b3534c9 GIT binary patch literal 10907 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/values-sw600dp/attr.xml b/app/src/main/res/values-sw600dp/attr.xml new file mode 100644 index 0000000..e9ab63c --- /dev/null +++ b/app/src/main/res/values-sw600dp/attr.xml @@ -0,0 +1,19 @@ + + + + true + \ No newline at end of file diff --git a/app/src/main/res/values-w900dp/attr.xml b/app/src/main/res/values-w900dp/attr.xml new file mode 100644 index 0000000..191743b --- /dev/null +++ b/app/src/main/res/values-w900dp/attr.xml @@ -0,0 +1,19 @@ + + + + true + \ No newline at end of file diff --git a/app/src/main/res/values/attr.xml b/app/src/main/res/values/attr.xml new file mode 100644 index 0000000..e429985 --- /dev/null +++ b/app/src/main/res/values/attr.xml @@ -0,0 +1,20 @@ + + + + false + false + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..35e2522 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,25 @@ + + + + #F57676 + #F57676 + #94d5ec + + @color/colorPrimary + @color/colorAccent + #cfcfcf + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..df3085d --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,33 @@ + + + + 4dp + 8dp + 16dp + 22dp + + 12sp + 14sp + 18sp + 22sp + + + 8dp + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..dc07daf --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,31 @@ + + + + Baking Recipes + Servings: %d + Recipe image + Internet connection appears to be offline + Recipes data not available. + Failed to fetch recipes data from the internet. + Failed to load recipe data + RecipeStep Detail + Ingredients + Add to widget + Step %d + widget_recipe_key + Recipe %s added to widget + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..1c9fcce --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + +