package openfoodfacts.github.scrachx.openfood.network;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.preference.PreferenceManager;
import android.text.Html;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.afollestad.materialdialogs.MaterialDialog;
import com.fasterxml.jackson.databind.JsonNode;
import com.squareup.picasso.Picasso;
import net.steamcrafted.loadtoast.LoadToast;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import me.dm7.barcodescanner.zxing.ZXingScannerView;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import openfoodfacts.github.scrachx.openfood.BuildConfig;
import openfoodfacts.github.scrachx.openfood.R;
import openfoodfacts.github.scrachx.openfood.models.AllergenDao;
import openfoodfacts.github.scrachx.openfood.models.AllergenRestResponse;
import openfoodfacts.github.scrachx.openfood.models.HistoryProduct;
import openfoodfacts.github.scrachx.openfood.models.HistoryProductDao;
import openfoodfacts.github.scrachx.openfood.models.Product;
import openfoodfacts.github.scrachx.openfood.models.ProductImage;
import openfoodfacts.github.scrachx.openfood.models.Search;
import openfoodfacts.github.scrachx.openfood.models.SendProduct;
import openfoodfacts.github.scrachx.openfood.models.State;
import openfoodfacts.github.scrachx.openfood.utils.Utils;
import openfoodfacts.github.scrachx.openfood.views.FullScreenImage;
import openfoodfacts.github.scrachx.openfood.views.ProductActivity;
import openfoodfacts.github.scrachx.openfood.views.SaveProductOfflineActivity;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.jackson.JacksonConverterFactory;
import static openfoodfacts.github.scrachx.openfood.models.ProductImageField.FRONT;
import static openfoodfacts.github.scrachx.openfood.models.ProductImageField.INGREDIENTS;
import static openfoodfacts.github.scrachx.openfood.models.ProductImageField.NUTRITION;
import static openfoodfacts.github.scrachx.openfood.network.OpenFoodAPIService.PRODUCT_API_COMMENT;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
public class OpenFoodAPIClient {
private AllergenDao mAllergenDao;
private HistoryProductDao mHistoryProductDao;
private static final JacksonConverterFactory jacksonConverterFactory = JacksonConverterFactory.create();
private final static OkHttpClient httpClient = new OkHttpClient.Builder()
.connectTimeout(5000, TimeUnit.MILLISECONDS)
.build();
private final OpenFoodAPIService apiService;
public OpenFoodAPIClient(Activity activity) {
this(BuildConfig.HOST);
mAllergenDao = Utils.getAppDaoSession(activity).getAllergenDao();
mHistoryProductDao = Utils.getAppDaoSession(activity).getHistoryProductDao();
}
private OpenFoodAPIClient(String apiUrl) {
apiService = new Retrofit.Builder()
.baseUrl(apiUrl)
.client(httpClient)
.addConverterFactory(jacksonConverterFactory)
.build()
.create(OpenFoodAPIService.class);
}
/**
* @return The API service to be able to use directly retrofit API mapping
*/
public OpenFoodAPIService getAPIService() {
return apiService;
}
/**
* Open the product activity if the barcode exist.
* Also add it in the history if the product exist.
* @param barcode product barcode
* @param activity
*/
public void getProduct(final String barcode, final Activity activity) {
apiService.getProductByBarcode(barcode).enqueue(new Callback<State>() {
@Override
public void onResponse(Call<State> call, Response<State> response) {
final State s = response.body();
if (s.getStatus() == 0) {
new MaterialDialog.Builder(activity)
.title(R.string.txtDialogsTitle)
.content(R.string.txtDialogsContent)
.positiveText(R.string.txtYes)
.negativeText(R.string.txtNo)
.onPositive((dialog, which) -> {
Intent intent = new Intent(activity, SaveProductOfflineActivity.class);
intent.putExtra("barcode", barcode);
activity.startActivity(intent);
activity.finish();
})
.show();
} else {
new HistoryTask().doInBackground(s.getProduct());
Intent intent = new Intent(activity, ProductActivity.class);
Bundle bundle = new Bundle();
bundle.putSerializable("state", s);
intent.putExtras(bundle);
activity.startActivity(intent);
}
}
@Override
public void onFailure(Call<State> call, Throwable t) {
new MaterialDialog.Builder(activity)
.title(R.string.txtDialogsTitle)
.content(R.string.txtDialogsContent)
.positiveText(R.string.txtYes)
.negativeText(R.string.txtNo)
.onPositive((dialog, which) -> {
Intent intent = new Intent(activity, SaveProductOfflineActivity.class);
intent.putExtra("barcode",barcode);
activity.startActivity(intent);
activity.finish();
})
.show();
Toast.makeText(activity, activity.getString(R.string.errorWeb), Toast.LENGTH_LONG).show();
}
});
}
/**
* Open the product activity if the barcode exist.
* Also add it in the history if the product exist.
* @param barcode product barcode
* @param activity
* @param camera needed when the function is called by the barcodefragment else null
* @param resultHandler needed when the function is called by the barcodefragment else null
*/
public void getProduct(final String barcode, final Activity activity, final ZXingScannerView camera, final ZXingScannerView.ResultHandler resultHandler) {
apiService.getProductByBarcode(barcode).enqueue(new Callback<State>() {
@Override
public void onResponse(Call<State> call, Response<State> response) {
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(activity.getBaseContext());
final State s = response.body();
if (s.getStatus() == 0) {
Toast.makeText(activity, R.string.txtDialogsContent, Toast.LENGTH_LONG).show();
Intent intent = new Intent(activity, SaveProductOfflineActivity.class);
intent.putExtra("barcode", barcode);
activity.startActivity(intent);
activity.finish();
} else {
final Product product = s.getProduct();
new HistoryTask().doInBackground(s.getProduct());
if (settings.getBoolean("powerMode", false) && camera != null) {
MaterialDialog dialog = new MaterialDialog.Builder(activity)
.title(product.getProductName())
.customView(R.layout.alert_powermode_image, true)
.neutralText(R.string.txtOk)
.positiveText(R.string.txtSeeMore)
.onPositive((materialDialog, which) -> {
Intent intent = new Intent(activity, ProductActivity.class);
Bundle bundle = new Bundle();
bundle.putSerializable("state", s);
intent.putExtras(bundle);
activity.startActivity(intent);
})
.onNeutral((materialDialog, which) -> camera.resumeCameraPreview(resultHandler))
.build();
ImageView imgPhoto = (ImageView) dialog.getCustomView().findViewById(R.id.imagePowerModeProduct);
ImageView imgNutriscore = (ImageView) dialog.getCustomView().findViewById(R.id.imageGrade);
TextView quantityProduct = (TextView) dialog.getCustomView().findViewById(R.id.textQuantityProduct);
TextView brandProduct = (TextView) dialog.getCustomView().findViewById(R.id.textBrandProduct);
if(product.getQuantity() != null && !product.getQuantity().trim().isEmpty()) {
quantityProduct.setText(Html.fromHtml("<b>" + activity.getResources().getString(R.string.txtQuantity) + "</b>" + ' ' + product.getQuantity()));
} else {
quantityProduct.setVisibility(View.GONE);
}
if(product.getBrands() != null && !product.getBrands().trim().isEmpty()) {
brandProduct.setText(Html.fromHtml("<b>" + activity.getResources().getString(R.string.txtBrands) + "</b>" + ' ' + product.getBrands()));
} else {
brandProduct.setVisibility(View.GONE);
}
if (isNotEmpty(s.getProduct().getImageUrl())) {
Picasso.with(activity)
.load(Utils.getImageGrade(product.getNutritionGradeFr()))
.into(imgNutriscore);
}
if (isNotEmpty(s.getProduct().getImageUrl())) {
Picasso.with(activity)
.load(s.getProduct().getImageUrl())
.into(imgPhoto);
imgPhoto.setOnClickListener(view -> {
Intent intent = new Intent(view.getContext(), FullScreenImage.class);
Bundle bundle = new Bundle();
bundle.putString("imageurl", product.getImageUrl());
intent.putExtras(bundle);
activity.startActivity(intent);
});
}
dialog.show();
} else {
Intent intent = new Intent(activity, ProductActivity.class);
Bundle bundle = new Bundle();
bundle.putSerializable("state", s);
intent.putExtras(bundle);
activity.startActivity(intent);
}
}
}
@Override
public void onFailure(Call<State> call, Throwable t) {
new MaterialDialog.Builder(activity)
.title(R.string.txtDialogsTitle)
.content(R.string.txtDialogsContent)
.positiveText(R.string.txtYes)
.negativeText(R.string.txtNo)
.callback(new MaterialDialog.ButtonCallback() {
@Override
public void onPositive(MaterialDialog dialog) {
Intent intent = new Intent(activity, SaveProductOfflineActivity.class);
intent.putExtra("barcode",barcode);
activity.startActivity(intent);
activity.finish();
}
@Override
public void onNegative(MaterialDialog dialog) {
return;
}
})
.show();
Toast.makeText(activity, activity.getString(R.string.errorWeb), Toast.LENGTH_LONG).show();
}
});
}
public void searchProduct(final String name, final int page, final Activity activity, final OnProductsCallback productsCallback) {
apiService.searchProductByName(name, page).enqueue(new Callback<Search>() {
@Override
public void onResponse(Call<Search> call, Response<Search> response) {
if (!response.isSuccess()) {
productsCallback.onProductsResponse(false, null, -1);
return;
}
Search s = response.body();
if(Integer.valueOf(s.getCount()) == 0){
Toast.makeText(activity, R.string.txt_product_not_found, Toast.LENGTH_LONG).show();
productsCallback.onProductsResponse(false, null, -1);
}else{
productsCallback.onProductsResponse(true, s.getProducts(), Integer.parseInt(s.getCount()));
}
}
@Override
public void onFailure(Call<Search> call, Throwable t) {
Toast.makeText(activity, activity.getString(R.string.errorWeb), Toast.LENGTH_LONG).show();
productsCallback.onProductsResponse(false, null, -1);
}
});
}
public void getAllergens(final OnAllergensCallback onAllergensCallback) {
apiService.getAllergens().enqueue(new Callback<AllergenRestResponse>() {
@Override
public void onResponse(Call<AllergenRestResponse> call, Response<AllergenRestResponse> response) {
if (!response.isSuccess()) {
onAllergensCallback.onAllergensResponse(false);
return;
}
mAllergenDao.insertOrReplaceInTx(response.body().getAllergens());
onAllergensCallback.onAllergensResponse(true);
}
@Override
public void onFailure(Call<AllergenRestResponse> call, Throwable t) {
}
});
}
public void post(final Activity activity, final SendProduct product, final OnProductSentCallback productSentCallback){
final LoadToast lt = new LoadToast(activity);
lt.setText(activity.getString(R.string.toastSending));
lt.setBackgroundColor(activity.getResources().getColor(R.color.blue));
lt.setTextColor(activity.getResources().getColor(R.color.white));
lt.show();
apiService.saveProduct(product.getBarcode(), product.getLang(), product.getName(), product.getBrands(), product.getQuantity(), product.getUserId(), product.getPassword(), PRODUCT_API_COMMENT).enqueue(new Callback<State>() {
@Override
public void onResponse(Call<State> call, Response<State> response) {
if (!response.isSuccess() || response.body().getStatus() == 0) {
lt.error();
productSentCallback.onProductSentResponse(false);
return;
}
String imguploadFront = product.getImgupload_front();
if (StringUtils.isNotEmpty(imguploadFront)) {
ProductImage image = new ProductImage(product.getBarcode(), FRONT, new File(imguploadFront));
postImg(activity, image);
}
String imguploadIngredients = product.getImgupload_ingredients();
if (StringUtils.isNotEmpty(imguploadIngredients)) {
postImg(activity, new ProductImage(product.getBarcode(), INGREDIENTS, new File(imguploadIngredients)));
}
String imguploadNutrition = product.getImgupload_nutrition();
if (StringUtils.isNotBlank(imguploadNutrition)) {
postImg(activity, new ProductImage(product.getBarcode(), NUTRITION, new File(imguploadNutrition)));
}
lt.success();
productSentCallback.onProductSentResponse(true);
}
@Override
public void onFailure(Call<State> call, Throwable t) {
lt.error();
productSentCallback.onProductSentResponse(false);
}
});
}
public void postImg(final Context context, final ProductImage image) {
final LoadToast lt = new LoadToast(context);
lt.setText(context.getString(R.string.toastSending));
lt.setBackgroundColor(context.getResources().getColor(R.color.blue));
lt.setTextColor(context.getResources().getColor(R.color.white));
lt.show();
String lang = Locale.getDefault().getLanguage();
Map<String, RequestBody> imgMap = new HashMap<>();
imgMap.put("code", image.getCode());
imgMap.put("imagefield", image.getField());
imgMap.put("imgupload_front\"; filename=\"front_"+ lang +".png\"", image.getImguploadFront());
imgMap.put("imgupload_ingredients\"; filename=\"ingredients_" + lang + ".png\"", image.getImguploadIngredients());
imgMap.put("imgupload_nutrition\"; filename=\"nutrition_" + lang + ".png\"", image.getImguploadNutrition());
imgMap.put("imgupload_other\"; filename=\"other_" + lang + ".png\"", image.getImguploadOther());
// Attribute the upload to the connected user
final SharedPreferences settings = context.getSharedPreferences("login", 0);
final String login = settings.getString("user", "");
final String password = settings.getString("pass", "");
if (!login.isEmpty() && !password.isEmpty()) {
imgMap.put("user_id", RequestBody.create(MediaType.parse("text/plain"), login));
imgMap.put("password", RequestBody.create(MediaType.parse("text/plain"), password));
}
apiService.saveImage(imgMap)
.enqueue(new Callback<JsonNode>() {
@Override
public void onResponse(Call<JsonNode> call, Response<JsonNode> response) {
if(!response.isSuccess()) {
Toast.makeText(context, context.getString(R.string.errorWeb), Toast.LENGTH_LONG).show();
lt.error();
return;
}
JsonNode body = response.body();
if (body == null || !body.isObject()) {
lt.error();
} else if (body.get("status").asText().contains("status not ok")) {
Toast.makeText(context, body.get("error").asText(), Toast.LENGTH_LONG).show();
lt.error();
} else {
lt.success();
}
}
@Override
public void onFailure(Call<JsonNode> call, Throwable t) {
Toast.makeText(context, context.getString(R.string.errorWeb), Toast.LENGTH_LONG).show();
lt.error();
}
});
}
public interface OnProductsCallback {
void onProductsResponse(boolean isOk, List<Product> products, int countProducts);
}
public interface OnAllergensCallback {
void onAllergensResponse(boolean value);
}
public interface OnProductSentCallback {
void onProductSentResponse(boolean value);
}
/**
* Create an history product asynchronously
*/
private class HistoryTask extends AsyncTask<Product, Void, Void> {
@Override
protected Void doInBackground(Product... products) {
Product product = products[0];
List<HistoryProduct> historyProducts = mHistoryProductDao.queryBuilder().where(HistoryProductDao.Properties.Barcode.eq(product.getCode())).list();
HistoryProduct hp;
if(historyProducts.size() == 1) {
hp = historyProducts.get(0);
hp.setLastSeen(new Date());
} else {
hp = new HistoryProduct(product.getProductName(), product.getBrands(), product.getImageFrontUrl(), product.getCode());
}
mHistoryProductDao.insertOrReplace(hp);
return null;
}
}
}