it.tidalwave.bluebill.mobile.android.taxonomy.browser.TaxonomyBrowserController Maven / Gradle / Ivy
The newest version!
/***********************************************************************************************************************
*
* blueBill Mobile - Android - open source birding
* Copyright (C) 2009-2011 by Tidalwave s.a.s. (http://www.tidalwave.it)
*
***********************************************************************************************************************
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
***********************************************************************************************************************
*
* WWW: http://bluebill.tidalwave.it/mobile
* SCM: https://java.net/hg/bluebill-mobile~android-src
*
**********************************************************************************************************************/
package it.tidalwave.bluebill.mobile.android.taxonomy.browser;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.inject.Provider;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import it.tidalwave.util.logging.Logger;
import it.tidalwave.util.NotFoundException;
import it.tidalwave.netbeans.util.Locator;
import it.tidalwave.bluebill.taxonomy.mobile.Taxon;
import it.tidalwave.bluebill.taxonomy.mobile.Taxon.Rank;
import it.tidalwave.bluebill.mobile.preferences.TaxonomyPreferences;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import it.tidalwave.mobile.android.ui.AndroidFlowController;
import it.tidalwave.bluebill.mobile.android.taxonomy.TaxonIntentHelper;
import it.tidalwave.bluebill.mobile.android.taxonomy.TaxonAdapter;
import it.tidalwave.role.Displayable;
import lombok.Getter;
/***********************************************************************************************************************
*
* FIXME: this controller is designed in a different way than others:
* 1. It doesn't implement an interface, since it is re-created multiple times by its view and the only basic operation
* it performs it is the creation (when it loads data) and the exposure of a list adapter and a list listener.
* 2. There's no non-Android defined behaviour.
* 3. It deals with caching sorted data to avoid performance problems (see loadTaxa()). This should be indeed handled
* in a proper Finder.
*
* @stereotype Controller
*
* @author Fabrizio Giudici
* @version $Id$
*
**********************************************************************************************************************/
public class TaxonomyBrowserController
{
private static final String CLASS = TaxonomyBrowserController.class.getName();
private static final Logger logger = Logger.getLogger(CLASS);
public static final int PICK = 1;
private static String cachedTaxonomyName = "";
private static List cachedSortedTaxa;
private static Locale cachedSortLocale;
@Nonnull
private final Provider taxonomyPreferences = Locator.createProviderFor(TaxonomyPreferences.class);
@Nonnull
private final Provider sharedPreferences = Locator.createProviderFor(SharedPreferences.class);
@Nonnull
private final Rank rank;
private final List taxa = new ArrayList();
private Locale latestSortLocale;
@Nonnull @Getter
private final TaxonAdapter taxonBrowserAdapter;
private AndroidFlowController controlFlow;
private boolean cacheEnabled;
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
@Getter
private final OnItemClickListener taxonBrowserListener = new OnItemClickListener()
{
@Override
public void onItemClick (final @Nonnull AdapterView> a,
final @Nonnull View view,
final @Nonnegative int position,
final @Nonnegative long id)
{
final Taxon taxon = (Taxon)taxonBrowserAdapter.getItem(position);
try
{
final Intent intent = TaxonIntentHelper.intentFor(taxon);
if (rank.equals(Rank.SPECIES)) // final one
{
controlFlow.toNextStep(intent);
}
else
{
controlFlow.toNextStep(intent, AndroidFlowController.USE_INTENT_FILTER);
}
}
catch (NotFoundException e)
{
throw new RuntimeException(e);
}
}
};
/*******************************************************************************************************************
*
* Recomputes the data whenever a preference have been changed. This is important because changing taxonomy or
* locale needs to re-sort data.
*
******************************************************************************************************************/
private final OnSharedPreferenceChangeListener preferenceChangeListener = new OnSharedPreferenceChangeListener()
{
public void onSharedPreferenceChanged (final @Nonnull SharedPreferences sp, final @Nonnull String string)
{
latestSortLocale = null; // mark "dirty"
clearCache();
}
};
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
public TaxonomyBrowserController (final @Nonnull Activity activity,
final @Nonnull Rank rank,
final @Nonnull Intent intent)
{
this.rank = rank;
loadTaxa(intent);
taxonBrowserAdapter = new TaxonAdapter(activity.getBaseContext(), this.taxa);
sharedPreferences.get().registerOnSharedPreferenceChangeListener(preferenceChangeListener);
controlFlow = AndroidFlowController.forActivity(activity);
}
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
public void dispose()
{
sharedPreferences.get().unregisterOnSharedPreferenceChangeListener(preferenceChangeListener);
}
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
public boolean needsResorting()
{
return (latestSortLocale == null) || !latestSortLocale.equals(taxonomyPreferences.get().getTaxonomyLocales().get(0));
}
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
public void ensureTaxaAreSorted()
{
if (needsResorting())
{
final String taxonomyName = taxonomyPreferences.get().getTaxonomy().as(Displayable.class).getDisplayName();
latestSortLocale = taxonomyPreferences.get().getTaxonomyLocales().get(0);
if (cacheEnabled
&& (cachedSortedTaxa != null)
&& taxonomyName.equals(cachedTaxonomyName)
&& latestSortLocale.equals(cachedSortLocale))
{
logger.info("not sorting taxa, using cached value");
taxa.clear();
taxa.addAll(cachedSortedTaxa);
}
else
{
logger.info("starting sort %d taxa...", taxa.size());
final long time = System.currentTimeMillis();
Collections.sort(taxa, taxonomyPreferences.get().getTaxonComparator());
logger.info(">>>> %d taxa sorted in %d msec", taxa.size(), System.currentTimeMillis() - time);
if (cacheEnabled)
{
cachedSortedTaxa = new ArrayList(taxa);
cachedTaxonomyName = taxonomyName;
cachedSortLocale = latestSortLocale;
}
}
}
}
/*******************************************************************************************************************
*
* Loads the taxa. If a {@link Taxon} is contained in the {@link Intent}, the sub taxa will be loaded and the cache
* disabled since there are no taxa with a number of sub taxa so large to create performance problems while sorting.
* If no {@code Taxon} is contained in the {@code Intent}, all the taxa with the {@link Rank} bound to this
* controller will be loaded; and the cache enabled if the rank is {@code SPECIES} since there can be so many
* items to create performance problems when sorting. The latter behaviour also happens if the {@code Intent}
* contains a {@code Taxon}, but it's not from the current taxonomy.
*
* TODO: This management of cache is cumbersome. Using Finders with sorting capabilities could be a solution,
* moving all the caching logics to the Finder themselves (and eventually could opaquely implement sorting
* based on OpenSesame query that could be fast enough to avoid caching).
*
******************************************************************************************************************/
@Nonnull
private void loadTaxa (final @Nonnull Intent intent)
{
taxa.clear();
cacheEnabled = false;
try
{
taxa.addAll(TaxonIntentHelper.getTaxon(intent).findSubTaxa().results());
}
catch (NotFoundException e) // no Taxon in the Intent
{
taxa.addAll(taxonomyPreferences.get().getTaxonomy().findTaxa().withRank(rank).results());
cacheEnabled = rank == Rank.SPECIES;
}
ensureTaxaAreSorted();
}
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
private static void clearCache()
{
logger.info("clearCache()");
cachedSortedTaxa = null;
cachedSortLocale = null;
}
}