package org.wordpress.android.ui.stats; import android.app.Activity; import android.net.http.SslError; import android.os.Build; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.webkit.SslErrorHandler; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.ArrayAdapter; import android.widget.LinearLayout; import org.wordpress.android.R; import org.wordpress.android.ui.stats.models.GeoviewModel; import org.wordpress.android.ui.stats.models.GeoviewsModel; import org.wordpress.android.ui.stats.service.StatsService; import org.wordpress.android.util.AppLog; import org.wordpress.android.util.DisplayUtils; import org.wordpress.android.util.FormatUtils; import org.wordpress.android.util.GravatarUtils; import org.wordpress.android.widgets.WPNetworkImageView; import java.util.List; public class StatsGeoviewsFragment extends StatsAbstractListFragment { public static final String TAG = StatsGeoviewsFragment.class.getSimpleName(); private GeoviewsModel mCountries; @Override protected boolean hasDataAvailable() { return mCountries != null; } @Override protected void saveStatsData(Bundle outState) { if (hasDataAvailable()) { outState.putSerializable(ARG_REST_RESPONSE, mCountries); } } @Override protected void restoreStatsData(Bundle savedInstanceState) { if (savedInstanceState.containsKey(ARG_REST_RESPONSE)) { mCountries = (GeoviewsModel) savedInstanceState.getSerializable(ARG_REST_RESPONSE); } } @SuppressWarnings("unused") public void onEventMainThread(StatsEvents.CountriesUpdated event) { if (!shouldUpdateFragmentOnUpdateEvent(event)) { return; } mCountries = event.mCountries; updateUI(); } @SuppressWarnings("unused") public void onEventMainThread(StatsEvents.SectionUpdateError event) { if (!shouldUpdateFragmentOnErrorEvent(event)) { return; } mCountries = null; showErrorUI(event.mError); } private void hideMap() { if (!isAdded()) { return; } mTopPagerContainer.setVisibility(View.GONE); } private void showMap(final List<GeoviewModel> countries) { if (!isAdded()) { return; } // setting up different margins for the map. We're basically remove left margins since the // chart service produce a map that's slightly shifted on the right. See the Web version. int dp4 = DisplayUtils.dpToPx(mTopPagerContainer.getContext(), 4); LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); layoutParams.setMargins(0, 0, dp4, 0); mTopPagerContainer.setLayoutParams(layoutParams); mTopPagerContainer.removeAllViews(); // must wait for mTopPagerContainer to be fully laid out (ie: measured). Then we can read the width and // calculate the right height for the map div mTopPagerContainer.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { mTopPagerContainer.getViewTreeObserver().removeGlobalOnLayoutListener(this); if (!isAdded()) { return; } StringBuilder dataToLoad = new StringBuilder(); for (int i = 0; i < countries.size(); i++) { final GeoviewModel currentCountry = countries.get(i); dataToLoad.append("['").append(currentCountry.getCountryFullName()).append("',") .append(currentCountry.getViews()).append("],"); } // This is the label that is shown when the user taps on a region String label = getResources().getString(getTotalsLabelResId()); // See: https://developers.google.com/chart/interactive/docs/gallery/geochart // Loading the v42 of the Google Charts API, since the latest stable version has a problem with the legend. https://github.com/wordpress-mobile/WordPress-Android/issues/4131 // https://developers.google.com/chart/interactive/docs/release_notes#release-candidate-details String htmlPage = "<html>" + "<head>" + "<script type=\"text/javascript\" src=\"https://www.gstatic.com/charts/loader.js\"></script>" + "<script type=\"text/javascript\" src=\"https://www.google.com/jsapi\"></script>" + "<script type=\"text/javascript\">" + "google.charts.load('42', {'packages':['geochart']});" + "google.charts.setOnLoadCallback(drawRegionsMap);" + "function drawRegionsMap() {" + "var data = google.visualization.arrayToDataTable(" + "[" + "['Country', '" + label + "']," + dataToLoad + "]);" + "var options = {keepAspectRatio: true, region: 'world', colorAxis: { colors: [ '#FFF088', '#F34605' ] }, enableRegionInteractivity: true};" + "var chart = new google.visualization.GeoChart(document.getElementById('regions_div'));" + "chart.draw(data, options);" + "}" + "</script>" + "</head>" + "<body>" + "<div id=\"regions_div\" style=\"width: 100%; height: 100%;\"></div>" + "</body>" + "</html>"; WebView webView = new WebView(getActivity()); mTopPagerContainer.addView(webView); int width = mTopPagerContainer.getWidth(); int height = width * 3 / 4; LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) webView.getLayoutParams(); params.width = WebView.LayoutParams.MATCH_PARENT; params.height = height; webView.setLayoutParams(params); webView.setWebViewClient(new MyWebViewClient()); // Hide map in case of unrecoverable errors webView.getSettings().setJavaScriptEnabled(true); webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE); webView.loadData(htmlPage, "text/html", "UTF-8"); } }); mTopPagerContainer.setVisibility(View.VISIBLE); } // Hide the Map in case of errors private class MyWebViewClient extends WebViewClient { @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { super.onReceivedError(view, errorCode, description, failingUrl); mTopPagerContainer.setVisibility(View.GONE); AppLog.e(AppLog.T.STATS, "Cannot load geochart." + " ErrorCode: " + errorCode + " Description: " + description + " Failing URL: " + failingUrl); } @Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { super.onReceivedSslError(view, handler, error); mTopPagerContainer.setVisibility(View.GONE); AppLog.e(AppLog.T.STATS, "Cannot load geochart. SSL ERROR. " + error.toString()); } } @Override protected void updateUI() { if (!isAdded()) { return; } if (hasCountries()) { List<GeoviewModel> countries = getCountries(); ArrayAdapter adapter = new GeoviewsAdapter(getActivity(), countries); StatsUIHelper.reloadLinearLayout(getActivity(), adapter, mList, getMaxNumberOfItemsToShowInList()); showHideNoResultsUI(false); showMap(countries); } else { showHideNoResultsUI(true); hideMap(); } } private boolean hasCountries() { return mCountries != null && mCountries.getCountries() != null; } private List<GeoviewModel> getCountries() { if (!hasCountries()) { return null; } return mCountries.getCountries(); } @Override protected boolean isViewAllOptionAvailable() { return (hasCountries() && mCountries.getCountries().size() > MAX_NUM_OF_ITEMS_DISPLAYED_IN_LIST); } @Override protected boolean isExpandableList() { return false; } private class GeoviewsAdapter extends ArrayAdapter<GeoviewModel> { private final List<GeoviewModel> list; private final Activity context; private final LayoutInflater inflater; public GeoviewsAdapter(Activity context, List<GeoviewModel> list) { super(context, R.layout.stats_list_cell, list); this.context = context; this.list = list; inflater = LayoutInflater.from(context); } @Override public View getView(int position, View convertView, ViewGroup parent) { View rowView = convertView; // reuse views if (rowView == null) { rowView = inflater.inflate(R.layout.stats_list_cell, parent, false); // configure view holder StatsViewHolder viewHolder = new StatsViewHolder(rowView); rowView.setTag(viewHolder); } final GeoviewModel currentRowData = list.get(position); StatsViewHolder holder = (StatsViewHolder) rowView.getTag(); // fill data String entry = currentRowData.getCountryFullName(); String imageUrl = currentRowData.getFlatFlagIconURL(); holder.totalsTextView.setText(FormatUtils.formatDecimal(currentRowData.getViews())); holder.setEntryText(entry); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // On Android >= 5.0, use the emoji flag holder.alternativeImage.setText(currentRowData.getFlagEmoji()); holder.alternativeImage.setVisibility(View.VISIBLE); } else { // On other Android versions, use the Gravatar image holder.networkImageView.setImageUrl( GravatarUtils.fixGravatarUrl(imageUrl, mResourceVars.headerAvatarSizePx), WPNetworkImageView.ImageType.BLAVATAR); holder.networkImageView.setVisibility(View.VISIBLE); } return rowView; } } @Override protected int getEntryLabelResId() { return R.string.stats_entry_country; } @Override protected int getTotalsLabelResId() { return R.string.stats_totals_views; } @Override protected int getEmptyLabelTitleResId() { return R.string.stats_empty_geoviews; } @Override protected int getEmptyLabelDescResId() { return R.string.stats_empty_geoviews_desc; } @Override protected StatsService.StatsEndpointsEnum[] sectionsToUpdate() { return new StatsService.StatsEndpointsEnum[]{ StatsService.StatsEndpointsEnum.GEO_VIEWS }; } @Override public String getTitle() { return getString(R.string.stats_view_countries); } }