package ca.josephroque.bowlingcompanion.fragment; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.SharedPreferences; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.v4.app.Fragment; import android.util.Pair; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ExpandableListView; import java.lang.ref.WeakReference; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import ca.josephroque.bowlingcompanion.Constants; import ca.josephroque.bowlingcompanion.MainActivity; import ca.josephroque.bowlingcompanion.R; import ca.josephroque.bowlingcompanion.adapter.StatsExpandableAdapter; import ca.josephroque.bowlingcompanion.database.Contract; import ca.josephroque.bowlingcompanion.database.DatabaseHelper; import ca.josephroque.bowlingcompanion.theme.Theme; import ca.josephroque.bowlingcompanion.utilities.DisplayUtils; import ca.josephroque.bowlingcompanion.utilities.FloatingActionButtonHandler; import ca.josephroque.bowlingcompanion.utilities.Score; import ca.josephroque.bowlingcompanion.utilities.StatUtils; import ca.josephroque.bowlingcompanion.view.AnimatedExpandableListView; /** * Created by Joseph Roque on 15-07-20. Manages the UI to display information about the stats in a list for a particular * bowler */ public class StatsListFragment extends Fragment implements Theme.ChangeableTheme, FloatingActionButtonHandler { /** Identifies output from this class in Logcat. */ @SuppressWarnings("unused") private static final String TAG = "StatsListFragment"; /** Adapter to manage data displayed in fragment. */ private StatsExpandableAdapter mAdapterStats; /** Indicates index of stat group in array, if the group exists at all. */ private byte mStatsGeneral = -1; /** Indicates index of stat group in array, if the group exists at all. */ private byte mStatsFirstBall = -1; /** Indicates index of stat group in array, if the group exists at all. */ private byte mStatsFouls = -1; /** Indicates index of stat group in array, if the group exists at all. */ private byte mStatsPins = -1; /** Indicates index of stat group in array, if the group exists at all. */ private byte mStatsGameAverage = -1; /** Indicates index of stat group in array, if the group exists at all. */ private byte mStatsMatch = -1; /** Indicates index of stat group in array, if the group exists at all. */ private byte mStatsOverall = -1; /** Number of static stats at the beginning of the first group of stats. */ private byte mNumberOfGeneralDetails = -1; /** Indicates the type of stats which will be loaded. */ private byte mStatsToLoad = -1; /** Indicates if Headpin + 2 should be counted as Headpins. */ private boolean mCountH2 = false; /** Indicates if Split + 2 should be counted as Splits. */ private boolean mCountS2 = false; /** List of group headers. */ private List<String> mListStatHeaders; /** List of list of map entries which hold a name and a value, for each group. */ private List<List<Pair<String, String>>> mListStatNamesAndValues; /** * Creates a new instance of {@code StatsListFragment} with the parameters provided. * * @return a new instance of StatsListFragment */ public static StatsListFragment newInstance() { return new StatsListFragment(); } @SuppressWarnings("deprecation") @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment View rootView = inflater.inflate(R.layout.fragment_stats_list, container, false); mListStatHeaders = new ArrayList<>(); mListStatNamesAndValues = new ArrayList<>(); mAdapterStats = new StatsExpandableAdapter(getContext(), mListStatHeaders, mListStatNamesAndValues); final AnimatedExpandableListView listView = (AnimatedExpandableListView) rootView.findViewById(R.id.elv_stats); listView.setAdapter(mAdapterStats); setExpandableListViewIndicator(listView); listView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() { @Override public boolean onChildClick( ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { if (mStatsToLoad != StatUtils.LOADING_BOWLER_STATS && mStatsToLoad != StatUtils.LOADING_LEAGUE_STATS) return true; if (groupPosition == 0 && childPosition - mNumberOfGeneralDetails >= 0) openStatGraph(groupPosition, childPosition - mNumberOfGeneralDetails); else if (groupPosition > 0) openStatGraph(groupPosition, childPosition); return true; } }); listView.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() { @Override public boolean onGroupClick( ExpandableListView parent, View v, int groupPosition, long id) { // We call collapseGroupWithAnimation(int) and // expandGroupWithAnimation(int) to animate group // expansion/collapse. if (listView.isGroupExpanded(groupPosition)) listView.collapseGroupWithAnimation(groupPosition); else listView.expandGroupWithAnimation(groupPosition); return true; } }); return rootView; } @Override public void onResume() { super.onResume(); if (getActivity() != null) { MainActivity mainActivity = (MainActivity) getActivity(); mainActivity.setDrawerState(false); // Check if the user has opted to count headpins + 2 as headpins or not, as well as splits + 2 as splits SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mainActivity); mCountH2 = preferences.getBoolean(Constants.KEY_COUNT_H2_AS_H, false); mCountS2 = preferences.getBoolean(Constants.KEY_COUNT_S2_AS_S, false); // Checks what type of stats should be displayed, depending // on what data is available in the parent activity at the time int titleToSet; if (mainActivity.getGameId() == -1) { if (mainActivity.getSeriesId() == -1) { if (mainActivity.getLeagueId() == -1) { titleToSet = R.string.title_stats_bowler; mStatsToLoad = StatUtils.LOADING_BOWLER_STATS; } else { titleToSet = R.string.title_stats_league; mStatsToLoad = StatUtils.LOADING_LEAGUE_STATS; } } else { titleToSet = R.string.title_stats_series; mStatsToLoad = StatUtils.LOADING_SERIES_STATS; } } else { titleToSet = R.string.title_stats_game; mStatsToLoad = StatUtils.LOADING_GAME_STATS; } if (mStatsToLoad == StatUtils.LOADING_BOWLER_STATS || mStatsToLoad == StatUtils.LOADING_LEAGUE_STATS) mainActivity.setFloatingActionButtonState(R.drawable.ic_timeline_black_24dp, 0); else mainActivity.setFloatingActionButtonState(0, 0); mainActivity.setActionBarTitle(titleToSet, true); new LoadStatsListTask(this).execute(mStatsToLoad); } updateTheme(); } @Override public void updateTheme() { mAdapterStats.updateTheme(); } @Override public void onFabClick() { if (mStatsToLoad == StatUtils.LOADING_BOWLER_STATS || mStatsToLoad == StatUtils.LOADING_LEAGUE_STATS) openStatsPrompt(); } @Override public void onSecondaryFabClick() { // does nothing - secondary fab has no function here } /** * Creates and sets the group indicator for the expandle list view. * * @param listView list view to set indicator for */ @SuppressWarnings("CheckStyle") private void setExpandableListViewIndicator(ExpandableListView listView) { final Drawable indicator = DisplayUtils.getDrawable(getResources(), R.drawable.stat_indicator); if (indicator != null) { listView.setGroupIndicator(indicator); if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) { listView.setIndicatorBounds( DisplayUtils.getPixelsFromDP(getResources().getDisplayMetrics().density, 16), DisplayUtils.getPixelsFromDP(getResources().getDisplayMetrics().density, 16) + indicator.getMinimumWidth()); } else { listView.setIndicatorBoundsRelative( DisplayUtils.getPixelsFromDP(getResources().getDisplayMetrics().density, 16), DisplayUtils.getPixelsFromDP(getResources().getDisplayMetrics().density, 16) + indicator.getMinimumWidth()); } } } /** * Opens a prompt to remind user they can click on stats to see graphs. */ private void openStatsPrompt() { new AlertDialog.Builder(getContext()) .setTitle(R.string.text_opening_graph) .setMessage(R.string.text_opening_graph_click) .setPositiveButton(R.string.dialog_okay, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); openStatGraph(0, 0); } }) .create() .show(); } /** * Adds headers and filler data to lists. * * @param mainActivity activity which created this object * @param statsToLoad type of stats which are being loaded * @param headers headers of groups * @param namesAndValues entries in each group */ @SuppressWarnings({"Convert2Diamond", "CheckStyle"}) private void prepareListData(MainActivity mainActivity, byte statsToLoad, List<String> headers, List<List<Pair<String, String>>> namesAndValues) { // Stat names which could possibly be displayed, depending on stats being loaded headers.add("General"); namesAndValues.add(new ArrayList<Pair<String, String>>()); mStatsGeneral = 0; namesAndValues.get(mStatsGeneral).add(Pair.create("Bowler", mainActivity.getBowlerName())); int i = 0; while (true) { try { namesAndValues.get(mStatsGeneral).add(Pair.create( StatUtils.getStatName(StatUtils.STAT_CATEGORY_GENERAL, i, false), "--")); } catch (IllegalArgumentException ex) { break; } i++; } headers.add("First Ball"); namesAndValues.add(new ArrayList<Pair<String, String>>()); mStatsFirstBall = 1; i = 0; while (true) { try { namesAndValues.get(mStatsFirstBall).add(Pair.create( StatUtils.getStatName(StatUtils.STAT_CATEGORY_FIRST_BALL, i, false), "--")); } catch (IllegalArgumentException ex) { break; } i++; } headers.add("Fouls"); namesAndValues.add(new ArrayList<Pair<String, String>>()); mStatsFouls = 2; i = 0; while (true) { try { namesAndValues.get(mStatsFouls).add(Pair.create( StatUtils.getStatName(StatUtils.STAT_CATEGORY_FOULS, i, false), "--")); } catch (IllegalArgumentException ex) { break; } i++; } headers.add("Pins Left on Deck"); namesAndValues.add(new ArrayList<Pair<String, String>>()); mStatsPins = 3; namesAndValues.get(mStatsPins).add(Pair.create(StatUtils.getStatName( StatUtils.STAT_CATEGORY_PINS, StatUtils.STAT_PINS_LEFT, false), "--")); if (statsToLoad < StatUtils.LOADING_SERIES_STATS) { headers.add("Average by Game"); namesAndValues.add(new ArrayList<Pair<String, String>>()); mStatsGameAverage = 4; final int numberOfGames = (statsToLoad >= StatUtils.LOADING_LEAGUE_STATS ? ((mainActivity.getLeagueName().equals(Constants.NAME_OPEN_LEAGUE)) ? 5 : mainActivity.getDefaultNumberOfGames()) : 20); for (i = 0; i < numberOfGames; i++) namesAndValues.get(mStatsGameAverage).add(Pair.create(StatUtils.getStatName( StatUtils.STAT_CATEGORY_AVERAGE_BY_GAME, i, false), "--")); } if (statsToLoad < StatUtils.LOADING_GAME_STATS) { namesAndValues.get(mStatsPins).add(Pair.create(StatUtils.getStatName( StatUtils.STAT_CATEGORY_PINS, StatUtils.STAT_PINS_AVERAGE, false), "--")); headers.add("Match Play"); namesAndValues.add(new ArrayList<Pair<String, String>>()); mStatsMatch = (byte) (mStatsGameAverage == -1 ? 4 : 5); i = 0; while (true) { try { namesAndValues.get(mStatsMatch).add(Pair.create( StatUtils.getStatName(StatUtils.STAT_CATEGORY_MATCH_PLAY, i, false), "--")); } catch (IllegalArgumentException ex) { break; } i++; } headers.add("Overall"); namesAndValues.add(new ArrayList<Pair<String, String>>()); mStatsOverall = (byte) (mStatsMatch + 1); i = 0; while (true) { try { namesAndValues.get(mStatsOverall).add(Pair.create( StatUtils.getStatName(StatUtils.STAT_CATEGORY_OVERALL, i, false), "--")); } catch (IllegalArgumentException ex) { break; } i++; } } } /** * Loads data from the database and calculates relevant stats depending on which type of stats are being loaded. */ private static final class LoadStatsListTask extends AsyncTask<Byte, Void, List<?>[]> { /** Weak reference to the parent fragment. */ private final WeakReference<StatsListFragment> mFragment; /** The base averages of the bowler or league. */ private List<Short> mBaseAverages = new ArrayList<>(); /** The number of games which contributed to the corresponding base average. */ private List<Integer> mBaseGameCounts = new ArrayList<>(); /** * Assigns a weak reference to the parent fragment. * * @param fragment parent fragment */ private LoadStatsListTask(StatsListFragment fragment) { mFragment = new WeakReference<>(fragment); } @Override protected void onPreExecute() { StatsListFragment fragment = mFragment.get(); if (fragment == null) return; fragment.mListStatHeaders.clear(); fragment.mListStatNamesAndValues.clear(); fragment.mAdapterStats.notifyDataSetChanged(); } @SuppressWarnings("CheckStyle") @Override protected List<?>[] doInBackground(Byte... statsToLoad) { StatsListFragment fragment = mFragment.get(); if (fragment == null || !fragment.isAdded()) return null; MainActivity mainActivity = (MainActivity) fragment.getActivity(); if (mainActivity == null) return null; MainActivity.waitForSaveThreads(new WeakReference<>(mainActivity)); final byte toLoad = statsToLoad[0]; Cursor cursor; int[][] statValues; List<String> listStatHeaders = new ArrayList<>(); List<List<Pair<String, String>>> listStatNamesAndValues = new ArrayList<>(); fragment.prepareListData(mainActivity, toLoad, listStatHeaders, listStatNamesAndValues); statValues = new int[listStatHeaders.size()][]; for (int i = 0; i < statValues.length; i++) statValues[i] = new int[listStatNamesAndValues.get(i).size()]; switch (toLoad) { case StatUtils.LOADING_BOWLER_STATS: fragment.mNumberOfGeneralDetails = 1; cursor = fragment.getBowlerOrLeagueCursor(false); StatUtils.getBaseAverages(fragment.getContext(), mainActivity.getBowlerId(), false, mBaseAverages, mBaseGameCounts); break; case StatUtils.LOADING_LEAGUE_STATS: fragment.mNumberOfGeneralDetails = 2; listStatNamesAndValues.get(fragment.mStatsGeneral).add(1, Pair.create("League/Event", mainActivity.getLeagueName())); cursor = fragment.getBowlerOrLeagueCursor(true); StatUtils.getBaseAverages(fragment.getContext(), mainActivity.getLeagueId(), true, mBaseAverages, mBaseGameCounts); break; case StatUtils.LOADING_SERIES_STATS: fragment.mNumberOfGeneralDetails = 3; listStatNamesAndValues.get(fragment.mStatsGeneral).add(1, Pair.create("League/Event", mainActivity.getLeagueName())); listStatNamesAndValues.get(fragment.mStatsGeneral).add(2, Pair.create("Date", mainActivity.getSeriesDate())); cursor = fragment.getSeriesCursor(); break; case StatUtils.LOADING_GAME_STATS: fragment.mNumberOfGeneralDetails = 4; listStatNamesAndValues.get(fragment.mStatsGeneral).add(1, Pair.create("League/Event", mainActivity.getLeagueName())); listStatNamesAndValues.get(fragment.mStatsGeneral).add(2, Pair.create("Date", mainActivity.getSeriesDate())); listStatNamesAndValues.get(fragment.mStatsGeneral).add(3, Pair.create("Game #", String.valueOf(mainActivity.getGameNumber()))); cursor = fragment.getGameCursor(); break; default: throw new IllegalArgumentException("invalid value for toLoad: " + toLoad + ". must be between 0 and 3 (inclusive)"); } /** * Passes through rows in cursor and updates stats which * are affected as each frame is analyzed */ final int numberOfGames = (toLoad >= StatUtils.LOADING_LEAGUE_STATS ? ((mainActivity.getLeagueName().equals(Constants.NAME_OPEN_LEAGUE)) ? 5 : mainActivity.getDefaultNumberOfGames()) : 20); int totalShotsAtMiddle = 0; int spareChances = 0; int seriesTotal = 0; int[] totalByGame = new int[numberOfGames]; int[] countByGame = new int[numberOfGames]; if (cursor.moveToFirst()) { while (!cursor.isAfterLast()) { byte frameNumber = (byte) cursor.getInt( cursor.getColumnIndex(Contract.FrameEntry.COLUMN_FRAME_NUMBER)); if (toLoad != StatUtils.LOADING_GAME_STATS && frameNumber == 1) { short gameScore = cursor.getShort(cursor.getColumnIndex(Contract.GameEntry.COLUMN_SCORE)); byte gameNumber = (byte) cursor.getInt(cursor.getColumnIndex(Contract.GameEntry.COLUMN_GAME_NUMBER)); byte matchResults = (byte) (cursor.getInt( cursor.getColumnIndex(Contract.GameEntry.COLUMN_MATCH_PLAY))); if (matchResults > 0) statValues[fragment.mStatsMatch][matchResults - 1]++; if (gameScore > 0) { totalByGame[gameNumber - 1] += gameScore; countByGame[gameNumber - 1]++; if (gameScore > statValues[fragment.mStatsOverall][StatUtils.STAT_HIGH_SINGLE]) statValues[fragment.mStatsOverall][StatUtils.STAT_HIGH_SINGLE] = gameScore; statValues[fragment.mStatsOverall][StatUtils.STAT_TOTAL_PINS] += gameScore; statValues[fragment.mStatsOverall][StatUtils.STAT_NUMBER_OF_GAMES]++; } if (gameNumber == 1) { if (statValues[fragment.mStatsOverall][StatUtils.STAT_HIGH_SERIES] < seriesTotal) statValues[fragment.mStatsOverall][StatUtils.STAT_HIGH_SERIES] = seriesTotal; seriesTotal = gameScore; } else { seriesTotal += gameScore; } } short gameScore = cursor.getShort(cursor.getColumnIndex(Contract.GameEntry.COLUMN_SCORE)); boolean gameIsManual = (cursor.getInt(cursor.getColumnIndex( Contract.GameEntry.COLUMN_IS_MANUAL)) == 1); if (gameIsManual || gameScore == 0) { cursor.moveToNext(); continue; } boolean frameAccessed = (cursor.getInt(cursor.getColumnIndex( Contract.FrameEntry.COLUMN_IS_ACCESSED)) == 1); if (toLoad == StatUtils.LOADING_GAME_STATS && !frameAccessed) break; String frameFouls = Score.foulIntToString(cursor.getInt(cursor.getColumnIndex( Contract.FrameEntry.COLUMN_FOULS))); boolean[][] pinState = new boolean[3][5]; for (byte i = 0; i < pinState.length; i++) { pinState[i] = Score.ballIntToBoolean(cursor.getInt(cursor.getColumnIndex( Contract.FrameEntry.COLUMN_PIN_STATE[i]))); } for (byte i = 1; i <= 3; i++) { if (frameFouls.contains(String.valueOf(i))) statValues[fragment.mStatsFouls][0]++; } if (frameNumber == Constants.NUMBER_OF_FRAMES) { totalShotsAtMiddle++; int ballValue = StatUtils.getFirstBallValue(pinState[0], fragment.mCountH2, fragment.mCountS2); if (ballValue != -1) statValues[fragment.mStatsGeneral][StatUtils.STAT_MIDDLE_HIT]++; else { if (pinState[0][0] || pinState[0][1]) statValues[fragment.mStatsGeneral][StatUtils.STAT_LEFT_OF_MIDDLE]++; if (pinState[0][3] || pinState[0][4]) statValues[fragment.mStatsGeneral][StatUtils.STAT_RIGHT_OF_MIDDLE]++; } fragment.increaseFirstBallStat(ballValue, statValues, 0); if (ballValue < 5 && ballValue != Constants.BALL_VALUE_STRIKE) spareChances++; if (ballValue != 0) { if (Arrays.equals(pinState[1], Constants.FRAME_PINS_DOWN)) { statValues[fragment.mStatsGeneral][StatUtils.STAT_SPARE_CONVERSIONS]++; fragment.increaseFirstBallStat(ballValue, statValues, 1); if (ballValue >= 5) spareChances++; } else { statValues[fragment.mStatsPins][StatUtils.STAT_PINS_LEFT] += fragment.countPinsLeftStanding(pinState[2]); } } else { totalShotsAtMiddle++; ballValue = StatUtils.getFirstBallValue(pinState[1], fragment.mCountH2, fragment.mCountS2); if (ballValue != -1) statValues[fragment.mStatsGeneral][StatUtils.STAT_MIDDLE_HIT]++; else { if (pinState[1][0] || pinState[1][1]) statValues[fragment.mStatsGeneral][StatUtils.STAT_LEFT_OF_MIDDLE]++; if (pinState[1][3] || pinState[1][4]) statValues[fragment.mStatsGeneral][StatUtils.STAT_RIGHT_OF_MIDDLE]++; } fragment.increaseFirstBallStat(ballValue, statValues, 0); if (ballValue != 0) { if (Arrays.equals(pinState[2], Constants.FRAME_PINS_DOWN)) { statValues[fragment.mStatsGeneral][StatUtils.STAT_SPARE_CONVERSIONS]++; fragment.increaseFirstBallStat(ballValue, statValues, 1); if (ballValue >= 5) spareChances++; } else { statValues[fragment.mStatsPins][StatUtils.STAT_PINS_LEFT] += fragment.countPinsLeftStanding(pinState[2]); } } else { totalShotsAtMiddle++; ballValue = StatUtils.getFirstBallValue(pinState[2], fragment.mCountH2, fragment.mCountS2); if (ballValue != -1) statValues[fragment.mStatsGeneral][StatUtils.STAT_MIDDLE_HIT]++; else { if (pinState[2][0] || pinState[2][1]) statValues[fragment.mStatsGeneral][StatUtils.STAT_LEFT_OF_MIDDLE]++; if (pinState[2][3] || pinState[2][4]) statValues[fragment.mStatsGeneral][StatUtils.STAT_RIGHT_OF_MIDDLE]++; } fragment.increaseFirstBallStat(ballValue, statValues, 0); if (ballValue != 0) { statValues[fragment.mStatsPins][StatUtils.STAT_PINS_LEFT] += fragment.countPinsLeftStanding(pinState[2]); } } } } else { totalShotsAtMiddle++; int ballValue = StatUtils.getFirstBallValue(pinState[0], fragment.mCountH2, fragment.mCountS2); if (ballValue != -1) statValues[fragment.mStatsGeneral][StatUtils.STAT_MIDDLE_HIT]++; else { if (pinState[0][0] || pinState[0][1]) statValues[fragment.mStatsGeneral][StatUtils.STAT_LEFT_OF_MIDDLE]++; if (pinState[0][3] || pinState[0][4]) statValues[fragment.mStatsGeneral][StatUtils.STAT_RIGHT_OF_MIDDLE]++; } fragment.increaseFirstBallStat(ballValue, statValues, 0); if (ballValue < 5 && ballValue != Constants.BALL_VALUE_STRIKE) spareChances++; if (ballValue != 0) { if (Arrays.equals(pinState[1], Constants.FRAME_PINS_DOWN)) { statValues[fragment.mStatsGeneral][StatUtils.STAT_SPARE_CONVERSIONS]++; fragment.increaseFirstBallStat(ballValue, statValues, 1); if (ballValue >= 5) spareChances++; } else { statValues[fragment.mStatsPins][StatUtils.STAT_PINS_LEFT] += fragment.countPinsLeftStanding(pinState[2]); } } } cursor.moveToNext(); } } if (toLoad != StatUtils.LOADING_GAME_STATS) { if (statValues[fragment.mStatsOverall][StatUtils.STAT_HIGH_SERIES] < seriesTotal) statValues[fragment.mStatsOverall][StatUtils.STAT_HIGH_SERIES] = seriesTotal; if (toLoad != StatUtils.LOADING_SERIES_STATS) { for (byte i = 0; i < numberOfGames; i++) statValues[fragment.mStatsGameAverage][i] = (countByGame[i] > 0) ? totalByGame[i] / countByGame[i] : 0; } // Add the base averages from the leagues to the stats for (int i = 0; i < mBaseAverages.size() && i < mBaseGameCounts.size(); i++) { statValues[fragment.mStatsOverall][StatUtils.STAT_TOTAL_PINS] += mBaseAverages.get(i) * mBaseGameCounts.get(i); statValues[fragment.mStatsOverall][StatUtils.STAT_NUMBER_OF_GAMES] += mBaseGameCounts.get(i); } if (statValues[fragment.mStatsOverall][StatUtils.STAT_NUMBER_OF_GAMES] > 0) { statValues[fragment.mStatsOverall][StatUtils.STAT_AVERAGE] = statValues[fragment.mStatsOverall][StatUtils.STAT_TOTAL_PINS] / statValues[fragment.mStatsOverall][StatUtils.STAT_NUMBER_OF_GAMES]; statValues[fragment.mStatsPins][StatUtils.STAT_PINS_AVERAGE] = statValues[fragment.mStatsPins][StatUtils.STAT_PINS_LEFT] / statValues[fragment.mStatsOverall][StatUtils.STAT_NUMBER_OF_GAMES]; } } cursor.close(); fragment.setGeneralAndDetailedStatValues(listStatNamesAndValues, statValues, totalShotsAtMiddle, spareChances, fragment.mNumberOfGeneralDetails, toLoad); return new List<?>[]{listStatHeaders, listStatNamesAndValues}; } @SuppressWarnings("unchecked") // Types of parameters are known @Override protected void onPostExecute(List<?>[] lists) { StatsListFragment fragment = mFragment.get(); if (lists == null || fragment == null) return; fragment.mListStatHeaders.addAll((List<String>) lists[0]); fragment.mListStatNamesAndValues.addAll( (List<List<Pair<String, String>>>) lists[1]); fragment.mAdapterStats.notifyDataSetChanged(); } } /** * Sets the strings in the list mListStatValues. * * @param listStatNamesAndValues stat names and values * @param statValues raw value of stat * @param totalShotsAtMiddle total "first ball" opportunities for a game, league or bowler * @param spareChances total chances a bowler had to spare a ball * @param statOffset position in mListStatValues to start altering * @param toLoad stats being loaded */ @SuppressWarnings("CheckStyle") private void setGeneralAndDetailedStatValues( List<List<Pair<String, String>>> listStatNamesAndValues, int[][] statValues, int totalShotsAtMiddle, int spareChances, int statOffset, byte toLoad) { int currentStatPosition = statOffset; final DecimalFormat decimalFormat = new DecimalFormat("##0.#"); if (statValues[mStatsGeneral][StatUtils.STAT_MIDDLE_HIT] > 0) { listStatNamesAndValues.get(mStatsGeneral).set(currentStatPosition, Pair.create( listStatNamesAndValues.get(mStatsGeneral).get(currentStatPosition).first, decimalFormat.format(statValues[mStatsGeneral][StatUtils.STAT_MIDDLE_HIT] / (double) totalShotsAtMiddle * 100) + "% [" + statValues[mStatsGeneral][StatUtils.STAT_MIDDLE_HIT] + "/" + totalShotsAtMiddle + "]")); } currentStatPosition++; if (statValues[mStatsGeneral][StatUtils.STAT_LEFT_OF_MIDDLE] > 0) { listStatNamesAndValues.get(mStatsGeneral).set(currentStatPosition, Pair.create( listStatNamesAndValues.get(mStatsGeneral).get(currentStatPosition).first, decimalFormat.format(statValues[mStatsGeneral][StatUtils.STAT_LEFT_OF_MIDDLE] / (double) totalShotsAtMiddle * 100) + "% [" + statValues[mStatsGeneral][StatUtils.STAT_LEFT_OF_MIDDLE] + "/" + totalShotsAtMiddle + "]")); } currentStatPosition++; if (statValues[mStatsGeneral][StatUtils.STAT_RIGHT_OF_MIDDLE] > 0) { listStatNamesAndValues.get(mStatsGeneral).set(currentStatPosition, Pair.create( listStatNamesAndValues.get(mStatsGeneral).get(currentStatPosition).first, decimalFormat.format(statValues[mStatsGeneral][StatUtils.STAT_RIGHT_OF_MIDDLE] / (double) totalShotsAtMiddle * 100) + "% [" + statValues[mStatsGeneral][StatUtils.STAT_RIGHT_OF_MIDDLE] + "/" + totalShotsAtMiddle + "]")); } currentStatPosition++; if (statValues[mStatsGeneral][StatUtils.STAT_STRIKES] > 0) { listStatNamesAndValues.get(mStatsGeneral).set(currentStatPosition, Pair.create( listStatNamesAndValues.get(mStatsGeneral).get(currentStatPosition).first, decimalFormat.format(statValues[mStatsGeneral][StatUtils.STAT_STRIKES] / (double) totalShotsAtMiddle * 100) + "% [" + statValues[mStatsGeneral][StatUtils.STAT_STRIKES] + "/" + totalShotsAtMiddle + "]")); } currentStatPosition++; if (spareChances > 0) { listStatNamesAndValues.get(mStatsGeneral).set(currentStatPosition, Pair.create( listStatNamesAndValues.get(mStatsGeneral).get(currentStatPosition).first, decimalFormat.format(statValues[mStatsGeneral][StatUtils.STAT_SPARE_CONVERSIONS] / (double) spareChances * 100) + "% [" + statValues[mStatsGeneral][StatUtils.STAT_SPARE_CONVERSIONS] + "/" + spareChances + "]")); } currentStatPosition = 0; for (int i = 0; i < StatUtils.STAT_RIGHT_SPLIT_SPARED; i += 2, currentStatPosition += 2) { if (statValues[mStatsFirstBall][i] > 0) { listStatNamesAndValues.get(mStatsFirstBall).set(currentStatPosition, Pair.create( listStatNamesAndValues.get(mStatsFirstBall).get(currentStatPosition).first, decimalFormat.format( statValues[mStatsFirstBall][i] / (double) totalShotsAtMiddle * 100) + "% [" + statValues[mStatsFirstBall][i] + "/" + totalShotsAtMiddle + "]")); listStatNamesAndValues.get(mStatsFirstBall).set(currentStatPosition + 1, Pair.create(listStatNamesAndValues.get(mStatsFirstBall) .get(currentStatPosition + 1).first, decimalFormat.format(statValues[mStatsFirstBall][i + 1] / (double) statValues[mStatsFirstBall][i] * 100) + "% [" + statValues[mStatsFirstBall][i + 1] + "/" + statValues[mStatsFirstBall][i] + "]")); } } listStatNamesAndValues.get(mStatsFouls).set(0, Pair.create( listStatNamesAndValues.get(mStatsFouls).get(0).first, String.valueOf(statValues[mStatsFouls][0]))); listStatNamesAndValues.get(mStatsPins).set(0, Pair.create( listStatNamesAndValues.get(mStatsPins).get(0).first, String.valueOf(statValues[mStatsPins][0]))); if (toLoad < StatUtils.LOADING_GAME_STATS) { if (toLoad != StatUtils.LOADING_SERIES_STATS) { for (byte i = 0; i < statValues[mStatsGameAverage].length; i++) listStatNamesAndValues.get(mStatsGameAverage).set(i, Pair.create( listStatNamesAndValues.get(mStatsGameAverage).get(i).first, String.valueOf(statValues[mStatsGameAverage][i]))); } listStatNamesAndValues.get(mStatsPins).set(1, Pair.create( listStatNamesAndValues.get(mStatsPins).get(1).first, String.valueOf(statValues[mStatsPins][1]))); int totalMatchPlayGames = 0; for (int stat : statValues[mStatsMatch]) totalMatchPlayGames += stat; if (totalMatchPlayGames > 0) { for (byte i = 0; i < statValues[mStatsMatch].length; i++) listStatNamesAndValues.get(mStatsMatch).set(i, Pair.create( listStatNamesAndValues.get(mStatsMatch).get(i).first, decimalFormat.format( statValues[mStatsMatch][i] / (double) totalMatchPlayGames * 100) + "% [" + statValues[mStatsMatch][i] + "/" + totalMatchPlayGames + "]")); } for (byte i = 0; i < statValues[mStatsOverall].length; i++) listStatNamesAndValues.get(mStatsOverall).set(i, Pair.create( listStatNamesAndValues.get(mStatsOverall).get(i).first, String.valueOf(statValues[mStatsOverall][i]))); } } /** * Counts the total value of pins which were left at the end of a frame on the third ball. * * @param thirdBall state of the pins after the third ball * @return total value of pins left standing */ @SuppressWarnings("CheckStyle") private int countPinsLeftStanding(boolean[] thirdBall) { int pinsLeftStanding = 0; for (int i = 0; i < thirdBall.length; i++) { if (!thirdBall[i]) { switch (i) { case 0: case 4: pinsLeftStanding += 2; break; case 1: case 3: pinsLeftStanding += 3; break; case 2: pinsLeftStanding += 5; break; default: // does nothing } } } return pinsLeftStanding; } /** * Checks which situation has occurred by the state of the pins in ball. * * @param ball result of the pins after a ball was thrown * @param statValues stat values to update * @param offset indicates a spare was thrown and the spare count should be increased for a stat */ private void increaseFirstBallStat(int ball, int[][] statValues, int offset) { if (offset > 1 || offset < 0) throw new IllegalArgumentException("Offset must be either 0 or 1: " + offset); switch (ball) { case Constants.BALL_VALUE_STRIKE: if (offset == 0) statValues[mStatsGeneral][StatUtils.STAT_STRIKES]++; break; case Constants.BALL_VALUE_LEFT: statValues[mStatsFirstBall][StatUtils.STAT_LEFT + offset]++; break; case Constants.BALL_VALUE_RIGHT: statValues[mStatsFirstBall][StatUtils.STAT_RIGHT + offset]++; break; case Constants.BALL_VALUE_LEFT_CHOP: statValues[mStatsFirstBall][StatUtils.STAT_LEFT_CHOP + offset]++; statValues[mStatsFirstBall][StatUtils.STAT_CHOP + offset]++; break; case Constants.BALL_VALUE_RIGHT_CHOP: statValues[mStatsFirstBall][StatUtils.STAT_RIGHT_CHOP + offset]++; statValues[mStatsFirstBall][StatUtils.STAT_CHOP + offset]++; break; case Constants.BALL_VALUE_ACE: statValues[mStatsFirstBall][StatUtils.STAT_ACES + offset]++; break; case Constants.BALL_VALUE_LEFT_SPLIT: statValues[mStatsFirstBall][StatUtils.STAT_LEFT_SPLIT + offset]++; statValues[mStatsFirstBall][StatUtils.STAT_SPLIT + offset]++; break; case Constants.BALL_VALUE_RIGHT_SPLIT: statValues[mStatsFirstBall][StatUtils.STAT_RIGHT_SPLIT + offset]++; statValues[mStatsFirstBall][StatUtils.STAT_SPLIT + offset]++; break; case Constants.BALL_VALUE_HEAD_PIN: statValues[mStatsFirstBall][StatUtils.STAT_HEAD_PINS + offset]++; break; default: // if (mCountH2) { // if ((pins[0] || pins[4]) && pins[2]) { // statValues[mStatsFirstBall][StatUtils.STAT_HEAD_PINS + offset]++; // } // } // // if (mCountS2) { // if (pins[1] && pins[2] && pins[4]) { // statValues[mStatsFirstBall][StatUtils.STAT_LEFT_SPLIT + offset]++; // statValues[mStatsFirstBall][StatUtils.STAT_SPLIT + offset]++; // } else if (pins[0] && pins[2] && pins[3]) { // statValues[mStatsFirstBall][StatUtils.STAT_RIGHT_SPLIT + offset]++; // statValues[mStatsFirstBall][StatUtils.STAT_SPLIT + offset]++; // } // } // break; } } /** * Returns a cursor from database to load either bowler or league stats. * * @param shouldGetLeagueStats if true, league stats will be loaded. Bowler stats will be loaded otherwise * @return a cursor with rows relevant to mBowlerId or mLeagueId */ private Cursor getBowlerOrLeagueCursor(boolean shouldGetLeagueStats) { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getContext()); boolean isEventIncluded = preferences.getBoolean(Constants.KEY_INCLUDE_EVENTS, true); boolean isOpenIncluded = preferences.getBoolean(Constants.KEY_INCLUDE_OPEN, true); SQLiteDatabase database = DatabaseHelper.getInstance(getContext()).getReadableDatabase(); String rawStatsQuery = "SELECT " + Contract.GameEntry.COLUMN_SCORE + ", " + Contract.GameEntry.COLUMN_GAME_NUMBER + ", " + Contract.GameEntry.COLUMN_IS_MANUAL + ", " + Contract.GameEntry.COLUMN_MATCH_PLAY + ", " + Contract.FrameEntry.COLUMN_FRAME_NUMBER + ", " + Contract.FrameEntry.COLUMN_IS_ACCESSED + ", " + Contract.FrameEntry.COLUMN_FOULS + ", " + Contract.FrameEntry.COLUMN_PIN_STATE[0] + ", " + Contract.FrameEntry.COLUMN_PIN_STATE[1] + ", " + Contract.FrameEntry.COLUMN_PIN_STATE[2] + " FROM " + Contract.LeagueEntry.TABLE_NAME + " AS league" + " INNER JOIN " + Contract.SeriesEntry.TABLE_NAME + " AS series" + " ON league." + Contract.LeagueEntry._ID + "=series." + Contract.SeriesEntry.COLUMN_LEAGUE_ID + " INNER JOIN " + Contract.GameEntry.TABLE_NAME + " AS game" + " ON series." + Contract.SeriesEntry._ID + "=game." + Contract.GameEntry.COLUMN_SERIES_ID + " INNER JOIN " + Contract.FrameEntry.TABLE_NAME + " AS frame" + " ON game." + Contract.GameEntry._ID + "=frame." + Contract.FrameEntry.COLUMN_GAME_ID + ((shouldGetLeagueStats) ? " WHERE league." + Contract.LeagueEntry._ID + "=?" : " WHERE league." + Contract.LeagueEntry.COLUMN_BOWLER_ID + "=?") + " AND " + ((!shouldGetLeagueStats && !isEventIncluded) ? Contract.LeagueEntry.COLUMN_IS_EVENT : "'0'") + "=?" + " AND " + ((!shouldGetLeagueStats && !isOpenIncluded) ? Contract.LeagueEntry.COLUMN_LEAGUE_NAME + "!" : "'0'") + "=?" + " ORDER BY league." + Contract.LeagueEntry._ID + ", series." + Contract.SeriesEntry._ID + ", game." + Contract.GameEntry.COLUMN_GAME_NUMBER + ", frame." + Contract.FrameEntry.COLUMN_FRAME_NUMBER; String[] rawStatsArgs = { ((shouldGetLeagueStats) ? String.valueOf(((MainActivity) getActivity()).getLeagueId()) : String.valueOf(((MainActivity) getActivity()).getBowlerId())), String.valueOf(0), ((!shouldGetLeagueStats && !isOpenIncluded) ? Constants.NAME_OPEN_LEAGUE : String.valueOf(0)) }; return database.rawQuery(rawStatsQuery, rawStatsArgs); } /** * Returns a cursor from database to load series stats. * * @return a cursor with rows relevant to mSeriesId */ private Cursor getSeriesCursor() { SQLiteDatabase database = DatabaseHelper.getInstance(getContext()).getReadableDatabase(); String rawStatsQuery = "SELECT " + Contract.GameEntry.COLUMN_SCORE + ", " + Contract.GameEntry.COLUMN_GAME_NUMBER + ", " + Contract.GameEntry.COLUMN_IS_MANUAL + ", " + Contract.GameEntry.COLUMN_MATCH_PLAY + ", " + Contract.FrameEntry.COLUMN_FRAME_NUMBER + ", " + Contract.FrameEntry.COLUMN_IS_ACCESSED + ", " + Contract.FrameEntry.COLUMN_FOULS + ", " + Contract.FrameEntry.COLUMN_PIN_STATE[0] + ", " + Contract.FrameEntry.COLUMN_PIN_STATE[1] + ", " + Contract.FrameEntry.COLUMN_PIN_STATE[2] + " FROM " + Contract.GameEntry.TABLE_NAME + " AS game" + " INNER JOIN " + Contract.FrameEntry.TABLE_NAME + " AS frame" + " ON game." + Contract.GameEntry._ID + "=frame." + Contract.FrameEntry.COLUMN_GAME_ID + " WHERE game." + Contract.GameEntry.COLUMN_SERIES_ID + "=?" + " ORDER BY game." + Contract.GameEntry.COLUMN_GAME_NUMBER + ", frame." + Contract.FrameEntry.COLUMN_FRAME_NUMBER; String[] rawStatsArgs = {String.valueOf(((MainActivity) getActivity()).getSeriesId())}; return database.rawQuery(rawStatsQuery, rawStatsArgs); } /** * Returns a cursor from the database to load game stats. * * @return a cursor with rows relevant to mGameId */ private Cursor getGameCursor() { SQLiteDatabase database = DatabaseHelper.getInstance(getContext()).getReadableDatabase(); String rawStatsQuery = "SELECT " + Contract.GameEntry.COLUMN_SCORE + ", " + Contract.GameEntry.COLUMN_IS_MANUAL + ", " + Contract.FrameEntry.COLUMN_FRAME_NUMBER + ", " + Contract.FrameEntry.COLUMN_IS_ACCESSED + ", " + Contract.FrameEntry.COLUMN_FOULS + ", " + Contract.FrameEntry.COLUMN_PIN_STATE[0] + ", " + Contract.FrameEntry.COLUMN_PIN_STATE[1] + ", " + Contract.FrameEntry.COLUMN_PIN_STATE[2] + " FROM " + Contract.GameEntry.TABLE_NAME + " AS game" + " INNER JOIN " + Contract.FrameEntry.TABLE_NAME + " AS frame" + " ON game." + Contract.GameEntry._ID + "=frame." + Contract.FrameEntry.COLUMN_GAME_ID + " WHERE game." + Contract.GameEntry._ID + "=?" + " ORDER BY " + Contract.FrameEntry.COLUMN_FRAME_NUMBER; String[] rawStatsArgs = {String.valueOf(((MainActivity) getActivity()).getGameId())}; return database.rawQuery(rawStatsQuery, rawStatsArgs); } /** * Displays the selected statistic as a graph. * * @param statCategory category of stat to display * @param statIndex index in category of stat to displau */ private void openStatGraph(int statCategory, int statIndex) { MainActivity mainActivity = (MainActivity) getActivity(); if (mainActivity != null) mainActivity.openStatGraph(statCategory, statIndex); } }