package org.wordpress.android.ui.stats; import android.app.Activity; import android.content.Context; import android.graphics.Color; import android.os.Bundle; import android.os.Handler; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; import android.util.SparseBooleanArray; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.BaseExpandableListAdapter; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; import com.android.volley.NoConnectionError; import com.android.volley.VolleyError; import com.jjoe64.graphview.GraphView; import com.jjoe64.graphview.GraphViewSeries; import com.wordpress.rest.RestRequest; import org.json.JSONException; import org.json.JSONObject; import org.wordpress.android.R; import org.wordpress.android.WordPress; import org.wordpress.android.analytics.AnalyticsTracker; import org.wordpress.android.networking.RestClientUtils; import org.wordpress.android.ui.ActivityId; import org.wordpress.android.ui.stats.models.PostViewsModel; import org.wordpress.android.ui.stats.models.VisitModel; import org.wordpress.android.util.AnalyticsUtils; import org.wordpress.android.util.AppLog; import org.wordpress.android.util.DisplayUtils; import org.wordpress.android.util.FormatUtils; import org.wordpress.android.util.NetworkUtils; import org.wordpress.android.util.ToastUtils; import org.wordpress.android.util.helpers.SwipeToRefreshHelper; import org.wordpress.android.util.widgets.CustomSwipeRefreshLayout; import java.lang.ref.WeakReference; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; /** * Single item details activity. */ public class StatsSingleItemDetailsActivity extends AppCompatActivity implements StatsBarGraph.OnGestureListener{ public static final String ARG_REMOTE_BLOG_ID = "ARG_REMOTE_BLOG_ID"; public static final String ARG_REMOTE_ITEM_ID = "ARG_REMOTE_ITEM_ID"; public static final String ARG_REMOTE_ITEM_TYPE = "ARG_REMOTE_ITEM_TYPE"; public static final String ARG_ITEM_TITLE = "ARG_ITEM_TITLE"; public static final String ARG_ITEM_URL = "ARG_ITEM_URL"; private static final String ARG_REST_RESPONSE = "ARG_REST_RESPONSE"; private static final String ARG_SELECTED_GRAPH_BAR = "ARG_SELECTED_GRAPH_BAR"; private static final String ARG_PREV_NUMBER_OF_BARS = "ARG_PREV_NUMBER_OF_BARS"; private static final String SAVED_STATS_SCROLL_POSITION = "SAVED_STATS_SCROLL_POSITION"; private boolean mIsUpdatingStats; private SwipeToRefreshHelper mSwipeToRefreshHelper; private ScrollViewExt mOuterScrollView; private final Handler mHandler = new Handler(); private LinearLayout mGraphContainer; private TextView mStatsViewsLabel; private TextView mStatsViewsTotals; private LinearLayout mMonthsAndYearsModule; private LinearLayout mMonthsAndYearsList; private RelativeLayout mMonthsAndYearsHeader; private LinearLayout mMonthsAndYearsEmptyPlaceholder; private LinearLayout mAveragesModule; private LinearLayout mAveragesList; private RelativeLayout mAveragesHeader; private LinearLayout mAveragesEmptyPlaceholder; private LinearLayout mRecentWeeksModule; private LinearLayout mRecentWeeksList; private RelativeLayout mRecentWeeksHeader; private LinearLayout mRecentWeeksEmptyPlaceholder; private long mRemoteBlogID; private String mRemoteItemID, mRemoteItemType, mItemTitle, mItemURL; private PostViewsModel mRestResponseParsed; private int mSelectedBarGraphIndex = -1; private int mPrevNumberOfBarsGraph = -1; private SparseBooleanArray mYearsIdToExpandedMap; private SparseBooleanArray mAveragesIdToExpandedMap; private SparseBooleanArray mRecentWeeksIdToExpandedMap; private static final String ARG_YEARS_EXPANDED_ROWS = "ARG_YEARS_EXPANDED_ROWS"; private static final String ARG_AVERAGES_EXPANDED_ROWS = "ARG_AVERAGES_EXPANDED_ROWS"; private static final String ARG_RECENT_EXPANDED_ROWS = "ARG_RECENT_EXPANDED_ROWS"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.stats_activity_single_post_details); ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.setDisplayHomeAsUpEnabled(true); } // pull to refresh setup mSwipeToRefreshHelper = new SwipeToRefreshHelper(this, (CustomSwipeRefreshLayout) findViewById(R.id.ptr_layout), new SwipeToRefreshHelper.RefreshListener() { @Override public void onRefreshStarted() { if (!NetworkUtils.checkConnection(getBaseContext())) { mSwipeToRefreshHelper.setRefreshing(false); return; } refreshStats(); } } ); TextView mStatsForLabel = (TextView) findViewById(R.id.stats_summary_title); mGraphContainer = (LinearLayout) findViewById(R.id.stats_bar_chart_fragment_container); mStatsViewsLabel = (TextView) findViewById(R.id.stats_views_label); mStatsViewsTotals = (TextView) findViewById(R.id.stats_views_totals); mMonthsAndYearsModule = (LinearLayout) findViewById(R.id.stats_months_years_module); mMonthsAndYearsHeader = (RelativeLayout) findViewById(R.id.stats_months_years_header); mMonthsAndYearsList = (LinearLayout) findViewById(R.id.stats_months_years_list_linearlayout); mMonthsAndYearsEmptyPlaceholder = (LinearLayout) findViewById(R.id.stats_months_years_empty_module_placeholder); mAveragesModule = (LinearLayout) findViewById(R.id.stats_averages_module); mAveragesHeader = (RelativeLayout) findViewById(R.id.stats_averages_list_header); mAveragesList = (LinearLayout) findViewById(R.id.stats_averages_list_linearlayout); mAveragesEmptyPlaceholder = (LinearLayout) findViewById(R.id.stats_averages_empty_module_placeholder); mRecentWeeksModule = (LinearLayout) findViewById(R.id.stats_recent_weeks_module); mRecentWeeksHeader = (RelativeLayout) findViewById(R.id.stats_recent_weeks_list_header); mRecentWeeksList = (LinearLayout) findViewById(R.id.stats_recent_weeks_list_linearlayout); mRecentWeeksEmptyPlaceholder = (LinearLayout) findViewById(R.id.stats_recent_weeks_empty_module_placeholder); mYearsIdToExpandedMap = new SparseBooleanArray(); mAveragesIdToExpandedMap = new SparseBooleanArray(); mRecentWeeksIdToExpandedMap = new SparseBooleanArray(); setTitle(R.string.stats); mOuterScrollView = (ScrollViewExt) findViewById(R.id.scroll_view_stats); if (savedInstanceState != null) { mRemoteItemID = savedInstanceState.getString(ARG_REMOTE_ITEM_ID); mRemoteBlogID = savedInstanceState.getLong(ARG_REMOTE_BLOG_ID, 0); mRemoteItemType = savedInstanceState.getString(ARG_REMOTE_ITEM_TYPE); mItemTitle = savedInstanceState.getString(ARG_ITEM_TITLE); mItemURL = savedInstanceState.getString(ARG_ITEM_URL); mRestResponseParsed = (PostViewsModel) savedInstanceState.getSerializable(ARG_REST_RESPONSE); mSelectedBarGraphIndex = savedInstanceState.getInt(ARG_SELECTED_GRAPH_BAR, -1); mPrevNumberOfBarsGraph = savedInstanceState.getInt(ARG_PREV_NUMBER_OF_BARS, -1); final int yScrollPosition = savedInstanceState.getInt(SAVED_STATS_SCROLL_POSITION); if(yScrollPosition != 0) { mOuterScrollView.postDelayed(new Runnable() { public void run() { if (!isFinishing()) { mOuterScrollView.scrollTo(0, yScrollPosition); } } }, StatsConstants.STATS_SCROLL_TO_DELAY); } if (savedInstanceState.containsKey(ARG_AVERAGES_EXPANDED_ROWS)) { mAveragesIdToExpandedMap = savedInstanceState.getParcelable(ARG_AVERAGES_EXPANDED_ROWS); } if (savedInstanceState.containsKey(ARG_RECENT_EXPANDED_ROWS)) { mRecentWeeksIdToExpandedMap = savedInstanceState.getParcelable(ARG_RECENT_EXPANDED_ROWS); } if (savedInstanceState.containsKey(ARG_YEARS_EXPANDED_ROWS)) { mYearsIdToExpandedMap = savedInstanceState.getParcelable(ARG_YEARS_EXPANDED_ROWS); } } else if (getIntent() != null && getIntent().getExtras() != null) { Bundle extras = getIntent().getExtras(); mRemoteItemID = extras.getString(ARG_REMOTE_ITEM_ID); mRemoteBlogID = extras.getLong(ARG_REMOTE_BLOG_ID, 0); mRemoteItemType = extras.getString(ARG_REMOTE_ITEM_TYPE); mItemTitle = extras.getString(ARG_ITEM_TITLE); mItemURL = extras.getString(ARG_ITEM_URL); mRestResponseParsed = (PostViewsModel) extras.getSerializable(ARG_REST_RESPONSE); mSelectedBarGraphIndex = extras.getInt(ARG_SELECTED_GRAPH_BAR, -1); } if (mRemoteBlogID == 0 || mRemoteItemID == null) { Toast.makeText(this, R.string.stats_generic_error, Toast.LENGTH_LONG).show(); finish(); return; } if (savedInstanceState == null) { AnalyticsUtils.trackWithSiteId(AnalyticsTracker.Stat.STATS_SINGLE_POST_ACCESSED, mRemoteBlogID); } // Setup the main top label that opens the post in the Reader where possible if (mItemTitle != null || mItemURL != null) { mStatsForLabel.setVisibility(View.VISIBLE); mStatsForLabel.setText(mItemTitle != null ? mItemTitle : mItemURL ); // make the label clickable if the URL is available if (mItemURL != null) { mStatsForLabel.setTextColor(getResources().getColor(R.color.stats_link_text_color)); mStatsForLabel.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { final Context ctx = v.getContext(); StatsUtils.openPostInReaderOrInAppWebview(ctx, mRemoteBlogID, mRemoteItemID, mRemoteItemType, mItemURL); } }); } else { mStatsForLabel.setTextColor(getResources().getColor(R.color.grey_darken_20)); } } else { mStatsForLabel.setVisibility(View.GONE); } } @Override protected void onSaveInstanceState(Bundle outState) { outState.putInt(ARG_SELECTED_GRAPH_BAR, mSelectedBarGraphIndex); outState.putInt(ARG_PREV_NUMBER_OF_BARS, mPrevNumberOfBarsGraph); outState.putLong(ARG_REMOTE_BLOG_ID, mRemoteBlogID); outState.putString(ARG_REMOTE_ITEM_ID, mRemoteItemID); outState.putString(ARG_REMOTE_ITEM_TYPE, mRemoteItemType); outState.putString(ARG_ITEM_TITLE, mItemTitle); outState.putString(ARG_ITEM_URL, mItemURL); outState.putSerializable(ARG_REST_RESPONSE, mRestResponseParsed); if (mOuterScrollView.getScrollY() != 0) { outState.putInt(SAVED_STATS_SCROLL_POSITION, mOuterScrollView.getScrollY()); } if (mAveragesIdToExpandedMap.size() > 0){ outState.putParcelable(ARG_AVERAGES_EXPANDED_ROWS, new SparseBooleanArrayParcelable(mAveragesIdToExpandedMap)); } if (mRecentWeeksIdToExpandedMap.size() > 0) { outState.putParcelable(ARG_RECENT_EXPANDED_ROWS, new SparseBooleanArrayParcelable(mRecentWeeksIdToExpandedMap)); } if (mYearsIdToExpandedMap.size() > 0) { outState.putParcelable(ARG_YEARS_EXPANDED_ROWS, new SparseBooleanArrayParcelable(mYearsIdToExpandedMap)); } super.onSaveInstanceState(outState); } @Override protected void onResume() { super.onResume(); if (mRestResponseParsed == null) { // check if network is available, if not shows the empty UI immediately if (!NetworkUtils.checkConnection(this)) { mSwipeToRefreshHelper.setRefreshing(false); setupEmptyUI(); } else { setupEmptyGraph(""); showHideEmptyModulesIndicator(true); refreshStats(); } } else { updateUI(); } ActivityId.trackLastActivity(ActivityId.STATS_POST_DETAILS); } @Override protected void onPause() { super.onPause(); mIsUpdatingStats = false; mSwipeToRefreshHelper.setRefreshing(false); } @Override public boolean onOptionsItemSelected(final MenuItem item) { switch (item.getItemId()) { case android.R.id.home: onBackPressed(); return true; default: return super.onOptionsItemSelected(item); } } private void refreshStats() { if (mIsUpdatingStats) { AppLog.w(AppLog.T.STATS, "stats details are already updating for the following postID " + mRemoteItemID + ", refresh cancelled."); return; } if (!NetworkUtils.checkConnection(this)) { mSwipeToRefreshHelper.setRefreshing(false); return; } final RestClientUtils restClientUtils = WordPress.getRestClientUtilsV1_1(); // View and visitor counts for a site final String singlePostRestPath = String.format( "/sites/%s/stats/post/%s", mRemoteBlogID, mRemoteItemID); AppLog.d(AppLog.T.STATS, "Enqueuing the following Stats request " + singlePostRestPath); RestBatchCallListener vListener = new RestBatchCallListener(this); restClientUtils.get(singlePostRestPath, vListener, vListener); mIsUpdatingStats = true; mSwipeToRefreshHelper.setRefreshing(true); } private void showHideEmptyModulesIndicator(boolean show) { if (isFinishing()) { return; } mMonthsAndYearsModule.setVisibility(View.VISIBLE); mRecentWeeksModule.setVisibility(View.VISIBLE); mAveragesModule.setVisibility(View.VISIBLE); mMonthsAndYearsHeader.setVisibility(show ? View.GONE : View.VISIBLE); mRecentWeeksHeader.setVisibility(show ? View.GONE : View.VISIBLE); mAveragesHeader.setVisibility(show ? View.GONE : View.VISIBLE); mMonthsAndYearsList.setVisibility(show ? View.GONE : View.VISIBLE); mAveragesList.setVisibility(show ? View.GONE : View.VISIBLE); mRecentWeeksList.setVisibility(show ? View.GONE : View.VISIBLE); mMonthsAndYearsEmptyPlaceholder.setVisibility(show ? View.VISIBLE : View.GONE); mRecentWeeksEmptyPlaceholder.setVisibility(show ? View.VISIBLE : View.GONE); mAveragesEmptyPlaceholder.setVisibility(show ? View.VISIBLE : View.GONE); } private void setupEmptyUI() { if (isFinishing()) { return; } setupEmptyGraph(null); mMonthsAndYearsModule.setVisibility(View.GONE); mRecentWeeksModule.setVisibility(View.GONE); mAveragesModule.setVisibility(View.GONE); mRecentWeeksIdToExpandedMap.clear(); mAveragesIdToExpandedMap.clear(); mYearsIdToExpandedMap.clear(); } private void setupEmptyGraph(String emptyLabel) { if (isFinishing()) { return; } Context context = mGraphContainer.getContext(); if (context != null) { LayoutInflater inflater = LayoutInflater.from(context); View emptyBarGraphView = inflater.inflate(R.layout.stats_bar_graph_empty, mGraphContainer, false); if (emptyLabel != null) { final TextView emptyLabelField = (TextView) emptyBarGraphView.findViewById(R.id.stats_bar_graph_empty_label); emptyLabelField.setText(emptyLabel); } mGraphContainer.removeAllViews(); mGraphContainer.addView(emptyBarGraphView); } mStatsViewsLabel.setText(""); mStatsViewsTotals.setText(""); } private VisitModel[] getDataToShowOnGraph () { if (mRestResponseParsed == null) { return new VisitModel[0]; } final VisitModel[] dayViews = mRestResponseParsed.getDayViews(); if (dayViews == null) { return new VisitModel[0]; } int numPoints = Math.min(StatsUIHelper.getNumOfBarsToShow(), dayViews.length); int currentPointIndex = numPoints - 1; VisitModel[] visitModels = new VisitModel[numPoints]; for (int i = dayViews.length - 1; i >= 0 && currentPointIndex >= 0; i--) { visitModels[currentPointIndex] = dayViews[i]; currentPointIndex--; } return visitModels; } private void updateUI() { if (isFinishing()) { return; } final VisitModel[] dataToShowOnGraph = getDataToShowOnGraph(); if (dataToShowOnGraph == null || dataToShowOnGraph.length == 0) { setupEmptyUI(); return; } final String[] horLabels = new String[dataToShowOnGraph.length]; String[] mStatsDate = new String[dataToShowOnGraph.length]; GraphView.GraphViewData[] views = new GraphView.GraphViewData[dataToShowOnGraph.length]; for (int i = 0; i < dataToShowOnGraph.length; i++) { int currentItemValue = dataToShowOnGraph[i].getViews(); views[i] = new GraphView.GraphViewData(i, currentItemValue); String currentItemStatsDate = dataToShowOnGraph[i].getPeriod(); horLabels[i] = StatsUtils.parseDate( currentItemStatsDate, StatsConstants.STATS_INPUT_DATE_FORMAT, StatsConstants.STATS_OUTPUT_DATE_MONTH_SHORT_DAY_SHORT_FORMAT ); mStatsDate[i] = currentItemStatsDate; } GraphViewSeries mCurrentSeriesOnScreen = new GraphViewSeries(views); mCurrentSeriesOnScreen.getStyle().color = getResources().getColor(R.color.stats_bar_graph_main_series); mCurrentSeriesOnScreen.getStyle().highlightColor = getResources().getColor(R.color.stats_bar_graph_main_series_highlight); mCurrentSeriesOnScreen.getStyle().outerhighlightColor = getResources().getColor(R.color.stats_bar_graph_outer_highlight); mCurrentSeriesOnScreen.getStyle().padding = DisplayUtils.dpToPx(this, 5); StatsBarGraph mGraphView; if (mGraphContainer.getChildCount() >= 1 && mGraphContainer.getChildAt(0) instanceof GraphView) { mGraphView = (StatsBarGraph) mGraphContainer.getChildAt(0); } else { mGraphContainer.removeAllViews(); mGraphView = new StatsBarGraph(this); mGraphContainer.addView(mGraphView); } mGraphView.removeAllSeries(); mGraphView.addSeries(mCurrentSeriesOnScreen); //mGraphView.getGraphViewStyle().setNumHorizontalLabels(getNumOfHorizontalLabels(dataToShowOnGraph.length)); mGraphView.getGraphViewStyle().setNumHorizontalLabels(dataToShowOnGraph.length); mGraphView.getGraphViewStyle().setMaxColumnWidth( DisplayUtils.dpToPx(this, StatsConstants.STATS_GRAPH_BAR_MAX_COLUMN_WIDTH_DP) ); mGraphView.setHorizontalLabels(horLabels); mGraphView.setGestureListener(this); // Reset the bar selected upon rotation of the device when the no. of bars can change with orientation. // Only happens on 720DP tablets if (mPrevNumberOfBarsGraph != -1 && mPrevNumberOfBarsGraph != dataToShowOnGraph.length) { mSelectedBarGraphIndex = dataToShowOnGraph.length - 1; } else { mSelectedBarGraphIndex = (mSelectedBarGraphIndex != -1) ? mSelectedBarGraphIndex : dataToShowOnGraph.length - 1; } mGraphView.highlightBar(mSelectedBarGraphIndex); mPrevNumberOfBarsGraph = dataToShowOnGraph.length; setMainViewsLabel( StatsUtils.parseDate( mStatsDate[mSelectedBarGraphIndex], StatsConstants.STATS_INPUT_DATE_FORMAT, StatsConstants.STATS_OUTPUT_DATE_MONTH_LONG_DAY_SHORT_FORMAT ), dataToShowOnGraph[mSelectedBarGraphIndex].getViews() ); showHideEmptyModulesIndicator(false); mMonthsAndYearsList.setVisibility(View.VISIBLE); List<PostViewsModel.Year> years = mRestResponseParsed.getYears(); MonthsAndYearsListAdapter monthsAndYearsListAdapter = new MonthsAndYearsListAdapter(this, years, mRestResponseParsed.getHighestMonth()); StatsUIHelper.reloadGroupViews(this, monthsAndYearsListAdapter, mYearsIdToExpandedMap, mMonthsAndYearsList); mAveragesList.setVisibility(View.VISIBLE); List<PostViewsModel.Year> averages = mRestResponseParsed.getAverages(); MonthsAndYearsListAdapter averagesListAdapter = new MonthsAndYearsListAdapter(this, averages, mRestResponseParsed.getHighestDayAverage()); StatsUIHelper.reloadGroupViews(this, averagesListAdapter, mAveragesIdToExpandedMap, mAveragesList); mRecentWeeksList.setVisibility(View.VISIBLE); List<PostViewsModel.Week> recentWeeks = mRestResponseParsed.getWeeks(); RecentWeeksListAdapter recentWeeksListAdapter = new RecentWeeksListAdapter(this, recentWeeks, mRestResponseParsed.getHighestWeekAverage()); StatsUIHelper.reloadGroupViews(this, recentWeeksListAdapter, mRecentWeeksIdToExpandedMap, mRecentWeeksList); } private void setMainViewsLabel(String dateFormatted, int totals) { mStatsViewsLabel.setText(getString(R.string.stats_views) + ": " + dateFormatted); mStatsViewsTotals.setText(FormatUtils.formatDecimal(totals)); } private class RecentWeeksListAdapter extends BaseExpandableListAdapter { public static final String GROUP_DATE_FORMAT = "MMM dd"; public final LayoutInflater inflater; private final List<PostViewsModel.Week> groups; private final int maxReachedValue; public RecentWeeksListAdapter(Context context, List<PostViewsModel.Week> groups, int maxReachedValue) { this.groups = groups; this.inflater = LayoutInflater.from(context); this.maxReachedValue = maxReachedValue; } @Override public Object getChild(int groupPosition, int childPosition) { PostViewsModel.Week currentWeek = groups.get(groupPosition); return currentWeek.getDays().get(childPosition); } @Override public long getChildId(int groupPosition, int childPosition) { return 0; } @Override public View getChildView(int groupPosition, final int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { final PostViewsModel.Day currentDay = (PostViewsModel.Day) getChild(groupPosition, childPosition); final StatsViewHolder holder; if (convertView == null) { convertView = inflater.inflate(R.layout.stats_list_cell, parent, false); holder = new StatsViewHolder(convertView); convertView.setTag(holder); } else { holder = (StatsViewHolder) convertView.getTag(); } holder.setEntryText(StatsUtils.parseDate(currentDay.getDay(), StatsConstants.STATS_INPUT_DATE_FORMAT, "EEE, MMM dd")); // Intercept clicks at row level and eat the event. We don't want to show the ripple here. holder.rowContent.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View view) { } }); holder.rowContent.setBackgroundColor(Color.TRANSPARENT); // totals holder.totalsTextView.setText(FormatUtils.formatDecimal(currentDay.getCount())); // show the trophy indicator if the value is the maximum reached if (currentDay.getCount() == maxReachedValue && maxReachedValue > 0) { holder.imgMore.setVisibility(View.VISIBLE); holder.imgMore.setImageDrawable(getResources().getDrawable(R.drawable.ic_trophy_alert_yellow_32dp)); holder.imgMore.setBackgroundColor(Color.TRANSPARENT); // Hide the default click indicator } else { holder.imgMore.setVisibility(View.GONE); } holder.networkImageView.setVisibility(View.GONE); return convertView; } @Override public int getChildrenCount(int groupPosition) { PostViewsModel.Week week = groups.get(groupPosition); return week.getDays().size(); } @Override public Object getGroup(int groupPosition) { return groups.get(groupPosition); } @Override public int getGroupCount() { return groups.size(); } @Override public long getGroupId(int groupPosition) { return 0; } @Override public View getGroupView(final int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { final StatsViewHolder holder; if (convertView == null) { convertView = inflater.inflate(R.layout.stats_list_cell, parent, false); holder = new StatsViewHolder(convertView); convertView.setTag(holder); } else { holder = (StatsViewHolder) convertView.getTag(); } PostViewsModel.Week week = (PostViewsModel.Week) getGroup(groupPosition); int total = week.getTotal(); // change the color of the text if one of its childs has reached maximum value int numberOfChilds = getChildrenCount(groupPosition); boolean shouldShowTheTrophyIcon = false; if (maxReachedValue > 0) { for (int i = 0; i < numberOfChilds; i++) { PostViewsModel.Day currentChild = (PostViewsModel.Day) getChild(groupPosition, i); if (currentChild.getCount() == maxReachedValue) { shouldShowTheTrophyIcon = true; } } } // Build the label to show on the group String name; PostViewsModel.Day firstChild = (PostViewsModel.Day) getChild(groupPosition, 0); if (numberOfChilds > 1) { PostViewsModel.Day lastChild = (PostViewsModel.Day) getChild(groupPosition, getChildrenCount(groupPosition) - 1); name = StatsUtils.parseDate(firstChild.getDay(), StatsConstants.STATS_INPUT_DATE_FORMAT, GROUP_DATE_FORMAT) + " - " + StatsUtils.parseDate(lastChild.getDay(), StatsConstants.STATS_INPUT_DATE_FORMAT, GROUP_DATE_FORMAT); } else { name = StatsUtils.parseDate(firstChild.getDay(), StatsConstants.STATS_INPUT_DATE_FORMAT, GROUP_DATE_FORMAT); } holder.setEntryText(name, getResources().getColor(R.color.stats_link_text_color)); holder.networkImageView.setVisibility(View.GONE); // totals holder.totalsTextView.setText(FormatUtils.formatDecimal(total)); if (shouldShowTheTrophyIcon) { holder.imgMore.setVisibility(View.VISIBLE); holder.imgMore.setImageDrawable(getResources().getDrawable(R.drawable.ic_trophy_alert_yellow_32dp)); holder.imgMore.setBackgroundColor(Color.TRANSPARENT); // Hide the default click indicator } else { holder.imgMore.setVisibility(View.GONE); } // expand/collapse chevron holder.chevronImageView.setVisibility(numberOfChilds > 0 ? View.VISIBLE : View.GONE); return convertView; } @Override public boolean hasStableIds() { return false; } @Override public boolean isChildSelectable(int groupPosition, int childPosition) { return false; } } private class MonthsAndYearsListAdapter extends BaseExpandableListAdapter { public final LayoutInflater inflater; private final List<PostViewsModel.Year> groups; private final int maxReachedValue; public MonthsAndYearsListAdapter(Context context, List<PostViewsModel.Year> groups, int maxReachedValue) { this.groups = groups; this.inflater = LayoutInflater.from(context); this.maxReachedValue = maxReachedValue; } @Override public Object getChild(int groupPosition, int childPosition) { PostViewsModel.Year currentYear = groups.get(groupPosition); return currentYear.getMonths().get(childPosition); } @Override public long getChildId(int groupPosition, int childPosition) { return 0; } @Override public View getChildView(int groupPosition, final int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { final PostViewsModel.Month currentMonth = (PostViewsModel.Month) getChild(groupPosition, childPosition); final StatsViewHolder holder; if (convertView == null) { convertView = inflater.inflate(R.layout.stats_list_cell, parent, false); holder = new StatsViewHolder(convertView); convertView.setTag(holder); } else { holder = (StatsViewHolder) convertView.getTag(); } holder.setEntryText(StatsUtils.parseDate(currentMonth.getMonth(), "MM", StatsConstants.STATS_OUTPUT_DATE_MONTH_LONG_FORMAT)); // Intercept clicks at row level and eat the event. We don't want to show the ripple here. holder.rowContent.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View view) { } }); holder.rowContent.setBackgroundColor(Color.TRANSPARENT); // totals holder.totalsTextView.setText(FormatUtils.formatDecimal(currentMonth.getCount())); // show the trophy indicator if the value is the maximum reached if (currentMonth.getCount() == maxReachedValue && maxReachedValue > 0) { holder.imgMore.setVisibility(View.VISIBLE); holder.imgMore.setImageDrawable(getResources().getDrawable(R.drawable.ic_trophy_alert_yellow_32dp)); holder.imgMore.setBackgroundColor(Color.TRANSPARENT); // Hide the default click indicator } else { holder.imgMore.setVisibility(View.GONE); } holder.networkImageView.setVisibility(View.GONE); return convertView; } @Override public int getChildrenCount(int groupPosition) { PostViewsModel.Year currentYear = groups.get(groupPosition); return currentYear.getMonths().size(); } @Override public Object getGroup(int groupPosition) { return groups.get(groupPosition); } @Override public int getGroupCount() { return groups.size(); } @Override public long getGroupId(int groupPosition) { return 0; } @Override public View getGroupView(final int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { final StatsViewHolder holder; if (convertView == null) { convertView = inflater.inflate(R.layout.stats_list_cell, parent, false); holder = new StatsViewHolder(convertView); convertView.setTag(holder); } else { holder = (StatsViewHolder) convertView.getTag(); } PostViewsModel.Year year = (PostViewsModel.Year) getGroup(groupPosition); String name = year.getLabel(); int total = year.getTotal(); // change the color of the text if one of its childs has reached maximum value int numberOfChilds = getChildrenCount(groupPosition); boolean shouldShowTheTrophyIcon = false; if (maxReachedValue > 0) { for (int i = 0; i < numberOfChilds; i++) { PostViewsModel.Month currentChild = (PostViewsModel.Month) getChild(groupPosition, i); if (currentChild.getCount() == maxReachedValue) { shouldShowTheTrophyIcon = true; break; } } } holder.setEntryText(name, getResources().getColor(R.color.stats_link_text_color)); if (shouldShowTheTrophyIcon) { holder.imgMore.setVisibility(View.VISIBLE); holder.imgMore.setImageDrawable(getResources().getDrawable(R.drawable.ic_trophy_alert_yellow_32dp)); holder.imgMore.setBackgroundColor(Color.TRANSPARENT); // Hide the default click indicator } else { holder.imgMore.setVisibility(View.GONE); } // totals holder.totalsTextView.setText(FormatUtils.formatDecimal(total)); holder.networkImageView.setVisibility(View.GONE); // expand/collapse chevron holder.chevronImageView.setVisibility(numberOfChilds > 0 ? View.VISIBLE : View.GONE); return convertView; } @Override public boolean hasStableIds() { return false; } @Override public boolean isChildSelectable(int groupPosition, int childPosition) { return false; } } private class RestBatchCallListener implements RestRequest.Listener, RestRequest.ErrorListener { private final WeakReference<Activity> mActivityRef; public RestBatchCallListener(Activity activity) { mActivityRef = new WeakReference<>(activity); } @Override public void onResponse(final JSONObject response) { if (mActivityRef.get() == null || mActivityRef.get().isFinishing()) { return; } mIsUpdatingStats = false; mSwipeToRefreshHelper.setRefreshing(false); // single background thread used to parse the response in BG. ThreadPoolExecutor parseResponseExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(1); parseResponseExecutor.submit(new Thread() { @Override public void run() { //AppLog.d(AppLog.T.STATS, "The REST response: " + response.toString()); mSelectedBarGraphIndex = -1; try { mRestResponseParsed = new PostViewsModel(response); } catch (JSONException e) { AppLog.e(AppLog.T.STATS, "Cannot parse the JSON response", e); resetModelVariables(); } // Update the UI mHandler.post(new Runnable() { @Override public void run() { updateUI(); } }); } }); } @Override public void onErrorResponse(final VolleyError volleyError) { StatsUtils.logVolleyErrorDetails(volleyError); if (mActivityRef.get() == null || mActivityRef.get().isFinishing()) { return; } resetModelVariables(); String label = mActivityRef.get().getString(R.string.error_refresh_stats); if (volleyError instanceof NoConnectionError) { label += "\n" + mActivityRef.get().getString(R.string.no_network_message); } ToastUtils.showToast(mActivityRef.get(), label, ToastUtils.Duration.LONG); mIsUpdatingStats = false; mSwipeToRefreshHelper.setRefreshing(false); // Update the UI mHandler.post(new Runnable() { @Override public void run() { updateUI(); } }); } } private void resetModelVariables() { mRestResponseParsed = null; mSelectedBarGraphIndex = -1; mAveragesIdToExpandedMap.clear(); mYearsIdToExpandedMap.clear(); } @Override public void onBarTapped(int tappedBar) { mSelectedBarGraphIndex = tappedBar; final VisitModel[] dataToShowOnGraph = getDataToShowOnGraph(); String currentItemStatsDate = dataToShowOnGraph[mSelectedBarGraphIndex].getPeriod(); currentItemStatsDate = StatsUtils.parseDate( currentItemStatsDate, StatsConstants.STATS_INPUT_DATE_FORMAT, StatsConstants.STATS_OUTPUT_DATE_MONTH_LONG_DAY_SHORT_FORMAT ); setMainViewsLabel(currentItemStatsDate, dataToShowOnGraph[mSelectedBarGraphIndex].getViews()); } }