/*
* 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;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.content.ContextCompat;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v4.widget.SwipeRefreshLayout.OnRefreshListener;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.pcinpact.adapters.ItemsAdapter;
import com.pcinpact.datastorage.CacheManager;
import com.pcinpact.datastorage.DAO;
import com.pcinpact.datastorage.ImageProvider;
import com.pcinpact.items.ArticleItem;
import com.pcinpact.items.Item;
import com.pcinpact.items.SectionItem;
import com.pcinpact.network.AsyncHTMLDownloader;
import com.pcinpact.network.RefreshDisplayInterface;
import com.pcinpact.utils.Constantes;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
/**
* Liste des articles.
*
* @author Anael
*/
public class ListeArticlesActivity extends AppCompatActivity implements RefreshDisplayInterface, OnItemClickListener {
/**
* Les articles.
*/
private ArrayList<ArticleItem> mesArticles = new ArrayList<>();
/**
* ItemAdapter.
*/
private ItemsAdapter monItemsAdapter;
/**
* BDD.
*/
private DAO monDAO;
/**
* Nombre de DL en cours.
*/
private int[] dlInProgress;
/**
* Menu.
*/
private Menu monMenu;
/**
* ListView.
*/
private ListView monListView;
/**
* SwipeRefreshLayout.
*/
private SwipeRefreshLayout monSwipeRefreshLayout;
/**
* TextView "Dernière synchro...".
*/
private TextView headerTextView;
/**
* Listener pour le changement de taille des textes.
*/
private SharedPreferences.OnSharedPreferenceChangeListener listenerOptions;
/**
* Une mise à jour du thème est-elle à effectuer ?
*/
private boolean updateTheme = false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Gestion du thème sombre (option utilisateur)
Boolean isThemeSombre = Constantes.getOptionBoolean(getApplicationContext(), R.string.idOptionThemeSombre,
R.bool.defautOptionThemeSombre);
if (isThemeSombre) {
// Si actif, on applique le style
setTheme(R.style.NextInpactThemeFonce);
}
// On définit la vue
setContentView(R.layout.activity_liste_articles);
// On récupère les éléments GUI
monListView = (ListView) findViewById(R.id.listeArticles);
monSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_container);
headerTextView = (TextView) findViewById(R.id.header_text);
// Initialisation de l'array de supervision des téléchargements
dlInProgress = new int[5];
dlInProgress[Constantes.IMAGE_MINIATURE_ARTICLE] = 0;
dlInProgress[Constantes.HTML_LISTE_ARTICLES] = 0;
dlInProgress[Constantes.HTML_ARTICLE] = 0;
// Mise en place de l'itemAdapter
monItemsAdapter = new ItemsAdapter(getApplicationContext(), getLayoutInflater(), mesArticles);
monListView.setAdapter(monItemsAdapter);
monListView.setOnItemClickListener(this);
// onRefresh
monSwipeRefreshLayout.setOnRefreshListener(new OnRefreshListener() {
@Override
public void onRefresh() {
telechargeListeArticles();
}
});
// On active le SwipeRefreshLayout ssi on est en haut de la listview
monListView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
int topRowVerticalPosition;
if (monListView == null || monListView.getChildCount() == 0) {
topRowVerticalPosition = 0;
} else {
topRowVerticalPosition = monListView.getFirstVisiblePosition();
}
// DEBUG
if (Constantes.DEBUG) {
Log.d("ListeArticlesActivity",
"onScroll() - SwipeRefreshLayout - topRowVerticalPosition : " + topRowVerticalPosition);
}
monSwipeRefreshLayout.setEnabled(topRowVerticalPosition <= 0);
}
});
// J'active la BDD
monDAO = DAO.getInstance(getApplicationContext());
// Chargement des articles & MàJ de l'affichage
monItemsAdapter.updateListeItems(prepareAffichage());
// Migration de préférences
int valeurDefaut = Integer.parseInt(getString(R.string.defautOptionTelechargerImagesv2Test));
if (Constantes.getOptionInt(getApplicationContext(), R.string.idOptionTelechargerImagesv2,
R.string.defautOptionTelechargerImagesv2Test) == valeurDefaut) {
// Si pas de valeur cohérente pour l'option de téléchargement des images
// Ancienne valeur
boolean valeurOld = Constantes.getOptionBoolean(getApplicationContext(), R.string.idOptionTelechargerImages,
R.bool.defautOptionTelechargerImages);
// Nouvelle valeur (actif tout le temps par défaut)
String valeurNew = getString(R.string.defautOptionTelechargerImagesv2);
if (!valeurOld) {
// Pas de téléchargement automatique des images
valeurNew = "0";
}
Constantes.setOptionInt(getApplicationContext(), R.string.idOptionTelechargerImagesv2, valeurNew);
}
// Gestion du changement d'options de l'application
listenerOptions = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
// Taille des textes
if (key.equals(getResources().getString(R.string.idOptionZoomTexte))) {
// Rafraichissement de l'affichage
monItemsAdapter.notifyDataSetChanged();
// DEBUG
if (Constantes.DEBUG) {
Log.w("ListeArticlesActivity",
"onCreate() - changement taille des textes => " + Constantes.getOptionInt(getApplicationContext(),
R.string.idOptionZoomTexte,
R.string.defautOptionZoomTexte));
}
}
// Menu debug
else if (key.equals(getResources().getString(R.string.idOptionDebug))) {
// invalidation du menu
supportInvalidateOptionsMenu();
// DEBUG
if (Constantes.DEBUG) {
Log.w("ListeArticlesActivity",
"onCreate() - changement option debug => " + Constantes.getOptionBoolean(getApplicationContext(),
R.string.idOptionDebug,
R.bool.defautOptionDebug));
}
}
// Debug - Effacement du cache
else if (key.equals(getResources().getString(R.string.idOptionDebugEffacerCache))) {
// Je vide ma liste d'articles...
nouveauChargementGUI(Constantes.HTML_LISTE_ARTICLES);
mesArticles.clear();
// Lancement du refresh de l'affichage
finChargementGUI(Constantes.HTML_LISTE_ARTICLES);
}
// Changement de thème
else if (key.equals(getResources().getString(R.string.idOptionThemeSombre))) {
// Note du changement de thème
updateTheme = true;
}
}
};
// Attachement du superviseur aux préférences
PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).registerOnSharedPreferenceChangeListener(
listenerOptions);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Je garde le menu pour pouvoir l'animer après
monMenu = menu;
super.onCreateOptionsMenu(monMenu);
// Je charge mon menu dans l'actionBar
MenuInflater inflater = getMenuInflater();
// Suis-je en mode DEBUG ?
Boolean modeDebug = Constantes.getOptionBoolean(getApplicationContext(), R.string.idOptionDebug,
R.bool.defautOptionDebug);
// DEBUG
if (Constantes.DEBUG) {
Log.i("ListeArticlesActivity", "onCreateOptionsMenu() - modeDebug => " + modeDebug);
}
// Chargement du fichier XML
if (modeDebug) {
// Mode DEBUG
inflater.inflate(R.menu.activity_liste_articles_debug_actions, monMenu);
} else {
// Mode standard
inflater.inflate(R.menu.activity_liste_articles_actions, monMenu);
}
// Est-ce la premiere utilisation de l'application ? [après création du menu]
Boolean premiereUtilisation = Constantes.getOptionBoolean(getApplicationContext(),
R.string.idOptionInstallationApplication,
R.bool.defautOptionInstallationApplication);
// Si première utilisation : on affiche un disclaimer
if (premiereUtilisation) {
// Effacement du cache de l'application v < 1.8.0
CacheManager.effacerCacheV180(getApplicationContext());
// Lancement d'un téléchargement des articles
telechargeListeArticles();
AlertDialog.Builder builder = new AlertDialog.Builder(this);
// Titre
builder.setTitle(getResources().getString(R.string.app_name));
// Contenu
builder.setMessage(getResources().getString(R.string.disclaimerContent));
// Bouton d'action
builder.setCancelable(false);
builder.setPositiveButton("Ok", null);
// On crée & affiche
builder.create().show();
// Enregistrement de l'affichage
Constantes.setOptionBoolean(getApplicationContext(), R.string.idOptionInstallationApplication, false);
}
return true;
}
/**
* Gestion du clic sur un article => l'ouvrir + marquer comme lu.
*/
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// récupère l'article en question
ArticleItem monArticle = (ArticleItem) monItemsAdapter.getItem(position);
// Marquer l'article comme lu en BDD
monDAO.marquerArticleLu(monArticle.getId());
// Lance l'ouverture de l'article
Intent monIntent = new Intent(getApplicationContext(), ArticleActivity.class);
monIntent.putExtra("ARTICLE_ID", monArticle.getId());
startActivity(monIntent);
}
@Override
protected void onRestart() {
// DEBUG
if (Constantes.DEBUG) {
Log.d("ListeArticlesActivity", "onRestart()");
}
// Changement du thème
if (updateTheme) {
// DEBUG
if (Constantes.DEBUG) {
Log.w("ListeArticlesActivity", "onRestart() - changement du thème");
}
// Mise à jour du thème utilisé
Boolean isThemeSombre = Constantes.getOptionBoolean(getApplicationContext(), R.string.idOptionThemeSombre,
R.bool.defautOptionThemeSombre);
if (isThemeSombre) {
setTheme(R.style.NextInpactThemeFonce);
} else {
setTheme(R.style.NextInpactTheme);
}
// invalidation du cache des view
monItemsAdapter.setResetView();
// C'est fini
updateTheme = false;
}
// Je met à jour les données qui sont potentiellement fausses suite à slide
monItemsAdapter.updateListeItems(prepareAffichage());
// Je notifie le changement pour un rafraichissement du contenu
monItemsAdapter.notifyDataSetChanged();
super.onRestart();
}
/**
* Ouverture du menu de l'actionbar à l'utilisation du bouton menu.
*/
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
// Bouton menu
if (keyCode == KeyEvent.KEYCODE_MENU) {
if (monMenu != null) {
monMenu.performIdentifierAction(R.id.action_overflow, 0);
} else {
// DEBUG
if (Constantes.DEBUG) {
Log.e("ListeArticlesActivity", "onKeyUp() - monMenu est null !");
}
}
}
return super.onKeyUp(keyCode, event);
}
/**
* Gestion des clic dans le menu d'options de l'activité.
*/
@Override
public boolean onOptionsItemSelected(final MenuItem pItem) {
switch (pItem.getItemId()) {
// Rafraichir la liste des articles
case R.id.action_refresh:
telechargeListeArticles();
break;
// Menu Options
case R.id.action_settings:
// Je lance l'activité options
Intent intentOptions = new Intent(getApplicationContext(), OptionsActivity.class);
startActivity(intentOptions);
break;
// A propos
case R.id.action_about:
Intent intentAbout = new Intent(getApplicationContext(), AboutActivity.class);
startActivity(intentAbout);
break;
// Debug
case R.id.action_debug:
Intent intentDebug = new Intent(getApplicationContext(), DebugActivity.class);
startActivity(intentDebug);
break;
// Support
case R.id.action_support:
// Envoi...
Intent intent = new Intent(Intent.ACTION_SENDTO);
// Mode texte
intent.setType("text/plain");
// Sujet du mail
intent.putExtra(Intent.EXTRA_SUBJECT, Constantes.getUserAgent(getApplicationContext()));
// Corps du mail
intent.putExtra(Intent.EXTRA_TEXT, getResources().getString(R.string.supportMessage));
// A qui...
intent.setData(Uri.parse("mailto:" + Constantes.MAIL_DEVELOPPEUR));
// Si touche retour : revient a l'application et pas aux mails
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
// Affichage du numéro de version
Toast monToast = Toast.makeText(getApplicationContext(), getString(R.string.erreurEnvoiMail),
Toast.LENGTH_LONG);
monToast.show();
// DEBUG
if (Constantes.DEBUG) {
Log.e("ListeArticlesActivity", "onOptionsItemSelected() - Support -> exception", e);
}
}
break;
default:
// DEBUG
if (Constantes.DEBUG) {
Log.e("ListeArticlesActivity", "onOptionsItemSelected() - cas default ! : " + pItem.getItemId());
// Peut-être clic sur menu hamburger
}
break;
}
return true;
}
/**
* Arrêt de l'activité.
*/
@Override
protected void onDestroy() {
try {
// Détachement du listener pour la taille des textes
PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).unregisterOnSharedPreferenceChangeListener(
listenerOptions);
} catch (Exception e) {
// DEBUG
if (Constantes.DEBUG) {
Log.e("ListeArticlesActivity", "onDestroy()", e);
}
}
// Nettoyage du cache de l'application
CacheManager.nettoyerCache(getApplicationContext());
super.onDestroy();
}
/**
* Lance le téléchargement de la liste des articles.
*/
private void telechargeListeArticles() {
// DEBUG
if (Constantes.DEBUG) {
Log.i("ListeArticlesActivity", "telechargeListeArticles()");
}
// Uniquement si on est pas déjà en train de faire un refresh...
if (dlInProgress[Constantes.HTML_LISTE_ARTICLES] == 0) {
// GUI : activité en cours...
nouveauChargementGUI(Constantes.HTML_LISTE_ARTICLES);
/**
* Nettoyage du cache
*/
CacheManager.nettoyerCache(getApplicationContext());
/**
* Téléchargement des articles dont le contenu n'avait pas été téléchargé
*/
telechargeArticles(monDAO.chargerArticlesATelecharger());
/**
* Téléchargement des pages de liste d'articles
*/
int nbArticles = Constantes.getOptionInt(getApplicationContext(), R.string.idOptionNbArticles,
R.string.defautOptionNbArticles);
int nbPages = nbArticles / Constantes.NB_ARTICLES_PAR_PAGE;
// téléchargement de chaque page...
for (int numPage = 1; numPage <= nbPages; numPage++) {
// Ma tâche de DL
AsyncHTMLDownloader monAHD = new AsyncHTMLDownloader(this, Constantes.HTML_LISTE_ARTICLES,
Constantes.NEXT_INPACT_URL_NUM_PAGE + numPage, monDAO,
getApplicationContext());
// Lancement du téléchargement
if (monAHD.run()) {
// MàJ animation
nouveauChargementGUI(Constantes.HTML_LISTE_ARTICLES);
}
}
/**
* Téléchargement des miniatures manquantes
*/
// Miniatures manquantes
ArrayList<String> miniaturesManquantes = CacheManager.getMiniaturesATelecharger(getApplicationContext());
// Pour chacune...
for (String imageURL : miniaturesManquantes) {
// Je lance son DL (sans log en BDD imageCache)
ImageProvider.telechargerImage(imageURL, Constantes.IMAGE_MINIATURE_ARTICLE, 0, getApplicationContext(), this);
// Je note la dde de DL
nouveauChargementGUI(Constantes.IMAGE_MINIATURE_ARTICLE);
}
}
// GUI : fin de l'activité en cours...
finChargementGUI(Constantes.HTML_LISTE_ARTICLES);
}
/**
* Lance le téléchargement des articles.
*
* @param desItems liste d'articles à télécharger
*/
private void telechargeArticles(final ArrayList<? extends Item> desItems) {
for (Item unItem : desItems) {
ArticleItem monItem = (ArticleItem) unItem;
// Tâche de DL HTML
AsyncHTMLDownloader monAHD;
// DL de l'image d'illustration ?
boolean dlIllustration = true;
// Est-ce un article abonné ?
if (((ArticleItem) unItem).isAbonne()) {
boolean isConnecteRequis = false;
// Ai-je déjà la version publique de l'article ?
if (!((ArticleItem) unItem).getContenu().equals("")) {
// Je requiert d'être connecté (sinon le DL ne sert à rien)
isConnecteRequis = true;
// Je ne veux pas DL l'image de l'article
dlIllustration = false;
}
// téléchargement de la ressource
monAHD = new AsyncHTMLDownloader(this, Constantes.HTML_ARTICLE, monItem.getUrl(), monDAO, getApplicationContext(),
isConnecteRequis);
} else {
// téléchargement de la ressource
monAHD = new AsyncHTMLDownloader(this, Constantes.HTML_ARTICLE, monItem.getUrl(), monDAO,
getApplicationContext());
}
// Lancement du téléchargement
if (monAHD.run()) {
// MàJ animation
nouveauChargementGUI(Constantes.HTML_ARTICLE);
}
// DL des miniatures des articles dont je récupère le contenu (sauf articles abonnés / contenu)
if (dlIllustration) {
// Je lance le téléchargement de sa miniature
ImageProvider.telechargerImage(monItem.getUrlIllustration(), Constantes.IMAGE_MINIATURE_ARTICLE, monItem.getId(),
getApplicationContext(), this);
nouveauChargementGUI(Constantes.IMAGE_MINIATURE_ARTICLE);
}
}
}
@Override
public void downloadHTMLFini(final String uneURL, final ArrayList<? extends Item> desItems) {
// Si c'est un refresh général
if (uneURL.startsWith(Constantes.NEXT_INPACT_URL_NUM_PAGE)) {
// Le asyncDL ne me retourne que des articles non présents en BDD => à DL
telechargeArticles(desItems);
// gestion du téléchargement GUI
finChargementGUI(Constantes.HTML_LISTE_ARTICLES);
} else {
// gestion du téléchargement GUI
finChargementGUI(Constantes.HTML_ARTICLE);
}
}
@Override
public void downloadImageFini(final String uneURL) {
// gestion du téléchargement GUI
finChargementGUI(Constantes.IMAGE_MINIATURE_ARTICLE);
}
/**
* Fournit une liste d'articles triés par date + sections.
*
* @return Liste d'articles
*/
private ArrayList<Item> prepareAffichage() {
ArrayList<Item> monRetour = new ArrayList<>();
String jourActuel = "";
// Nombre d'articles à afficher
int maLimite = Constantes.getOptionInt(getApplicationContext(), R.string.idOptionNbArticles,
R.string.defautOptionNbArticles);
// Chargement des articles depuis la BDD (trié, limité)
mesArticles = monDAO.chargerArticlesTriParDate(maLimite);
for (ArticleItem article : mesArticles) {
// Si ce n'est pas la même journée que l'article précédent
if (!article.getDatePublication().equals(jourActuel)) {
// Je met à jour ma date
jourActuel = article.getDatePublication();
// J'ajoute un sectionItem
monRetour.add(new SectionItem(jourActuel));
}
// J'ajoute mon article
monRetour.add(article);
}
// MàJ de la date de dernier refresh
long dernierRefresh = monDAO.chargerDateRefresh(Constantes.DB_REFRESH_ID_LISTE_ARTICLES);
if (dernierRefresh == 0) {
// Jamais synchro...
headerTextView.setText(getString(R.string.lastUpdateNever));
} else {
String monTexte = getString(R.string.lastUpdate) + new SimpleDateFormat(Constantes.FORMAT_DATE_DERNIER_REFRESH,
Constantes.LOCALE).format(dernierRefresh);
// Une MàJ à déjà été faite
headerTextView.setText(monTexte);
}
return monRetour;
}
/**
* Gère les animations de téléchargement.
*/
private void nouveauChargementGUI(int typeDL) {
// Si c'est le premier => activation des gri-gri GUI
if (dlInProgress[Constantes.HTML_LISTE_ARTICLES] + dlInProgress[Constantes.HTML_ARTICLE]
+ dlInProgress[Constantes.IMAGE_MINIATURE_ARTICLE] == 0) {
// DEBUG
if (Constantes.DEBUG) {
Log.w("ListeArticlesActivity", "nouveauChargementGUI() - Lancement animation");
}
// Couleurs du RefreshLayout
monSwipeRefreshLayout.setColorSchemeColors(ContextCompat.getColor(getApplicationContext(), R.color.refreshBleu),
ContextCompat.getColor(getApplicationContext(), R.color.refreshOrange));
// Animation du RefreshLayout
monSwipeRefreshLayout.setRefreshing(true);
// Grisage de l'icône d'action
MenuItem monItem = monMenu.findItem(R.id.action_refresh);
monItem.getIcon().setAlpha(130);
monItem.setEnabled(false);
}
// Je note le téléchargement en cours
dlInProgress[typeDL]++;
// DEBUG
if (Constantes.DEBUG) {
Log.i("ListeArticlesActivity", "nouveauChargementGUI() - " + Arrays.toString(dlInProgress));
}
}
/**
* Gère les animations de téléchargement.
*/
private void finChargementGUI(int typeDL) {
// Je note la fin du téléchargement
dlInProgress[typeDL]--;
// Si la liste d'articles et de miniatures est chargée (et qu'on ne vient pas de finir de télécharger un article...)
if (dlInProgress[Constantes.HTML_LISTE_ARTICLES] + dlInProgress[Constantes.IMAGE_MINIATURE_ARTICLE] == 0
&& typeDL != Constantes.HTML_ARTICLE) {
// DEBUG
if (Constantes.DEBUG) {
Log.w("ListeArticlesActivity", "finChargementGUI() - Rafraichissement liste articles");
}
// Je met à jour les données
monItemsAdapter.updateListeItems(prepareAffichage());
// Je notifie le changement pour un rafraichissement du contenu
monItemsAdapter.notifyDataSetChanged();
}
// Si toutes les données sont téléchargées...
if (dlInProgress[Constantes.HTML_LISTE_ARTICLES] + dlInProgress[Constantes.IMAGE_MINIATURE_ARTICLE] +
dlInProgress[Constantes.HTML_ARTICLE] == 0) {
// DEBUG
if (Constantes.DEBUG) {
Log.w("ListeArticlesActivity", "finChargementGUI() - Arrêt animation");
}
// On stoppe l'animation du SwipeRefreshLayout
monSwipeRefreshLayout.setRefreshing(false);
// Dégrisage de l'icône
MenuItem monItem = monMenu.findItem(R.id.action_refresh);
monItem.getIcon().setAlpha(255);
monItem.setEnabled(true);
}
// DEBUG
if (Constantes.DEBUG) {
Log.i("ListeArticlesActivity", "finChargementGUI() - " + Arrays.toString(dlInProgress));
}
}
}