/*
* Copyright 2015, 2016 Anael Mobilia
*
* This file is part of NextINpact-Unofficial.
*
* NextINpact-Unofficial is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NextINpact-Unofficial is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NextINpact-Unofficial. If not, see <http://www.gnu.org/licenses/>
*/
package com.pcinpact.datastorage;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.support.v4.content.ContextCompat;
import android.text.Html;
import android.text.Html.ImageGetter;
import android.text.Spanned;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
import com.pcinpact.R;
import com.pcinpact.items.Item;
import com.pcinpact.network.AsyncImageDownloader;
import com.pcinpact.network.RefreshDisplayInterface;
import com.pcinpact.parseur.TagHandler;
import com.pcinpact.utils.Constantes;
import com.pcinpact.utils.Tools;
import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
/**
* Fournit des images. Peut lancer des téléchargements. Peut gérer du callback.
*
* @author Anael
*/
public class ImageProvider implements ImageGetter, RefreshDisplayInterface {
/**
* Context de l'application.
*/
private final Context monContext;
/**
* Type d'images.
*/
private final int monTypeImages;
/**
* View dans laquelle l'image est affichée.
*/
private final View maView;
/**
* Texte du contenu si TextView.
*/
private String monContenu = null;
/**
* ID de l'article / commentaire associé
*/
private final int idReference;
/**
* Liste des fichiers déjà en cours de DL.
*/
private HashSet<String> mesDL = new HashSet<>();
/**
* Constructeur TextView (Smileys, ContenuArticle, commentaires).
*
* @param unContext context de l'application
* @param uneTextView TextView concernée
* @param unContenu Contenu de la textview
* @param unTypeImages type d'images
* @param IDreference ID (unique) de l'élément
*/
public ImageProvider(final Context unContext, final TextView uneTextView, final String unContenu, final int unTypeImages,
final int IDreference) {
monContext = unContext.getApplicationContext();
// Type d'images
monTypeImages = unTypeImages;
// TextView
maView = uneTextView;
// Contenu actuel de la TextView
monContenu = unContenu;
// ID de l'article / commentaire
idReference = IDreference;
}
/**
* Constructeur ImageView (Miniature article)
*
* @param unContext context de l'application
* @param uneImageView ImageView concernée
* @param IDreference ID (unique) de l'élément
*/
public ImageProvider(final Context unContext, final ImageView uneImageView, final int IDreference) {
monContext = unContext.getApplicationContext();
// Type d'images
monTypeImages = Constantes.IMAGE_MINIATURE_ARTICLE;
// ImageView
maView = uneImageView;
// ID de l'article / commentaire
idReference = IDreference;
}
/**
* Fournit une image (URL). Peut être appelé n fois pour un même élément View
*/
@Override
public Drawable getDrawable(final String urlSource) {
// Image de retour
Drawable monRetour;
// Fichier ressources OU existant en cache ?
if (urlSource.startsWith(Constantes.SCHEME_IFRAME_DRAWABLE) || isImageEnCache(urlSource, monContext, monTypeImages)) {
if (urlSource.startsWith(Constantes.SCHEME_IFRAME_DRAWABLE)) {
// Image ressource (drawable)
Integer idDrawable = Integer.valueOf(urlSource.substring(Constantes.SCHEME_IFRAME_DRAWABLE.length()));
// On charge le drawable
monRetour = gestionTaille(ContextCompat.getDrawable(monContext, idDrawable));
// DEBUG
if (Constantes.DEBUG) {
Log.d("ImageProvider", "getDrawable() - Drawable " + urlSource + " fourni");
}
} else {
// Image "standard" en cache
// Path & nom du fichier
String pathFichier = getPathAndFile(urlSource, monContext, monTypeImages);
try {
// Je récupère directement mon image
monRetour = gestionTaille(Drawable.createFromPath(pathFichier));
// DEBUG
if (Constantes.DEBUG) {
Log.d("ImageProvider", "getDrawable() - " + pathFichier + " fourni depuis le cache");
}
} catch (Exception e) {
// Catch pour un OOM potentiel si trop d'images chargées simultanément et RAM < 2 Go
// Chargera image par défaut
monRetour = null;
// DEBUG
if (Constantes.DEBUG) {
Log.e("ImageProvider", "getDrawable() - " + pathFichier + " exception : ", e);
}
}
}
} else {
// Téléchargement des images ?
boolean telechargerImages = true;
int valeurOption = Constantes.getOptionInt(monContext, R.string.idOptionTelechargerImagesv2,
R.string.defautOptionTelechargerImagesv2);
if (valeurOption == 0) {
// Pas de téléchargement des images
telechargerImages = false;
} else if (valeurOption == 1) {
// Téléchargement uniquement en WiFi
ConnectivityManager cm = (ConnectivityManager) monContext.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
// Est-on connecté en WiFi ?
if (activeNetwork == null || activeNetwork.getType() != ConnectivityManager.TYPE_WIFI) {
telechargerImages = false;
}
}
// L'image est-elle déjà en DL (ou à déjà échoué) ? || pas de téléchargement des images
if (mesDL.contains(urlSource) || !telechargerImages) {
// DEBUG
if (Constantes.DEBUG && mesDL.contains(urlSource)) {
Log.d("ImageProvider", "getDrawable() - DL déjà traité || pas de téléchargement des images - " + urlSource);
}
// Retour d'une image générique en ERREUR (logo NXI)
monRetour = gestionTaille(ContextCompat.getDrawable(monContext, R.drawable.smiley_nextinpact_erreur));
} else {
// Sinon on lance le DL !
// DEBUG
if (Constantes.DEBUG) {
Log.i("ImageProvider", "getDrawable() - Demande de téléchargement de : " + urlSource);
}
// Je note le DL de l'image
mesDL.add(urlSource);
// Lancement du DL
telechargerImage(urlSource, monTypeImages, idReference, monContext, this);
// Retour d'une image générique (logo NXI)
monRetour = gestionTaille(ContextCompat.getDrawable(monContext, R.drawable.smiley_nextinpact));
}
}
//#203 - Parfois des images "null"
if (monRetour == null) {
// Debug
if (Constantes.DEBUG) {
Log.e("ImageProvider", "getDrawable - uneImage == null ");
}
// Image par défaut (erreur) dans ce cas là !
monRetour = gestionTaille(ContextCompat.getDrawable(monContext, R.drawable.smiley_nextinpact_erreur));
}
// Je retourne mon image
return monRetour;
}
/**
* Zoome une image.
*
* @param uneImage ressource Image
* @return Drawable redimensionnée si besoin
*/
private Drawable gestionTaille(final Drawable uneImage) {
// Fix #203 : présence d'image "null"
if (uneImage == null) {
// Debug
if (Constantes.DEBUG) {
Log.e("ImageProvider", "gestionTaille - uneImage == null ");
}
return null;
}
// Taile par défaut
int tailleDefaut = Integer.valueOf(monContext.getResources().getString(R.string.defautOptionZoomTexte));
// L'option selectionnée
int tailleUtilisateur = Constantes.getOptionInt(monContext, R.string.idOptionZoomTexte, R.string.defautOptionZoomTexte);
float monCoeffZoom = (float) tailleUtilisateur / tailleDefaut;
DisplayMetrics metrics = new DisplayMetrics();
((WindowManager) monContext.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(metrics);
// DEBUG
if (Constantes.DEBUG) {
Log.d("ImageProvider",
"gestionTaille() - (" + monTypeImages + ") Ecran : largeur = " + metrics.widthPixels + " hauteur = "
+ metrics.heightPixels + " densité = " + metrics.densityDpi);
}
float monCoeff = 1;
// Gestion des images trop larges (30 pixels pris pour les marges d'affichage)
if (uneImage.getIntrinsicWidth() > metrics.widthPixels - Constantes.MARGE_DROITE_IMAGE) {
// Mise à l'échelle de la largeur de l'écran
monCoeff = (float) (metrics.widthPixels - Constantes.MARGE_DROITE_IMAGE) / uneImage.getIntrinsicWidth();
}
// Redimensionnement uniquement pour les smileys
if (monTypeImages == Constantes.IMAGE_SMILEY) {
if (metrics.densityDpi == DisplayMetrics.DENSITY_DEFAULT) {
/**
* Si on est sur la résolution par défaut, on reste à 1
*/
monCoeff = 1 * monCoeffZoom;
} else {
/**
* Sinon, calcul du zoom à appliquer (coeff 2 évite les images trop petites)
*/
monCoeff = 2 * (metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT) * monCoeffZoom;
}
}
// On évite un coeff inférieur à 0 (image non affichée !)
if (Float.compare(monCoeff, 0) <= 0) {
monCoeff = 1;
}
// On définit la taille de l'image
uneImage.setBounds(0, 0, Math.round(uneImage.getIntrinsicWidth() * monCoeff),
Math.round(uneImage.getIntrinsicHeight() * monCoeff));
// DEBUG
if (Constantes.DEBUG) {
Log.d("ImageProvider", "gestionTaille() - coeefZoom = " + monCoeff + " => largeur = " + Math.round(
uneImage.getIntrinsicWidth() * monCoeff) + " hauteur = " + Math.round(
uneImage.getIntrinsicHeight() * monCoeff));
}
return uneImage;
}
/**
* Téléchargement d'une image, enregistrement en cache.
*
* @param URL URL de l'image
* @param type type d'image (cf Constantes)
* @param articleID ID de l'article associé (0 s'il ne faut pas logguer en BDD)
* @param unContext context applicatif
*/
public static void telechargerImage(final String URL, final int type, final int articleID, final Context unContext,
final RefreshDisplayInterface parent) {
/**
* Enregistrement en BDD - cache
*/
// Ouverture d'un lien sur la BDD
DAO monDAO = DAO.getInstance(unContext.getApplicationContext());
// Enregistrement en BDD imageCache
// 0 est un indicateur de DL d'une image déjà présente en BDD, mais pas sur le FS.
if (articleID != 0) {
// Enregistrement pour la gestion du cache
monDAO.cacheEnregistrerImage(articleID, URL, type);
if (Constantes.DEBUG) {
Log.d("ImageProvider",
"telechargerImage() - enregistrement cache pour " + URL + " - " + articleID + " - " + type);
}
}
/**
* Gestion du téléchargement
*/
String pathFichier = getPathAndFile(URL, unContext.getApplicationContext(), type);
// L'image existe-t-elle déjà en cache ?
if (isImageEnCache(URL, unContext, type)) {
// Retour au parent que tout est OK
parent.downloadImageFini(URL);
}
// Si non, lancement du DL
else {
// DEBUG
if (Constantes.DEBUG) {
Log.w("ImageProvider", "telechargerImage() - " + URL + " demande de téléchargement....");
}
// A défaut, on la télécharge, sans retour en UI !
AsyncImageDownloader monAID = new AsyncImageDownloader(unContext, parent, pathFichier, URL);
// Lancement du téléchargement
if (!monAID.run()) {
// Retour au parent de la fin du téléchargement (échoué)
parent.downloadImageFini(URL);
}
}
}
/**
* Path et nom de l'image sur le FS.
*
* @param urlImage URL de l'image
* @param unContext Context
* @param typeImage type d'image
* @return String Path FQ
*/
private static String getPathAndFile(final String urlImage, final Context unContext, final int typeImage) {
// Context
Context monContext = unContext.getApplicationContext();
// Nom du fichier
String nomFichier = Tools.md5(urlImage);
// Path du fichier
String pathFichier = "";
// Détermination du path du fichier
switch (typeImage) {
// Smiley
case Constantes.IMAGE_SMILEY:
pathFichier = monContext.getFilesDir() + Constantes.PATH_IMAGES_SMILEYS;
break;
// Illustration d'un article
case Constantes.IMAGE_CONTENU_ARTICLE:
pathFichier = monContext.getFilesDir() + Constantes.PATH_IMAGES_ILLUSTRATIONS;
break;
// Miniature d'un article
case Constantes.IMAGE_MINIATURE_ARTICLE:
pathFichier = monContext.getFilesDir() + Constantes.PATH_IMAGES_MINIATURES;
break;
// Défaut...
default:
// DEBUG
if (Constantes.DEBUG) {
Log.e("ImageProvider", "getPathAndFile() - cas défaut pour " + urlImage + " - type : " + typeImage);
}
break;
}
return pathFichier + nomFichier;
}
/**
* Teste la présence en cache d'une image.
*
* @param urlImage URL de l'image
* @param unContext Context
* @param typeImage type d'image
* @return boolean Existe ?
*/
private static boolean isImageEnCache(final String urlImage, final Context unContext, final int typeImage) {
// Retour
boolean monRetour = false;
// Path & Nom du fichier
String pathFichier = getPathAndFile(urlImage, unContext, typeImage);
// Le fichier existe-t-il en local ?
File leFichier = new File(pathFichier);
if (leFichier.exists()) {
monRetour = true;
}
// DEBUG
if (Constantes.DEBUG) {
Log.i("ImageProvider", "isImageEnCache() - " + urlImage + " => " + monRetour);
}
return monRetour;
}
@Override
public void downloadHTMLFini(final String uneURL, final ArrayList<? extends Item> mesItems) {
// Rien n'arrive ici...
}
@Override
public void downloadImageFini(final String uneURL) {
// DEBUG
if (Constantes.DEBUG) {
Log.i("ImageProvider", "downloadImageFini() - " + uneURL);
}
// ImageView
if (monTypeImages == Constantes.IMAGE_MINIATURE_ARTICLE) {
ImageView monImageView = (ImageView) maView;
monImageView.setImageDrawable(getDrawable(uneURL));
monImageView.postInvalidate();
}
// TextView
else {
// Vérification que la textview concerne toujours le même article / commentaire (via son ID)
if (maView.getId() == idReference) {
// DEBUG
if (Constantes.DEBUG) {
Log.d("ImageProvider", "downloadImageFini() - ID de la textview identique => MàJ " + uneURL);
}
// J'actualise le texte
Spanned spannedContent = Html.fromHtml(monContenu, this, new TagHandler());
((TextView) maView).setText(spannedContent);
} else {
// DEBUG
if (Constantes.DEBUG) {
Log.d("ImageProvider",
"downloadImageFini() - ID de la textview DIFFERENT " + uneURL + " - " + maView.getId() + " != "
+ idReference);
}
}
}
}
}