/**
* Copyright (C) 2013 Romain Guefveneu.
*
* This file is part of naonedbus.
*
* Naonedbus 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.
*
* Naonedbus 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.naonedbus.card.impl;
import java.io.IOException;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import net.naonedbus.BuildConfig;
import net.naonedbus.R;
import net.naonedbus.activity.impl.HorairesActivity;
import net.naonedbus.activity.impl.WebViewActivity;
import net.naonedbus.bean.Arret;
import net.naonedbus.bean.horaire.Horaire;
import net.naonedbus.card.Card;
import net.naonedbus.fragment.impl.ArretDetailFragment.OnArretChangeListener;
import net.naonedbus.manager.impl.HoraireManager;
import net.naonedbus.utils.FormatUtils;
import net.naonedbus.utils.ViewHelper;
import net.naonedbus.utils.ViewHelper.OnTagFoundHandler;
import org.joda.time.DateMidnight;
import org.joda.time.DateTime;
import org.joda.time.Minutes;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.TableRow;
import android.widget.TextView;
public class HoraireCard extends Card<List<Horaire>> implements OnArretChangeListener {
public static interface OnHoraireClickListener {
void onHoraireClick(Horaire horaire);
}
private static final String LOG_TAG = "HoraireCard";
private static final boolean DBG = BuildConfig.DEBUG;
private final static IntentFilter intentFilter;
static {
intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_TIME_TICK);
intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
intentFilter.addAction(Intent.ACTION_TIME_CHANGED);
}
/** Reçoit les intents de notre intentFilter */
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
restartLoader(null, HoraireCard.this).onContentChanged();
}
};
private Arret mArret;
private final DateFormat mTimeFormat;
private final List<TextView> mHoraireViews;
private final List<TextView> mDelaiViews;
private final TerminusManager mTerminusManager;
private final LayoutInflater mLayoutInflater;
private ViewGroup mTerminusView;
private boolean mHasSchedules;
public HoraireCard(final Context context, final LoaderManager loaderManager, final FragmentManager fragmentManager,
final Arret arret) {
super(context, loaderManager, fragmentManager, R.string.card_horaires_title, R.layout.card_horaire);
mLayoutInflater = LayoutInflater.from(context);
mArret = arret;
mHoraireViews = new ArrayList<TextView>();
mDelaiViews = new ArrayList<TextView>();
mTimeFormat = android.text.format.DateFormat.getTimeFormat(context);
mTerminusManager = new TerminusManager();
}
@Override
public void onResume() {
super.onResume();
getContext().registerReceiver(mIntentReceiver, intentFilter);
if (mDelaiViews.size() > 2) {
restartLoader(null, this);
}
}
@Override
public void onPause() {
super.onPause();
getContext().unregisterReceiver(mIntentReceiver);
}
@Override
protected Intent getMoreIntent() {
final Intent intent;
if (mHasSchedules) {
intent = new Intent(getContext(), HorairesActivity.class);
intent.putExtra(HorairesActivity.PARAM_ARRET, mArret);
intent.putExtra(Intent.EXTRA_TITLE, R.string.card_more_horaires);
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, R.drawable.ic_card_list);
} else {
intent = new Intent(getContext(), WebViewActivity.class);
intent.putExtra(WebViewActivity.PARAM_RAW_ID, R.raw.server_notice);
intent.putExtra(Intent.EXTRA_TITLE, R.string.server_notice_title);
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, R.drawable.ic_card_warning);
}
return intent;
}
@Override
protected void bindView(final Context context, final View base, final View view) {
mTerminusView = (ViewGroup) view.findViewById(R.id.terminus);
mHoraireViews.clear();
mDelaiViews.clear();
ViewHelper.findViewsByTag(view, context.getString(R.string.cardHoraireTag), new OnTagFoundHandler() {
@Override
public void onTagFound(final View v) {
mHoraireViews.add((TextView) v);
setTypefaceRobotoLight((TextView) v);
}
});
ViewHelper.findViewsByTag(view, context.getString(R.string.cardDelaiTag), new OnTagFoundHandler() {
@Override
public void onTagFound(final View v) {
mDelaiViews.add((TextView) v);
}
});
final ViewTreeObserver obs = base.getViewTreeObserver();
obs.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
if (base.getMeasuredWidth() != 0) {
base.getViewTreeObserver().removeOnPreDrawListener(this);
fillView((ViewGroup) base, (ViewGroup) view);
}
return true;
}
});
}
private void fillView(final ViewGroup base, final ViewGroup parent) {
final int viewsCount = getTextViewCount(base);
final TableRow rowTop = (TableRow) parent.findViewById(R.id.rowTop);
final TableRow rowBottom = (TableRow) parent.findViewById(R.id.rowBottom);
for (int i = 0; i < viewsCount; i++) {
int layoutHoraire;
if (i == 0)
layoutHoraire = R.layout.card_horaire_text_first;
else if (i < viewsCount - 1)
layoutHoraire = R.layout.card_horaire_text;
else
layoutHoraire = R.layout.card_horaire_text_last;
final TextView horaireView = (TextView) mLayoutInflater.inflate(layoutHoraire, null);
final TextView delayView = (TextView) mLayoutInflater.inflate(R.layout.card_horaire_delay, null);
setTypefaceRobotoLight(horaireView);
rowTop.addView(horaireView);
rowBottom.addView(delayView);
mHoraireViews.add(horaireView);
mDelaiViews.add(delayView);
}
initLoader(null, this).forceLoad();
}
private int getTextViewCount(final ViewGroup parent) {
final TextView textView = (TextView) mLayoutInflater.inflate(R.layout.card_horaire_text, parent, false);
final DateTime noon = new DateTime().withHourOfDay(12).withMinuteOfHour(00);
final int padding = getContext().getResources().getDimensionPixelSize(R.dimen.padding_medium);
textView.setText(FormatUtils.formatTimeAmPm(getContext(), mTimeFormat.format(noon.toDate())));
final int specY = MeasureSpec.makeMeasureSpec(parent.getHeight(), MeasureSpec.UNSPECIFIED);
final int specX = MeasureSpec.makeMeasureSpec(parent.getWidth(), MeasureSpec.UNSPECIFIED);
textView.measure(specX, specY);
return (parent.getWidth() - padding) / textView.getMeasuredWidth();
}
@Override
public void onArretChange(final Arret newArret) {
mArret = newArret;
showLoader();
restartLoader(null, this);
}
@Override
public Loader<List<Horaire>> onCreateLoader(final int id, final Bundle bundle) {
return new LoaderTask(getContext(), mArret, mHoraireViews.size() + 1);
}
@Override
public void onLoadFinished(final Loader<List<Horaire>> loader, final List<Horaire> horaires) {
if (horaires != null && !horaires.isEmpty()) {
final DateTime now = new DateTime().withSecondOfMinute(0).withMillisOfSecond(0);
int minutes;
int indexView = 0;
int indexHoraire = -1;
int firstNextHoraireIndex = -1;
// Gérer le précédent passage si présent
DateTime date = new DateTime();
for (int i = 0; i < horaires.size(); i++) {
date = horaires.get(i).getHoraire();
if (date.isBefore(now)) {
indexHoraire = i;
} else {
break;
}
}
if (indexHoraire == -1) {
// Pas de précédent
indexView = 1;
indexHoraire = 0;
firstNextHoraireIndex = 0;
mHoraireViews.get(0).setVisibility(View.GONE);
mDelaiViews.get(0).setVisibility(View.GONE);
} else {
// Afficher le précédent
firstNextHoraireIndex = 1;
mHoraireViews.get(0).setVisibility(View.VISIBLE);
mDelaiViews.get(0).setVisibility(View.VISIBLE);
}
while (indexHoraire < horaires.size() && indexView < mHoraireViews.size()) {
String delai = "";
final Horaire horaire = horaires.get(indexHoraire);
final TextView horaireView = mHoraireViews.get(indexView);
CharSequence formattedTime = FormatUtils.formatTimeAmPm(getContext(),
mTimeFormat.format(horaire.getHoraire().toDate()));
if (horaire.getTerminus() != null) {
CharSequence terminusLetter = mTerminusManager.getTerminusLetter(horaire.getTerminus());
terminusLetter = FormatUtils.formatTerminusLetter(getContext(), terminusLetter);
if (indexHoraire > firstNextHoraireIndex) {
formattedTime = TextUtils.concat(formattedTime, "\n", terminusLetter);
} else {
formattedTime = TextUtils.concat(formattedTime, terminusLetter);
}
}
horaireView.setText(formattedTime);
if (indexView > 0) {
minutes = Minutes.minutesBetween(now,
new DateTime(horaire.getHoraire()).withSecondOfMinute(0).withMillisOfSecond(0))
.getMinutes();
if (minutes > 60) {
delai = getString(R.string.msg_depart_heure_short, minutes / 60);
} else if (minutes > 0) {
delai = getString(R.string.msg_depart_min_short, minutes);
} else if (minutes == 0) {
delai = getString(R.string.msg_depart_proche);
}
mDelaiViews.get(indexView).setText(delai);
}
indexHoraire++;
indexView++;
}
// Add terminus information
mTerminusView.removeAllViews();
final Map<String, String> terminus = mTerminusManager.getTerminus();
for (final Entry<String, String> item : terminus.entrySet()) {
final TextView textView = (TextView) mLayoutInflater.inflate(R.layout.card_horaire_terminus,
mTerminusView, false);
textView.setText(item.getValue() + " " + item.getKey());
mTerminusView.addView(textView);
}
mHasSchedules = true;
showContent();
} else {
mHasSchedules = false;
showMessage(R.string.msg_nothing_horaires);
}
}
private class TerminusManager {
private final Map<String, String> mLetterMap;
private char mCurrentLetter = 0x278A;
public TerminusManager() {
mLetterMap = new LinkedHashMap<String, String>();
}
public String getTerminusLetter(final String terminus) {
if (!mLetterMap.containsKey(terminus)) {
mLetterMap.put(terminus, String.valueOf(mCurrentLetter));
mCurrentLetter++;
}
return mLetterMap.get(terminus);
}
public Map<String, String> getTerminus() {
return mLetterMap;
}
}
private static class LoaderTask extends AsyncTaskLoader<List<Horaire>> {
private final HoraireManager mHoraireManager;
private final Arret mArret;
private final int mHorairesCount;
private List<Horaire> mHoraires;
public LoaderTask(final Context context, final Arret arret, final int horairesCount) {
super(context);
mArret = arret;
mHorairesCount = horairesCount;
mHoraireManager = HoraireManager.getInstance();
}
@Override
public List<Horaire> loadInBackground() {
List<Horaire> horaires = null;
try {
horaires = mHoraireManager.getNextSchedules(getContext().getContentResolver(), mArret,
new DateMidnight(), mHorairesCount, 5);
} catch (final IOException e) {
if (DBG)
Log.e(LOG_TAG, "Erreur de chargement des horaires.", e);
}
mHoraires = horaires;
return horaires;
}
/**
* Called when there is new data to deliver to the client. The super
* class will take care of delivering it; the implementation here just
* adds a little more logic.
*/
@Override
public void deliverResult(final List<Horaire> horaires) {
mHoraires = horaires;
if (isStarted()) {
// If the Loader is currently started, we can immediately
// deliver its results.
super.deliverResult(horaires);
}
}
/**
* Handles a request to start the Loader.
*/
@Override
protected void onStartLoading() {
if (mHoraires != null) {
// If we currently have a result available, deliver it
// immediately.
deliverResult(mHoraires);
}
if (takeContentChanged() || mHoraires == null) {
// If the data has changed since the last time it was loaded
// or is not currently available, start a load.
forceLoad();
}
}
}
}