package ca.josephroque.bowlingcompanion.fragment; import android.app.AlertDialog; import android.content.ContentValues; import android.content.Context; 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.DialogFragment; import android.support.v4.app.Fragment; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.helper.ItemTouchHelper; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import java.lang.ref.WeakReference; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Locale; import ca.josephroque.bowlingcompanion.Constants; import ca.josephroque.bowlingcompanion.MainActivity; import ca.josephroque.bowlingcompanion.R; import ca.josephroque.bowlingcompanion.adapter.NameAverageAdapter; import ca.josephroque.bowlingcompanion.wrapper.Bowler; import ca.josephroque.bowlingcompanion.wrapper.LeagueEvent; import ca.josephroque.bowlingcompanion.database.Contract.BowlerEntry; import ca.josephroque.bowlingcompanion.database.Contract.GameEntry; import ca.josephroque.bowlingcompanion.database.Contract.LeagueEntry; import ca.josephroque.bowlingcompanion.database.Contract.SeriesEntry; import ca.josephroque.bowlingcompanion.database.DatabaseHelper; import ca.josephroque.bowlingcompanion.dialog.NameBowlerDialog; import ca.josephroque.bowlingcompanion.utilities.DisplayUtils; import ca.josephroque.bowlingcompanion.utilities.FacebookUtils; import ca.josephroque.bowlingcompanion.utilities.FloatingActionButtonHandler; /** * Created by Joseph Roque on 15-03-13. Manages the UI to display information about the bowlers being tracked by the * application, and offers a callback interface {@code BowlerFragment.BowlerCallback} for handling interactions. */ @SuppressWarnings("Convert2Lambda") public class BowlerFragment extends Fragment implements NameAverageAdapter.NameAverageEventHandler, NameBowlerDialog.NameBowlerDialogListener, FloatingActionButtonHandler { /** Identifies output from this class in Logcat. */ @SuppressWarnings("unused") private static final String TAG = "BowlerFragment"; /** View to display bowler names and averages to user. */ private RecyclerView mRecyclerViewBowlers; /** Adapter to manage data displayed in mRecyclerViewBowlers. */ private NameAverageAdapter<Bowler> mAdapterBowlers; /** Callback listener for user events related to bowlers. */ private BowlerCallback mBowlerCallback; /** Callback listener for user events related to leagues. */ private LeagueEventFragment.LeagueEventCallback mLeagueSelectedCallback; /** Callback listener for user events related to series. */ private SeriesFragment.SeriesCallback mSeriesCallback; /** List to store bowler data from bowler table in database. */ private List<Bowler> mListBowlers; /** Id from 'bowler' database which represents the most recently used bowler. */ private long mRecentBowlerId = -1; /** Id from 'league' database which represents the most recently edited league. */ private long mRecentLeagueId = -1; /** Number of games in the most recently edited league. */ private int mRecentNumberOfGames = -1; /** Name of most recently edited bowler. */ private String mRecentBowlerName; /** Name of most recently edited league. */ private String mRecentLeagueName; /** Id from 'bowler' database which represents the preferred bowler for a quick series. */ private long mQuickBowlerId = -1; /** Id from 'league' database which represents the preferred league for quick series. */ private long mQuickLeagueId = -1; /** Number of games in the preferred league. */ private int mQuickNumberOfGames = -1; /** Name of preferred bowler. */ private String mQuickBowlerName; /** Name of preferred league. */ private String mQuickLeagueName; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); } @Override public void onAttach(Context context) { super.onAttach(context); /* * This makes sure the container Activity has implemented * the callback interface. If not, an exception is thrown */ try { mBowlerCallback = (BowlerCallback) context; mLeagueSelectedCallback = (LeagueEventFragment.LeagueEventCallback) context; mSeriesCallback = (SeriesFragment.SeriesCallback) context; } catch (ClassCastException ex) { throw new ClassCastException(context.toString() + " must implement OnBowlerSelectedListener, OnLeagueSelectedListener," + " SeriesListener"); } } @Override public void onDetach() { super.onDetach(); mBowlerCallback = null; mLeagueSelectedCallback = null; mSeriesCallback = null; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View rootView = inflater.inflate(R.layout.fragment_list, container, false); mListBowlers = new ArrayList<>(); mRecyclerViewBowlers = (RecyclerView) rootView.findViewById(R.id.rv_names); mRecyclerViewBowlers.setHasFixedSize(true); ItemTouchHelper.SimpleCallback touchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) { @Override public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { return false; } @Override public void onSwiped(final RecyclerView.ViewHolder viewHolder, int direction) { final int position = viewHolder.getAdapterPosition(); mListBowlers.get(position).setIsDeleted(!mListBowlers.get(position).wasDeleted()); mAdapterBowlers.notifyItemChanged(position); } }; ItemTouchHelper itemTouchHelper = new ItemTouchHelper(touchCallback); itemTouchHelper.attachToRecyclerView(mRecyclerViewBowlers); RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getContext()); mRecyclerViewBowlers.setLayoutManager(layoutManager); mAdapterBowlers = new NameAverageAdapter<>(this, mListBowlers, NameAverageAdapter.DATA_BOWLERS); mRecyclerViewBowlers.setAdapter(mAdapterBowlers); displayFacebookPagePromotion(rootView); return rootView; } @Override public void onResume() { super.onResume(); if (getActivity() != null) { MainActivity mainActivity = (MainActivity) getActivity(); mainActivity.setActionBarTitle(R.string.app_name, true); mainActivity.setDrawerState(false); mainActivity.setFloatingActionButtonState(R.drawable.ic_person_add_black_24dp, 0); // Loads values for member variables from preferences, if they exist SharedPreferences prefs = getActivity().getSharedPreferences(Constants.PREFS, Context.MODE_PRIVATE); mRecentBowlerId = prefs.getLong(Constants.PREF_RECENT_BOWLER_ID, -1); mRecentLeagueId = prefs.getLong(Constants.PREF_RECENT_LEAGUE_ID, -1); mQuickBowlerId = prefs.getLong(Constants.PREF_QUICK_BOWLER_ID, -1); mQuickLeagueId = prefs.getLong(Constants.PREF_QUICK_LEAGUE_ID, -1); boolean averageAsDecimal = PreferenceManager.getDefaultSharedPreferences(getContext()) .getBoolean(Constants.KEY_AVERAGE_AS_DECIMAL, false); mAdapterBowlers.setDisplayAverageAsDecimal(averageAsDecimal); } // Creates AsyncTask to load data from database new LoadBowlerAndRecentTask(this).execute(); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.menu_bowlers, menu); super.onCreateOptionsMenu(menu, inflater); } @Override public void onPrepareOptionsMenu(Menu menu) { MainActivity mainActivity = (MainActivity) getActivity(); if (mainActivity == null) { super.onPrepareOptionsMenu(menu); return; } boolean drawerOpen = mainActivity.isDrawerOpen(); MenuItem menuItem = menu.findItem(R.id.action_quick_series).setVisible(!drawerOpen); Drawable drawable = menuItem.getIcon(); if (drawable != null) drawable.setAlpha(DisplayUtils.BLACK_ICON_ALPHA); super.onPrepareOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_quick_series: showQuickSeriesDialog(); return true; case R.id.action_transfer: if (mBowlerCallback != null) mBowlerCallback.onDataTransferSelected(); return true; default: return super.onOptionsItemSelected(item); } } @Override public void onNAItemClick(final int position) { // When bowler name is clicked, their leagues are displayed in new fragment new OpenBowlerLeaguesTask(this).execute(position); } @Override public void onNAItemLongClick(final int position) { // When bowler name is long clicked, a dialog is opened to change or delete the item showLongClickBowlerDialog(position); } @Override public void onNAItemDelete(long id) { for (int i = 0; i < mListBowlers.size(); i++) { if (mListBowlers.get(i).getBowlerId() == id) { Bowler bowler = mListBowlers.remove(i); mAdapterBowlers.notifyItemRemoved(i); deleteBowler(bowler.getBowlerId()); } } } @Override public void onNAItemUndoDelete(long id) { for (int i = 0; i < mListBowlers.size(); i++) { if (mListBowlers.get(i).getBowlerId() == id) { mListBowlers.get(i).setIsDeleted(false); mAdapterBowlers.notifyItemChanged(i); } } } @Override public void onChangeBowlerName(final Bowler bowlerToChange, String name) { boolean validInput = true; int invalidInputMessage = -1; final Bowler bowlerWithNewName = new Bowler(bowlerToChange.getId(), name, bowlerToChange.getAverage()); if (mListBowlers.contains(bowlerWithNewName)) { // Bowler name already exists in the list validInput = false; invalidInputMessage = R.string.dialog_name_exists; } else if (!name.matches(Constants.REGEX_NAME)) { // Name is not made up of letters and spaces validInput = false; invalidInputMessage = R.string.dialog_name_letters_spaces; } if (!validInput) { // Displays an error dialog if the input was not valid and exits the method new AlertDialog.Builder(getContext()) .setMessage(invalidInputMessage) .setPositiveButton(R.string.dialog_okay, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }) .create() .show(); } else { int position = mListBowlers.indexOf(bowlerToChange); mListBowlers.set(position, bowlerWithNewName); mAdapterBowlers.notifyItemChanged(position); ((MainActivity) getActivity()).addSavingThread(new Thread(new Runnable() { @Override public void run() { SQLiteDatabase database = DatabaseHelper.getInstance(getContext()).getWritableDatabase(); String[] whereArgs = {String.valueOf(bowlerWithNewName.getId())}; ContentValues values = new ContentValues(); values.put(BowlerEntry.COLUMN_BOWLER_NAME, bowlerWithNewName.getBowlerName()); database.beginTransaction(); try { database.update(BowlerEntry.TABLE_NAME, values, BowlerEntry._ID + "=?", whereArgs); database.setTransactionSuccessful(); } catch (Exception ex) { Log.e(TAG, "Error updating bowler name.", ex); } finally { database.endTransaction(); } } })); } } @Override public void onAddNewBowler(String bowlerName) { boolean validInput = true; int invalidInputMessage = -1; Bowler newBowler = new Bowler(0, bowlerName, (short) 0); if (mListBowlers.contains(newBowler)) { // Bowler name already exists in the list validInput = false; invalidInputMessage = R.string.dialog_name_exists; } else if (!bowlerName.matches(Constants.REGEX_NAME)) { // Name is not made up of letters and spaces validInput = false; invalidInputMessage = R.string.dialog_name_letters_spaces; } /* * If the input was invalid for any reason, a dialog is shown * to the user and the method does not continue */ if (!validInput) { AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); builder.setMessage(invalidInputMessage) .setPositiveButton(R.string.dialog_okay, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }) .create() .show(); return; } /* * Creates a new database entry for the bowler whose name * was received by input via the dialog */ new NewBowlerTask(this).execute(newBowler); } @Override public void onFabClick() { showNewBowlerDialog(); } @Override public void onSecondaryFabClick() { // does nothing } /** * Invoked when a bowler item is long clicked by the user. Displays a dialog with events relevant to the item. * * @param position position of the long clicked item */ private void showLongClickBowlerDialog(final int position) { DialogInterface.OnClickListener onClickListener = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (which == DialogInterface.BUTTON_POSITIVE) { int selectedPosition = ((AlertDialog) dialog).getListView().getCheckedItemPosition(); if (selectedPosition == 0) promptChangeItemName(position); else promptDeleteItem(position); } dialog.dismiss(); } }; final Bowler bowler = mListBowlers.get(position); new AlertDialog.Builder(getContext()) .setTitle(bowler.getBowlerName()) .setSingleChoiceItems(R.array.arr_long_click_name_average, 0, null) .setPositiveButton(R.string.dialog_okay, onClickListener) .setNegativeButton(R.string.dialog_cancel, onClickListener) .create() .show(); } /** * Prompts user to change the name of a bowler. * * @param position position of the bowler to change the name of */ private void promptChangeItemName(final int position) { DialogFragment dialogFragment = NameBowlerDialog.newInstance(this, true, mListBowlers.get(position)); dialogFragment.show(getFragmentManager(), "ChangeNameBowlerDialog"); } /** * Prompts the user to delete a bowler. * * @param position position of the item to delete */ private void promptDeleteItem(final int position) { final Bowler bowler = mListBowlers.get(position); DialogInterface.OnClickListener onClickListener = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (which == DialogInterface.BUTTON_POSITIVE) { mListBowlers.remove(position); mAdapterBowlers.notifyItemRemoved(position); deleteBowler(bowler.getId()); } dialog.dismiss(); } }; new AlertDialog.Builder(getContext()) .setTitle("Warning!") .setMessage("Are you sure you want to delete " + bowler.getBowlerName() + "?" + " This cannot be undone!") .setPositiveButton(R.string.dialog_delete, onClickListener) .setNegativeButton(R.string.dialog_cancel, onClickListener) .create() .show(); } /** * Prompts user to create a new series with mRecentBowlerId and mRecentLeagueId or mQuickBowlerId and * mQuickLeagueId. */ private void showQuickSeriesDialog() { if ((mQuickBowlerId > -1 && mQuickLeagueId > -1) || (mRecentBowlerId > -1 && mRecentLeagueId > -1)) { /* * If a quick bowler was set, or a bowler has been previously selected, * a dialog is displayed to prompt user to create a new series */ final boolean quickOrRecent; AlertDialog.Builder quickSeriesBuilder = new AlertDialog.Builder(getContext()); if (mQuickBowlerId == -1 || mQuickLeagueId == -1) { quickSeriesBuilder.setMessage("Create a new series with these settings?" + "\nBowler: " + mRecentBowlerName + "\nLeague: " + mRecentLeagueName); quickOrRecent = false; } else { quickSeriesBuilder.setMessage("Create a new series with these settings?" + "\nBowler: " + mQuickBowlerName + "\nLeague: " + mQuickLeagueName); quickOrRecent = true; } quickSeriesBuilder.setTitle("Quick Series") .setPositiveButton(R.string.dialog_okay, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (quickOrRecent) { Bowler quickBowler = new Bowler(mQuickBowlerId, mQuickBowlerName, (short) 0); if (mBowlerCallback != null && mLeagueSelectedCallback != null && mSeriesCallback != null) { mBowlerCallback.onBowlerSelected(quickBowler, false, true); mLeagueSelectedCallback.onLeagueSelected(new LeagueEvent( mQuickLeagueId, mQuickLeagueName, true, (short) 0, (short) -1, -1, mQuickNumberOfGames), false); mSeriesCallback.onCreateNewSeries(false); } } else { Bowler recentBowler = new Bowler(mRecentBowlerId, mRecentBowlerName, (short) 0); if (mBowlerCallback != null && mLeagueSelectedCallback != null && mSeriesCallback != null) { mBowlerCallback.onBowlerSelected(recentBowler, false, true); mLeagueSelectedCallback.onLeagueSelected(new LeagueEvent( mRecentLeagueId, mRecentLeagueName, true, (short) 0, (short) -1, -1, mRecentNumberOfGames), false); mSeriesCallback.onCreateNewSeries(false); } } dialog.dismiss(); } }) .setNegativeButton(R.string.dialog_cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }) .create() .show(); } else { // If no recent/quick bowler, dialog is displayed to inform user of options AlertDialog.Builder quickSeriesDisabledBuilder = new AlertDialog.Builder(getContext()); quickSeriesDisabledBuilder.setTitle("Quick Series") .setMessage(R.string.dialog_quick_series_instructions) .setPositiveButton(R.string.dialog_okay, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }) .create() .show(); } } /** * Prepares and shows promotional content for Facebook page, if the user has not visited the page before. * * @param rootView fragment root view */ private void displayFacebookPagePromotion(View rootView) { if (getContext() == null) return; final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getContext()); if (preferences.getBoolean(Constants.PREF_FACEBOOK_PAGE_OPENED, false) || preferences.getBoolean(Constants.PREF_FACEBOOK_CLOSED, false)) return; final LinearLayout linearLayout = (LinearLayout) rootView.findViewById(R.id.ll_facebook_promotion); linearLayout.setVisibility(View.VISIBLE); View.OnClickListener openFacebookListener = new View.OnClickListener() { @Override public void onClick(View v) { linearLayout.setVisibility(View.GONE); preferences.edit().putBoolean(Constants.PREF_FACEBOOK_PAGE_OPENED, true).apply(); startActivity(FacebookUtils.newFacebookIntent(getContext().getPackageManager())); } }; linearLayout.findViewById(R.id.iv_facebook).setOnClickListener(openFacebookListener); linearLayout.findViewById(R.id.tv_facebook).setOnClickListener(openFacebookListener); linearLayout.findViewById(R.id.iv_facebook_clear).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { preferences.edit().putBoolean(Constants.PREF_FACEBOOK_CLOSED, true).apply(); linearLayout.setVisibility(View.GONE); } }); } /** * Opens an instance of NewBowlerDialog and displays it to the user. */ private void showNewBowlerDialog() { DialogFragment dialogFragment = NameBowlerDialog.newInstance(this, false, null); dialogFragment.show(getFragmentManager(), "NewBowlerDialog"); } /** * Deletes all data regarding a certain bowler id in the database. * * @param bowlerId id of bowler whose data will be deleted */ private void deleteBowler(final long bowlerId) { SharedPreferences prefs = getActivity().getSharedPreferences(Constants.PREFS, Context.MODE_PRIVATE); SharedPreferences.Editor prefsEditor = prefs.edit(); long recentId = prefs.getLong(Constants.PREF_RECENT_BOWLER_ID, -1); long quickId = prefs.getLong(Constants.PREF_QUICK_BOWLER_ID, -1); // Clears recent/quick ids if they match the deleted bowler if (recentId == bowlerId) { prefsEditor.putLong(Constants.PREF_RECENT_BOWLER_ID, -1) .putLong(Constants.PREF_RECENT_LEAGUE_ID, -1); } if (quickId == bowlerId) { prefsEditor.putLong(Constants.PREF_QUICK_BOWLER_ID, -1) .putLong(Constants.PREF_QUICK_LEAGUE_ID, -1); } prefsEditor.apply(); new Thread(new Runnable() { @Override public void run() { SQLiteDatabase database = DatabaseHelper.getInstance(getContext()).getWritableDatabase(); String[] whereArgs = {String.valueOf(bowlerId)}; database.beginTransaction(); try { database.delete(BowlerEntry.TABLE_NAME, BowlerEntry._ID + "=?", whereArgs); database.setTransactionSuccessful(); } catch (Exception e) { // does nothing } finally { database.endTransaction(); } } }).start(); } /** * Creates a new instance of this fragment to display. * * @return a new instance of BowlerFragment */ public static BowlerFragment newInstance() { return new BowlerFragment(); } /** * Loads names of bowlers, along with other relevant data, and adds them to recycler view. */ private static final class LoadBowlerAndRecentTask extends AsyncTask<Void, Void, List<Bowler>> { /** Weak reference to the parent fragment. */ private final WeakReference<BowlerFragment> mFragment; /** * Assigns a weak reference to the parent fragment. * * @param fragment parent fragment */ private LoadBowlerAndRecentTask(BowlerFragment fragment) { mFragment = new WeakReference<>(fragment); } @Override protected void onPreExecute() { BowlerFragment fragment = mFragment.get(); if (fragment == null) return; fragment.mListBowlers.clear(); fragment.mAdapterBowlers.notifyDataSetChanged(); } @Override protected List<Bowler> doInBackground(Void... params) { // Method exits if fragment gets detached before reaching this call BowlerFragment 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)); SQLiteDatabase database = DatabaseHelper.getInstance(mainActivity).getReadableDatabase(); List<Bowler> listBowlers = new ArrayList<>(); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mainActivity); boolean includeEvents = preferences.getBoolean(Constants.KEY_INCLUDE_EVENTS, true); boolean includeOpen = preferences.getBoolean(Constants.KEY_INCLUDE_OPEN, true); String gameSumAndCountQuery = "SELECT " + "league2." + LeagueEntry._ID + " AS lid2, " + "SUM(game2." + GameEntry.COLUMN_SCORE + ") AS gameSum, " + "COUNT(game2." + GameEntry._ID + ") AS gameCount" + " FROM " + LeagueEntry.TABLE_NAME + " AS league2" + " INNER JOIN " + SeriesEntry.TABLE_NAME + " AS series2" + " ON lid2=" + SeriesEntry.COLUMN_LEAGUE_ID + " INNER JOIN " + GameEntry.TABLE_NAME + " AS game2" + " ON series2." + SeriesEntry._ID + "=" + GameEntry.COLUMN_SERIES_ID + " WHERE " + " game2." + GameEntry.COLUMN_SCORE + ">?" + " AND " + (!includeEvents ? LeagueEntry.COLUMN_IS_EVENT : "'0'") + "=?" + " AND " + (!includeOpen ? LeagueEntry.COLUMN_LEAGUE_NAME + "!" : "'0'") + "=?" + " GROUP BY league2." + LeagueEntry._ID; String baseAverageAndGamesQuery = "SELECT " + "league3." + LeagueEntry._ID + " AS lid3, " + LeagueEntry.COLUMN_BASE_AVERAGE + " * " + LeagueEntry.COLUMN_BASE_GAMES + " AS baseSum, " + LeagueEntry.COLUMN_BASE_GAMES + " AS baseGames" + " FROM " + LeagueEntry.TABLE_NAME + " AS league3" + " WHERE " + " league3." + LeagueEntry.COLUMN_BASE_AVERAGE + ">?"; // Query to retrieve bowler names and averages from database String rawBowlerQuery = "SELECT " + "bowler." + BowlerEntry.COLUMN_BOWLER_NAME + ", " + "bowler." + BowlerEntry._ID + " AS bid, " + "SUM(t.gameSum) AS totalSum, " + "SUM(t.gameCount) AS totalCount, " + "SUM(u.baseSum) AS totalBaseSum, " + "SUM(u.baseGames) AS totalBaseGames" + " FROM " + BowlerEntry.TABLE_NAME + " AS bowler" + " LEFT JOIN " + LeagueEntry.TABLE_NAME + " AS league" + " ON bowler." + BowlerEntry._ID + "=" + LeagueEntry.COLUMN_BOWLER_ID + " LEFT JOIN (" + gameSumAndCountQuery + ") AS t" + " ON t.lid2=league." + LeagueEntry._ID + " LEFT JOIN (" + baseAverageAndGamesQuery + ") AS u" + " ON u.lid3=league." + LeagueEntry._ID + " GROUP BY bowler." + BowlerEntry._ID + " ORDER BY bowler." + BowlerEntry.COLUMN_DATE_MODIFIED + " DESC"; String[] rawBowlerArgs = { String.valueOf(0), String.valueOf(0), (!includeOpen ? Constants.NAME_OPEN_LEAGUE : String.valueOf(0)), String.valueOf(0) }; // Adds loaded bowler names and averages to lists to display Cursor cursor = database.rawQuery(rawBowlerQuery, rawBowlerArgs); if (cursor.moveToFirst()) { while (!cursor.isAfterLast()) { int totalSum = cursor.getInt(cursor.getColumnIndex("totalSum")); int totalCount = cursor.getInt(cursor.getColumnIndex("totalCount")); int totalBaseSum = cursor.getInt(cursor.getColumnIndex("totalBaseSum")); int totalBaseGames = cursor.getInt(cursor.getColumnIndex("totalBaseGames")); float bowlerAverage = ((totalCount + totalBaseGames > 0) ? (totalSum + totalBaseSum) / (float) (totalCount + totalBaseGames) : 0); Bowler bowler = new Bowler(cursor.getLong(cursor.getColumnIndex("bid")), cursor.getString(cursor.getColumnIndex(BowlerEntry.COLUMN_BOWLER_NAME)), bowlerAverage); listBowlers.add(bowler); cursor.moveToNext(); } } cursor.close(); // If a recent bowler exists, their name and league is loaded to be used for quick series if (fragment.mRecentBowlerId > -1 && fragment.mRecentLeagueId > -1) { String rawRecentQuery = "SELECT " + BowlerEntry.COLUMN_BOWLER_NAME + ", " + LeagueEntry.COLUMN_LEAGUE_NAME + ", " + LeagueEntry.COLUMN_NUMBER_OF_GAMES + " FROM " + BowlerEntry.TABLE_NAME + " AS bowler" + " INNER JOIN " + LeagueEntry.TABLE_NAME + " AS league" + " ON bowler." + BowlerEntry._ID + "=league." + LeagueEntry.COLUMN_BOWLER_ID + " WHERE bowler." + BowlerEntry._ID + "=? " + "AND league." + LeagueEntry._ID + "=?"; String[] rawRecentArgs = new String[]{ String.valueOf(fragment.mRecentBowlerId), String.valueOf(fragment.mRecentLeagueId) }; cursor = database.rawQuery(rawRecentQuery, rawRecentArgs); if (cursor.moveToFirst()) { fragment.mRecentBowlerName = cursor.getString( cursor.getColumnIndex(BowlerEntry.COLUMN_BOWLER_NAME)); fragment.mRecentLeagueName = cursor.getString( cursor.getColumnIndex(LeagueEntry.COLUMN_LEAGUE_NAME)); fragment.mRecentNumberOfGames = (byte) cursor.getInt( cursor.getColumnIndex(LeagueEntry.COLUMN_NUMBER_OF_GAMES)); } else { fragment.mRecentBowlerId = -1; fragment.mRecentLeagueId = -1; } cursor.close(); } // If a custom bowler is set, their name and league is loaded to be used for quick series if (fragment.mQuickBowlerId > -1 && fragment.mQuickLeagueId > -1) { String rawRecentQuery = "SELECT " + BowlerEntry.COLUMN_BOWLER_NAME + ", " + LeagueEntry.COLUMN_LEAGUE_NAME + ", " + LeagueEntry.COLUMN_NUMBER_OF_GAMES + " FROM " + BowlerEntry.TABLE_NAME + " AS bowler" + " INNER JOIN " + LeagueEntry.TABLE_NAME + " AS league" + " ON bowler." + BowlerEntry._ID + "=league." + LeagueEntry.COLUMN_BOWLER_ID + " WHERE bowler." + BowlerEntry._ID + "=?" + "AND league." + LeagueEntry._ID + "=?"; String[] rawRecentArgs = new String[]{ String.valueOf(fragment.mQuickBowlerId), String.valueOf(fragment.mQuickLeagueId) }; cursor = database.rawQuery(rawRecentQuery, rawRecentArgs); if (cursor.moveToFirst()) { fragment.mQuickBowlerName = cursor.getString(cursor.getColumnIndex(BowlerEntry.COLUMN_BOWLER_NAME)); fragment.mQuickLeagueName = cursor.getString(cursor.getColumnIndex(LeagueEntry.COLUMN_LEAGUE_NAME)); fragment.mQuickNumberOfGames = (byte) cursor.getInt(cursor.getColumnIndex(LeagueEntry.COLUMN_NUMBER_OF_GAMES)); } else { fragment.mQuickBowlerId = -1; fragment.mQuickLeagueId = -1; } cursor.close(); } return listBowlers; } @Override protected void onPostExecute(List<Bowler> listBowlers) { BowlerFragment fragment = mFragment.get(); if (listBowlers == null || fragment == null) return; fragment.mListBowlers.addAll(listBowlers); fragment.mAdapterBowlers.notifyDataSetChanged(); } } /** * Sets data to be displayed in new instance of LeagueEventFragment. */ private static final class OpenBowlerLeaguesTask extends AsyncTask<Integer, Void, Bowler> { /** Weak reference to the parent fragment. */ private final WeakReference<BowlerFragment> mFragment; /** * Assigns a weak reference to the parent fragment. * * @param fragment parent fragment */ private OpenBowlerLeaguesTask(BowlerFragment fragment) { mFragment = new WeakReference<>(fragment); } @Override protected Bowler doInBackground(Integer... position) { BowlerFragment fragment = mFragment.get(); if (fragment == null || !fragment.isAdded()) return null; MainActivity mainActivity = (MainActivity) fragment.getActivity(); if (mainActivity == null) return null; Bowler bowler = fragment.mListBowlers.get(position[0]); // Updates date which bowler was last accessed in database SQLiteDatabase database = DatabaseHelper.getInstance(mainActivity).getWritableDatabase(); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CANADA); ContentValues values = new ContentValues(); values.put(BowlerEntry.COLUMN_DATE_MODIFIED, dateFormat.format(new Date())); database.beginTransaction(); try { database.update(BowlerEntry.TABLE_NAME, values, BowlerEntry._ID + "=?", new String[]{String.valueOf(bowler.getBowlerId())}); database.setTransactionSuccessful(); } catch (Exception ex) { // does nothing } finally { database.endTransaction(); } return bowler; } @Override protected void onPostExecute(Bowler result) { BowlerFragment fragment = mFragment.get(); if (result == null || fragment == null) return; // Creates new instance of LeagueEventFragment for bowler if (fragment.mBowlerCallback != null) fragment.mBowlerCallback.onBowlerSelected(result, true, false); } } /** * Creates new bowler in the database and adds them to the recycler view. */ private static final class NewBowlerTask extends AsyncTask<Bowler, Void, Bowler> { /** Weak reference to the parent fragment. */ private final WeakReference<BowlerFragment> mFragment; /** * Assigns a weak reference to the parent fragment. * * @param fragment parent fragment */ private NewBowlerTask(BowlerFragment fragment) { mFragment = new WeakReference<>(fragment); } @Override protected Bowler doInBackground(Bowler... bowler) { BowlerFragment fragment = mFragment.get(); if (fragment == null || !fragment.isAdded()) return null; MainActivity mainActivity = (MainActivity) fragment.getActivity(); if (mainActivity == null) return null; bowler[0].setBowlerId(-1); SQLiteDatabase database = DatabaseHelper.getInstance(mainActivity).getWritableDatabase(); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CANADA); String currentDate = dateFormat.format(new Date()); ContentValues values = new ContentValues(); values.put(BowlerEntry.COLUMN_BOWLER_NAME, bowler[0].getBowlerName()); values.put(BowlerEntry.COLUMN_DATE_MODIFIED, currentDate); database.beginTransaction(); try { bowler[0].setBowlerId(database.insert(BowlerEntry.TABLE_NAME, null, values)); /* * Creates an entry in the 'league' table for a default league * for the new bowler being added */ if (bowler[0].getBowlerId() != -1) { values = new ContentValues(); values.put(LeagueEntry.COLUMN_LEAGUE_NAME, Constants.NAME_OPEN_LEAGUE); values.put(LeagueEntry.COLUMN_DATE_MODIFIED, currentDate); values.put(LeagueEntry.COLUMN_BOWLER_ID, bowler[0].getBowlerId()); values.put(LeagueEntry.COLUMN_NUMBER_OF_GAMES, 1); database.insert(LeagueEntry.TABLE_NAME, null, values); } database.setTransactionSuccessful(); } catch (Exception ex) { // does nothing } finally { database.endTransaction(); } return bowler[0]; } @Override protected void onPostExecute(Bowler newBowler) { BowlerFragment fragment = mFragment.get(); /* * Adds the new bowler information to the corresponding lists * and displays them in the recycler view */ if (newBowler != null && fragment != null && newBowler.getBowlerId() != -1) { fragment.mListBowlers.add(0, newBowler); fragment.mAdapterBowlers.notifyItemInserted(0); fragment.mRecyclerViewBowlers.scrollToPosition(0); } } } /** * Container Activity must implement this interface to allow LeagueEventFragment to be loaded when a bowler is * selected. */ public interface BowlerCallback { /** * Should be overridden to create a LeagueEventFragment with the leagues belonging to the bowler represented by * bowlerId. * * @param bowler bowler whose leagues / events will be displayed * @param openLeagueFragment if new fragment should be opened * @param isQuickSeries if a quick series is being created */ void onBowlerSelected(Bowler bowler, boolean openLeagueFragment, boolean isQuickSeries); /** * Should be overridden to begin the process of transferring the user's data to a new device. */ void onDataTransferSelected(); } }