├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── bapspatil │ │ └── captainchef │ │ ├── adapters │ │ ├── FoodItemsRecyclerViewAdapter.kt │ │ ├── IngredientsRecyclerViewAdapter.kt │ │ ├── MyRecyclerView.kt │ │ └── StepsListRecyclerViewAdapter.kt │ │ ├── model │ │ ├── FoodItem.kt │ │ ├── Ingredient.kt │ │ └── RecipeStep.kt │ │ ├── network │ │ └── BakingAPI.kt │ │ ├── sync │ │ ├── RecipeWidgetProvider.kt │ │ ├── RecipeWidgetRemoteViewsService.kt │ │ └── UpdateRecipeService.kt │ │ ├── ui │ │ ├── FoodItemsActivity.kt │ │ ├── RecipeActivity.kt │ │ ├── RecipeDetailsActivity.kt │ │ ├── SplashScreenActivity.kt │ │ ├── StepsDetailsFragment.kt │ │ └── StepsListFragment.kt │ │ └── utils │ │ ├── CaptainChefGlideModule.kt │ │ └── EndlessScrollListener.kt │ └── res │ ├── drawable-nodpi │ ├── fallback_recipe_thumbnail.png │ ├── recipe_widget_preview.png │ └── splash_screen_logo.png │ ├── font │ └── autour_one.ttf │ ├── layout-land │ └── fragment_steps_details.xml │ ├── layout-sw600dp │ ├── activity_food_items.xml │ ├── activity_recipe.xml │ ├── fragment_steps_details.xml │ └── rv_food_item.xml │ ├── layout │ ├── activity_food_items.xml │ ├── activity_recipe.xml │ ├── activity_recipe_details.xml │ ├── activity_splash_screen.xml │ ├── fragment_steps_details.xml │ ├── fragment_steps_list.xml │ ├── recipe_widget_before_selection.xml │ ├── recipe_widget_list_item_view.xml │ ├── recipe_widget_provider.xml │ ├── rv_food_item.xml │ ├── rv_ingredients.xml │ └── rv_steps_list.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── values-sw600dp │ └── strings.xml │ ├── values-v14 │ └── dimens.xml │ ├── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ └── recipe_widget_provider_info.xml ├── build.gradle ├── design ├── screen0.png ├── screen1.png ├── screen2.png ├── screen3.png └── screen4.png ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── resources ├── baking.json ├── brownie.jpg ├── cheesecake.jpg ├── fallback_recipe_thumbnail.png ├── nutella_pie.jpg └── yellow_cake.jpg └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .DS_Store 5 | /build 6 | /captures 7 | .externalNativeBuild 8 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # CaptainChef 3 | 4 | A Material design baking/cooking recipes app. 5 | 6 | Get it on Google Play 7 | 8 | ## Screenshots 9 | 10 | 11 | 12 | 13 | 14 | ## Libraries Used 15 | 16 | * [Android Support Library](https://developer.android.com/topic/libraries/support-library/) 17 | * [Anko](https://github.com/Kotlin/anko/) 18 | * [Retrofit](https://github.com/square/retrofit/) 19 | * [Glide](https://github.com/bumptech/glide/) 20 | * [Gson](https://github.com/google/gson/) 21 | * [Toasty](https://github.com/GrenderG/Toasty/) 22 | * [ExoPlayer](https://github.com/google/ExoPlayer/) 23 | 24 | ## Developed By 25 | 26 | Bapusaheb Patil 27 | 28 | 29 | 30 | https://bapspatil.com 31 | 32 | ## License 33 | 34 | Copyright 2018 Bapusaheb Patil 35 | 36 | Licensed under the Apache License, Version 2.0 (the "License"); 37 | you may not use this file except in compliance with the License. 38 | You may obtain a copy of the License at 39 | 40 | http://www.apache.org/licenses/LICENSE-2.0 41 | 42 | Unless required by applicable law or agreed to in writing, software 43 | distributed under the License is distributed on an "AS IS" BASIS, 44 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 45 | See the License for the specific language governing permissions and 46 | limitations under the License. 47 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'kotlin-kapt' 5 | 6 | android { 7 | compileSdkVersion 27 8 | buildToolsVersion "28.0.1" 9 | defaultConfig { 10 | applicationId "bapspatil.captainchef" 11 | minSdkVersion 21 12 | targetSdkVersion 27 13 | versionCode 3 14 | versionName "1.2" 15 | } 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | compileOptions { 23 | sourceCompatibility JavaVersion.VERSION_1_8 24 | targetCompatibility JavaVersion.VERSION_1_8 25 | } 26 | } 27 | 28 | kotlin { 29 | experimental { 30 | coroutines "enable" 31 | } 32 | } 33 | 34 | androidExtensions { 35 | experimental = true 36 | } 37 | 38 | ext { 39 | ankoVersion = '0.10.5' 40 | support = '27.1.1' 41 | glideVersion = '4.7.1' 42 | exoplayerVersion = '2.8.2' 43 | toastyVersion = '1.3.0' 44 | retrofitVer = '2.4.0' 45 | gsonVer = '2.8.5' 46 | } 47 | 48 | dependencies { 49 | // Kotlin 50 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlinVer" 51 | implementation "org.jetbrains.anko:anko:$ankoVersion" 52 | implementation "org.jetbrains.anko:anko-design:$ankoVersion" 53 | 54 | // Android Support Library 55 | implementation "com.android.support:appcompat-v7:$support" 56 | implementation "com.android.support:support-v4:$support" 57 | implementation "com.android.support:support-v13:$support" 58 | implementation "com.android.support:support-annotations:$support" 59 | implementation "com.android.support:cardview-v7:$support" 60 | implementation "com.android.support:design:$support" 61 | implementation "com.android.support:recyclerview-v7:$support" 62 | implementation 'com.android.support.constraint:constraint-layout:1.0.2' 63 | 64 | // App dependencies 65 | implementation "com.github.bumptech.glide:glide:$glideVersion" 66 | kapt "com.github.bumptech.glide:compiler:$glideVersion" 67 | implementation "com.google.android.exoplayer:exoplayer:$exoplayerVersion" 68 | implementation "com.github.GrenderG:Toasty:$toastyVersion" 69 | implementation "com.squareup.retrofit2:retrofit:$retrofitVer" 70 | implementation "com.squareup.retrofit2:converter-gson:$retrofitVer" 71 | implementation "com.google.code.gson:gson:$gsonVer" 72 | } 73 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\Users\bapoo\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -keep public class * implements com.bumptech.glide.module.GlideModule 27 | -keep public class * extends com.bumptech.glide.AppGlideModule 28 | -keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** { 29 | **[] $VALUES; 30 | public *; 31 | } 32 | 33 | # For GSON and Retrofit 34 | -dontwarn okio.** 35 | -dontwarn javax.annotation.** -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 17 | 18 | 21 | 22 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 40 | 41 | 42 | 45 | 46 | 47 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /app/src/main/java/bapspatil/captainchef/adapters/FoodItemsRecyclerViewAdapter.kt: -------------------------------------------------------------------------------- 1 | package bapspatil.captainchef.adapters 2 | 3 | import android.content.Context 4 | import android.support.v4.content.res.ResourcesCompat 5 | import android.support.v7.widget.RecyclerView 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import android.widget.ImageView 10 | import android.widget.TextView 11 | import bapspatil.captainchef.R 12 | import bapspatil.captainchef.model.FoodItem 13 | import bapspatil.captainchef.utils.GlideApp 14 | import butterknife.BindView 15 | import butterknife.ButterKnife 16 | import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions 17 | import java.util.* 18 | 19 | /** 20 | * Created by bapspatil 21 | */ 22 | 23 | class FoodItemsRecyclerViewAdapter(private val mContext: Context, private val mFoodItemsList: ArrayList, private val mClickListener: OnFoodItemClickListener?) : RecyclerView.Adapter() { 24 | 25 | override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): FoodItemsViewHolder { 26 | val view = LayoutInflater.from(mContext).inflate(R.layout.rv_food_item, viewGroup, false) 27 | return FoodItemsViewHolder(view) 28 | } 29 | 30 | override fun onBindViewHolder(holder: FoodItemsViewHolder, position: Int) { 31 | val typeface = ResourcesCompat.getFont(mContext, R.font.autour_one) 32 | holder.mFoodItemTextView!!.typeface = typeface 33 | holder.mFoodItemTextView!!.text = mFoodItemsList[position].foodName 34 | GlideApp.with(mContext) 35 | .load(mFoodItemsList[position].imageUrl) 36 | .centerCrop() 37 | .error(R.drawable.fallback_recipe_thumbnail) 38 | .fallback(R.drawable.fallback_recipe_thumbnail) 39 | .transition(DrawableTransitionOptions().crossFade()) 40 | .into(holder.mFoodItemImageView!!) 41 | } 42 | 43 | override fun getItemCount(): Int { 44 | return mFoodItemsList.size 45 | } 46 | 47 | inner class FoodItemsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener { 48 | @BindView(R.id.food_item_tv) 49 | var mFoodItemTextView: TextView? = null 50 | @BindView(R.id.food_item_iv) 51 | var mFoodItemImageView: ImageView? = null 52 | 53 | init { 54 | ButterKnife.bind(this, itemView) 55 | itemView.setOnClickListener(this) 56 | } 57 | 58 | override fun onClick(v: View) { 59 | mClickListener?.onFoodItemClicked(adapterPosition, mFoodItemTextView) 60 | } 61 | } 62 | 63 | interface OnFoodItemClickListener { 64 | fun onFoodItemClicked(position: Int, textView: TextView?) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/bapspatil/captainchef/adapters/IngredientsRecyclerViewAdapter.kt: -------------------------------------------------------------------------------- 1 | package bapspatil.captainchef.adapters 2 | 3 | import android.content.Context 4 | import android.support.v4.content.res.ResourcesCompat 5 | import android.support.v7.widget.RecyclerView 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import android.widget.TextView 10 | import bapspatil.captainchef.R 11 | import bapspatil.captainchef.model.Ingredient 12 | import butterknife.BindView 13 | import butterknife.ButterKnife 14 | import java.util.* 15 | 16 | /** 17 | * Created by bapspatil 18 | */ 19 | 20 | class IngredientsRecyclerViewAdapter(private val mContext: Context, private val mIngredientsList: ArrayList) : RecyclerView.Adapter() { 21 | 22 | override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): IngredientsViewHolder { 23 | val view = LayoutInflater.from(mContext).inflate(R.layout.rv_ingredients, viewGroup, false) 24 | return IngredientsViewHolder(view) 25 | } 26 | 27 | override fun onBindViewHolder(ingredientsViewHolder: IngredientsViewHolder, i: Int) { 28 | val (quant, measuredWith, ingredientName) = mIngredientsList[i] 29 | val typeface = ResourcesCompat.getFont(mContext, R.font.autour_one) 30 | ingredientsViewHolder.mIngredientTextView!!.typeface = typeface 31 | ingredientsViewHolder.mMeasureTextView!!.typeface = typeface 32 | ingredientsViewHolder.mQuantityTextView!!.typeface = typeface 33 | ingredientsViewHolder.mIngredientTextView!!.text = ingredientName 34 | ingredientsViewHolder.mQuantityTextView!!.text = quant.toString() 35 | ingredientsViewHolder.mMeasureTextView!!.text = measuredWith 36 | } 37 | 38 | override fun getItemCount(): Int { 39 | return mIngredientsList.size 40 | } 41 | 42 | inner class IngredientsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 43 | @BindView(R.id.ingredient_tv) 44 | var mIngredientTextView: TextView? = null 45 | @BindView(R.id.quantity_tv) 46 | var mQuantityTextView: TextView? = null 47 | @BindView(R.id.measure_tv) 48 | var mMeasureTextView: TextView? = null 49 | 50 | init { 51 | ButterKnife.bind(this, itemView) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/bapspatil/captainchef/adapters/MyRecyclerView.kt: -------------------------------------------------------------------------------- 1 | package bapspatil.captainchef.adapters 2 | 3 | import android.content.Context 4 | import android.os.Bundle 5 | import android.os.Parcelable 6 | import android.support.v7.widget.RecyclerView 7 | import android.util.AttributeSet 8 | 9 | /** 10 | * Created by bapspatil 11 | */ 12 | 13 | class MyRecyclerView : RecyclerView { 14 | private var mLayoutManagerSavedState: Parcelable? = null 15 | 16 | constructor(context: Context) : super(context) 17 | 18 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) 19 | 20 | constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) 21 | 22 | override fun onSaveInstanceState(): Parcelable? { 23 | val bundle = Bundle() 24 | bundle.putParcelable(SAVED_SUPER_STATE, super.onSaveInstanceState()) 25 | bundle.putParcelable(SAVED_LAYOUT_MANAGER, this.layoutManager.onSaveInstanceState()) 26 | return bundle 27 | } 28 | 29 | override fun onRestoreInstanceState(state: Parcelable?) { 30 | var mState = state 31 | if (mState is Bundle) { 32 | val bundle = mState as Bundle? 33 | mLayoutManagerSavedState = bundle!!.getParcelable(SAVED_LAYOUT_MANAGER) 34 | mState = bundle.getParcelable(SAVED_SUPER_STATE) 35 | } 36 | super.onRestoreInstanceState(state) 37 | } 38 | 39 | override fun setAdapter(adapter: RecyclerView.Adapter<*>) { 40 | super.setAdapter(adapter) 41 | restorePosition() 42 | } 43 | 44 | /** 45 | * Restores scroll position after configuration change. 46 | * NOTE: Must be called after adapter has been set. 47 | */ 48 | fun restorePosition() { 49 | if (mLayoutManagerSavedState != null) { 50 | this.layoutManager.onRestoreInstanceState(mLayoutManagerSavedState) 51 | mLayoutManagerSavedState = null 52 | } 53 | } 54 | 55 | companion object { 56 | private const val SAVED_SUPER_STATE = "super-state" 57 | private const val SAVED_LAYOUT_MANAGER = "layout-manager-state" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/bapspatil/captainchef/adapters/StepsListRecyclerViewAdapter.kt: -------------------------------------------------------------------------------- 1 | package bapspatil.captainchef.adapters 2 | 3 | import android.content.Context 4 | import android.support.v4.content.res.ResourcesCompat 5 | import android.support.v7.widget.RecyclerView 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import android.widget.ImageView 10 | import android.widget.TextView 11 | import bapspatil.captainchef.R 12 | import bapspatil.captainchef.model.RecipeStep 13 | import bapspatil.captainchef.utils.GlideApp 14 | import butterknife.BindView 15 | import butterknife.ButterKnife 16 | import java.util.* 17 | 18 | /** 19 | * Created by bapspatil 20 | */ 21 | 22 | class StepsListRecyclerViewAdapter(private val mContext: Context, private val mRecipeStepsList: ArrayList, private val mClickListener: OnRecipeStepClickedListener?) : RecyclerView.Adapter() { 23 | 24 | override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): StepsListViewHolder { 25 | val view = LayoutInflater.from(mContext).inflate(R.layout.rv_steps_list, viewGroup, false) 26 | return StepsListViewHolder(view) 27 | } 28 | 29 | override fun onBindViewHolder(stepsListViewHolder: StepsListViewHolder, i: Int) { 30 | val (stepId, shortInfo, _, _, thumbnailUrl) = mRecipeStepsList[i] 31 | val recipeStepString = stepId.toString() + ". " + shortInfo 32 | val typeface = ResourcesCompat.getFont(mContext, R.font.autour_one) 33 | stepsListViewHolder.mStepTextView!!.typeface = typeface 34 | stepsListViewHolder.mStepTextView!!.text = recipeStepString 35 | GlideApp.with(mContext) 36 | .load(thumbnailUrl) 37 | .centerCrop() 38 | .fallback(R.drawable.fallback_recipe_thumbnail) 39 | .error(R.drawable.fallback_recipe_thumbnail) 40 | .placeholder(R.drawable.fallback_recipe_thumbnail) 41 | .into(stepsListViewHolder.mStepImageView!!) 42 | } 43 | 44 | override fun getItemCount(): Int { 45 | return mRecipeStepsList.size 46 | } 47 | 48 | inner class StepsListViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener { 49 | @BindView(R.id.step_item_tv) 50 | var mStepTextView: TextView? = null 51 | @BindView(R.id.step_item_iv) 52 | var mStepImageView: ImageView? = null 53 | 54 | init { 55 | ButterKnife.bind(this, itemView) 56 | itemView.setOnClickListener(this) 57 | } 58 | 59 | override fun onClick(v: View) { 60 | if (mClickListener != null) { 61 | val recipeStep = mRecipeStepsList[adapterPosition] 62 | mClickListener.onRecipeStepClicked(recipeStep) 63 | } 64 | } 65 | } 66 | 67 | interface OnRecipeStepClickedListener { 68 | fun onRecipeStepClicked(recipeStep: RecipeStep) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/java/bapspatil/captainchef/model/FoodItem.kt: -------------------------------------------------------------------------------- 1 | package bapspatil.captainchef.model 2 | 3 | import android.os.Parcelable 4 | import com.google.gson.annotations.SerializedName 5 | import kotlinx.android.parcel.Parcelize 6 | import java.util.* 7 | 8 | @Parcelize 9 | data class FoodItem( 10 | @SerializedName("id") var foodId: Int, 11 | @SerializedName("name") var foodName: String?, 12 | @SerializedName("image") var imageUrl: String?, 13 | @SerializedName("ingredients") var ingredients: ArrayList?, 14 | @SerializedName("steps") var steps: ArrayList? 15 | ) : Parcelable -------------------------------------------------------------------------------- /app/src/main/java/bapspatil/captainchef/model/Ingredient.kt: -------------------------------------------------------------------------------- 1 | package bapspatil.captainchef.model 2 | 3 | import android.os.Parcelable 4 | import com.google.gson.annotations.SerializedName 5 | import kotlinx.android.parcel.Parcelize 6 | 7 | @Parcelize 8 | data class Ingredient( 9 | @SerializedName("quantity") var quant: Float, 10 | @SerializedName("measure") var measuredWith: String?, 11 | @SerializedName("ingredient") var ingredientName: String? 12 | ) : Parcelable 13 | -------------------------------------------------------------------------------- /app/src/main/java/bapspatil/captainchef/model/RecipeStep.kt: -------------------------------------------------------------------------------- 1 | package bapspatil.captainchef.model 2 | 3 | import android.os.Parcelable 4 | import com.google.gson.annotations.SerializedName 5 | import kotlinx.android.parcel.Parcelize 6 | 7 | @Parcelize 8 | data class RecipeStep( 9 | @SerializedName("id") var stepId: Int, 10 | @SerializedName("shortDescription") var shortInfo: String?, 11 | @SerializedName("description") var info: String?, 12 | @SerializedName("videoURL") var videoUrl: String?, 13 | @SerializedName("thumbnailURL") var thumbnailUrl: String? 14 | ) : Parcelable -------------------------------------------------------------------------------- /app/src/main/java/bapspatil/captainchef/network/BakingAPI.kt: -------------------------------------------------------------------------------- 1 | package bapspatil.captainchef.network 2 | 3 | import bapspatil.captainchef.model.FoodItem 4 | import retrofit2.Call 5 | import retrofit2.Retrofit 6 | import retrofit2.converter.gson.GsonConverterFactory 7 | import retrofit2.http.GET 8 | import java.util.* 9 | 10 | /** 11 | * Created by bapspatil 12 | */ 13 | 14 | interface BakingAPI { 15 | 16 | @get:GET("resources/baking.json") 17 | val foodItems: Call> 18 | 19 | companion object { 20 | 21 | const val BASE_URL = "https://raw.githubusercontent.com/bapspatil/CaptainChef/master/" 22 | 23 | val retrofit = Retrofit.Builder() 24 | .baseUrl(BASE_URL) 25 | .addConverterFactory(GsonConverterFactory.create()) 26 | .build() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/bapspatil/captainchef/sync/RecipeWidgetProvider.kt: -------------------------------------------------------------------------------- 1 | package bapspatil.captainchef.sync 2 | 3 | import android.appwidget.AppWidgetManager 4 | import android.appwidget.AppWidgetProvider 5 | import android.content.ComponentName 6 | import android.content.Context 7 | import android.content.Intent 8 | import android.widget.RemoteViews 9 | import bapspatil.captainchef.R 10 | import bapspatil.captainchef.model.Ingredient 11 | import java.util.* 12 | 13 | /** 14 | * Created by bapspatil 15 | */ 16 | class RecipeWidgetProvider : AppWidgetProvider() { 17 | 18 | override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { 19 | /*for (int appWidgetId : appWidgetIds) { 20 | updateAppWidget(context, appWidgetManager, appWidgetId); 21 | }*/ 22 | } 23 | 24 | override fun onEnabled(context: Context) { 25 | // Enter relevant functionality for when the first widget is created 26 | } 27 | 28 | override fun onDisabled(context: Context) { 29 | // Enter relevant functionality for when the last widget is disabled 30 | } 31 | 32 | override fun onReceive(context: Context, intent: Intent) { 33 | // Get the AppWidgetManager & appWidgetIds from the context 34 | val appWidgetManager = AppWidgetManager.getInstance(context) 35 | val appWidgetIds = appWidgetManager.getAppWidgetIds(ComponentName(context, RecipeWidgetProvider::class.java)) 36 | 37 | // Get the action present in the Intent passed by the UpdateRecipeService 38 | val action = intent.action 39 | if (action == "android.appwidget.action.RECIPE_UPDATE") { 40 | 41 | // Get the extras from the Intent obtained from the UpdateRecipeService 42 | ingredientArrayList = intent.getParcelableArrayListExtra("ingredientsList") 43 | foodItemName = intent.getStringExtra("foodItemName") 44 | 45 | // Notify the AppWidgetManager that the data in the widget has changed 46 | appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.widget_ingredients_list_view) 47 | 48 | // Update the data displayed in the widget 49 | updateRecipeWidgets(context, appWidgetManager, appWidgetIds) 50 | super.onReceive(context, intent) 51 | } 52 | } 53 | 54 | companion object { 55 | 56 | var ingredientArrayList = ArrayList() 57 | lateinit var foodItemName: String 58 | 59 | fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int) { 60 | 61 | // Construct the RemoteViews object 62 | val views = RemoteViews(context.packageName, R.layout.recipe_widget_provider) 63 | 64 | // Setting the Food Item name 65 | views.setTextViewText(R.id.appwidget_food_item_title, foodItemName) 66 | 67 | // Setting the RemoteAdapter for the list view of Ingredients 68 | val intentForListView = Intent(context, RecipeWidgetRemoteViewsService::class.java) 69 | views.setRemoteAdapter(R.id.widget_ingredients_list_view, intentForListView) 70 | 71 | // Instruct the widget manager to update the widget 72 | appWidgetManager.updateAppWidget(appWidgetId, views) 73 | } 74 | 75 | fun updateRecipeWidgets(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { 76 | for (appWidgetId in appWidgetIds) { 77 | updateAppWidget(context, appWidgetManager, appWidgetId) 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/bapspatil/captainchef/sync/RecipeWidgetRemoteViewsService.kt: -------------------------------------------------------------------------------- 1 | package bapspatil.captainchef.sync 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.widget.RemoteViews 6 | import android.widget.RemoteViewsService 7 | import bapspatil.captainchef.R 8 | import bapspatil.captainchef.model.Ingredient 9 | import java.util.* 10 | 11 | /** 12 | * Created by bapspatil 13 | */ 14 | 15 | class RecipeWidgetRemoteViewsService : RemoteViewsService() { 16 | 17 | internal var remoteIngredientsList: ArrayList? = null 18 | 19 | override fun onGetViewFactory(intent: Intent): RemoteViewsService.RemoteViewsFactory { 20 | return RecipeWidgetRemoteViewsFactory(this.applicationContext, intent) 21 | } 22 | 23 | inner class RecipeWidgetRemoteViewsFactory(internal var mContext: Context, intent: Intent) : RemoteViewsService.RemoteViewsFactory { 24 | 25 | override fun onCreate() { 26 | } 27 | 28 | override fun onDataSetChanged() { 29 | remoteIngredientsList = RecipeWidgetProvider.Companion.ingredientArrayList 30 | } 31 | 32 | override fun onDestroy() { 33 | } 34 | 35 | override fun getCount(): Int { 36 | return if (remoteIngredientsList == null) 37 | 0 38 | else 39 | remoteIngredientsList!!.size 40 | } 41 | 42 | override fun getViewAt(i: Int): RemoteViews { 43 | // Construct the RemoteViews for the individual ingredient list item 44 | val views = RemoteViews(mContext.packageName, R.layout.recipe_widget_list_item_view) 45 | 46 | // Set the TextView in the layout of those individual ingredient list items 47 | views.setTextViewText(R.id.widget_ingredients_text_view, remoteIngredientsList!![i].ingredientName + "\n\t\t\tQuantity: " + remoteIngredientsList!![i].quant + " " + remoteIngredientsList!![i].measuredWith) 48 | 49 | // Return the RemoteViews 50 | return views 51 | } 52 | 53 | override fun getLoadingView(): RemoteViews? { 54 | return null 55 | } 56 | 57 | override fun getViewTypeCount(): Int { 58 | return 1 59 | } 60 | 61 | override fun getItemId(position: Int): Long { 62 | return position.toLong() 63 | } 64 | 65 | override fun hasStableIds(): Boolean { 66 | return true 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/java/bapspatil/captainchef/sync/UpdateRecipeService.kt: -------------------------------------------------------------------------------- 1 | package bapspatil.captainchef.sync 2 | 3 | import android.app.IntentService 4 | import android.content.Context 5 | import android.content.Intent 6 | import bapspatil.captainchef.model.Ingredient 7 | import java.util.* 8 | 9 | /** 10 | * Created by bapspatil 11 | */ 12 | 13 | class UpdateRecipeService : IntentService("UpdateRecipeService") { 14 | 15 | override fun onHandleIntent(intent: Intent?) { 16 | if (intent != null) { 17 | val foodItemName = intent.getStringExtra("foodItemName") 18 | val ingredientArrayList = intent.getParcelableArrayListExtra("ingredientsList") 19 | handleRecipeWidgetUpdate(foodItemName, ingredientArrayList) 20 | } 21 | } 22 | 23 | // Handle the recipe update action and send the broadcast to the WidgetProvider which updates the widget 24 | fun handleRecipeWidgetUpdate(foodItemName: String, ingredientArrayList: ArrayList) { 25 | val intentToWidget = Intent("android.appwidget.action.RECIPE_UPDATE") 26 | intentToWidget.action = "android.appwidget.action.RECIPE_UPDATE" 27 | intentToWidget.putExtra("foodItemName", foodItemName) 28 | intentToWidget.putParcelableArrayListExtra("ingredientsList", ingredientArrayList) 29 | sendBroadcast(intentToWidget) 30 | } 31 | 32 | companion object { 33 | 34 | // Helper method to explicitly start the UpdateRecipeService 35 | fun startRecipeWidgetService(context: Context, ingredientArrayList: ArrayList, foodItemName: String) { 36 | val intent = Intent(context, UpdateRecipeService::class.java) 37 | intent.putParcelableArrayListExtra("ingredientsList", ingredientArrayList) 38 | intent.putExtra("foodItemName", foodItemName) 39 | context.startService(intent) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/bapspatil/captainchef/ui/FoodItemsActivity.kt: -------------------------------------------------------------------------------- 1 | package bapspatil.captainchef.ui 2 | 3 | import android.app.ActivityOptions 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import android.support.v7.app.AppCompatActivity 7 | import android.support.v7.widget.GridLayoutManager 8 | import android.support.v7.widget.LinearLayoutManager 9 | import android.support.v7.widget.RecyclerView 10 | import android.support.v7.widget.Toolbar 11 | import android.widget.TextView 12 | import bapspatil.captainchef.R 13 | import bapspatil.captainchef.adapters.FoodItemsRecyclerViewAdapter 14 | import bapspatil.captainchef.model.FoodItem 15 | import bapspatil.captainchef.network.BakingAPI 16 | import butterknife.BindView 17 | import butterknife.ButterKnife 18 | import es.dmoral.toasty.Toasty 19 | import retrofit2.Call 20 | import retrofit2.Callback 21 | import retrofit2.Response 22 | import java.util.* 23 | 24 | class FoodItemsActivity : AppCompatActivity(), FoodItemsRecyclerViewAdapter.OnFoodItemClickListener { 25 | private val foodItemsList = ArrayList() 26 | private var mAdapter: FoodItemsRecyclerViewAdapter? = null 27 | 28 | @BindView(R.id.food_items_rv) 29 | var mFoodItemsRecyclerView: RecyclerView? = null 30 | @BindView(R.id.toolbar) 31 | var toolbar: Toolbar? = null 32 | 33 | private val isPhone: Boolean 34 | get() { 35 | val screenType = resources.getString(R.string.device) 36 | return screenType != "tablet" 37 | } 38 | 39 | override fun onCreate(savedInstanceState: Bundle?) { 40 | super.onCreate(savedInstanceState) 41 | setContentView(R.layout.activity_food_items) 42 | ButterKnife.bind(this) 43 | setSupportActionBar(toolbar) 44 | Toasty.info(applicationContext, "App developed by Bapusaheb Patil", 5000).show() 45 | 46 | if (isPhone) 47 | mFoodItemsRecyclerView!!.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) 48 | else 49 | mFoodItemsRecyclerView!!.layoutManager = GridLayoutManager(this, 3) 50 | mAdapter = FoodItemsRecyclerViewAdapter(applicationContext, foodItemsList, this) 51 | mFoodItemsRecyclerView!!.adapter = mAdapter 52 | mFoodItemsRecyclerView!!.setHasFixedSize(true) 53 | 54 | fetchFoodItems() 55 | } 56 | 57 | private fun fetchFoodItems() { 58 | val bakingAPI = BakingAPI.retrofit.create(BakingAPI::class.java) 59 | val foodItemsCall = bakingAPI.foodItems 60 | foodItemsCall.enqueue(object : Callback> { 61 | override fun onResponse(call: Call>, response: Response>) { 62 | if (response.body() != null) { 63 | foodItemsList.addAll(response.body()!!) 64 | mAdapter!!.notifyDataSetChanged() 65 | } else { 66 | Toasty.error(applicationContext, "Couldn't load food items!", 5000).show() 67 | } 68 | } 69 | 70 | override fun onFailure(call: Call>, t: Throwable) { 71 | Toasty.error(applicationContext, "Couldn't load food items! " + t.message, 5000).show() 72 | } 73 | }) 74 | } 75 | 76 | override fun onFoodItemClicked(position: Int, textView: TextView?) { 77 | Toasty.info(applicationContext, "This recipe serves 8 people", 5000).show() 78 | val foodItem = foodItemsList[position] 79 | val startRecipeActivity = Intent(this, RecipeActivity::class.java) 80 | startRecipeActivity.putExtra("foodItem", foodItem) 81 | val options = ActivityOptions.makeCustomAnimation(this, android.R.anim.fade_in, android.R.anim.fade_out) 82 | startActivity(startRecipeActivity, options.toBundle()) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/java/bapspatil/captainchef/ui/RecipeActivity.kt: -------------------------------------------------------------------------------- 1 | package bapspatil.captainchef.ui 2 | 3 | import android.app.ActivityOptions 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import android.support.v4.app.FragmentManager 7 | import android.support.v7.app.AppCompatActivity 8 | import android.support.v7.widget.Toolbar 9 | import android.view.View 10 | import android.widget.TextView 11 | import bapspatil.captainchef.R 12 | import bapspatil.captainchef.model.FoodItem 13 | import bapspatil.captainchef.model.Ingredient 14 | import bapspatil.captainchef.model.RecipeStep 15 | import butterknife.BindView 16 | import butterknife.ButterKnife 17 | import java.util.* 18 | 19 | class RecipeActivity : AppCompatActivity(), StepsListFragment.OnStepClickListener, StepsDetailsFragment.OnButtonClickListener { 20 | 21 | private var ingredientsList: ArrayList? = ArrayList() 22 | private var recipeStepsList: ArrayList? = ArrayList() 23 | private lateinit var fragmentManager: FragmentManager 24 | private var mTwoPane: Boolean = false 25 | @BindView(R.id.toolbar) 26 | var toolbar: Toolbar? = null 27 | @BindView(R.id.recipe_toolbar_tv) 28 | var recipeToolbarTextView: TextView? = null 29 | 30 | private val isPhone: Boolean 31 | get() { 32 | val screenType = resources.getString(R.string.device) 33 | return screenType != "tablet" 34 | } 35 | 36 | override fun onCreate(savedInstanceState: Bundle?) { 37 | super.onCreate(savedInstanceState) 38 | setContentView(R.layout.activity_recipe) 39 | ButterKnife.bind(this) 40 | toolbar!!.title = "" 41 | setSupportActionBar(toolbar) 42 | 43 | fragmentManager = supportFragmentManager 44 | 45 | val (_, foodItemName, _, ingredients, steps) = intent.getParcelableExtra("foodItem") 46 | 47 | recipeToolbarTextView!!.text = foodItemName 48 | 49 | ingredientsList = ingredients 50 | recipeStepsList = steps 51 | if (isPhone) { 52 | mTwoPane = false 53 | if (savedInstanceState == null) { 54 | val stepsListFragment = StepsListFragment.newInstance(ingredientsList!!, recipeStepsList!!, foodItemName!!) 55 | fragmentManager.beginTransaction() 56 | .setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out) 57 | .replace(R.id.recipe_container, stepsListFragment) 58 | .commit() 59 | } 60 | } else { 61 | mTwoPane = true 62 | if (savedInstanceState == null) { 63 | val stepsListFragment = StepsListFragment.newInstance(ingredientsList!!, recipeStepsList!!, foodItemName!!) 64 | fragmentManager.beginTransaction() 65 | .setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out) 66 | .replace(R.id.recipe_container, stepsListFragment) 67 | .commit() 68 | } 69 | } 70 | } 71 | 72 | override fun onStepClicked(mRecipeStep: RecipeStep) { 73 | if (!mTwoPane) { 74 | val startRecipeDetailsActivity = Intent(this, RecipeDetailsActivity::class.java) 75 | startRecipeDetailsActivity.putExtra("recipeStep", mRecipeStep) 76 | startRecipeDetailsActivity.putParcelableArrayListExtra("recipeList", recipeStepsList) 77 | val options = ActivityOptions.makeCustomAnimation(applicationContext, android.R.anim.fade_in, android.R.anim.fade_out) 78 | startActivity(startRecipeDetailsActivity, options.toBundle()) 79 | } else { 80 | val stepsDetailsFragment = StepsDetailsFragment.newInstance(mRecipeStep, recipeStepsList!!) 81 | fragmentManager = supportFragmentManager 82 | fragmentManager.beginTransaction() 83 | .setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out) 84 | .replace(R.id.recipe_details_container, stepsDetailsFragment) 85 | .commit() 86 | } 87 | } 88 | 89 | override fun onBackPressed() { 90 | super.onBackPressed() 91 | this@RecipeActivity.overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out) 92 | } 93 | 94 | override fun onButtonClicked(buttonClicked: Int, recipeStep: RecipeStep?, recipeSteps: ArrayList?, view: View) { 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /app/src/main/java/bapspatil/captainchef/ui/RecipeDetailsActivity.kt: -------------------------------------------------------------------------------- 1 | package bapspatil.captainchef.ui 2 | 3 | import android.content.res.Configuration 4 | import android.os.Bundle 5 | import android.support.v4.app.FragmentManager 6 | import android.support.v7.app.AppCompatActivity 7 | import android.view.View 8 | import bapspatil.captainchef.R 9 | import bapspatil.captainchef.model.RecipeStep 10 | import butterknife.ButterKnife 11 | import java.util.* 12 | 13 | class RecipeDetailsActivity : AppCompatActivity(), StepsDetailsFragment.OnButtonClickListener { 14 | private var fragmentManager: FragmentManager? = null 15 | private var mRecipeStep: RecipeStep? = null 16 | private var mRecipeStepsList: ArrayList? = null 17 | 18 | private val isLandscape: Boolean 19 | get() = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE 20 | 21 | override fun onCreate(savedInstanceState: Bundle?) { 22 | super.onCreate(savedInstanceState) 23 | setContentView(R.layout.activity_recipe_details) 24 | ButterKnife.bind(this) 25 | 26 | mRecipeStep = intent.getParcelableExtra("recipeStep") 27 | mRecipeStepsList = intent.getParcelableArrayListExtra("recipeList") 28 | fragmentManager = supportFragmentManager 29 | if (savedInstanceState == null) { 30 | val stepsDetailsFragment = StepsDetailsFragment.newInstance(mRecipeStep!!, mRecipeStepsList!!) 31 | fragmentManager!!.beginTransaction() 32 | .setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out) 33 | .replace(R.id.recipe_details_container, stepsDetailsFragment) 34 | .commit() 35 | } 36 | } 37 | 38 | override fun onWindowFocusChanged(hasFocus: Boolean) { 39 | super.onWindowFocusChanged(hasFocus) 40 | if (hasFocus && isLandscape) { 41 | window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE 42 | or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 43 | or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 44 | or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION 45 | or View.SYSTEM_UI_FLAG_FULLSCREEN 46 | or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) 47 | } 48 | } 49 | 50 | override fun onButtonClicked(buttonClicked: Int, recipeStep: RecipeStep?, recipeSteps: ArrayList?, view: View) { 51 | if (buttonClicked == StepsDetailsFragment.PREV_BUTTON) { 52 | var id = recipeStep!!.stepId 53 | id-- 54 | val prevRecipeStep = recipeSteps!![id] 55 | val stepsDetailsFragment = StepsDetailsFragment.newInstance(prevRecipeStep, mRecipeStepsList!!) 56 | fragmentManager = supportFragmentManager 57 | fragmentManager!!.beginTransaction() 58 | .setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out) 59 | .replace(R.id.recipe_details_container, stepsDetailsFragment) 60 | .commit() 61 | } else { 62 | var id = recipeStep!!.stepId 63 | id++ 64 | val nextRecipeStep = recipeSteps!![id] 65 | val stepsDetailsFragment = StepsDetailsFragment.newInstance(nextRecipeStep, mRecipeStepsList!!) 66 | fragmentManager = supportFragmentManager 67 | fragmentManager!!.beginTransaction() 68 | .setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out) 69 | .replace(R.id.recipe_details_container, stepsDetailsFragment) 70 | .commit() 71 | } 72 | } 73 | 74 | override fun onBackPressed() { 75 | super.onBackPressed() 76 | this@RecipeDetailsActivity.overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /app/src/main/java/bapspatil/captainchef/ui/SplashScreenActivity.kt: -------------------------------------------------------------------------------- 1 | package bapspatil.captainchef.ui 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import android.os.Handler 6 | import android.support.v7.app.AppCompatActivity 7 | import android.view.View 8 | 9 | import bapspatil.captainchef.R 10 | 11 | class SplashScreenActivity : AppCompatActivity() { 12 | 13 | override fun onCreate(savedInstanceState: Bundle?) { 14 | super.onCreate(savedInstanceState) 15 | setContentView(R.layout.activity_splash_screen) 16 | immersiveMode() 17 | val SPLASH_TIME_OUT = 1000 18 | Handler().postDelayed({ 19 | val i = Intent(this@SplashScreenActivity, FoodItemsActivity::class.java) 20 | startActivity(i) 21 | finish() 22 | }, SPLASH_TIME_OUT.toLong()) 23 | } 24 | 25 | private fun immersiveMode() { 26 | window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE 27 | or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 28 | or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 29 | or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar 30 | 31 | or View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar 32 | 33 | or View.SYSTEM_UI_FLAG_IMMERSIVE) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/bapspatil/captainchef/ui/StepsDetailsFragment.kt: -------------------------------------------------------------------------------- 1 | package bapspatil.captainchef.ui 2 | 3 | import android.content.Context 4 | import android.net.Uri 5 | import android.os.Bundle 6 | import android.os.Handler 7 | import android.support.v4.app.Fragment 8 | import android.support.v7.widget.CardView 9 | import android.view.LayoutInflater 10 | import android.view.View 11 | import android.view.ViewGroup 12 | import android.widget.Button 13 | import android.widget.TextView 14 | import bapspatil.captainchef.R 15 | import bapspatil.captainchef.model.RecipeStep 16 | import butterknife.BindView 17 | import butterknife.ButterKnife 18 | import butterknife.OnClick 19 | import butterknife.Unbinder 20 | import com.google.android.exoplayer2.ExoPlayerFactory 21 | import com.google.android.exoplayer2.SimpleExoPlayer 22 | import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory 23 | import com.google.android.exoplayer2.source.ExtractorMediaSource 24 | import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection 25 | import com.google.android.exoplayer2.trackselection.DefaultTrackSelector 26 | import com.google.android.exoplayer2.ui.SimpleExoPlayerView 27 | import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter 28 | import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory 29 | import com.google.android.exoplayer2.util.Util 30 | import java.util.* 31 | 32 | /** 33 | * A simple [Fragment] subclass. 34 | */ 35 | class StepsDetailsFragment : Fragment() { 36 | @BindView(R.id.step_description_tv) 37 | var mStepDescription: TextView? = null 38 | @BindView(R.id.video_exoplayer_view) 39 | var mPlayerView: SimpleExoPlayerView? = null 40 | @BindView(R.id.next_button) 41 | var nextButton: Button? = null 42 | @BindView(R.id.prev_button) 43 | var prevButton: Button? = null 44 | @BindView(R.id.next_card_view) 45 | var nextCardView: CardView? = null 46 | @BindView(R.id.prev_card_view) 47 | var prevCardView: CardView? = null 48 | private var mPlayer: SimpleExoPlayer? = null 49 | private var unbinder: Unbinder? = null 50 | private var recipeStep: RecipeStep? = null 51 | private var position: Long = -1 52 | private var videoUri: Uri? = null 53 | private var recipeStepsList: ArrayList? = null 54 | lateinit var mButtonListener: OnButtonClickListener 55 | 56 | override fun onCreateView( 57 | inflater: LayoutInflater, 58 | container: ViewGroup?, 59 | savedInstanceState: Bundle? 60 | ): View? { 61 | val rootView = inflater.inflate(R.layout.fragment_steps_details, container, false) 62 | unbinder = ButterKnife.bind(this, rootView) 63 | 64 | if (savedInstanceState != null) 65 | position = savedInstanceState.getLong("SAVED_POSITION") 66 | recipeStep = arguments!!.getParcelable("recipeStep") 67 | recipeStepsList = arguments!!.getParcelableArrayList("recipeList") 68 | if (recipeStep != null) { 69 | mStepDescription!!.text = recipeStep!!.info 70 | if (recipeStep!!.videoUrl == "" || recipeStep!!.videoUrl == null) { 71 | mPlayerView!!.visibility = View.GONE 72 | } else { 73 | getPlayer() 74 | } 75 | when { 76 | recipeStep!!.stepId == 0 -> { 77 | prevButton!!.visibility = View.INVISIBLE 78 | prevCardView!!.visibility = View.INVISIBLE 79 | } 80 | recipeStep!!.stepId == recipeStepsList!!.size - 1 -> { 81 | nextButton!!.visibility = View.INVISIBLE 82 | nextCardView!!.visibility = View.INVISIBLE 83 | } 84 | else -> { 85 | prevButton!!.visibility = View.VISIBLE 86 | nextButton!!.visibility = View.VISIBLE 87 | prevCardView!!.visibility = View.VISIBLE 88 | nextCardView!!.visibility = View.VISIBLE 89 | } 90 | } 91 | } 92 | return rootView 93 | } 94 | 95 | override fun onDestroyView() { 96 | super.onDestroyView() 97 | unbinder!!.unbind() 98 | } 99 | 100 | override fun onPause() { 101 | super.onPause() 102 | if (mPlayer != null) { 103 | position = mPlayer!!.currentPosition 104 | mPlayer!!.stop() 105 | mPlayer!!.release() 106 | mPlayer = null 107 | } 108 | } 109 | 110 | override fun onResume() { 111 | super.onResume() 112 | if (mPlayer != null) { 113 | if (position.toInt() != -1) mPlayer!!.seekTo(position) 114 | } 115 | } 116 | 117 | override fun onSaveInstanceState(outState: Bundle) { 118 | super.onSaveInstanceState(outState) 119 | outState.putLong("SAVED_POSITION", position) 120 | } 121 | 122 | @OnClick(R.id.prev_button) 123 | internal fun prevStep(view: View) { 124 | mButtonListener.onButtonClicked(PREV_BUTTON, recipeStep, recipeStepsList, view) 125 | } 126 | 127 | @OnClick(R.id.next_button) 128 | internal fun nextStep(view: View) { 129 | mButtonListener.onButtonClicked(NEXT_BUTTON, recipeStep, recipeStepsList, view) 130 | } 131 | 132 | private fun getPlayer() { 133 | val mainHandler = Handler() 134 | val bandwidthMeter = DefaultBandwidthMeter() 135 | val videoTrackSelectionFactory = AdaptiveTrackSelection.Factory(bandwidthMeter) 136 | val trackSelector = DefaultTrackSelector(videoTrackSelectionFactory) 137 | mPlayer = ExoPlayerFactory.newSimpleInstance(context, trackSelector) 138 | 139 | mPlayerView!!.useController = true 140 | mPlayerView!!.requestFocus() 141 | mPlayerView!!.player = mPlayer 142 | // Measures bandwidth during playback. Can be null if not required. 143 | val defaultBandwidthMeter = DefaultBandwidthMeter() 144 | // Produces DataSource instances through which media data is loaded. 145 | val dataSourceFactory = DefaultDataSourceFactory(context!!, 146 | Util.getUserAgent(context, "CaptainChef"), defaultBandwidthMeter) 147 | // Produces Extractor instances for parsing the media data. 148 | val extractorsFactory = DefaultExtractorsFactory() 149 | // This is the MediaSource representing the media to be played. 150 | videoUri = Uri.parse(recipeStep!!.videoUrl) 151 | val videoSource = ExtractorMediaSource(videoUri, 152 | dataSourceFactory, extractorsFactory, null, null) 153 | // Prepare the player with the source. 154 | if (position.toInt() != -1) 155 | mPlayer!!.seekTo(position) 156 | mPlayer!!.prepare(videoSource) 157 | mPlayer!!.playWhenReady = true 158 | } 159 | 160 | interface OnButtonClickListener { 161 | fun onButtonClicked(buttonClicked: Int, recipeStep: RecipeStep?, recipeSteps: ArrayList?, view: View) 162 | } 163 | 164 | override fun onAttach(context: Context?) { 165 | super.onAttach(context) 166 | try { 167 | mButtonListener = context as OnButtonClickListener 168 | } catch (e: ClassCastException) { 169 | throw ClassCastException(context!!.toString() + " must implement OnButtonClickListener") 170 | } 171 | } 172 | 173 | companion object { 174 | 175 | val PREV_BUTTON = 0 176 | val NEXT_BUTTON = 1 177 | 178 | fun newInstance(recipeStep: RecipeStep, recipeStepArrayList: ArrayList): StepsDetailsFragment { 179 | val stepsDetailsFragment = StepsDetailsFragment() 180 | val bundle = Bundle() 181 | bundle.putParcelable("recipeStep", recipeStep) 182 | bundle.putParcelableArrayList("recipeList", recipeStepArrayList) 183 | stepsDetailsFragment.arguments = bundle 184 | return stepsDetailsFragment 185 | } 186 | } 187 | } // Required empty public constructor 188 | -------------------------------------------------------------------------------- /app/src/main/java/bapspatil/captainchef/ui/StepsListFragment.kt: -------------------------------------------------------------------------------- 1 | package bapspatil.captainchef.ui 2 | 3 | import android.content.Context 4 | import android.os.Bundle 5 | import android.support.v4.app.Fragment 6 | import android.support.v7.widget.LinearLayoutManager 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import bapspatil.captainchef.R 11 | import bapspatil.captainchef.adapters.IngredientsRecyclerViewAdapter 12 | import bapspatil.captainchef.adapters.StepsListRecyclerViewAdapter 13 | import bapspatil.captainchef.model.Ingredient 14 | import bapspatil.captainchef.model.RecipeStep 15 | import bapspatil.captainchef.sync.UpdateRecipeService 16 | import butterknife.ButterKnife 17 | import butterknife.OnClick 18 | import butterknife.Unbinder 19 | import es.dmoral.toasty.Toasty 20 | import kotlinx.android.synthetic.main.fragment_steps_list.* 21 | import java.util.* 22 | 23 | /** 24 | * Created by bapspatil 25 | */ 26 | class StepsListFragment : Fragment(), StepsListRecyclerViewAdapter.OnRecipeStepClickedListener { 27 | 28 | private var ingredientsList: ArrayList? = null 29 | private var mIngredientsAdapter: IngredientsRecyclerViewAdapter? = null 30 | private var recipeStepsList: ArrayList? = null 31 | private var mStepsListAdapter: StepsListRecyclerViewAdapter? = null 32 | private var foodItemName: String? = null 33 | private var unbinder: Unbinder? = null 34 | lateinit var mStepClickListener: OnStepClickListener 35 | 36 | override fun onRecipeStepClicked(recipeStep: RecipeStep) { 37 | mStepClickListener.onStepClicked(recipeStep) 38 | } 39 | 40 | interface OnStepClickListener { 41 | fun onStepClicked(mRecipeStep: RecipeStep) 42 | } 43 | 44 | @OnClick(R.id.add_to_widget_button) 45 | internal fun addToWidget(view: View) { 46 | // Start the UpdateRecipeService to update the ingredients list widget in the homescreen 47 | UpdateRecipeService.startRecipeWidgetService(context!!, ingredientsList!!, foodItemName!!) 48 | 49 | Toasty.info(context!!, "Recipe ingredients have been added to homescreen widget!", 5000).show() 50 | } 51 | 52 | override fun onStart() { 53 | super.onStart() 54 | ingredientsList = arguments!!.getParcelableArrayList("ingredientsList") 55 | recipeStepsList = arguments!!.getParcelableArrayList("recipeStepsList") 56 | foodItemName = arguments!!.getString("foodItemName") 57 | 58 | mIngredientsAdapter = IngredientsRecyclerViewAdapter(context!!, ingredientsList!!) 59 | mStepsListAdapter = StepsListRecyclerViewAdapter(context!!, recipeStepsList!!, this) 60 | 61 | ingredients_rv!!.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) 62 | val linearLayoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) 63 | steps_rv!!.layoutManager = linearLayoutManager 64 | 65 | ingredients_rv!!.adapter = mIngredientsAdapter!! 66 | steps_rv!!.adapter = mStepsListAdapter!! 67 | } 68 | 69 | override fun onCreateView( 70 | inflater: LayoutInflater, 71 | container: ViewGroup?, 72 | savedInstanceState: Bundle? 73 | ): View? { 74 | // Inflate the layout for this fragment 75 | val rootView = inflater.inflate(R.layout.fragment_steps_list, container, false) 76 | unbinder = ButterKnife.bind(this, rootView) 77 | 78 | return rootView 79 | } 80 | 81 | override fun onAttach(context: Context?) { 82 | super.onAttach(context) 83 | 84 | // This makes sure that the host activity has implemented the callback interface 85 | // If not, it throws an exception 86 | try { 87 | mStepClickListener = context as OnStepClickListener 88 | } catch (e: ClassCastException) { 89 | throw ClassCastException(context!!.toString() + " must implement OnStepClickListener") 90 | } 91 | } 92 | 93 | override fun onDestroyView() { 94 | super.onDestroyView() 95 | unbinder!!.unbind() 96 | } 97 | 98 | companion object { 99 | 100 | fun newInstance(mIngredientsList: ArrayList, mRecipeList: ArrayList, mFoodItemName: String): StepsListFragment { 101 | val stepsListFragment = StepsListFragment() 102 | val bundle = Bundle() 103 | bundle.putParcelableArrayList("ingredientsList", mIngredientsList) 104 | bundle.putParcelableArrayList("recipeStepsList", mRecipeList) 105 | bundle.putString("foodItemName", mFoodItemName) 106 | stepsListFragment.arguments = bundle 107 | return stepsListFragment 108 | } 109 | } 110 | } // Empty constructor 111 | -------------------------------------------------------------------------------- /app/src/main/java/bapspatil/captainchef/utils/CaptainChefGlideModule.kt: -------------------------------------------------------------------------------- 1 | package bapspatil.captainchef.utils 2 | 3 | import com.bumptech.glide.annotation.GlideModule 4 | import com.bumptech.glide.module.AppGlideModule 5 | 6 | /** 7 | * Created by bapspatil 8 | */ 9 | 10 | @GlideModule 11 | class CaptainChefGlideModule : AppGlideModule() 12 | -------------------------------------------------------------------------------- /app/src/main/java/bapspatil/captainchef/utils/EndlessScrollListener.kt: -------------------------------------------------------------------------------- 1 | package bapspatil.captainchef.utils 2 | 3 | import android.support.v7.widget.GridLayoutManager 4 | import android.support.v7.widget.LinearLayoutManager 5 | import android.support.v7.widget.RecyclerView 6 | import android.support.v7.widget.StaggeredGridLayoutManager 7 | 8 | /** 9 | * Created by bapspatil 10 | */ 11 | 12 | abstract class EndlessScrollListener : RecyclerView.OnScrollListener { 13 | // The minimum amount of items to have below your current scroll position 14 | // before loading more. 15 | private var visibleThreshold = 5 16 | // The current offset index of data you have loaded 17 | private var currentPage = 1 18 | // The total number of items in the dataset after the last load 19 | private var previousTotalItemCount = 0 20 | // True if we are still waiting for the last set of data to load. 21 | private var loading = true 22 | // Sets the starting page index 23 | private val startingPageIndex = 0 24 | 25 | internal var mLayoutManager: RecyclerView.LayoutManager 26 | 27 | constructor(layoutManager: LinearLayoutManager) { 28 | this.mLayoutManager = layoutManager 29 | } 30 | 31 | constructor(layoutManager: GridLayoutManager) { 32 | this.mLayoutManager = layoutManager 33 | visibleThreshold = visibleThreshold * layoutManager.spanCount 34 | } 35 | 36 | constructor(layoutManager: StaggeredGridLayoutManager) { 37 | this.mLayoutManager = layoutManager 38 | visibleThreshold = visibleThreshold * layoutManager.spanCount 39 | } 40 | 41 | fun getLastVisibleItem(lastVisibleItemPositions: IntArray): Int { 42 | var maxSize = 0 43 | for (i in lastVisibleItemPositions.indices) { 44 | if (i == 0) { 45 | maxSize = lastVisibleItemPositions[i] 46 | } else if (lastVisibleItemPositions[i] > maxSize) { 47 | maxSize = lastVisibleItemPositions[i] 48 | } 49 | } 50 | return maxSize 51 | } 52 | 53 | // This happens many times a second during a scroll, so be wary of the code you place here. 54 | // We are given a few useful parameters to help us work out if we need to load some more data, 55 | // but first we check if we are waiting for the previous load to finish. 56 | override fun onScrolled(view: RecyclerView?, dx: Int, dy: Int) { 57 | var lastVisibleItemPosition = 0 58 | val totalItemCount = mLayoutManager.itemCount 59 | 60 | if (mLayoutManager is StaggeredGridLayoutManager) { 61 | val lastVisibleItemPositions = (mLayoutManager as StaggeredGridLayoutManager).findLastVisibleItemPositions(null) 62 | // get maximum element within the list 63 | lastVisibleItemPosition = getLastVisibleItem(lastVisibleItemPositions) 64 | } else if (mLayoutManager is LinearLayoutManager) { 65 | lastVisibleItemPosition = (mLayoutManager as LinearLayoutManager).findLastVisibleItemPosition() 66 | } else if (mLayoutManager is GridLayoutManager) { 67 | lastVisibleItemPosition = (mLayoutManager as GridLayoutManager).findLastVisibleItemPosition() 68 | } 69 | 70 | // If the total item count is zero and the previous isn't, assume the 71 | // list is invalidated and should be reset back to initial state 72 | if (totalItemCount < previousTotalItemCount) { 73 | this.currentPage = this.startingPageIndex 74 | this.previousTotalItemCount = totalItemCount 75 | if (totalItemCount == 0) { 76 | this.loading = true 77 | } 78 | } 79 | // If it’s still loading, we check to see if the dataset count has 80 | // changed, if so we conclude it has finished loading and update the current page 81 | // number and total item count. 82 | if (loading && totalItemCount > previousTotalItemCount) { 83 | loading = false 84 | previousTotalItemCount = totalItemCount 85 | } 86 | 87 | // If it isn’t currently loading, we check to see if we have breached 88 | // the visibleThreshold and need to reload more data. 89 | // If we do need to reload some more data, we execute onLoadMore to fetch the data. 90 | // threshold should reflect how many total columns there are too 91 | if (!loading && lastVisibleItemPosition + visibleThreshold > totalItemCount) { 92 | currentPage++ 93 | onLoadMore(currentPage, totalItemCount) 94 | loading = true 95 | } 96 | } 97 | 98 | // Defines the process for actually loading more data based on page 99 | abstract fun onLoadMore(page: Int, totalItemsCount: Int) 100 | } 101 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/fallback_recipe_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapspatil/CaptainChef/2ecdd8e3d13a9f434d1649d0cd55c91c1c2db78a/app/src/main/res/drawable-nodpi/fallback_recipe_thumbnail.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/recipe_widget_preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapspatil/CaptainChef/2ecdd8e3d13a9f434d1649d0cd55c91c1c2db78a/app/src/main/res/drawable-nodpi/recipe_widget_preview.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/splash_screen_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapspatil/CaptainChef/2ecdd8e3d13a9f434d1649d0cd55c91c1c2db78a/app/src/main/res/drawable-nodpi/splash_screen_logo.png -------------------------------------------------------------------------------- /app/src/main/res/font/autour_one.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapspatil/CaptainChef/2ecdd8e3d13a9f434d1649d0cd55c91c1c2db78a/app/src/main/res/font/autour_one.ttf -------------------------------------------------------------------------------- /app/src/main/res/layout-land/fragment_steps_details.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | 16 | 19 | 20 | 30 | 31 | 43 | 44 | 45 | 46 | 50 | 51 | 61 | 62 |