diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..11db06a --- /dev/null +++ b/.gitignore @@ -0,0 +1,51 @@ +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 +local.properties +.DS_Store +/Database/content.db +/Database/build + +# User-specific stuff: +.idea/workspace.xml +.idea/tasks.xml + +# Sensitive or high-churn files: +.idea/dataSources/ +.idea/dataSources.ids +.idea/dataSources.xml +.idea/dataSources.local.xml +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml +.idea + +# Gradle: +.idea/gradle.xml +.idea/libraries +.gradle +/build/ + + +# Mongo Explorer plugin: +.idea/mongoSettings.xml + +## File-based project format: +*.iws +*.iml + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties diff --git a/README.md b/README.md index 2a4754e..7dea91d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,4 @@ # FoodManager Android project + +Ссылка на сервер: https://github.com/shavkunov/FoodManagerServer \ No newline at end of file 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..d87dac6 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,38 @@ +apply plugin: 'com.android.application' + +sourceCompatibility = 1.7 + +android { + compileSdkVersion 25 + buildToolsVersion "25.0.0" + defaultConfig { + applicationId "ru.spbau.mit.foodmanager" + minSdkVersion 23 + targetSdkVersion 25 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + lintOptions { + abortOnError false + } + + sourceSets { main { java.srcDirs = ['src/main/java', 'src/main/java/mod'] } } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + compile 'com.android.support:appcompat-v7:25.0.0' + compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.13' + compile group: 'com.cloudinary', name: 'cloudinary-android', version: '1.5.0' + testCompile 'junit:junit:4.12' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..55cf66c --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /home/ArgentumWalker/Android/Sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# 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 *; +#} diff --git a/app/src/androidTest/java/ru/spbau/mit/foodmanager/ExampleInstrumentedTest.java b/app/src/androidTest/java/ru/spbau/mit/foodmanager/ExampleInstrumentedTest.java new file mode 100644 index 0000000..bb39557 --- /dev/null +++ b/app/src/androidTest/java/ru/spbau/mit/foodmanager/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package ru.spbau.mit.foodmanager; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumentation test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("ru.spbau.mit.foodmanager", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..81625e6 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/BootCompleteReceiver.java b/app/src/main/java/ru/spbau/mit/foodmanager/BootCompleteReceiver.java new file mode 100644 index 0000000..6c6cc24 --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/BootCompleteReceiver.java @@ -0,0 +1,20 @@ +package ru.spbau.mit.foodmanager; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +/** + * Узнает о запуске Андроида и запускает процесс нотификаций + */ + +public class BootCompleteReceiver extends BroadcastReceiver { + public BootCompleteReceiver() { + + } + @Override + public void onReceive(Context context, Intent intent) { + context.startService(new Intent(context, NotificationService.class)); + } +} diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/Category.java b/app/src/main/java/ru/spbau/mit/foodmanager/Category.java new file mode 100644 index 0000000..7f953cc --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/Category.java @@ -0,0 +1,34 @@ +package ru.spbau.mit.foodmanager; + +import android.content.Context; +import android.graphics.Bitmap; + +import java.util.ArrayList; + +public class Category { + private String description; + private int ID; + private Bitmap image; + + public Category(int ID, String description, Bitmap image) { + this.ID = ID; + this.description = description; + this.image = image; + } + + public int getID() { + return ID; + } + + public String getDescription() { + return description; + } + + public ArrayList getRecipes(Context context) { + return CookBookStorage.getInstance(context).getRecipesOfCategory(this.ID); + } + + public Bitmap getImage() { + return image; + } +} diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/ChooseMealtimePresetActivity.java b/app/src/main/java/ru/spbau/mit/foodmanager/ChooseMealtimePresetActivity.java new file mode 100644 index 0000000..02b6c27 --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/ChooseMealtimePresetActivity.java @@ -0,0 +1,124 @@ +package ru.spbau.mit.foodmanager; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.HashMap; + +public class ChooseMealtimePresetActivity extends AppCompatActivity { + private static final int REQUEST_ADD = 0; + private static final int REQUEST_EDIT = 1; + private MenuSettings menuSettings; + private ArrayList presets; + private HashMap presetView; + private LayoutInflater inflater; + private LinearLayout presetList; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.choose_mealtime_preset); + menuSettings = MenuSettings.getInstance(this); + presets = DaySettings.getMealtimePresets(this); + inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + presetList = (LinearLayout) findViewById(R.id.choose_mealtime_preset_list); + presetView = new HashMap<>(); + fillPresets(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + DaySettings.savePresets(this); + } + + private void fillPresets() { + final LinearLayout v = presetList; + for (final DaySettings.MealtimeSettings settings : presets) { + v.addView(generatePresetView(v, settings)); + } + ImageButton addBtn = (ImageButton) findViewById(R.id.choose_mealtime_preset_add); + addBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(ChooseMealtimePresetActivity.this, + MealtimeSettingsActivity.class); + startActivityForResult(intent, REQUEST_ADD); + } + }); + } + + public void onActivityResult(int requestCode, int errorCode, Intent resultContainer) { + switch (requestCode) { + case REQUEST_ADD : + if (errorCode == RESULT_OK) { + DaySettings.MealtimeSettings result = + (DaySettings.MealtimeSettings) resultContainer.getSerializableExtra("Result"); + presets.add(result); + presetList.addView(generatePresetView(presetList, result)); + } + break; + case REQUEST_EDIT : + if (errorCode == RESULT_OK) { + DaySettings.MealtimeSettings result = + (DaySettings.MealtimeSettings) resultContainer.getSerializableExtra("Result"); + Integer position = resultContainer.getIntExtra("Position", -1); + presets.get(position).clone(result); + TextView mealtimeName = (TextView) presetView.get(presets.get(position)) + .findViewById(R.id.menu_settings_day_mealtime_name); + mealtimeName.setText(result.getName()); + } + break; + } + } + + private View generatePresetView(final LinearLayout v, final DaySettings.MealtimeSettings settings) { + final View preset = inflater.inflate(R.layout.menu_settings_day_mealtime, null); + TextView mealtimeName = (TextView) preset.findViewById(R.id.menu_settings_day_mealtime_name); + ImageButton editBtn = (ImageButton) preset.findViewById(R.id.menu_settings_day_mealtime_edit); + ImageButton delBtn = (ImageButton) preset.findViewById(R.id.menu_settings_day_mealtime_delete); + final Integer position = presets.indexOf(settings); + + mealtimeName.setText(settings.getName()); + mealtimeName.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + returnResult(settings); + } + }); + editBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(ChooseMealtimePresetActivity.this, + MealtimeSettingsActivity.class); + intent.putExtra("Settings", settings); + intent.putExtra("Position", position); + startActivityForResult(intent, REQUEST_EDIT); + } + }); + delBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + presets.remove(settings); + v.removeView(preset); + } + }); + presetView.put(settings, preset); + return preset; + } + + private void returnResult(DaySettings.MealtimeSettings result) { + Intent intent = getIntent(); + intent.putExtra("Result", result); + setResult(RESULT_OK, intent); + finish(); + } +} diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/Commands.java b/app/src/main/java/ru/spbau/mit/foodmanager/Commands.java new file mode 100644 index 0000000..2f8baca --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/Commands.java @@ -0,0 +1,27 @@ +package ru.spbau.mit.foodmanager; + +public class Commands { + public static final String getRecipeCommand = "/getRecipe"; + public static final String getRecipeCategoriesCommand = "/getRecipeCategories"; + public static final String getRecipeIngredientsCommand = "/getRecipeIngredients"; + public static final String getRecipeStepsCommand = "/getRecipeSteps"; + public static final String getRecipesByFilterCommand = "/getRecipesByFilter"; + public static final String getUserSettingsCommand = "/getUserSettings"; + public static final String getUserLikeCommand = "/getUserLike"; + public static final String getRecipeLikesCommand = "/getRecipeLikes"; + public static final String getFavoritesCommand = "/getFavorites"; + public static final String getRecipesOfCategoryCommand = "/getRecipesOfCategory"; + public static final String getCategoryByIDCommand = "/getCategoryByID"; + public static final String getCategoriesListCommand = "/getCategoriesList"; + public static final String setUserLikeCommand = "/setLike"; + public static final String setUserNotLikeCommand = "/setNotLike"; + public static final String addToFavoritesCommand = "/addToFavorites"; + public static final String removeFromFavoritesCommand = "/removeFromFavorites"; + public static final String saveUserSettingsCommand = "/saveUserSettings"; + public static final String getRandomDishCommand = "/getRandomDishOfCategory"; + public static final String isUserOwnRecipeCommand = "/ownRecipe"; + public static final String insertRecipeCommand = "/insertRecipe"; + public static final String deleteRecipeCommand = "/deleteRecipe"; + public static final String changeRecipeCommand = "/changeRecipe"; +} + diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/CookBookActivity.java b/app/src/main/java/ru/spbau/mit/foodmanager/CookBookActivity.java new file mode 100755 index 0000000..0452a07 --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/CookBookActivity.java @@ -0,0 +1,177 @@ +package ru.spbau.mit.foodmanager; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.Spinner; + +import java.util.ArrayList; + +public class CookBookActivity extends AppCompatActivity { + private Intent task; + public static final int GROUPINGBY_TYPE_OF_DISH = 0; + public static final int GROUPINGBY_NATIONAL_KITCHEN = 1; + public static final int TARGET_NO = 0; + public static final int TARGET_CATEGORY = 1; + public static final int TARGET_RECIPE = 2; + private Integer target; + private static final String[] CATEGORY_TYPES = + {"Тип блюда", + "Национальные кухни"}; + private final ArrayList categories = new ArrayList<>(); + private CookBookStorage cookbook; + private GifImageView loaderAnimation; + private LinearLayout informationLayout; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.cook_book); + + //Init loaderAnimation + loaderAnimation = (GifImageView) findViewById(R.id.loader_animation_view); + loaderAnimation.setGifImageResource(LoaderAnimationSelector.getRandomLoaderResource()); + loaderAnimation.setLayerType(View.LAYER_TYPE_SOFTWARE, null); + informationLayout = (LinearLayout) findViewById(R.id.information_layout); + //Init Task + task = getIntent(); + target = task.getIntExtra("Target", TARGET_NO); + //Init Categories + cookbook = CookBookStorage.getInstance(this); + //Group by init + ArrayAdapter adapter = new ArrayAdapter(this, + android.R.layout.simple_spinner_dropdown_item, CATEGORY_TYPES); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + Spinner groupingBy = (Spinner) findViewById(R.id.cook_book_group_by); + groupingBy.setAdapter(adapter); + groupingBy.setSelection(0); + groupingBy.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int i, long l) { + ContentLoader contentLoader; + Thread loader; + switch (i) { + case 0: + contentLoader = new ContentLoader(GROUPINGBY_TYPE_OF_DISH); + loader = new Thread(contentLoader); + loader.start(); + break; + case 1: + contentLoader = new ContentLoader(GROUPINGBY_NATIONAL_KITCHEN); + loader = new Thread(contentLoader); + loader.start(); + break; + } + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + //categories.clear(); + } + }); + } + + public void onSearchClick(View v) { + Intent intent = new Intent(this, SearchRecipeActivity.class); + startActivityForResult(intent, target); + } + + public void onActivityResult(int requestCode, int errorCode, Intent resultContainer) { + if (errorCode == RESULT_OK) { + if (target == TARGET_RECIPE) { + setResult(RESULT_OK, resultContainer); + finish(); + } else { + int recipeID = resultContainer.getIntExtra("Result", 0); + Intent intent = new Intent(this, RecipeViewActivity.class); + intent.putExtra("Recipe", recipeID); + startActivity(intent); + } + } + } + + private void showCategories() { + ListView listView = (ListView) findViewById(R.id.cook_book_list); + //TODO: сделать собственные адаптеры для CookBook и Category + ArrayList names = new ArrayList<>(); + for (Category c : categories) { + names.add(c.getDescription()); + } + ArrayAdapter adapter = new ArrayAdapter<>(this, + android.R.layout.simple_list_item_1, names); + listView.setAdapter(adapter); + listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView adapterView, View view, int i, long l) { + Intent intent; + switch (target) { + case TARGET_NO: + intent = new Intent(CookBookActivity.this, CookBookCategoryActivity.class); + intent.putExtra("Category", categories.get(i).getID()); + startActivity(intent); + break; + case TARGET_RECIPE: + intent = new Intent(CookBookActivity.this, CookBookCategoryActivity.class); + intent.putExtras(task); + intent.putExtra("Category", categories.get(i).getID()); + startActivityForResult(intent, TARGET_RECIPE); + break; + case TARGET_CATEGORY: + intent = task; + intent.putExtra("Result", categories.get(i).getID()); + setResult(RESULT_OK, intent); + finish(); + break; + } + } + }); + } + + public class ContentLoader implements Runnable { + private int groupingBy; + public ContentLoader(int groupingBy) { + this.groupingBy = groupingBy; + } + + @Override + public void run() { + CookBookActivity.this.runOnUiThread(new Runnable() { + @Override + public void run() { + loaderAnimation.setVisibility(View.VISIBLE); + informationLayout.setVisibility(View.INVISIBLE); + } + }); + categories.clear(); + while(categories.isEmpty()) { + categories.clear(); + try { + switch (groupingBy) { + case GROUPINGBY_TYPE_OF_DISH: + categories.addAll(cookbook.getRecipesTypeOfDish()); + break; + case GROUPINGBY_NATIONAL_KITCHEN: + categories.addAll(cookbook.getRecipesNationalKitchen()); + break; + } + } catch (Throwable e) { + //Repeat + } + } + CookBookActivity.this.runOnUiThread(new Runnable() { + @Override + public void run() { + showCategories(); + loaderAnimation.setVisibility(View.INVISIBLE); + informationLayout.setVisibility(View.VISIBLE); + loaderAnimation.setGifImageResource(LoaderAnimationSelector.getRandomLoaderResource()); + } + }); + } + } +} diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/CookBookCategoryActivity.java b/app/src/main/java/ru/spbau/mit/foodmanager/CookBookCategoryActivity.java new file mode 100755 index 0000000..800a1fe --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/CookBookCategoryActivity.java @@ -0,0 +1,138 @@ +package ru.spbau.mit.foodmanager; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ListView; + +import java.util.ArrayList; + +public class CookBookCategoryActivity extends AppCompatActivity { + public static final int TARGET_FAVOURITES = 10; + private Category category; + private Intent task; + private Integer target; + private ArrayList recipes; + private GifImageView loaderAnimation; + private ListView informationLayout; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.cook_book_category); + //Init loaderAnimation + loaderAnimation = (GifImageView) findViewById(R.id.loader_animation_view); + loaderAnimation.setGifImageResource(LoaderAnimationSelector.getRandomLoaderResource()); + loaderAnimation.setLayerType(View.LAYER_TYPE_SOFTWARE, null); + informationLayout = (ListView) findViewById(R.id.cook_book_category_list); + //Task init + task = getIntent(); + target = task.getIntExtra("Target",CookBookActivity.TARGET_NO); + ContentLoader contentLoader = new ContentLoader(task.getIntExtra("Category", -1)); + Thread loader = new Thread(contentLoader); + loader.start(); + //category = CookBookStorage.getInstance().getCategoryByID(task.getIntExtra("Category", -1)); + } + + public void onActivityResult(int requestCode, int errorCode, Intent resultContainer) { + if (errorCode == RESULT_OK) { + if (target == CookBookActivity.TARGET_RECIPE) { + setResult(RESULT_OK, resultContainer); + finish(); + } else { + int recipeID = resultContainer.getIntExtra("Result", 0); + Intent intent = new Intent(this, RecipeViewActivity.class); + intent.putExtra("Recipe", recipeID); + startActivity(intent); + } + } + } + + public void onSearchClick(View v) { + Intent intent = new Intent(this, SearchRecipeActivity.class); + startActivityForResult(intent, target); + } + + private void showRecipies() { + Log.d("ShowRecipies", "Show"); + ListView listView = (ListView) findViewById(R.id.cook_book_category_list); + ArrayList names = new ArrayList<>(); + if (recipes != null) { + for (Recipe r : recipes) { + names.add(r.getName()); + } + } + Log.d("names", ((Integer)names.size()).toString()); + ArrayAdapter adapter = new ArrayAdapter<>(this, + android.R.layout.simple_list_item_1, names); + listView.setAdapter(adapter); + listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView adapterView, View view, int i, long l) { + Intent intent; + switch (target) { + case CookBookActivity.TARGET_NO : + intent = new Intent(CookBookCategoryActivity.this, RecipeViewActivity.class); + intent.putExtra("Recipe", recipes.get(i).getID()); + startActivity(intent); + break; + case TARGET_FAVOURITES : + intent = new Intent(CookBookCategoryActivity.this, RecipeViewActivity.class); + intent.putExtra("Recipe", recipes.get(i).getID()); + startActivity(intent); + break; + case CookBookActivity.TARGET_RECIPE : + intent = task; + intent.putExtra("Result", recipes.get(i).getID()); + setResult(RESULT_OK, intent); + finish(); + break; + } + } + }); + } + + public class ContentLoader implements Runnable { + private int categoryID; + public ContentLoader(int id) { + this.categoryID = id; + } + + @Override + public void run() { + CookBookCategoryActivity.this.runOnUiThread(new Runnable() { + @Override + public void run() { + loaderAnimation.setVisibility(View.VISIBLE); + informationLayout.setVisibility(View.INVISIBLE); + } + }); + recipes = null; + while (recipes == null) { + try { + if (target != TARGET_FAVOURITES) { + recipes = CookBookStorage.getInstance(CookBookCategoryActivity.this).getRecipesOfCategory(categoryID); + } else { + recipes = CookBookStorage.getInstance(CookBookCategoryActivity.this).getFavorites(); + } + } + catch (Throwable e) { + //Repeat + } + } + CookBookCategoryActivity.this.runOnUiThread(new Runnable() { + @Override + public void run() { + loaderAnimation.setVisibility(View.INVISIBLE); + informationLayout.setVisibility(View.VISIBLE); + showRecipies(); + loaderAnimation.setGifImageResource(LoaderAnimationSelector.getRandomLoaderResource()); + } + }); + } + } +} diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/CookBookStorage.java b/app/src/main/java/ru/spbau/mit/foodmanager/CookBookStorage.java new file mode 100644 index 0000000..f0fd5d0 --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/CookBookStorage.java @@ -0,0 +1,875 @@ +package ru.spbau.mit.foodmanager; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.util.Log; + +import com.cloudinary.Cloudinary; +import com.cloudinary.utils.ObjectUtils; + +import org.json.JSONObject; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.Map; + +import static ru.spbau.mit.foodmanager.Commands.addToFavoritesCommand; +import static ru.spbau.mit.foodmanager.Commands.changeRecipeCommand; +import static ru.spbau.mit.foodmanager.Commands.deleteRecipeCommand; +import static ru.spbau.mit.foodmanager.Commands.getCategoriesListCommand; +import static ru.spbau.mit.foodmanager.Commands.getCategoryByIDCommand; +import static ru.spbau.mit.foodmanager.Commands.getFavoritesCommand; +import static ru.spbau.mit.foodmanager.Commands.getRandomDishCommand; +import static ru.spbau.mit.foodmanager.Commands.getRecipeCategoriesCommand; +import static ru.spbau.mit.foodmanager.Commands.getRecipeCommand; +import static ru.spbau.mit.foodmanager.Commands.getRecipeIngredientsCommand; +import static ru.spbau.mit.foodmanager.Commands.getRecipeLikesCommand; +import static ru.spbau.mit.foodmanager.Commands.getRecipeStepsCommand; +import static ru.spbau.mit.foodmanager.Commands.getRecipesByFilterCommand; +import static ru.spbau.mit.foodmanager.Commands.getRecipesOfCategoryCommand; +import static ru.spbau.mit.foodmanager.Commands.getUserLikeCommand; +import static ru.spbau.mit.foodmanager.Commands.getUserSettingsCommand; +import static ru.spbau.mit.foodmanager.Commands.insertRecipeCommand; +import static ru.spbau.mit.foodmanager.Commands.isUserOwnRecipeCommand; +import static ru.spbau.mit.foodmanager.Commands.removeFromFavoritesCommand; +import static ru.spbau.mit.foodmanager.Commands.saveUserSettingsCommand; +import static ru.spbau.mit.foodmanager.Commands.setUserLikeCommand; +import static ru.spbau.mit.foodmanager.Commands.setUserNotLikeCommand; + +/** + * Хранилище всех рецептов. Singleton. В этом классе слишком много методов, которые можно отнести + * к другим классам. Будет изменено после рефакторинга. + */ +public class CookBookStorage { + private static final String CLOUD_SERVER_IP = "138.68.91.54"; + private static final String LOCAL_SERVER_IP = "192.168.211.199"; + private static final int port = 48800; // free random port; + private static final int HTTP_CONNECT_TIMEOUT_MS = 5000; + private static final int HTTP_READ_TIMEOUT_MS = 5000; + private static final int MAX_ATTEMPTS = 3; + + /** + * Инстанс класса CookBookStorage. + */ + private static CookBookStorage instance; + + /** + * Вспомогательный тег для отладки. + */ + private final String LOG_TAG = "CookBookStorageTAG"; + + /** + * Логин для хостинга картинок cloudinary. + */ + private final String CLOUDINARY_URL = "cloudinary://285162791646134:yGqzM1FdReQ8uPa1taEUZihoNgI@dxc952wrd"; + + /** + * ID пользователя. + */ + private String userID; + + private CookBookStorage(Context context) { + userID = Installation.getUserID(context); + } + + /** + * Изменение информации о рецепте. Можно было изменить рецепт, удалив его и добавив новый, + * но использование этого метода эффективнее в количестве SQL запросов. + * @param recipe информация этого рецепта будет помещена в БД. + */ + public void changeRecipe(RecipeToChange recipe) throws Exception { + for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + HttpURLConnection connection = storeRecipeInformation(recipe, changeRecipeCommand); + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + continue; + } + break; + } + } + + /** + * Добавление рецепта в базу данных на сервере. Если одна из операций вставок провалилась, + * то рецепт не будет встален полностью. + * @param recipe добавление рецепта в базу данных на сервере. + * @return ID рецепта. + */ + public int insertRecipe(RecipeToChange recipe) throws Exception { + int recipeID = -1; + for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + HttpURLConnection connection = storeRecipeInformation(recipe, insertRecipeCommand); + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + continue; + } + + ObjectInputStream input = new ObjectInputStream(connection.getInputStream()); + recipeID = input.readInt(); + input.close(); + + break; + } + + return recipeID; + } + + private HttpURLConnection storeRecipeInformation(RecipeToChange recipe, String command) throws Exception { + final HttpURLConnection connection = + openHttpURLConnectionForServerCommand(command); + + ObjectOutputStream output = new ObjectOutputStream(connection.getOutputStream()); + output.writeObject(recipe.getName()); + output.writeObject(recipe.getDescription()); + output.writeObject(recipe.getCategoryIDs()); + output.writeObject(userID); + output.writeObject(recipe.getIngredients()); + + ArrayList descriptions = new ArrayList<>(); + ArrayList links = new ArrayList<>(); + if (recipe.getSteps() != null) { + for (Step step : recipe.getSteps()) { + descriptions.add(step.getDescription()); + links.add(uploadImage(step.getImage())); + } + } + + output.writeObject(descriptions); + output.writeObject(links); + + if (command.equals(deleteRecipeCommand) || command.equals(changeRecipeCommand)) { + output.writeInt(recipe.getID()); + } + output.flush(); + output.close(); + + return connection; + } + + /** + * Загрузка изображения. + * @param bitmap изображение. + * @return онлайн ссылка на изображение в сервисе cloudinary + */ + private String uploadImage(Bitmap bitmap) throws Exception { + InputStream imageIn = convertImage(bitmap); + Cloudinary cloudinary = new Cloudinary(CLOUDINARY_URL); + Map result = cloudinary.uploader().upload(imageIn, ObjectUtils.emptyMap()); + JSONObject jsonObject = new JSONObject(result); + + return jsonObject.getString("url"); + } + + private ByteArrayInputStream convertImage(Bitmap bitmap) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.PNG, 0 /*ignored for PNG*/, bos); + byte[] bitmapData = bos.toByteArray(); + return new ByteArrayInputStream(bitmapData); + } + + /** + * Добавить рецепт в избранное, предполагается, что его там нет. + * @param recipe рецепт, который хотим добавить. + */ + public void addToFavorites(Recipe recipe) { + for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + try { + HttpURLConnection connection = openHttpURLConnectionForServerCommand( + addToFavoritesCommand); + ObjectOutputStream output = new ObjectOutputStream(connection.getOutputStream()); + output.writeInt(recipe.getID()); + output.writeObject(userID); + output.flush(); + output.close(); + + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + continue; + } + break; + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + /** + * Удаление рецепта из БД. + */ + public void deleteRecipe(RecipeToChange recipe) { + try { + for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + HttpURLConnection connection = storeRecipeInformation(recipe, deleteRecipeCommand); + + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + continue; + } + + break; + } + } catch (Exception e) { + Log.d(LOG_TAG, "Unable to delete recipe"); + e.printStackTrace(); + } + } + + /** + * Удаление рецепта из избранного. + * @param recipeID ID рецепта. + */ + public void removeFromFavorites(int recipeID) { + for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + try { + HttpURLConnection connection = openHttpURLConnectionForServerCommand( + removeFromFavoritesCommand); + ObjectOutputStream output = new ObjectOutputStream(connection.getOutputStream()); + output.writeInt(recipeID); + output.flush(); + output.close(); + + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + continue; + } + break; + } catch (Exception e) { + Log.d(LOG_TAG, "Не удалось убрать рецепт из избранного"); + e.printStackTrace(); + } + } + } + + /** + * Получение рецепта по его уникальному идентификатору. + */ + public Recipe getRecipe(int ID) { + Recipe recipe = null; + + try { + for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + final HttpURLConnection connection = + openHttpURLConnectionForServerCommand(getRecipeCommand); + + ObjectOutputStream stream = new ObjectOutputStream(connection.getOutputStream()); + stream.writeInt(ID); + stream.flush(); + stream.close(); + + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + continue; + } + + ObjectInputStream input = new ObjectInputStream(connection.getInputStream()); + recipe = (Recipe) input.readObject(); + input.close(); + break; + } + } catch (Exception e) { + Log.d(LOG_TAG, "Unable to get recipe"); + e.printStackTrace(); + } + + return recipe; + } + + /** + * Получение категорий, к которым принадлежит рецепт. + * @param ID ID рецепта. + */ + public ArrayList getRecipeCategories(int ID) { + ArrayList ids = null; + try { + for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + final HttpURLConnection connection = openHttpURLConnectionForServerCommand( + getRecipeCategoriesCommand); + + ObjectOutputStream output = new ObjectOutputStream(connection.getOutputStream()); + output.writeInt(ID); + output.flush(); + output.close(); + + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + continue; + } + + ObjectInputStream input = new ObjectInputStream(connection.getInputStream()); + ids = (ArrayList) input.readObject(); + break; + } + } catch (Exception e) { + e.printStackTrace(); + Log.d(LOG_TAG, "Unable to get categories of recipe"); + } + + return ids; + } + + /** + * Получение ингредиентов из двух таблиц: Ingredient_to_recipe и Ingredient + * @param ID ID рецепта. + */ + public ArrayList getRecipeIngredients(int ID) { + ArrayList recipeIngredients = new ArrayList<>(); + + try { + for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + final HttpURLConnection connection = openHttpURLConnectionForServerCommand( + getRecipeIngredientsCommand); + + ObjectOutputStream output = new ObjectOutputStream(connection.getOutputStream()); + output.writeInt(ID); + output.flush(); + output.close(); + + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + continue; + } + + ObjectInputStream input = new ObjectInputStream(connection.getInputStream()); + recipeIngredients = (ArrayList) input.readObject(); + + break; + } + } catch (Exception e) { + e.printStackTrace(); + } + + return recipeIngredients; + } + + /** + * Получение инструкции для готовки из двух таблиц -- Step и Image. + * Картинки не загружаются. + * @param ID ID рецепта. + */ + public ArrayList getRecipeSteps(int ID) { + ArrayList recipeSteps = new ArrayList<>(); + + for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + try { + HttpURLConnection connection = openHttpURLConnectionForServerCommand( + getRecipeStepsCommand); + ObjectOutputStream output = new ObjectOutputStream(connection.getOutputStream()); + output.writeInt(ID); + output.flush(); + output.close(); + + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + continue; + } + + ObjectInputStream input = new ObjectInputStream(connection.getInputStream()); + ArrayList> stepsData = (ArrayList>) + input.readObject(); + + for (ArrayList data : stepsData) { + String stepDescription = data.get(0); + String imageURL = data.get(1); + recipeSteps.add(new Step(stepDescription, imageURL)); + } + + break; + } catch (Exception e) { + e.printStackTrace(); + } + } + + return recipeSteps; + } + + /** + * Получение списка рецептов по фильтру, т.е. по префиксу. + */ + public ArrayList getRecipesByFilter(String filter) { + filter = filter.toLowerCase(); + if (filter.length() > 0) { + filter = filter.substring(0, 1).toUpperCase() + filter.substring(1); + } + + ArrayList res = new ArrayList<>(); + for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + try { + HttpURLConnection connection = openHttpURLConnectionForServerCommand( + getRecipesByFilterCommand); + + ObjectOutputStream output = new ObjectOutputStream(connection.getOutputStream()); + output.writeObject(filter); + output.flush(); + output.close(); + + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + continue; + } + + ObjectInputStream input = new ObjectInputStream(connection.getInputStream()); + res = (ArrayList) input.readObject(); + + } catch (Exception e) { + Log.d(LOG_TAG, "Unable to filter recipes"); + e.printStackTrace(); + } + } + + return res; + } + + /** + * Получение пользовательских настроек в БД. + * @return settings настройки хранятся как сериализованный instance MenuSettings в этой строчке. + */ + public String getUserSettings() { + String userSettings = ""; + for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + try { + HttpURLConnection connection = openHttpURLConnectionForServerCommand( + getUserSettingsCommand); + + ObjectOutputStream output = new ObjectOutputStream(connection.getOutputStream()); + output.writeObject(userID); + output.flush(); + output.close(); + + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + continue; + } + + ObjectInputStream input = new ObjectInputStream(connection.getInputStream()); + userSettings = (String) input.readObject(); + input.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + return userSettings; + } + + /** + * Получение инстанса класса CookBookStorage. + * @param context контекст приложения. + * @return инстанс класса. + */ + public static CookBookStorage getInstance(Context context) { + if (instance == null) { + instance = new CookBookStorage(context); + } + + return instance; + } + + /** + * Возвращает количество лайков рецепта. + * @param recipe вернется количество лайков этого рецепта. + * @return null если не удалось соединится с сервером, иначе количество лайков. + */ + public Integer getRecipeLikes(Recipe recipe) { + for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + try { + HttpURLConnection connection = openHttpURLConnectionForServerCommand( + getRecipeLikesCommand); + ObjectOutputStream output = new ObjectOutputStream(connection.getOutputStream()); + output.writeInt(recipe.getID()); + output.flush(); + output.close(); + + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + continue; + } + + ObjectInputStream input = new ObjectInputStream(connection.getInputStream()); + int likes = input.readInt(); + input.close(); + return likes; + } catch (Exception e) { + e.printStackTrace(); + } + } + + return null; + } + + /** + * Если пользователь поставил лайк то вернется True, иначе False. Null если не получилось + * соединиться с сервером. + * @param recipe вернется лайк пользователя этого рецепта. + * @return если пользователь поставил лайк то вернется True, иначе False. Null если не получилось + * соединиться с сервером. + */ + public Boolean getUserLike(Recipe recipe) { + for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + try { + HttpURLConnection connection = openHttpURLConnectionForServerCommand(getUserLikeCommand); + ObjectOutputStream output = new ObjectOutputStream(connection.getOutputStream()); + output.writeInt(recipe.getID()); + output.writeObject(userID); + output.flush(); + output.close(); + + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + continue; + } + + ObjectInputStream input = new ObjectInputStream(connection.getInputStream()); + int like = input.readInt(); + input.close(); + return like == 1; + } catch (Exception e) { + Log.d(LOG_TAG, "Не удалось получить лайк"); + e.printStackTrace(); + } + } + + return null; + } + + /** + * Получение избранных рецептов. + * @return пустой список если в избранном пусто. + */ + public ArrayList getFavorites() { + ArrayList favorites = new ArrayList<>(); + for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + try { + HttpURLConnection connection = openHttpURLConnectionForServerCommand( + getFavoritesCommand); + ObjectOutputStream output = new ObjectOutputStream(connection.getOutputStream()); + output.writeObject(userID); + output.flush(); + output.close(); + + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + continue; + } + + ObjectInputStream input = new ObjectInputStream(connection.getInputStream()); + favorites = (ArrayList) input.readObject(); + input.close(); + break; + } catch (Exception e) { + Log.d(LOG_TAG, "Не удалось получить избранное"); + e.printStackTrace(); + } + } + + return favorites; + } + + /** + * Получение всех рецептов категории. + * @param ID идентификатор категории. + * @return рецепты категории. + */ + public ArrayList getRecipesOfCategory(int ID) { + ArrayList recipes = new ArrayList<>(); + for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + try { + HttpURLConnection connection = openHttpURLConnectionForServerCommand( + getRecipesOfCategoryCommand); + ObjectOutputStream output = new ObjectOutputStream(connection.getOutputStream()); + output.writeInt(ID); + output.flush(); + output.close(); + + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + continue; + } + + ObjectInputStream input = new ObjectInputStream(connection.getInputStream()); + recipes = (ArrayList) input.readObject(); + input.close(); + break; + } catch (Exception e) { + Log.d(LOG_TAG, "Не удалось получить рецепты категории"); + e.printStackTrace(); + } + } + + return recipes; + } + + /** + * Получение категории по ID. + * @param ID идентификатор категории, которую хотим получить. + * @return инстанс класса Category + */ + public Category getCategoryByID(int ID) { + Category category = null; + + for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + try { + HttpURLConnection connection = openHttpURLConnectionForServerCommand( + getCategoryByIDCommand); + ObjectOutputStream output = new ObjectOutputStream(connection.getOutputStream()); + output.writeInt(ID); + output.flush(); + output.close(); + + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + continue; + } + + ObjectInputStream input = new ObjectInputStream(connection.getInputStream()); + String categoryName = (String) input.readObject(); + category = new Category(ID, categoryName, null); + input.close(); + break; + } catch (Exception e) { + Log.d(LOG_TAG, "Не удалось получить категорию"); + e.printStackTrace(); + } + } + + return category; + } + + /** + * Получение списка категорий. + * @param categoryType тип категорий, который хотим получить. + * @return Linked List категорий, полученных из запроса. + */ + private LinkedList getCategoryByType(String categoryType) { + LinkedList categories = new LinkedList<>(); + + for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + try { + HttpURLConnection connection = openHttpURLConnectionForServerCommand( + getCategoriesListCommand); + ObjectOutputStream output = new ObjectOutputStream(connection.getOutputStream()); + output.writeObject(categoryType); + output.flush(); + output.close(); + + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + continue; + } + + ObjectInputStream input = new ObjectInputStream(connection.getInputStream()); + ArrayList ids = (ArrayList) input.readObject(); + ArrayList names = (ArrayList) input.readObject(); + for (int i = 0; i < ids.size(); i++) { + categories.add(new Category(ids.get(i), names.get(i), null)); + } + + input.close(); + break; + } catch (Exception e) { + Log.d(LOG_TAG, "Не удалось получить список категорий"); + e.printStackTrace(); + } + } + + return categories; + } + + /** + * Получение списка категорий по типу блюда. + * @return список категорий по типу блюда. + */ + public LinkedList getRecipesTypeOfDish() { + String categoryType = "category_dish"; + + return getCategoryByType(categoryType); + } + + /** + * Получение списка категорий по национальной кухне блюда. + * @return список категорий по национальной кухне блюда. + */ + public LinkedList getRecipesNationalKitchen() { + String categoryType = "national_kitchen"; + + return getCategoryByType(categoryType); + } + + //----------------------------------rest------------------------------- + + /** + * Сохранение пользовательских настроек в БД. + * @param settings настройки хранятся как сериализованный instance MenuSettings в этой строчке. + */ + public void saveUserSettings(String settings) { + for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + try { + HttpURLConnection connection = openHttpURLConnectionForServerCommand( + saveUserSettingsCommand); + ObjectOutputStream output = new ObjectOutputStream(connection.getOutputStream()); + output.writeObject(userID); + output.writeObject(settings); + output.flush(); + output.close(); + + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + continue; + } + + break; + } catch (Exception e) { + Log.d(LOG_TAG, "Не удалось сохранить настройки"); + e.printStackTrace(); + } + } + } + + /** + * Загрузка картинки шага. + * @param step загрузка прямо в поле объекта. + */ + public void downloadStepImage(Step step) { + Log.d(LOG_TAG, step.getImageLink()); + try { + Bitmap image = BitmapFactory.decodeStream((InputStream)new URL(step.getImageLink()) + .getContent()); + + step.setImage(image); + } catch (IOException e) { + Log.d(LOG_TAG, "Unable to load step image\ndescription: " + step.getDescription()); + e.printStackTrace(); + } + } + + /** + * Метод ставит лайк пользователя. + * @param recipeID ID рецепта. + * @return true если операция прошла успешно, иначе false. + */ + public boolean setLike(int recipeID) { + for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + try { + HttpURLConnection connection = openHttpURLConnectionForServerCommand(setUserLikeCommand); + ObjectOutputStream output = new ObjectOutputStream(connection.getOutputStream()); + output.writeInt(recipeID); + output.writeObject(userID); + output.flush(); + output.close(); + + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + continue; + } + + return true; + } catch (Exception e) { + Log.d(LOG_TAG, "Не удалось поставить лайк"); + e.printStackTrace(); + } + } + + return false; + } + + /** + * Метод убирает лайк пользователя. + * @param recipeID ID рецепта. + * @return true, если операция прошла успешно, false иначе. + */ + public boolean setNotLike(int recipeID) { + for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + try { + HttpURLConnection connection = openHttpURLConnectionForServerCommand( + setUserNotLikeCommand); + ObjectOutputStream output = new ObjectOutputStream(connection.getOutputStream()); + output.writeInt(recipeID); + output.writeObject(userID); + output.flush(); + output.close(); + + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + continue; + } + + return true; + } catch (Exception e) { + Log.d(LOG_TAG, "Не удалось убрать лайк"); + e.printStackTrace(); + } + } + + return false; + } + + /** + * Выбор случайного блюда категории. + */ + public Recipe chooseRandomDishFromCategory(int categoryID) { + Recipe recipe = null; + + for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + try { + HttpURLConnection connection = openHttpURLConnectionForServerCommand( + getRandomDishCommand); + ObjectOutputStream output = new ObjectOutputStream(connection.getOutputStream()); + output.writeInt(categoryID); + output.flush(); + output.close(); + + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + continue; + } + + ObjectInputStream input = new ObjectInputStream(connection.getInputStream()); + recipe = (Recipe) input.readObject(); + input.close(); + + break; + } catch (Exception e) { + Log.d(LOG_TAG, "Не удалось получить случайный рецепт категории"); + e.printStackTrace(); + } + } + + return recipe; + } + + /** + * Является ли текущий пользователь владельцем рецепта. + * @param recipe рецепт, который хотим проверить + * @return true если recipe принадлежить пользователь и false иначе. + */ + public boolean isUserOwnRecipe(Recipe recipe) { + boolean answer = false; + + for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + try { + HttpURLConnection connection = openHttpURLConnectionForServerCommand( + isUserOwnRecipeCommand); + ObjectOutputStream output = new ObjectOutputStream(connection.getOutputStream()); + output.writeInt(recipe.getID()); + output.writeObject(userID); + output.flush(); + output.close(); + + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + continue; + } + + ObjectInputStream input = new ObjectInputStream(connection.getInputStream()); + answer = input.readBoolean(); + input.close(); + + break; + } catch (Exception e) { + Log.d(LOG_TAG, "Не удалось проверить принадлежность рецепта"); + e.printStackTrace(); + } + } + + return answer; + } + + private static HttpURLConnection openHttpURLConnectionForServerCommand(String command) throws IOException { + final String urlString = "http://" + CLOUD_SERVER_IP + ':' + port + command; + + final URL url = new URL(urlString); + + final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("POST"); + connection.setDoOutput(true); + connection.setConnectTimeout(HTTP_CONNECT_TIMEOUT_MS); + connection.setReadTimeout(HTTP_READ_TIMEOUT_MS); + + return connection; + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/Day.java b/app/src/main/java/ru/spbau/mit/foodmanager/Day.java new file mode 100644 index 0000000..7c170c1 --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/Day.java @@ -0,0 +1,3 @@ +package ru.spbau.mit.foodmanager; + +enum Day {Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday} diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/DayMenu.java b/app/src/main/java/ru/spbau/mit/foodmanager/DayMenu.java new file mode 100644 index 0000000..9cde0ed --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/DayMenu.java @@ -0,0 +1,39 @@ +package ru.spbau.mit.foodmanager; + +import android.content.Intent; + +import java.io.Serializable; +import java.util.ArrayList; + +/** + * Описывет меню на день. + */ + +public class DayMenu implements Serializable{ + private ArrayList mealtimes; + + /** + * Просто сохраняет список Mealtime + */ + public DayMenu(ArrayList dishes) { + mealtimes = dishes; + } + + /** + * Возвращает сохраненный список Mealtime + */ + public ArrayList getMealtimes() { + return mealtimes; + } + + /** + * Возвращает список всех рецептов из Mealtime + */ + public ArrayList getDishes() { + ArrayList result = new ArrayList<>(); + for (Mealtime m : mealtimes) { + result.addAll(m.getRecipeIDs()); + } + return result; + } +} diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/DaySettings.java b/app/src/main/java/ru/spbau/mit/foodmanager/DaySettings.java new file mode 100644 index 0000000..efdf142 --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/DaySettings.java @@ -0,0 +1,118 @@ +package ru.spbau.mit.foodmanager; + +import android.content.Context; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.ArrayList; + +/** + * Настройки меню определенного дня + */ + +public class DaySettings implements Serializable { + private ArrayList mealtimeSettings = new ArrayList<>(); + static private ArrayList presets; + private static final String presetsFilename = "Presets"; + + public static void savePresets(Context context) { + try { + FileOutputStream output = context.openFileOutput( + presetsFilename, Context.MODE_PRIVATE); + + ObjectOutputStream outputStream = new ObjectOutputStream(output); + outputStream.writeObject(presets); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void loadPresets(Context context) { + File settings = new File(context.getFilesDir(), presetsFilename); + if (settings.exists()) { + try { + FileInputStream input = context.openFileInput(presetsFilename); + ObjectInputStream inputStream = new ObjectInputStream(input); + presets = (ArrayList) inputStream.readObject(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + static public ArrayList getMealtimePresets(Context context) { + if (presets == null) { + loadPresets(context); + } + if (presets == null) { + presets = new ArrayList<>(); + { + ArrayList categories = new ArrayList<>(); + categories.add(10); + presets.add(new MealtimeSettings("Завтрак", categories)); + } + { + ArrayList categories = new ArrayList<>(); + categories.add(9); + //categories.add(1); + //categories.add(0); + presets.add(new MealtimeSettings("Обед", categories)); + } + { + ArrayList categories = new ArrayList<>(); + categories.add(8); + presets.add(new MealtimeSettings("Ужин", categories)); + } + savePresets(context); + } + return presets; + } + + public DaySettings(ArrayList settings) { + mealtimeSettings = new ArrayList<>(); + for (MealtimeSettings s : settings) { + mealtimeSettings.add(new MealtimeSettings(s.getName(), s.dishesCategories())); + } + } + + public ArrayList getMealtimeSettings() { + return mealtimeSettings; + } + + public ArrayList getCategoryIDs() { + ArrayList result = new ArrayList<>(); + for (MealtimeSettings settigs : mealtimeSettings) { + result.addAll(settigs.dishesCategories()); + } + return result; + } + + public static class MealtimeSettings implements Serializable { + private String name; + //TODO: Использовать объединение ID категорий + private ArrayList categories; + + public MealtimeSettings(String name, ArrayList settings) { + this.name = name; + this.categories = new ArrayList<>(settings); + } + + public void clone(MealtimeSettings settings) { + this.name = settings.name; + this.categories = settings.categories; + } + + public String getName() { + return name; + } + + public ArrayList dishesCategories() { + return categories; + } + } +} diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/EditRecipeActivity.java b/app/src/main/java/ru/spbau/mit/foodmanager/EditRecipeActivity.java new file mode 100644 index 0000000..09225b7 --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/EditRecipeActivity.java @@ -0,0 +1,325 @@ +package ru.spbau.mit.foodmanager; + +import android.Manifest; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.net.Uri; +import android.provider.MediaStore; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.Toast; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.ArrayList; + +//TODO Add, Delete and Edit tags +public class EditRecipeActivity extends AppCompatActivity { + private final static int REQUEST_PICK_IMAGE = 0; + private final static int REQUEST_EDIT_STEPS = 1; + private final static int REQUEST_PICK_CATEGORY = 2; + private int recipeID; + private Object saveSynchronizer = new Object(); + private ArrayList uriSteps; + private ArrayList ingredients; + private ArrayList tags; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.edit_recipe); + recipeID = getIntent().getIntExtra("RecipeID", 0); + ingredients = new ArrayList<>(); + uriSteps = new ArrayList<>(); + tags = new ArrayList<>(); + if (recipeID != 0) { + EditText name = (EditText) findViewById(R.id.edit_recipe_header_name); + EditText description = (EditText) findViewById(R.id.edit_recipe_body_description); + CookBookStorage cookbook = CookBookStorage.getInstance(this); + Recipe recipe = cookbook.getRecipe(recipeID); + ingredients = cookbook.getRecipeIngredients(recipeID); + for (Step s : cookbook.getRecipeSteps(recipeID)) { + uriSteps.add(new UriStep(s)); + } + while (tags == null) { + tags = cookbook.getRecipeCategories(recipeID); + } + name.setText(recipe.getName()); + description.setText(recipe.getDescription()); + showRecipeData(); + } + } + + @Override + protected void onActivityResult(int requestCode, int errorCode, Intent resultContainer) { + if (errorCode == RESULT_OK) { + switch (requestCode) { + /*case REQUEST_PICK_IMAGE: + Uri newImage = resultContainer.getData(); + ImageView recipeImage = (ImageView) findViewById(R.id.edit_recipe_header_photo); + try { + recipeImage.setImageBitmap(MediaStore.Images.Media.getBitmap(this.getContentResolver(), newImage)); + } + catch (IOException e) { + //Cant upload image; + } + break;*/ + case REQUEST_EDIT_STEPS: + uriSteps = (ArrayList) resultContainer.getSerializableExtra("UriSteps"); + break; + case REQUEST_PICK_CATEGORY: + final Integer category = resultContainer.getIntExtra("Result", 0); + tags.add(category); + addTagToView(category); + break; + } + } + } + + public void onSaveClick(View v) { + EditText name = (EditText) findViewById(R.id.edit_recipe_header_name); + EditText description = (EditText) findViewById(R.id.edit_recipe_body_description); + final RecipeToChange result = new RecipeToChange(recipeID, + description.getText().toString(), name.getText().toString()); + result.setCategoryIDs(tags); + result.setIngredients(ingredients); + //INISteps + final ArrayList steps = new ArrayList<>(); + new Thread(new Runnable() { + @Override + public void run() { + synchronized (saveSynchronizer) { + for (UriStep uriStep : uriSteps) { + String descr = uriStep.getDescription(); + Bitmap image; + try { + if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED) { + requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, + MainActivity.PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE); + } + if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED) { + return; + } + image = MediaStore.Images.Media.getBitmap( + EditRecipeActivity.this.getContentResolver(), uriStep.getImageUri()); + } catch (IOException e) { + Step s = new Step( + uriStep.getDescription(), + uriStep.getImageUri().toString()); + CookBookStorage.getInstance(EditRecipeActivity.this).downloadStepImage(s); + image = s.getImage(); + } + steps.add(new Step(descr, image)); + } + result.setSteps(steps); + try { + if (recipeID == 0) { + recipeID = CookBookStorage.getInstance(EditRecipeActivity.this).insertRecipe(result); + } else { + CookBookStorage.getInstance(EditRecipeActivity.this).changeRecipe(result); + } + + EditRecipeActivity.this.runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(EditRecipeActivity.this, "Saved", Toast.LENGTH_SHORT).show(); + } + }); + } catch (final Exception e) { + EditRecipeActivity.this.runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(EditRecipeActivity.this, + "Unable to save recipe\n" + "Cause:\n" + e.toString(), + Toast.LENGTH_LONG).show(); + } + }); + } + } + } + }).start(); + } + + public void onStepsClick(View v) { + Intent task = new Intent(this, EditStepActivity.class); + task.putExtra("UriSteps", uriSteps); + try { + ObjectOutputStream test = new ObjectOutputStream(new ByteArrayOutputStream()); + test.writeObject(uriSteps); + Log.d("STEP CLICK", test.toString()); + } + catch (Exception e) { + Log.d("STEP BOOM", e.toString()); + } + startActivityForResult(task, REQUEST_EDIT_STEPS); + } + + //Finished + public void onAddIngredientClick(View v) { + final Ingredient newIngredient = new Ingredient("", Measure.gr, 0.0); + ingredients.add(newIngredient); + addIngredientToView(newIngredient); + } + + //We have no separated pic of recipe yet + /*public void onRecipeImageClick(View v) { + Intent getIntent = new Intent(Intent.ACTION_GET_CONTENT); + getIntent.setType("image/*"); + Intent pickIntent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI); + pickIntent.setType("image/*"); + Intent chooserIntent = Intent.createChooser(getIntent, "Select Image"); + chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] {pickIntent}); + startActivityForResult(chooserIntent, REQUEST_PICK_IMAGE); + }*/ + + public void onAddTagClick(View view) { + Intent task = new Intent(this, CookBookActivity.class); + task.putExtra("Target", CookBookActivity.TARGET_CATEGORY); + startActivityForResult(task, REQUEST_PICK_CATEGORY); + } + + public static class UriStep implements Serializable { + private String description; + private String imageUri; + + public UriStep() {} + public UriStep(Step s) { + description = s.getDescription(); + imageUri = s.getImageLink(); + } + public Uri getImageUri() { + if (imageUri != null) { + return Uri.parse(imageUri); + } else { + return null; + } + } + public void setImageUri(Uri uri) { + if (uri != null) { + imageUri = uri.toString(); + } else { + imageUri = null; + } + } + + public String getDescription() { + return description; + } + public void setDescription(String s) { + description = s; + } + } + + public void showRecipeData() { + for (Integer i : tags) { + addTagToView(i); + } + for (Ingredient i : ingredients) { + addIngredientToView(i); + } + } + + private void addTagToView(final Integer category) { + final LinearLayout tagsLayout = (LinearLayout) findViewById(R.id.edit_recipe_header_tags); + final View newTag = ((LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE)) + .inflate(R.layout.edit_recipe_tag, null); + TextView newTagName = (TextView) newTag.findViewById(R.id.edit_recipe_tag_name); + ImageButton newTagDelete = (ImageButton) newTag.findViewById(R.id.edit_recipe_delete_tag); + Category newCategory = null; + while (newCategory == null) { + newCategory = CookBookStorage.getInstance(this).getCategoryByID(category); + } + newTagName.setText(newCategory.getDescription()); + newTagDelete.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + tagsLayout.removeView(newTag); + tags.remove(category); + } + }); + tagsLayout.addView(newTag); + } + + private void addIngredientToView(final Ingredient newIngredient) { + final LinearLayout ingredientsLayout = (LinearLayout) findViewById(R.id.edit_recipe_body_ingredients); + LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); + //Create ingredient view + final View view = inflater.inflate(R.layout.edit_recipe_ingredient, null); + EditText ingredientNameView = (EditText) view.findViewById(R.id.edit_recipe_ingredient_name); + EditText ingredientCountView = (EditText) view.findViewById(R.id.edit_recipe_ingredient_count); + Spinner ingredientMeasureView = (Spinner) view.findViewById(R.id.edit_recipe_ingredient_measure); + ImageButton delete = (ImageButton) view.findViewById(R.id.edit_recipe_ingredient_delete); + //Change name + ingredientNameView.setText(newIngredient.getName()); + ingredientNameView.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {} + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + newIngredient.setName(charSequence.toString()); + } + @Override + public void afterTextChanged(Editable editable) {} + }); + //Change count + ingredientCountView.setText(new Double(newIngredient.getQuantity()).toString()); + ingredientCountView.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {} + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + if (charSequence.length() != 0) { + newIngredient.setQuantity(Double.parseDouble(charSequence.toString())); + } + } + @Override + public void afterTextChanged(Editable editable) {} + }); + //Change measurement + ArrayList measurementNames = new ArrayList<>(); + for (Measure m : Measure.values()) { + measurementNames.add(Ingredient.getMeasureName(m)); + } + ArrayAdapter adapter = new ArrayAdapter<>(this, + android.R.layout.simple_spinner_dropdown_item, measurementNames); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + ingredientMeasureView.setAdapter(adapter); + ingredientMeasureView.setSelection(newIngredient.getMeasure().ordinal()); + ingredientMeasureView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int i, long l) { + newIngredient.setMeasure(Measure.values()[i]); + } + + @Override + public void onNothingSelected(AdapterView adapterView) {} + }); + //remove ingredient + delete.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + ingredientsLayout.removeView(view); + ingredients.remove(newIngredient); + } + }); + ingredientsLayout.addView(view); + } +} diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/EditStepActivity.java b/app/src/main/java/ru/spbau/mit/foodmanager/EditStepActivity.java new file mode 100644 index 0000000..f18d86c --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/EditStepActivity.java @@ -0,0 +1,169 @@ +package ru.spbau.mit.foodmanager; + +import android.Manifest; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.provider.MediaStore; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.View; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.TextView; + +import java.io.IOException; +import java.util.ArrayList; + +public class EditStepActivity extends AppCompatActivity { + private static final int REQUEST_PICK_IMAGE = 0; + private ArrayList uriSteps; + private Integer stepPosition; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.edit_step); + + Intent task = getIntent(); + uriSteps = (ArrayList) task.getSerializableExtra("UriSteps"); + if (uriSteps == null) { + uriSteps = new ArrayList<>(); + } + stepPosition = 0; + showStep(stepPosition); + //Initialize edit text + EditText stepDescription = (EditText) findViewById(R.id.edit_step_step_text); + stepDescription.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {} + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + if (uriSteps.size() > stepPosition) { + uriSteps.get(stepPosition).setDescription(charSequence.toString()); + } + } + @Override + public void afterTextChanged(Editable editable) {} + }); + } + + @Override + protected void onActivityResult(int requestCode, int errorCode, Intent resultContainer) { + if (errorCode == RESULT_OK) { + switch (requestCode) { + case REQUEST_PICK_IMAGE: + try { + Uri imageUri = resultContainer.getData(); + ImageView image = (ImageView) findViewById(R.id.edit_step_step_image); + image.setImageBitmap(MediaStore.Images.Media.getBitmap(this.getContentResolver(), imageUri)); + uriSteps.get(stepPosition).setImageUri(imageUri); + } + catch (IOException e) { + //Failed load image + } + break; + } + } + } + + public void onAddClick(View v) { + EditRecipeActivity.UriStep newStep = new EditRecipeActivity.UriStep(); + uriSteps.add(stepPosition, newStep); + showStep(stepPosition); + } + + public void onDeleteClick(View v) { + if (stepPosition > 0) { + uriSteps.remove((int)stepPosition); + stepPosition--; + } + showStep(stepPosition); + } + + public void onImageClick(View v) { + if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED) { + requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, + MainActivity.PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE); + } + if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED) { + return; + } + Intent getIntent = new Intent(Intent.ACTION_GET_CONTENT); + getIntent.setType("image/*"); + Intent pickIntent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI); + pickIntent.setType("image/*"); + Intent chooserIntent = Intent.createChooser(getIntent, "Select Image"); + chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] {pickIntent}); + startActivityForResult(chooserIntent, REQUEST_PICK_IMAGE); + } + + public void onSaveClick(View v) { + Boolean checkingOK = true; + for (EditRecipeActivity.UriStep step : uriSteps) { + if (step.getDescription().equals("") || step.getImageUri() == null) { + checkingOK = false; + //TODO: Show alarm + } + } + if (checkingOK) { + Intent result = new Intent(); + result.putExtra("UriSteps", uriSteps); + setResult(RESULT_OK, result); + //TODO: Show result ok + finish(); + } + } + + public void onPrevBtnClick(View view) { + if (stepPosition > 0) { + stepPosition--; + showStep(stepPosition); + } + } + + public void onNextBtnClick(View view) { + if (stepPosition < uriSteps.size() - 1) { + stepPosition++; + showStep(stepPosition); + } + } + + private void showStep(int id) { + //TODO If there are no step, hide edit field and image field + if (uriSteps.size() > 0) { + EditText description = (EditText) findViewById(R.id.edit_step_step_text); + if (uriSteps.get(id).getDescription() != null) { + description.setText(uriSteps.get(id).getDescription()); + } else { + description.setText(""); + } + ImageView image = (ImageView) findViewById(R.id.edit_step_step_image); + if (uriSteps.get(id).getImageUri() != null) { + try { + image.setImageBitmap( + MediaStore.Images.Media.getBitmap(this.getContentResolver(), uriSteps.get(id).getImageUri())); + } catch (IOException e) { + Step s = new Step( + uriSteps.get(id).getDescription(), + uriSteps.get(id).getImageUri().toString()); + CookBookStorage.getInstance(this).downloadStepImage(s); + image.setImageBitmap(s.getImage()); + } + } else { + //TODO upload image + image.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.add_image)); + } + TextView counter = (TextView) findViewById(R.id.edit_step_position); + counter.setText(((Integer) (id + 1)).toString()); + TextView count = (TextView) findViewById(R.id.edit_step_step_count); + count.setText(((Integer) uriSteps.size()).toString()); + + } + } +} diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/EntityType.java b/app/src/main/java/ru/spbau/mit/foodmanager/EntityType.java new file mode 100644 index 0000000..6592ac2 --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/EntityType.java @@ -0,0 +1,3 @@ +package ru.spbau.mit.foodmanager; + +public enum EntityType {step, recipe, category} \ No newline at end of file diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/GifImageView.java b/app/src/main/java/ru/spbau/mit/foodmanager/GifImageView.java new file mode 100644 index 0000000..74e289e --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/GifImageView.java @@ -0,0 +1,79 @@ +package ru.spbau.mit.foodmanager; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Movie; +import android.os.SystemClock; +import android.util.AttributeSet; +import android.view.View; + +import java.io.InputStream; + +/** + * Copypaste from Internet. I just need a view, which plays animated gif + */ + +public class GifImageView extends View { + + private InputStream mInputStream; + private Movie mMovie; + private int mWidth, mHeight; + private long mStart; + private Context mContext; + public GifImageView(Context context) { + super(context); + this.mContext = context; + } + + public GifImageView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public GifImageView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + this.mContext = context; + if (attrs.getAttributeName(1).equals("background")) { + int id = Integer.parseInt(attrs.getAttributeValue(1).substring(1)); + setGifImageResource(id); + } + } + + + private void init() { + setFocusable(true); + mMovie = Movie.decodeStream(mInputStream); + mWidth = mMovie.width(); + mHeight = mMovie.height(); + mStart = SystemClock.uptimeMillis(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(mWidth, mHeight); + } + + @Override + protected void onDraw(Canvas canvas) { + canvas.drawColor(Color.TRANSPARENT); + super.onDraw(canvas); + long now = SystemClock.uptimeMillis(); + if (mMovie != null) { + int duration = mMovie.duration(); + if (duration == 0) { + duration = 1; + } + int relTime; + relTime = (int) ((now - mStart) % duration); + mMovie.setTime(relTime); + mMovie.draw(canvas, 0, 0); + this.invalidate(); + } + } + + public void setGifImageResource(int id) { + mInputStream = mContext.getResources().openRawResource(id); + init(); + } + +} \ No newline at end of file diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/Ingredient.java b/app/src/main/java/ru/spbau/mit/foodmanager/Ingredient.java new file mode 100644 index 0000000..4e16288 --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/Ingredient.java @@ -0,0 +1,73 @@ +package ru.spbau.mit.foodmanager; + +import java.io.Serializable; + +public class Ingredient implements Serializable { + private String name; + private Measure type; + private double quantity; + + public Ingredient(String name, Measure type, double amount) { + this.name = name; + this.type = type; + this.quantity = amount; + } + + public String getName() { + return name; + } + + public String getTypeName() { + return getMeasureName(type); + } + + + public static String getMeasureName(Measure m) { + switch (m) { + case gr: + return "гр."; + + case ml: + return "мл."; + + case apiece: + return "шт."; + + case teaspoon: + return "ч.л."; + + case tablespoon: + return "ст.л."; + + case pinch: + return "щепотка"; + + case byTaste: + return "по вкусу"; + + case cloves: + return "зуб."; + } + return null; + } + + public Measure getMeasure() { + return type; + } + + public double getQuantity() { + return quantity; + } + + public void setName(String s) { + name = s; + } + + public void setQuantity(double q) { + quantity = q; + } + + public void setMeasure(Measure m) { + type = m; + } +} diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/Installation.java b/app/src/main/java/ru/spbau/mit/foodmanager/Installation.java new file mode 100644 index 0000000..88af9d6 --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/Installation.java @@ -0,0 +1,47 @@ +package ru.spbau.mit.foodmanager; + +import android.content.Context; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.UUID; + +public class Installation { + private static String sID = null; + private static final String INSTALLATION = "INSTALLATION"; + + /** + * Получение пользовательского ID. Подробнее по ссылке. + * https://android-developers.googleblog.com/2011/03/identifying-app-installations.html + */ + public synchronized static String getUserID(Context context) { + if (sID == null) { + File installation = new File(context.getFilesDir(), INSTALLATION); + try { + if (!installation.exists()) + writeInstallationFile(installation); + sID = readInstallationFile(installation); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + return sID; + } + + private static String readInstallationFile(File installation) throws IOException { + RandomAccessFile f = new RandomAccessFile(installation, "r"); + byte[] bytes = new byte[(int) f.length()]; + f.readFully(bytes); + f.close(); + return new String(bytes); + } + + private static void writeInstallationFile(File installation) throws IOException { + FileOutputStream out = new FileOutputStream(installation); + String id = UUID.randomUUID().toString(); + out.write(id.getBytes()); + out.close(); + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/MainActivity.java b/app/src/main/java/ru/spbau/mit/foodmanager/MainActivity.java new file mode 100644 index 0000000..f9d07de --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/MainActivity.java @@ -0,0 +1,57 @@ +package ru.spbau.mit.foodmanager; + +import android.content.Intent; +import android.os.Bundle; +import android.os.StrictMode; +import android.support.v7.app.AppCompatActivity; +import android.view.View; + +public class MainActivity extends AppCompatActivity { + public final static int PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); + StrictMode.setThreadPolicy(policy); + new Thread(new Runnable() { + @Override + public void run() { + while (CookBookStorage.getInstance(MainActivity.this) == null); + startService(new Intent(MainActivity.this, NotificationService.class)); + } + }).start(); + } + + public void onOpenCookBookClick(View view) { + Intent intent = new Intent(this, CookBookActivity.class); + startActivity(intent); + } + + public void onWeekMenuClick(View view) { + Intent intent = new Intent(this, MenuViewActivity.class); + startActivity(intent); + } + + public void onShoppingListClick(View view) { + Intent intent = new Intent(this, ShoppingListActivity.class); + startActivity(intent); + } + + public void onSettingsClick(View view) { + Intent intent = new Intent(this, SettingsActivity.class); + startActivity(intent); + } + + public void onFavouritesClick(View view) { + Intent intent = new Intent(this, CookBookCategoryActivity.class); + intent.putExtra("Target", CookBookCategoryActivity.TARGET_FAVOURITES); + startActivity(intent); + } + + public void onAddRecipeClick(View view) { + Intent intent = new Intent(this, EditRecipeActivity.class); + startActivity(intent); + } +} diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/Mealtime.java b/app/src/main/java/ru/spbau/mit/foodmanager/Mealtime.java new file mode 100644 index 0000000..1f34956 --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/Mealtime.java @@ -0,0 +1,35 @@ +package ru.spbau.mit.foodmanager; + +import java.io.Serializable; +import java.util.ArrayList; + +/** + * Описывает один прием пищи + */ +public class Mealtime implements Serializable { + private ArrayList recipeIDs; + private String name; + //Нужно ли время? + + /** + * Задает название и блюда приема пищи + */ + public Mealtime(String name, ArrayList recipes) { + this.name = name; + this.recipeIDs = recipes; + } + + /** + * Возвращает имя приема пищи + */ + public String getName() { + return name; + } + + /** + * Возвращает список ID блюд приема пищи + */ + public ArrayList getRecipeIDs() { + return recipeIDs; + } +} diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/MealtimeSettingsActivity.java b/app/src/main/java/ru/spbau/mit/foodmanager/MealtimeSettingsActivity.java new file mode 100644 index 0000000..7f58253 --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/MealtimeSettingsActivity.java @@ -0,0 +1,122 @@ +package ru.spbau.mit.foodmanager; + +import android.content.Context; +import android.content.Intent; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.HashMap; + +public class MealtimeSettingsActivity extends AppCompatActivity { + private static final int REQUEST_ADD = 0; + private static final int REQUEST_EDIT = 1; + private CookBookStorage cookbook; + private ArrayList categories = new ArrayList<>(); + private HashMap views = new HashMap<>(); + private LayoutInflater inflater; + private LinearLayout dishes; + private Intent task; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.mealtime_settings); + + cookbook = CookBookStorage.getInstance(this); + inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + dishes = (LinearLayout) findViewById(R.id.mealtime_settings_dishes); + task = getIntent(); + DaySettings.MealtimeSettings settings = (DaySettings.MealtimeSettings) + task.getSerializableExtra("Settings"); + + if (settings != null) { + EditText name = (EditText) findViewById(R.id.mealtime_settings_name); + categories.addAll(settings.dishesCategories()); + + name.setText(settings.getName()); + for (Integer categoryID : categories) { + dishes.addView(generateDishView(dishes, categoryID)); + } + } + + ImageButton addBtn = (ImageButton) findViewById(R.id.mealtime_settings_add); + addBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(MealtimeSettingsActivity.this, CookBookActivity.class); + intent.putExtra("Target", CookBookActivity.TARGET_CATEGORY); + startActivityForResult(intent, REQUEST_ADD); + } + }); + } + + public void onActivityResult(int requestCode, int errorCode, Intent resultContainer) { + switch (requestCode) { + case REQUEST_ADD : + if (errorCode == RESULT_OK) { + Integer result = resultContainer.getIntExtra("Result", -1); + categories.add(result); + dishes.addView(generateDishView(dishes, result)); + } + break; + case REQUEST_EDIT : + if (errorCode == RESULT_OK) { + Integer result = resultContainer.getIntExtra("Result", -1); + Integer position = resultContainer.getIntExtra("Position", -1); + categories.set(position, result); + TextView categoryName = (TextView) views.get(position).findViewById(R.id.mealtime_settings_dish_name); + categoryName.setText(cookbook.getCategoryByID(result).getDescription()); + } + break; + } + } + + private View generateDishView(final LinearLayout parent, final Integer id) { + final View dish = inflater.inflate(R.layout.mealtime_settings_dish, null); + TextView dishName = (TextView) dish.findViewById(R.id.mealtime_settings_dish_name); + ImageButton editBtn = (ImageButton) dish.findViewById(R.id.mealtime_settings_dish_edit); + ImageButton delBtn = (ImageButton) dish.findViewById(R.id.mealtime_settings_dish_delete); + Category category = cookbook.getCategoryByID(id); + final Integer position = categories.indexOf(id); //TODO fix possible problems + + dishName.setText(category.getDescription()); + editBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(MealtimeSettingsActivity.this, CookBookActivity.class); + intent.putExtra("Target", CookBookActivity.TARGET_CATEGORY); + intent.putExtra("Position", position); + startActivityForResult(intent, REQUEST_EDIT); + } + }); + delBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + categories.remove(id); + parent.removeView(dish); + } + }); + views.put(position, dish); + return dish; + } + + public void onSaveClick(View v) { + EditText name = (EditText) findViewById(R.id.mealtime_settings_name); + returnResult(new DaySettings.MealtimeSettings( + name.getText().toString(), + categories)); + } + private void returnResult(DaySettings.MealtimeSettings result) { + Intent resultIntent = task; + resultIntent.putExtra("Result", result); + setResult(RESULT_OK, resultIntent); + finish(); + } +} diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/Measure.java b/app/src/main/java/ru/spbau/mit/foodmanager/Measure.java new file mode 100644 index 0000000..f276262 --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/Measure.java @@ -0,0 +1,3 @@ +package ru.spbau.mit.foodmanager; + +public enum Measure {gr, ml, apiece, teaspoon, tablespoon, pinch, byTaste, cloves} diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/MenuSettings.java b/app/src/main/java/ru/spbau/mit/foodmanager/MenuSettings.java new file mode 100644 index 0000000..7d5cb31 --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/MenuSettings.java @@ -0,0 +1,99 @@ +package ru.spbau.mit.foodmanager; + + +import android.content.Context; +import android.util.Log; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; + +public class MenuSettings implements Serializable { + private HashMap settingsByDay; + private HashMap isCookingDay; + private static MenuSettings instance; + + private MenuSettings(Context context) { + settingsByDay = new HashMap<>(); + isCookingDay = new HashMap<>(); + for (Day d : Day.values()) { + isCookingDay.put(d, true); + //DEBUG ONLY + ArrayList mealtieSettings = new ArrayList<>(); + mealtieSettings.add(DaySettings.getMealtimePresets(context).get(0)); + mealtieSettings.add(DaySettings.getMealtimePresets(context).get(1)); + mealtieSettings.add(DaySettings.getMealtimePresets(context).get(2)); + DaySettings settings = new DaySettings(mealtieSettings); + settingsByDay.put(d, settings); + //DEBUG ONLY + } + } + + /** + * Сохраняет instance MenuSettings в БД. + * @return возвращает сериализованный instance настроек. + */ + public static String saveMenuSettings(Context context) { + try { + ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream(); + ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteOutputStream); + objectOutputStream.writeObject(instance); + objectOutputStream.flush(); + + String serializedInstance = objectOutputStream.toString(); + CookBookStorage.getInstance(context).saveUserSettings(serializedInstance); + return serializedInstance; + } catch (Exception e) { + e.printStackTrace(); + } + + return null; + } + + /** + * Загрузка поля instance из БД. + */ + public static void loadMenuSettings(Context context) { + String serializedInstance = CookBookStorage.getInstance(context).getUserSettings(); + if (serializedInstance == null) { + instance = null; + return; + } + byte bytes[] = serializedInstance.getBytes(); + ByteArrayInputStream byteInputStream = new ByteArrayInputStream(bytes); + try { + ObjectInputStream objectInputStream = new ObjectInputStream(byteInputStream); + instance = (MenuSettings) objectInputStream.readObject(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static MenuSettings getInstance(Context context) { + Log.d("MenuSettings", "getInstance"); + loadMenuSettings(context); + + if (instance == null) { + instance = new MenuSettings(context); + saveMenuSettings(context); + } + + return instance; + } + + public DaySettings getDaySettings(Day day) { + return settingsByDay.get(day); + } + + public Boolean isCookingDay(Day day) { + return isCookingDay.get(day); + } + + public void setCookingDay(Day day, Boolean isCookingDay, Context c) { + this.isCookingDay.put(day, isCookingDay); + } +} diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/MenuSettingsActivity.java b/app/src/main/java/ru/spbau/mit/foodmanager/MenuSettingsActivity.java new file mode 100644 index 0000000..db5a786 --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/MenuSettingsActivity.java @@ -0,0 +1,152 @@ +package ru.spbau.mit.foodmanager; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.HashMap; + +public class MenuSettingsActivity extends AppCompatActivity { + private static final int REQUEST_ADD = 0; + private static final int REQUEST_EDIT = 1; + private MenuSettings menuSettings; + private HashMap daySettingViews; + private HashMap> mealtimeSettings; + private HashMap> mealtimeSettingsViews; + private LayoutInflater inflater; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.menu_settings); + menuSettings = MenuSettings.getInstance(this); + LinearLayout daysSettings = (LinearLayout) findViewById(R.id.menu_settings_days); + inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + daySettingViews = new HashMap<>(); + for (Day d : Day.values()) { + View v = inflater.inflate(R.layout.menu_settings_day, null); + ((TextView)v.findViewById(R.id.menu_settings_day_name)) + .setText(MenuStorage.getDayNames()[d.ordinal()]); + daySettingViews.put(d, v); + daysSettings.addView(v); + } + fillDays(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + MenuSettings.saveMenuSettings(this); + } + + public void onActivityResult(int requestCode, int errorCode, Intent resultContainer) { + if (errorCode == RESULT_OK) { + DaySettings.MealtimeSettings result; + Day d; + switch (requestCode) { + case REQUEST_ADD : + d = (Day) resultContainer.getSerializableExtra("Day"); + result = (DaySettings.MealtimeSettings) + resultContainer.getSerializableExtra("Result"); + LinearLayout mealtimes = (LinearLayout) + daySettingViews.get(d).findViewById(R.id.menu_settings_day_mealtimes); + DaySettings.MealtimeSettings settings = new DaySettings.MealtimeSettings( + result.getName(), result.dishesCategories()); + mealtimeSettings.get(d).add(settings); + mealtimeSettingsViews.get(d).put(settings, + generateMealtimeView(d, mealtimes, settings)); + break; + case REQUEST_EDIT : + d = (Day) resultContainer.getSerializableExtra("Day"); + result = (DaySettings.MealtimeSettings) + resultContainer.getSerializableExtra("Result"); + Integer position = resultContainer.getIntExtra("Position", -1); + TextView mealtimeName = (TextView) + mealtimeSettingsViews.get(d).get(mealtimeSettings.get(d).get(position)) + .findViewById(R.id.menu_settings_day_mealtime_name); + mealtimeName.setText(result.getName()); + mealtimeSettings.get(d).get(position).clone(result); + break; + } + } + } + + private void fillDays() { + mealtimeSettingsViews = new HashMap<>(); + mealtimeSettings = new HashMap<>(); + for (final Day d : Day.values()) { + View v = daySettingViews.get(d); + CheckBox isCookingDay = (CheckBox) v.findViewById(R.id.menu_settings_day_is_cookingday); + ImageButton addMealtime = (ImageButton) v.findViewById(R.id.menu_settings_day_mealtime_add); + LinearLayout mealtimes = (LinearLayout) v.findViewById(R.id.menu_settings_day_mealtimes); + + mealtimeSettingsViews.put(d, new HashMap()); + isCookingDay.setChecked(menuSettings.isCookingDay(d)); + mealtimeSettings.put(d, menuSettings.getDaySettings(d).getMealtimeSettings()); + for (DaySettings.MealtimeSettings settings : mealtimeSettings.get(d)) { + mealtimeSettingsViews.get(d).put(settings, + generateMealtimeView(d, mealtimes, settings)); + } + + isCookingDay.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean b) { + menuSettings.setCookingDay(d, b, MenuSettingsActivity.this); + } + }); + + addMealtime.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(MenuSettingsActivity.this, + ChooseMealtimePresetActivity.class); + intent.putExtra("Day", d); + startActivityForResult(intent, REQUEST_ADD); + } + }); + } + } + + private View generateMealtimeView (final Day d, final LinearLayout mealtimes, + final DaySettings.MealtimeSettings settings) { + final View mealtimeView = inflater.inflate(R.layout.menu_settings_day_mealtime, null); + ImageButton editMealtime = (ImageButton) + mealtimeView.findViewById(R.id.menu_settings_day_mealtime_edit); + ImageButton deleteMealtime = (ImageButton) + mealtimeView.findViewById(R.id.menu_settings_day_mealtime_delete); + TextView mealtimeName = (TextView) + mealtimeView.findViewById(R.id.menu_settings_day_mealtime_name); + final Integer position = mealtimeSettings.get(d).indexOf(settings); + + mealtimeName.setText(settings.getName()); + deleteMealtime.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + menuSettings.getDaySettings(d).getMealtimeSettings().remove(settings); + mealtimes.removeView(mealtimeView); + } + }); + editMealtime.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(MenuSettingsActivity.this, + ChooseMealtimePresetActivity.class); + intent.putExtra("Day", d); + intent.putExtra("Position", position); + startActivityForResult(intent, REQUEST_EDIT); + } + }); + + mealtimes.addView(mealtimeView); + return mealtimeView; + } +} diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/MenuStorage.java b/app/src/main/java/ru/spbau/mit/foodmanager/MenuStorage.java new file mode 100644 index 0000000..b8e2792 --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/MenuStorage.java @@ -0,0 +1,157 @@ +package ru.spbau.mit.foodmanager; + +import android.content.Context; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Хранит меню для каждого дня недели + */ +public class MenuStorage implements Serializable { + private HashMap dayMenus; + private static MenuStorage instance; + private static final String menuStorageFilename = "MenuStorage"; + static final private String[] DAY_NAMES = { + "Понедельник", + "Вторник", + "Среда", + "Четверг", + "Пятница", + "Суббота", + "Воскресенье"}; + + private MenuStorage() { + dayMenus = new HashMap<>(); + } + + static public String[] getDayNames() { + return DAY_NAMES; + } + + static public MenuStorage getInstance(Context context) { + if (instance == null) { + loadMenuStorage(context); + } + if (instance == null) { + instance = new MenuStorage(); + saveMenuStorage(context); + } + return instance; + } + + /** + * Возвращает HashMap, где ключ - день, значение - DayMenu + */ + public HashMap getMenu() { + return dayMenus; + } + + /** + * Записывает новое меню на определенный день + */ + public void setNewDayMenu(Day day, DayMenu dayMenu) { + if (day != null) { + dayMenus.put(day,dayMenu); + } + } + + public static void saveMenuStorage(Context context) { + try { + FileOutputStream output = context.openFileOutput( + menuStorageFilename, Context.MODE_PRIVATE); + + ObjectOutputStream outputStream = new ObjectOutputStream(output); + outputStream.writeObject(instance); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void loadMenuStorage(Context context) { + File settings = new File(context.getFilesDir(), menuStorageFilename); + if (settings.exists()) { + try { + FileInputStream input = context.openFileInput(menuStorageFilename); + ObjectInputStream inputStream = new ObjectInputStream(input); + instance = (MenuStorage) inputStream.readObject(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + /** + * Генерирует меню на один день, учитывая настройки и имеющиеся рецепты + * @param settings настройки меню на этот день + * @param recipes Map из CategoryID в RecipeID. Если для какой-то категории есть рецепт, + * то будет использован именно он + */ + public DayMenu generateDayMenu(DaySettings settings, HashMap recipes, Context context) { + ArrayList dishesForDay = new ArrayList<>(); + for (DaySettings.MealtimeSettings mealtimeSettings : settings.getMealtimeSettings()) { + ArrayList mealtimeRecipes = new ArrayList<>(); + for (Integer categoryID : mealtimeSettings.dishesCategories()) { + if (recipes.get(categoryID) == null) { + CookBookStorage cookbook = CookBookStorage.getInstance(context); + recipes.put(categoryID, cookbook.chooseRandomDishFromCategory(categoryID).getID()); + } + mealtimeRecipes.add(recipes.get(categoryID)); + } + dishesForDay.add(new Mealtime(mealtimeSettings.getName(), mealtimeRecipes)); + } + return new DayMenu(dishesForDay); + } + + /** + * Генерирует меню на неделю + */ + public void generateWeekMenu(Context context) { + MenuSettings settings = MenuSettings.getInstance(context); + Day firstCookingDay = Day.Monday; + for (Day day : Day.values()) { + if (settings.isCookingDay(day)) { + firstCookingDay = day; + break; + } + } + if (!settings.isCookingDay(firstCookingDay)) { + //TODO обработать случай, когда нет дней готовки. + } + //Получаем для каждого дня готовки список дней, на которые готовим. + HashMap> cookForDays = new HashMap<>(); + Day currentCookingDay = firstCookingDay; + for (Integer day = firstCookingDay.ordinal(); day < Day.values().length; day++) { + if (settings.isCookingDay(Day.values()[day])) { + currentCookingDay = Day.values()[day]; + cookForDays.put(currentCookingDay, new ArrayList()); + } + cookForDays.get(currentCookingDay).add(Day.values()[day]); + } + for (Integer day = 0; day < firstCookingDay.ordinal(); day++) { + cookForDays.get(currentCookingDay).add(Day.values()[day]); + } + //Получаем рецепты для каждого дня готовки + for (Day cookingDay : cookForDays.keySet()) { + //TODO Использовать множество категорий + //Создаем меню на каждый день + HashMap categoryRecipes = new HashMap<>(); + for (Day day : cookForDays.get(cookingDay)) { + DaySettings daySettings = settings.getDaySettings(day); + dayMenus.put(day, null); + if (daySettings != null) { + DayMenu dayMenu = generateDayMenu(daySettings, categoryRecipes, context); + dayMenus.put(day, dayMenu); + } + } + } + saveMenuStorage(context); + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/MenuViewActivity.java b/app/src/main/java/ru/spbau/mit/foodmanager/MenuViewActivity.java new file mode 100644 index 0000000..31da776 --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/MenuViewActivity.java @@ -0,0 +1,215 @@ +package ru.spbau.mit.foodmanager; + +import android.content.Context; +import android.content.Intent; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.TextView; + +import java.util.HashMap; + +public class MenuViewActivity extends AppCompatActivity { + private final static int REQUEST_ADD = 0; + private final static int REQUEST_EDIT = 1; + private HashMap allDayMenu; + private HashMap recipes; + private CookBookStorage cookbook; + private MenuStorage menu; + private LayoutInflater inflater; + private GifImageView loaderAnimation; + private LinearLayout informationLayout; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.menu_view); + //Init loaderAnimation + loaderAnimation = (GifImageView) findViewById(R.id.loader_animation_view); + loaderAnimation.setGifImageResource(LoaderAnimationSelector.getRandomLoaderResource()); + loaderAnimation.setLayerType(View.LAYER_TYPE_SOFTWARE, null); + informationLayout = (LinearLayout) findViewById(R.id.information_layout); + + inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + cookbook = CookBookStorage.getInstance(this); + menu = MenuStorage.getInstance(this); + recipes = new HashMap<>(); + //Init Task + ContentLoader contentLoader = new ContentLoader(false); + Thread loader = new Thread(contentLoader); + loader.start(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + MenuStorage.saveMenuStorage(this); + } + + public void onGenerateBtnClick(View v) { + //Init Task + ContentLoader contentLoader = new ContentLoader(true); + Thread loader = new Thread(contentLoader); + loader.start(); + } + + public void onActivityResult(int requestCode, int errorCode, Intent resultContainer) { + if (errorCode == RESULT_OK) { + Day day = (Day) resultContainer.getSerializableExtra("Day"); + Integer mealtimePos = resultContainer.getIntExtra("MealtimePosition", -1); + Integer result = resultContainer.getIntExtra("Result", -1); + switch (requestCode) { + case REQUEST_ADD : + allDayMenu.get(day).getMealtimes().get(mealtimePos).getRecipeIDs().add(result); + break; + case REQUEST_EDIT : + Integer position = resultContainer.getIntExtra("Position", -1); + allDayMenu.get(day).getMealtimes().get(mealtimePos).getRecipeIDs().set(position, result); + break; + } + ContentLoader contentLoader = new ContentLoader(false); + Thread loader = new Thread(contentLoader); + loader.start(); + } + } + + private void showRecipes() { + LinearLayout daysListView = (LinearLayout) findViewById(R.id.menu_view_days); + daysListView.removeAllViews(); + for (Day d : Day.values()) { + generateDayMenuView(d, daysListView); + } + } + + private void generateDayMenuView(Day day, LinearLayout parent) { + View view = inflater.inflate(R.layout.menu_view_list_element, null); + DayMenu menu = allDayMenu.get(day); + TextView dayName = (TextView) view.findViewById(R.id.menu_day_name); + LinearLayout dayMealtimes = (LinearLayout) view.findViewById(R.id.menu_day_mealtimes); + + if (menu != null) { + dayName.setText(MenuStorage.getDayNames()[day.ordinal()]); + for (Integer i = 0; i < menu.getMealtimes().size(); i++) { + generateMealtimeView(menu, i, dayMealtimes, day); + } + parent.addView(view); + } + } + + private void generateMealtimeView(DayMenu menu, final Integer position, LinearLayout parent, + final Day day) { + LinearLayout mealtimeElement = (LinearLayout) inflater.inflate( + R.layout.menu_view_day_menu_list_element, null); + TextView mealtimeName = (TextView) mealtimeElement.findViewById( + R.id.menu_day_mealtime_name); + LinearLayout recipeList = (LinearLayout) mealtimeElement.findViewById( + R.id.menu_day_mealtime_dishes); + Mealtime mealtime = menu.getMealtimes().get(position); + ImageButton addDish = (ImageButton) mealtimeElement.findViewById(R.id.menu_day_mealtime_dish_add); + + + mealtimeName.setText(mealtime.getName()); + for (Integer i = 0; i < mealtime.getRecipeIDs().size(); i++) { + generateRecipeView(mealtime, i, recipeList, day, position); + } + addDish.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent task = new Intent(MenuViewActivity.this, CookBookActivity.class); + task.putExtra("Target", CookBookActivity.TARGET_RECIPE); + task.putExtra("Day", day); + task.putExtra("MealtimePosition", position); + startActivityForResult(task, REQUEST_ADD); + } + }); + parent.addView(mealtimeElement); + } + + private void generateRecipeView(final Mealtime mealtime, final Integer position, LinearLayout parent, + final Day day, final Integer mealtimePos) { + View recipeView = inflater.inflate(R.layout.menu_view_day_menu_meltime_list_element, null); + TextView recipeName = (TextView) recipeView.findViewById(R.id.menu_day_mealtime_dish_name); + final Integer recipeID = mealtime.getRecipeIDs().get(position); //WTF + ImageButton editDish = (ImageButton) recipeView.findViewById(R.id.menu_day_mealtime_dish_edit); + ImageButton deleteDish = (ImageButton) recipeView.findViewById(R.id.menu_day_mealtime_dish_delete); + Log.d("LOLOLOLOL", recipeID.toString() + " " + recipes.containsKey(recipeID)); + recipeName.setText(recipes.get(recipeID).getName()); + recipeName.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(MenuViewActivity.this, RecipeViewActivity.class); + intent.putExtra("Recipe", recipeID); + startActivity(intent); + } + }); + deleteDish.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + allDayMenu.get(day).getMealtimes().get(mealtimePos).getRecipeIDs().remove((int)position); + Log.d("Try to remove", "Try tot remove"); + showRecipes(); //We need to do this because positions in other buttons now broken + } + }); + editDish.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent task = new Intent(MenuViewActivity.this, CookBookActivity.class); + task.putExtra("Target", CookBookActivity.TARGET_RECIPE); + task.putExtra("Day", day); + task.putExtra("MealtimePosition", mealtimePos); + task.putExtra("Position", position); + startActivityForResult(task, REQUEST_EDIT); + } + }); + parent.addView(recipeView); + } + + public class ContentLoader implements Runnable { + private boolean generateNewMenu; + + public ContentLoader(boolean generate) { + generateNewMenu = generate; + } + + @Override + public void run() { + MenuViewActivity.this.runOnUiThread(new Runnable() { + @Override + public void run() { + loaderAnimation.setVisibility(View.VISIBLE); + informationLayout.setVisibility(View.INVISIBLE); + } + }); + if (generateNewMenu) { + menu.generateWeekMenu(MenuViewActivity.this); + } + allDayMenu = menu.getMenu(); + for (DayMenu dm : allDayMenu.values()) { + if (dm != null) { + for (Mealtime m : dm.getMealtimes()) { + for (Integer id : m.getRecipeIDs()) { + Log.d("ADDT", id.toString()); + while (recipes.get(id) == null) { + recipes.put(id, cookbook.getRecipe(id)); + } + } + } + } + } + + MenuViewActivity.this.runOnUiThread(new Runnable() { + @Override + public void run() { + showRecipes(); + loaderAnimation.setVisibility(View.INVISIBLE); + informationLayout.setVisibility(View.VISIBLE); + loaderAnimation.setGifImageResource(LoaderAnimationSelector.getRandomLoaderResource()); + } + }); + } + } +} diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/NotificationService.java b/app/src/main/java/ru/spbau/mit/foodmanager/NotificationService.java new file mode 100644 index 0000000..1aef35c --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/NotificationService.java @@ -0,0 +1,125 @@ +package ru.spbau.mit.foodmanager; + +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Intent; +import android.icu.util.Calendar; +import android.os.IBinder; +import android.support.v4.app.NotificationCompat; +import android.util.Log; + +import java.util.concurrent.TimeUnit; + +/** + * Сервис нотифицирования пользователя + */ +public class NotificationService extends Service { + private static final int COOK_NOTIFICATION_ID = 0; + private static final String COOK_NOTIFICATION_TITLE = "Время начинать готовить"; + private static final String COOK_NOTIFICATION_TEXT = "Сейчас самое время приготовить что-нибудь вкусное!"; + private NotificationManager notificationManager; + private static Day lastCookDayNotify; + private static Boolean notifyedToday; + private static Boolean serviceStopped; + private Calendar calendar = Calendar.getInstance(); + private MenuSettings menuSettings; + private NotificationSettings notificationSettings; + private Thread serviceActionThread; + + @Override + public void onCreate() { + super.onCreate(); + serviceStopped = false; + notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + notificationSettings = NotificationSettings.getInstance(this); + menuSettings = MenuSettings.getInstance(this); + serviceActionThread = new Thread(new Runnable() { + @Override + public void run() { + while (!serviceStopped) { + try { + if (calendarDayToDay(calendar.get(Calendar.DAY_OF_WEEK)) != lastCookDayNotify) { + notifyedToday = false; + } + if (notificationSettings.getShowCookNotifications() && !notifyedToday) { + sendCookNotification(); + } + TimeUnit.SECONDS.sleep(30); + } catch (Exception e) { + //Finish thread + } + } + } + }); + serviceActionThread.start(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (serviceActionThread.isInterrupted()) { + serviceActionThread.start(); + } + return START_STICKY; + } + + /** + * Проверяет, нужно ли послать оповещение о времени готовить или нет + */ + private void sendCookNotification() { + Log.d("NOTIFICATIONS", "Checkpoint0"); + long timeOfDay = System.currentTimeMillis() % calendar.get(Calendar.MILLISECONDS_IN_DAY); + long notificationTimeBegin = notificationSettings.getTimeOfDayBeginCookNotifications(); + long notificationTimeEnd = notificationSettings.getTimeOfDayEndCookNotifications(); + Day day = calendarDayToDay(calendar.get(Calendar.DAY_OF_WEEK)); + Log.d("NOTIFICATIONS", "Checkpoint1"); + if (timeOfDay > notificationTimeBegin && + timeOfDay < notificationTimeEnd && + menuSettings.isCookingDay(day)) { + Intent intent = new Intent(this, MainActivity.class); + PendingIntent pendingIntent = PendingIntent.getActivity(this, COOK_NOTIFICATION_ID, intent, 0); + NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this); + notificationBuilder.setSmallIcon(R.mipmap.ic_launcher); + notificationBuilder.setContentTitle(COOK_NOTIFICATION_TITLE); + notificationBuilder.setContentText(COOK_NOTIFICATION_TEXT); + notificationBuilder.setContentIntent(pendingIntent); + notificationManager.notify(COOK_NOTIFICATION_ID, notificationBuilder.build()); + lastCookDayNotify = day; //TODO делать нормальную проверку, т.к. если только один день готовки, то все плохо. + notifyedToday = true; + } + Log.d("NOTIFICATIONS", "Checkpoint2"); + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onDestroy() { + super.onDestroy(); + serviceStopped = true; + serviceActionThread.interrupt(); + } + + private Day calendarDayToDay (int calendarDay) { + switch (calendarDay) { + case Calendar.MONDAY : + return Day.Monday; + case Calendar.TUESDAY: + return Day.Tuesday; + case Calendar.WEDNESDAY : + return Day.Wednesday; + case Calendar.THURSDAY : + return Day.Thursday; + case Calendar.FRIDAY : + return Day.Friday; + case Calendar.SATURDAY : + return Day.Saturday; + case Calendar.SUNDAY : + return Day.Sunday; + } + return null; + } + +} diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/NotificationSettings.java b/app/src/main/java/ru/spbau/mit/foodmanager/NotificationSettings.java new file mode 100644 index 0000000..1b33773 --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/NotificationSettings.java @@ -0,0 +1,121 @@ +package ru.spbau.mit.foodmanager; + +import android.content.Context; +import android.icu.util.Calendar; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.concurrent.TimeUnit; +/** + * Настройки сервера нотификации + */ + +public class NotificationSettings implements Serializable { + private Boolean showCookNotifications; + private long timeOfDayBeginCookNotifications; + private long timeOfDayEndCookNotifications; + private static NotificationSettings instance; + private static final String notificationSettingsFilename = "NotificationSettings"; + + static public NotificationSettings getInstance(Context context) { + loadNotificationSettings(context); + + if (instance == null) { + instance = new NotificationSettings(); + saveNotificationSettings(context); + } + + return instance; + } + + public static void saveNotificationSettings(Context context) { + try { + FileOutputStream output = context.openFileOutput( + notificationSettingsFilename, Context.MODE_PRIVATE); + + ObjectOutputStream outputStream = new ObjectOutputStream(output); + outputStream.writeObject(instance); + + } catch (Throwable e) { + e.printStackTrace(); + } + } + + public static void loadNotificationSettings(Context context) { + File settings = new File(context.getFilesDir(), notificationSettingsFilename); + if (settings.exists()) { + try { + FileInputStream input = context.openFileInput(notificationSettingsFilename); + ObjectInputStream inputStream = new ObjectInputStream(input); + instance = (NotificationSettings) inputStream.readObject(); + } catch (Throwable e) { + e.printStackTrace(); + } + } + } + + private NotificationSettings() { + showCookNotifications = false; + timeOfDayBeginCookNotifications = 0; + timeOfDayEndCookNotifications = Calendar.getInstance().get(Calendar.MILLISECONDS_IN_DAY); + } + + public void setShowCookNotifications(Boolean b, Context c) { + showCookNotifications = b; + saveNotificationSettings(c); + } + + public void setTimeOfDayBeginCookNotifications(long timeOfDay, Context c) { + timeOfDayBeginCookNotifications = timeOfDay % Calendar.MILLISECONDS_IN_DAY; + saveNotificationSettings(c); + } + + public void setTimeOfDayBeginCookNotifications(long hour, long minute, Context c) { + hour = hour % 24; + minute = minute % 60; + timeOfDayBeginCookNotifications = TimeUnit.HOURS.toMillis(hour) + TimeUnit.MINUTES.toMillis(minute); + saveNotificationSettings(c); + } + + public void setTimeOfDayEndCookNotifications(long timeOfDay, Context c) { + timeOfDayEndCookNotifications = timeOfDay % Calendar.MILLISECONDS_IN_DAY; + saveNotificationSettings(c); + } + public void setTimeOfDayEndCookNotifications(long hour, long minute, Context c) { + hour = hour % 24; + minute = minute % 60; + timeOfDayEndCookNotifications = TimeUnit.HOURS.toMillis(hour) + TimeUnit.MINUTES.toMillis(minute); + saveNotificationSettings(c); + } + public Boolean getShowCookNotifications() { + return showCookNotifications; + } + + public long getTimeOfDayBeginCookNotifications() { + return timeOfDayBeginCookNotifications; + } + + public long getTimeOfDayEndCookNotifications() { + return timeOfDayEndCookNotifications; + } + + public long getHourOfDayBeginCookNotifications() { + return TimeUnit.MILLISECONDS.toHours(timeOfDayBeginCookNotifications); + } + + public long getHourOfDayEndCookNotifications() { + return TimeUnit.MILLISECONDS.toHours(timeOfDayEndCookNotifications); + } + + public long getMinuteOfDayBeginCookNotifications() { + return TimeUnit.MILLISECONDS.toMinutes(timeOfDayBeginCookNotifications) % 60; + } + + public long getMinuteOfDayEndCookNotifications() { + return TimeUnit.MILLISECONDS.toMinutes(timeOfDayEndCookNotifications) % 60; + } +} diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/NotificationSettingsActivity.java b/app/src/main/java/ru/spbau/mit/foodmanager/NotificationSettingsActivity.java new file mode 100644 index 0000000..ad8d03d --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/NotificationSettingsActivity.java @@ -0,0 +1,95 @@ +package ru.spbau.mit.foodmanager; + +import android.app.TimePickerDialog; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.widget.CompoundButton; +import android.widget.Switch; +import android.widget.TextView; +import android.widget.TimePicker; + +public class NotificationSettingsActivity extends AppCompatActivity { + private NotificationSettings settings; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.notification_settings); + + settings = NotificationSettings.getInstance(this); + Switch switchCookNotify = (Switch) findViewById(R.id.notification_settings_toggle_cook_notify); + showTimes(); + switchCookNotify.setChecked(settings.getShowCookNotifications()); + switchCookNotify.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean b) { + settings.setShowCookNotifications(b, NotificationSettingsActivity.this); + } + }); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + NotificationSettings.saveNotificationSettings(this); + } + + private void showTimes() { + TextView cookNotifyFromHours = (TextView) findViewById(R.id.notification_settings_time_cook_notify_from_hours); + TextView cookNotifyFromMinutes = (TextView) findViewById(R.id.notification_settings_time_cook_notify_from_minutes); + TextView cookNotifyToHours = (TextView) findViewById(R.id.notification_settings_time_cook_notify_to_hours); + TextView cookNotifyToMinutes = (TextView) findViewById(R.id.notification_settings_time_cook_notify_to_minutes); + String s; + s = ((Long)settings.getHourOfDayBeginCookNotifications()).toString(); + if (s.length() < 2) { + s = "0".concat(s); + } + cookNotifyFromHours.setText(s); + s = ((Long)settings.getHourOfDayEndCookNotifications()).toString(); + if (s.length() < 2) { + s = "0".concat(s); + } + cookNotifyToHours.setText(s); + s = ((Long)settings.getMinuteOfDayBeginCookNotifications()).toString(); + if (s.length() < 2) { + s = "0".concat(s); + } + cookNotifyFromMinutes.setText(s); + s = ((Long)settings.getMinuteOfDayEndCookNotifications()).toString(); + if (s.length() < 2) { + s = "0".concat(s); + } + cookNotifyToMinutes.setText(s); + } + + public void onCookNotifyTimeFromEditClick(View v) { + //final TextView cookNotifyFromHours = (TextView) findViewById(R.id.notification_settings_time_cook_notify_from_hours); + //final TextView cookNotifyFromMinutes = (TextView) findViewById(R.id.notification_settings_time_cook_notify_from_minutes); + TimePickerDialog dialog = new TimePickerDialog(this, new TimePickerDialog.OnTimeSetListener() { + @Override + public void onTimeSet(TimePicker timePicker, int hour, int minute) { + settings.setTimeOfDayBeginCookNotifications(hour, minute, NotificationSettingsActivity.this); + showTimes(); + } + }, + (int)settings.getHourOfDayBeginCookNotifications(), + (int)settings.getMinuteOfDayBeginCookNotifications(), true); + dialog.show(); + } + + public void onCookNotifyTimeToEditClick(View v) { + //final TextView cookNotifyToHours = (TextView) findViewById(R.id.notification_settings_time_cook_notify_to_hours); + //final TextView cookNotifyToMinutes = (TextView) findViewById(R.id.notification_settings_time_cook_notify_to_minutes); + TimePickerDialog dialog = new TimePickerDialog(this, new TimePickerDialog.OnTimeSetListener() { + @Override + public void onTimeSet(TimePicker timePicker, int hour, int minute) { + settings.setTimeOfDayEndCookNotifications(hour, minute, NotificationSettingsActivity.this); + showTimes(); + } + }, + (int)settings.getHourOfDayEndCookNotifications(), + (int)settings.getMinuteOfDayEndCookNotifications(), true); + dialog.show(); + } +} diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/Recipe.java b/app/src/main/java/ru/spbau/mit/foodmanager/Recipe.java new file mode 100644 index 0000000..7943dba --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/Recipe.java @@ -0,0 +1,44 @@ +package ru.spbau.mit.foodmanager; + +import java.io.Serializable; +import java.util.ArrayList; + +public class Recipe implements Serializable { + /** Название рецепта */ + private String name; + + /** Описание рецепта */ + private String description; + + /** ID рецепта */ + private int ID; + + /** Содержит идентификаторы категорий, к которым принадлежит блюдо */ + private ArrayList categoryID; + + public Recipe(int ID, String description, String name) { + this.ID = ID; + this.description = description; + this.name = name; + } + + public int getID() { + return ID; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public void setCategoryID(ArrayList categoryID) { + this.categoryID = categoryID; + } + + public ArrayList getCategoryID() { + return categoryID; + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/RecipeToChange.java b/app/src/main/java/ru/spbau/mit/foodmanager/RecipeToChange.java new file mode 100644 index 0000000..ca85c2c --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/RecipeToChange.java @@ -0,0 +1,79 @@ +package ru.spbau.mit.foodmanager; + +import java.util.ArrayList; + +/** + * Класс, созданный специально для добавления и удаления рецепта в БД. + */ +public class RecipeToChange { + /** Название рецепта */ + private String name; + + /** Описание рецепта */ + private String description; + + /** ID добавляемого рецепта */ + private int ID; + + /** Содержит идентификаторы категорий, к которым принадлежит блюдо */ + private ArrayList categoryIDs; + + /** Пошаговая инструкция готовки блюда */ + private ArrayList steps; + + /** Ингредиенты необходимые для приготовления блюда */ + private ArrayList ingredients; + + public RecipeToChange(int ID, String description, String name) { + categoryIDs = null; + steps = null; + ingredients = null; + this.ID = ID; + this.description = description; + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { this.name = name; } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { this.description = description; } + + public int getID() { + return ID; + } + + public void setID(int ID) { + this.ID = ID; + } + + public void setIngredients(ArrayList ingredients) { + this.ingredients = ingredients; + } + + public ArrayList getIngredients() { + return ingredients; + } + + public void setCategoryIDs(ArrayList categoryIDs) { + this.categoryIDs = categoryIDs; + } + + public ArrayList getCategoryIDs() { + return categoryIDs; + } + + public void setSteps(ArrayList steps) { + this.steps = steps; + } + + public ArrayList getSteps() { + return steps; + } +} diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/RecipeViewActivity.java b/app/src/main/java/ru/spbau/mit/foodmanager/RecipeViewActivity.java new file mode 100644 index 0000000..719e501 --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/RecipeViewActivity.java @@ -0,0 +1,276 @@ +package ru.spbau.mit.foodmanager; + +import android.content.Intent; +import android.graphics.BitmapFactory; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.util.TypedValue; +import android.view.View; +import android.widget.Button; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.HashMap; + +public class RecipeViewActivity extends AppCompatActivity { + private static final String INGRIDIENT_LIST_DIVIDER = " - "; + private static final String INGRIDIENT_LIST_COUNT_DIVIDER = " "; + private static final int IMAGES_WIDTH = 300; //dp + private static final int IMAGES_HEIGHT = 200; //dp + private static final int CATEGORY_NAME_PADDING = 12; //dp + private Recipe recipe; + private ArrayList steps; + private ArrayList categories; + private ArrayList ingredients; + private HashMap categoryDescriptions; + private GifImageView loaderAnimation; + private LinearLayout informationLayout; + private ImageButton likeBtn; + private ImageButton favoriteBtn; + private ImageButton deleteBtn; + private ImageButton editBtn; + private TextView likeCounter; + private int likeCount; + private boolean liked; + private boolean inFavourites; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.recipe_view); + + //Init loaderAnimation + loaderAnimation = (GifImageView) findViewById(R.id.loader_animation_view); + loaderAnimation.setGifImageResource(LoaderAnimationSelector.getRandomLoaderResource()); + loaderAnimation.setLayerType(View.LAYER_TYPE_SOFTWARE, null); + informationLayout = (LinearLayout) findViewById(R.id.information_layout); + //Init like & selected btn + likeBtn = (ImageButton) findViewById(R.id.recipe_like); + likeCounter = (TextView) findViewById(R.id.recipe_like_count); + favoriteBtn = (ImageButton) findViewById(R.id.recipe_add_to_favorites); + } + + @Override + protected void onResume() { + super.onResume(); + Intent intent = getIntent(); + ContentLoader contentLoader = new ContentLoader(intent.getIntExtra("Recipe", -1)); + Thread loader = new Thread(contentLoader); + loader.start(); + } + + public void onCookClick(View v) { + Intent intent = new Intent(this, StepViewActivity.class); + intent.putExtra("Recipe", recipe.getID()); + startActivity(intent); + } + + public void onEditClick(View v) { + Intent intent = new Intent(this, EditRecipeActivity.class); + intent.putExtra("RecipeID", recipe.getID()); + startActivity(intent); + } + + public void onDeleteClick(View v) { + boolean complete = false; + while(!complete) { + try { + CookBookStorage.getInstance(this).deleteRecipe(new RecipeToChange(recipe.getID(), "", "")); + complete = true; + } + catch (Exception e) { + //Repeat + } + } + finish(); + } + + public void onLikeClick(View v) { + if (liked) { + likeBtn.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.like_off)); + CookBookStorage.getInstance(this).setNotLike(recipe.getID()); + } else { + likeBtn.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.like_on)); + CookBookStorage.getInstance(this).setLike(recipe.getID()); + } + liked = !liked; + likeCounter.setText(String.valueOf(CookBookStorage.getInstance(this).getRecipeLikes(recipe))); + } + + public void onFavoriteClick(View v) { + if (inFavourites) { + favoriteBtn.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.favourite_off)); + CookBookStorage.getInstance(this).removeFromFavorites(recipe.getID()); + } else { + favoriteBtn.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.favourite_on)); + CookBookStorage.getInstance(this).addToFavorites(recipe); + } + inFavourites = !inFavourites; + } + private void showRecipe() { + ////Header + TextView nameView = (TextView)findViewById(R.id.recipe_header_name); + LinearLayout categoryList = (LinearLayout)findViewById(R.id.recipe_header_tags); + //Categories + categoryList.removeAllViews(); + for (final Integer id : categories) { + LinearLayout categoryNameLayout = new LinearLayout(this); + categoryNameLayout.setPadding(dpToPix(CATEGORY_NAME_PADDING), 0, 0, 0); + TextView categoryNameView = new TextView(this); + //TextView categoryNameView = new TextView(this); + categoryNameView.setBackgroundColor(0x00_00_00_00); + categoryNameView.setTextAppearance(this, R.style.RecipeView_CategoryShowStyle); //I can't found another method + categoryNameView.setText(categoryDescriptions.get(id)); + categoryNameView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(RecipeViewActivity.this, CookBookCategoryActivity.class); + intent.putExtra("Category", id); + startActivity(intent); + } + }); + categoryNameLayout.addView(categoryNameView); + categoryList.addView(categoryNameLayout); + } + + //Body + TextView descriptionView = (TextView)findViewById(R.id.recipe_body_description); + TextView ingridientsView = (TextView)findViewById(R.id.recipe_body_ingredients); + + nameView.setText(recipe.getName()); + descriptionView.setText(recipe.getDescription()); + StringBuilder ingredientList = new StringBuilder(); + for (Ingredient i : ingredients) { + if (i.getQuantity() >= 0.0) { + ingredientList = ingredientList.append(INGRIDIENT_LIST_DIVIDER + i.getName() + + INGRIDIENT_LIST_COUNT_DIVIDER + i.getQuantity() + " " + + i.getTypeName() + "\n"); + } else { + ingredientList = ingredientList.append(INGRIDIENT_LIST_DIVIDER + i.getName() + + INGRIDIENT_LIST_COUNT_DIVIDER + + i.getTypeName() + "\n"); + } + } + ingridientsView.setText(ingredientList.toString()); + } + + private int dpToPix(int value) { + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, getResources().getDisplayMetrics()); + } + + public class ContentLoader implements Runnable { + private int recipeID; + public ContentLoader(int id) { + this.recipeID = id; + } + + @Override + public void run() { + RecipeViewActivity.this.runOnUiThread(new Runnable() { + @Override + public void run() { + loaderAnimation.setVisibility(View.VISIBLE); + informationLayout.setVisibility(View.INVISIBLE); + } + }); + final CookBookStorage cookbook = CookBookStorage.getInstance(RecipeViewActivity.this); + boolean loadingComplete = false; + while (!loadingComplete) { + try { + recipe = cookbook.getRecipe(recipeID); + steps = cookbook.getRecipeSteps(recipe.getID()); + categories = cookbook.getRecipeCategories(recipe.getID()); + liked = cookbook.getUserLike(recipe); + inFavourites = false; + for (Recipe r : cookbook.getFavorites()) { + if (r.getID() == recipe.getID()) { + inFavourites = true; + } + } + ingredients = cookbook.getRecipeIngredients(recipeID); + likeCount = cookbook.getRecipeLikes(recipe); + categoryDescriptions = new HashMap<>(); + for (Integer id : categories) { + categoryDescriptions.put(id, cookbook.getCategoryByID(id).getDescription()); + } + loadingComplete = true; + } + catch (Throwable e) { + //Repeat + } + } + final boolean userPermission = cookbook.isUserOwnRecipe(recipe); + RecipeViewActivity.this.runOnUiThread(new Runnable() { + @Override + public void run() { + showRecipe(); + //Check user permissions + deleteBtn = (ImageButton) findViewById(R.id.recipe_delete); + editBtn = (ImageButton) findViewById(R.id.recipe_edit); + if (userPermission) { + deleteBtn.setVisibility(View.INVISIBLE); + editBtn.setVisibility(View.INVISIBLE); + } + loaderAnimation.setVisibility(View.INVISIBLE); + informationLayout.setVisibility(View.VISIBLE); + loaderAnimation.setGifImageResource(LoaderAnimationSelector.getRandomLoaderResource()); + if (!liked) { + likeBtn.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.like_off)); + } else { + likeBtn.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.like_on)); + } + if (!inFavourites) { + favoriteBtn.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.favourite_off)); + } else { + favoriteBtn.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.favourite_on)); + } + likeCounter.setText(String.valueOf(likeCount)); + } + }); + + //Image loader + final int widthInPx = dpToPix(IMAGES_WIDTH); + final int heightInPx = dpToPix(IMAGES_HEIGHT); + final LinearLayout recipeImages = (LinearLayout)findViewById(R.id.recipe_body_images); + RecipeViewActivity.this.runOnUiThread( + new Runnable() { + @Override + public void run() { + recipeImages.removeAllViews(); + } + } + ); + for (final Step s : steps) { + cookbook.downloadStepImage(s); + RecipeViewActivity.this.runOnUiThread( + new Runnable() { + @Override + public void run() { + ImageView photo = new ImageView(RecipeViewActivity.this); + photo.setScaleType(ImageView.ScaleType.CENTER_INSIDE); + photo.setLayoutParams(new LinearLayout.LayoutParams( + Math.min(widthInPx, s.getImage().getWidth()), + Math.min(heightInPx, s.getImage().getHeight()))); + photo.setImageBitmap(s.getImage()); + recipeImages.addView(photo); + } + } + ); + } + RecipeViewActivity.this.runOnUiThread( + new Runnable() { + @Override + public void run() { + if (steps.size() > 0) { + ImageView photoView = (ImageView) findViewById(R.id.recipe_header_photo); + photoView.setImageBitmap(steps.get(steps.size() - 1).getImage()); + } + } + } + ); + } + } +} diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/SearchRecipeActivity.java b/app/src/main/java/ru/spbau/mit/foodmanager/SearchRecipeActivity.java new file mode 100644 index 0000000..96fb53b --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/SearchRecipeActivity.java @@ -0,0 +1,109 @@ +package ru.spbau.mit.foodmanager; + +import android.content.Intent; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Log; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.ListView; + +import java.util.ArrayList; + +public class SearchRecipeActivity extends AppCompatActivity { + + private ArrayList recipes; + private Intent task; + private GifImageView loaderAnimation; + private ListView recipesList; + private Thread searchingThread; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.search_recipe); + final EditText searchText = (EditText)findViewById(R.id.search_text); + + //Init loaderAnimation + loaderAnimation = (GifImageView) findViewById(R.id.loader_animation_view); + loaderAnimation.setGifImageResource(LoaderAnimationSelector.getRandomLoaderResource()); + loaderAnimation.setLayerType(View.LAYER_TYPE_SOFTWARE, null); + loaderAnimation.setVisibility(View.INVISIBLE); + recipesList = (ListView) findViewById(R.id.search_recipes_list); + task = getIntent(); + + + searchText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {} + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {} + + @Override + public void afterTextChanged(Editable editable) { + if (searchingThread != null) { + searchingThread.interrupt(); + } + searchingThread = new Thread(new ContentLoader(editable.toString())); + searchingThread.start(); + } + }); + } + + private void showRecipes() { + Log.d("ShowRecipies", "Show"); + ArrayList names = new ArrayList<>(); + if (recipes != null) { + for (Recipe r : recipes) { + names.add(r.getName()); + } + } + Log.d("names", ((Integer)names.size()).toString()); + ArrayAdapter adapter = new ArrayAdapter<>(this, + R.layout.search_recipe_element, names); + recipesList.setAdapter(adapter); + recipesList.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView adapterView, View view, int i, long l) { + task.putExtra("Result", recipes.get(i).getID()); + setResult(RESULT_OK, task); + finish(); + } + }); + } + + public class ContentLoader implements Runnable { + private String searchFilter; + public ContentLoader(String searchFilter) { + this.searchFilter = searchFilter; + } + + @Override + public void run() { + SearchRecipeActivity.this.runOnUiThread(new Runnable() { + @Override + public void run() { + //loaderAnimation.setVisibility(View.VISIBLE); + } + }); + + recipes = null; + while (recipes == null) { + recipes = CookBookStorage.getInstance(SearchRecipeActivity.this).getRecipesByFilter(searchFilter); + } + SearchRecipeActivity.this.runOnUiThread(new Runnable() { + @Override + public void run() { + //loaderAnimation.setVisibility(View.INVISIBLE); + showRecipes(); + loaderAnimation.setGifImageResource(LoaderAnimationSelector.getRandomLoaderResource()); + } + }); + } + } +} diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/SettingsActivity.java b/app/src/main/java/ru/spbau/mit/foodmanager/SettingsActivity.java new file mode 100644 index 0000000..ee64beb --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/SettingsActivity.java @@ -0,0 +1,25 @@ +package ru.spbau.mit.foodmanager; + +import android.content.Intent; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.view.View; + +public class SettingsActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.settings); + } + + public void onMenuSettingsClick(View v) { + Intent intent = new Intent(this, MenuSettingsActivity.class); + startActivity(intent); + } + + public void onNotificationSettingsClick(View v) { + Intent intent = new Intent(this, NotificationSettingsActivity.class); + startActivity(intent); + } +} diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/ShoppingListActivity.java b/app/src/main/java/ru/spbau/mit/foodmanager/ShoppingListActivity.java new file mode 100644 index 0000000..79ebcc0 --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/ShoppingListActivity.java @@ -0,0 +1,213 @@ +package ru.spbau.mit.foodmanager; + +import android.content.Context; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AbsListView; +import android.widget.BaseAdapter; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.TextView; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; + +public class ShoppingListActivity extends AppCompatActivity { + private static final String shoppingListFilename = "ShoppingList"; + private CookBookStorage cookbook; + private ArrayList recipes; + private ArrayList ingredients; + private GifImageView loaderAnimation; + private LinearLayout informationLayout; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.shopping_list); + //Init loaderAnimation + loaderAnimation = (GifImageView) findViewById(R.id.loader_animation_view); + loaderAnimation.setGifImageResource(LoaderAnimationSelector.getRandomLoaderResource()); + loaderAnimation.setLayerType(View.LAYER_TYPE_SOFTWARE, null); + informationLayout = (LinearLayout) findViewById(R.id.information_layout); + //Init cookbook + cookbook = CookBookStorage.getInstance(this); + //Init Task + ContentLoader contentLoader = new ContentLoader(); + Thread loader = new Thread(contentLoader); + loader.start(); + } + + public void saveMenuStorage() { + try { + FileOutputStream output = this.openFileOutput( + shoppingListFilename, Context.MODE_PRIVATE); + + ObjectOutputStream outputStream = new ObjectOutputStream(output); + outputStream.writeObject(ingredients); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void loadMenuStorage() { + File settings = new File(this.getFilesDir(), shoppingListFilename); + if (settings.exists()) { + try { + FileInputStream input = this.openFileInput(shoppingListFilename); + ObjectInputStream inputStream = new ObjectInputStream(input); + ingredients = (ArrayList) inputStream.readObject(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private void showIngredients() { + ListView listView = (ListView) findViewById(R.id.shopping_list_view); + listView.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE); + IngredientAdapter adapter = new IngredientAdapter(ingredients); + listView.setAdapter(adapter); + } + + public class IngredientAdapter extends BaseAdapter { + private ArrayList ingredients; + private HashMap isChecked; + private LayoutInflater inflater; + + public IngredientAdapter(ArrayList ing) { + ingredients = ing; + inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); + isChecked = new HashMap<>(); + } + + @Override + public int getCount() { + return ingredients.size(); + } + + @Override + public Object getItem(int i) { + return ingredients.get(i); + } + + @Override + public long getItemId(int i) { + return i; + } + + @Override + public View getView(int i, View view, ViewGroup viewGroup) { + if (view == null) { + view = inflater.inflate(R.layout.shopping_list_element, null); + } + TextView name = (TextView) view.findViewById(R.id.shopping_list_element_text); + TextView count = (TextView) view.findViewById(R.id.shopping_list_element_count); + CheckBox checkBox = (CheckBox) view.findViewById(R.id.shopping_list_element_checkbox); + name.setText(ingredients.get(i).getName()); + String countStr = ""; + if (ingredients.get(i).getQuantity() > 0) { + countStr = ((Double)ingredients.get(i).getQuantity()).toString() + " "; + } + countStr = countStr + ingredients.get(i).getTypeName(); + count.setText(countStr); + if (isChecked.get(i) == null) { + isChecked.put(i, false); + } + checkBox.setTag(i); + checkBox.setChecked(isChecked.get(i)); + checkBox.setOnCheckedChangeListener(onCheckedChangeListener); + return view; + } + + private CompoundButton.OnCheckedChangeListener onCheckedChangeListener = + new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean b) { + isChecked.put((Integer)compoundButton.getTag(), b); + } + }; + } + + public class ContentLoader implements Runnable { + + public ContentLoader() { + } + + @Override + public void run() { + ShoppingListActivity.this.runOnUiThread(new Runnable() { + @Override + public void run() { + loaderAnimation.setVisibility(View.VISIBLE); + informationLayout.setVisibility(View.INVISIBLE); + } + }); + MenuStorage menu = MenuStorage.getInstance(ShoppingListActivity.this); + HashMap menuRecipes = menu.getMenu(); + recipes = new ArrayList<>(); + for (DayMenu rs : menuRecipes.values()) { + for (Integer id : rs.getDishes()) { + recipes.add(cookbook.getRecipe(id)); + //TODO: do in background + } + } + loadMenuStorage(); + while (ingredients == null) { + try { + ArrayList allIngredients = new ArrayList<>(); + for (Recipe r : recipes) { + allIngredients.addAll(cookbook.getRecipeIngredients(r.getID())); + } + ingredients = new ArrayList<>(); + for (Ingredient i : allIngredients) { + Boolean newIngredient = true; + for (Ingredient j : ingredients) { + if (i.getName().equals(j.getName()) && i.getMeasure().equals(j.getMeasure())) { + newIngredient = false; + j.setQuantity(j.getQuantity() + i.getQuantity()); + break; + } + } + if (newIngredient) { + ingredients.add(i); + } + } + + Collections.sort(ingredients, new Comparator() { + @Override + public int compare(Ingredient i1, Ingredient i2) { + return i1.getName().compareTo(i2.getName()); + } + }); + } + catch (Throwable e) { + //Repeat + } + saveMenuStorage(); + } + + ShoppingListActivity.this.runOnUiThread(new Runnable() { + @Override + public void run() { + showIngredients(); + loaderAnimation.setVisibility(View.INVISIBLE); + informationLayout.setVisibility(View.VISIBLE); + loaderAnimation.setGifImageResource(LoaderAnimationSelector.getRandomLoaderResource()); + } + }); + } + } +} diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/Step.java b/app/src/main/java/ru/spbau/mit/foodmanager/Step.java new file mode 100644 index 0000000..adb9a78 --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/Step.java @@ -0,0 +1,36 @@ +package ru.spbau.mit.foodmanager; + +import android.graphics.Bitmap; + +public class Step { + /** + * Шаг инструкции. + */ + private String description; + private String imageLink; + private Bitmap image; + + public Step(String description, String imageLink) { + this.description = description; + this.imageLink = imageLink; + this.image = null; + } + + public Step(String description, Bitmap image) { + this.description = description; + this.image = image; + this.imageLink = null; + } + + public String getDescription() { + return description; + } + + public Bitmap getImage() { + return image; + } + + public void setImage(Bitmap image) { this.image = image; } + + public String getImageLink() { return imageLink; } +} \ No newline at end of file diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/StepViewActivity.java b/app/src/main/java/ru/spbau/mit/foodmanager/StepViewActivity.java new file mode 100644 index 0000000..95ec1d6 --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/StepViewActivity.java @@ -0,0 +1,124 @@ +package ru.spbau.mit.foodmanager; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import java.util.ArrayList; + +public class StepViewActivity extends AppCompatActivity { + private Recipe recipe; + private ArrayList steps; + private Integer stepPosition = 0; + private GifImageView loaderAnimation; + private LinearLayout informationLayout; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.step_view); + //Init loaderAnimation + loaderAnimation = (GifImageView) findViewById(R.id.loader_animation_view); + loaderAnimation.setGifImageResource(LoaderAnimationSelector.getRandomLoaderResource()); + loaderAnimation.setLayerType(View.LAYER_TYPE_SOFTWARE, null); + informationLayout = (LinearLayout) findViewById(R.id.information_layout); + //Init Task + Intent i = getIntent(); + ContentLoader contentLoader = new ContentLoader(i.getIntExtra("Recipe", -1)); + Thread loader = new Thread(contentLoader); + loader.start(); + + } + + private void showRecipe() { + TextView recipeName = (TextView)findViewById(R.id.step_view_recipe_name); + recipeName.setText(recipe.getName()); + TextView stepCount = (TextView)findViewById(R.id.step_view_step_count); + stepCount.setText(((Integer)steps.size()).toString()); + showStep(stepPosition); + } + + private void showStep(int id) { + if (steps.size() > id) { + TextView description = (TextView) findViewById(R.id.step_view_step_text); + description.setText(steps.get(id).getDescription()); + ImageView image = (ImageView) findViewById(R.id.step_view_step_image); + if (steps.get(id).getImage() != null) { + image.setImageBitmap(steps.get(id).getImage()); + } + TextView counter = (TextView) findViewById(R.id.step_view_position); + counter.setText(((Integer) (id + 1)).toString()); + } + } + + public void onNextBtnClick(View v) { + if (stepPosition < steps.size() - 1) { + stepPosition++; + showStep(stepPosition); + } + } + + public void onPrevBtnClick(View v) { + if (stepPosition > 0) { + stepPosition--; + showStep(stepPosition); + } + } + public class ContentLoader implements Runnable { + private int recipeID; + public ContentLoader(int id) { + this.recipeID = id; + } + + @Override + public void run() { + StepViewActivity.this.runOnUiThread(new Runnable() { + @Override + public void run() { + loaderAnimation.setVisibility(View.VISIBLE); + informationLayout.setVisibility(View.INVISIBLE); + } + }); + steps = null; + CookBookStorage cookbook = CookBookStorage.getInstance(StepViewActivity.this); + while (steps == null) { + try { + recipe = cookbook.getRecipe(recipeID); + steps = cookbook.getRecipeSteps(recipe.getID()); + } + catch (Throwable e) { + //Repeat + } + } + StepViewActivity.this.runOnUiThread(new Runnable() { + @Override + public void run() { + showRecipe(); + loaderAnimation.setVisibility(View.INVISIBLE); + informationLayout.setVisibility(View.VISIBLE); + loaderAnimation.setGifImageResource(LoaderAnimationSelector.getRandomLoaderResource()); + } + }); + int i = 0; + for (final Step s : steps) { + cookbook.downloadStepImage(s); + if (i == stepPosition) { + StepViewActivity.this.runOnUiThread(new Runnable() { + @Override + public void run() { + ImageView image = (ImageView)findViewById(R.id.step_view_step_image); + if (s.getImage() != null) { + image.setImageBitmap(s.getImage()); + } + } + }); + } + i++; + } + } + } +} diff --git a/app/src/main/java/ru/spbau/mit/foodmanager/loaderAnimationSelector.java b/app/src/main/java/ru/spbau/mit/foodmanager/loaderAnimationSelector.java new file mode 100644 index 0000000..ed69bf4 --- /dev/null +++ b/app/src/main/java/ru/spbau/mit/foodmanager/loaderAnimationSelector.java @@ -0,0 +1,32 @@ +package ru.spbau.mit.foodmanager; + +import java.util.ArrayList; +import java.util.Random; + +/** + * Generates number of resource for loader animations + */ + +public class LoaderAnimationSelector { + private static ArrayList loaderResources; + private static Random random = new Random(); + + static { + loaderResources = new ArrayList<>(); + loaderResources.add(R.drawable.loader); + //loaderResources.add(R.drawable.loading_animation); + //loaderResources.add(R.drawable.loading_animation2); + //loaderResources.add(R.drawable.loading_animation3); + //loaderResources.add(R.drawable.loading_animation4); + //loaderResources.add(R.drawable.loading_animation7); + //loaderResources.add(R.drawable.loading_animation6); + } + + /** + * return random loader animation's resource ID + */ + public static int getRandomLoaderResource() { + int resID = loaderResources.get(Math.abs(random.nextInt()) % loaderResources.size()); + return resID; + } +} diff --git a/app/src/main/res/drawable/add.png b/app/src/main/res/drawable/add.png new file mode 100644 index 0000000..896ec32 Binary files /dev/null and b/app/src/main/res/drawable/add.png differ diff --git a/app/src/main/res/drawable/add_image.png b/app/src/main/res/drawable/add_image.png new file mode 100644 index 0000000..65dd52c Binary files /dev/null and b/app/src/main/res/drawable/add_image.png differ diff --git a/app/src/main/res/drawable/border.xml b/app/src/main/res/drawable/border.xml new file mode 100644 index 0000000..dc6234f --- /dev/null +++ b/app/src/main/res/drawable/border.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/delete.png b/app/src/main/res/drawable/delete.png new file mode 100644 index 0000000..d2c53ae Binary files /dev/null and b/app/src/main/res/drawable/delete.png differ diff --git a/app/src/main/res/drawable/edit.png b/app/src/main/res/drawable/edit.png new file mode 100644 index 0000000..bef9aba Binary files /dev/null and b/app/src/main/res/drawable/edit.png differ diff --git a/app/src/main/res/drawable/favourite_off.png b/app/src/main/res/drawable/favourite_off.png new file mode 100644 index 0000000..a4cda25 Binary files /dev/null and b/app/src/main/res/drawable/favourite_off.png differ diff --git a/app/src/main/res/drawable/favourite_on.png b/app/src/main/res/drawable/favourite_on.png new file mode 100644 index 0000000..1ec07d3 Binary files /dev/null and b/app/src/main/res/drawable/favourite_on.png differ diff --git a/app/src/main/res/drawable/like_off.png b/app/src/main/res/drawable/like_off.png new file mode 100644 index 0000000..479e2d3 Binary files /dev/null and b/app/src/main/res/drawable/like_off.png differ diff --git a/app/src/main/res/drawable/like_on.png b/app/src/main/res/drawable/like_on.png new file mode 100644 index 0000000..b9ce1d1 Binary files /dev/null and b/app/src/main/res/drawable/like_on.png differ diff --git a/app/src/main/res/drawable/loader.gif b/app/src/main/res/drawable/loader.gif new file mode 100644 index 0000000..88bf0b4 Binary files /dev/null and b/app/src/main/res/drawable/loader.gif differ diff --git a/app/src/main/res/drawable/loading_animation.gif b/app/src/main/res/drawable/loading_animation.gif new file mode 100644 index 0000000..56a47ab Binary files /dev/null and b/app/src/main/res/drawable/loading_animation.gif differ diff --git a/app/src/main/res/drawable/loading_animation2.gif b/app/src/main/res/drawable/loading_animation2.gif new file mode 100644 index 0000000..125b7d8 Binary files /dev/null and b/app/src/main/res/drawable/loading_animation2.gif differ diff --git a/app/src/main/res/drawable/loading_animation3.gif b/app/src/main/res/drawable/loading_animation3.gif new file mode 100644 index 0000000..417f7df Binary files /dev/null and b/app/src/main/res/drawable/loading_animation3.gif differ diff --git a/app/src/main/res/drawable/loading_animation4.gif b/app/src/main/res/drawable/loading_animation4.gif new file mode 100644 index 0000000..fc1a553 Binary files /dev/null and b/app/src/main/res/drawable/loading_animation4.gif differ diff --git a/app/src/main/res/drawable/loading_animation5.gif b/app/src/main/res/drawable/loading_animation5.gif new file mode 100644 index 0000000..9f67b70 Binary files /dev/null and b/app/src/main/res/drawable/loading_animation5.gif differ diff --git a/app/src/main/res/drawable/loading_animation6.gif b/app/src/main/res/drawable/loading_animation6.gif new file mode 100644 index 0000000..33e3f5a Binary files /dev/null and b/app/src/main/res/drawable/loading_animation6.gif differ diff --git a/app/src/main/res/drawable/loading_animation7.gif b/app/src/main/res/drawable/loading_animation7.gif new file mode 100644 index 0000000..f27d0de Binary files /dev/null and b/app/src/main/res/drawable/loading_animation7.gif differ diff --git a/app/src/main/res/drawable/logo.png b/app/src/main/res/drawable/logo.png new file mode 100644 index 0000000..fafef43 Binary files /dev/null and b/app/src/main/res/drawable/logo.png differ diff --git a/app/src/main/res/drawable/next.png b/app/src/main/res/drawable/next.png new file mode 100644 index 0000000..e48bc00 Binary files /dev/null and b/app/src/main/res/drawable/next.png differ diff --git a/app/src/main/res/drawable/previous.png b/app/src/main/res/drawable/previous.png new file mode 100644 index 0000000..7bbaf0c Binary files /dev/null and b/app/src/main/res/drawable/previous.png differ diff --git a/app/src/main/res/drawable/search.png b/app/src/main/res/drawable/search.png new file mode 100644 index 0000000..ec7dfbc Binary files /dev/null and b/app/src/main/res/drawable/search.png differ diff --git a/app/src/main/res/layout/choose_mealtime_preset.xml b/app/src/main/res/layout/choose_mealtime_preset.xml new file mode 100644 index 0000000..c7f2ec2 --- /dev/null +++ b/app/src/main/res/layout/choose_mealtime_preset.xml @@ -0,0 +1,34 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/cook_book.xml b/app/src/main/res/layout/cook_book.xml new file mode 100644 index 0000000..c68a54c --- /dev/null +++ b/app/src/main/res/layout/cook_book.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/cook_book_category.xml b/app/src/main/res/layout/cook_book_category.xml new file mode 100644 index 0000000..d1ecadb --- /dev/null +++ b/app/src/main/res/layout/cook_book_category.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/edit_recipe.xml b/app/src/main/res/layout/edit_recipe.xml new file mode 100644 index 0000000..b5843d5 --- /dev/null +++ b/app/src/main/res/layout/edit_recipe.xml @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + +