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.util.Pair;
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 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.utilities.Score;
import ca.josephroque.bowlingcompanion.database.Contract.FrameEntry;
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.NameLeagueEventDialog;
import ca.josephroque.bowlingcompanion.utilities.DisplayUtils;
import ca.josephroque.bowlingcompanion.utilities.FloatingActionButtonHandler;
import ca.josephroque.bowlingcompanion.wrapper.LeagueEvent;
import ca.josephroque.bowlingcompanion.wrapper.Series;
/**
* Created by Joseph Roque on 15-03-15. Manages the UI to display information about the leagues being tracked by the
* application, and offers a callback interface {@code LeagueEventFragment.LeagueEventCallback} for handling
* interactions.
*/
@SuppressWarnings("Convert2Lambda")
public class LeagueEventFragment
extends Fragment
implements NameAverageAdapter.NameAverageEventHandler,
NameLeagueEventDialog.NameLeagueEventDialogListener,
FloatingActionButtonHandler {
/** Identifies output from this class in Logcat. */
@SuppressWarnings("unused")
private static final String TAG = "LeagueEventFragment";
/** View to display league and event names and averages to user. */
private RecyclerView mRecyclerViewLeagueEvents;
/** Adapter to manage data displayed in mRecyclerViewLeagueEvents. */
private NameAverageAdapter<LeagueEvent> mAdapterLeagueEvents;
/** Callback listener for user events related to leagues. */
private LeagueEventCallback mLeagueCallback;
/** Callback listener for user events related to series. */
private SeriesFragment.SeriesCallback mSeriesCallback;
/** List to store league / event data from league / event table in database. */
private List<LeagueEvent> mListLeaguesEvents;
/**
* Indicates if the user should be prompted to update names of leagues or events that may have been affected by a
* bug which existed until v2.1.3.
*/
private boolean mPromptToUpdateIncorrectName = false;
@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 {
mLeagueCallback = (LeagueEventCallback) context;
mSeriesCallback = (SeriesFragment.SeriesCallback) context;
} catch (ClassCastException ex) {
throw new ClassCastException(context.toString()
+ " must implement OnLeagueSelectedListener and SeriesListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mLeagueCallback = null;
mSeriesCallback = null;
}
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState) {
final View rootView = inflater.inflate(R.layout.fragment_list, container, false);
mListLeaguesEvents = new ArrayList<>();
mRecyclerViewLeagueEvents = (RecyclerView) rootView.findViewById(R.id.rv_names);
mRecyclerViewLeagueEvents.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();
if (mListLeaguesEvents.get(position).getLeagueEventName().equals(Constants.NAME_OPEN_LEAGUE)) {
mAdapterLeagueEvents.notifyItemChanged(position);
return;
}
mListLeaguesEvents.get(position).setIsDeleted(!mListLeaguesEvents.get(position).wasDeleted());
mAdapterLeagueEvents.notifyItemChanged(position);
}
};
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(touchCallback);
itemTouchHelper.attachToRecyclerView(mRecyclerViewLeagueEvents);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getContext());
mRecyclerViewLeagueEvents.setLayoutManager(layoutManager);
mAdapterLeagueEvents = new NameAverageAdapter<>(this,
mListLeaguesEvents,
NameAverageAdapter.DATA_LEAGUES_EVENTS);
mRecyclerViewLeagueEvents.setAdapter(mAdapterLeagueEvents);
return rootView;
}
@Override
public void onResume() {
super.onResume();
if (getActivity() != null) {
MainActivity mainActivity = (MainActivity) getActivity();
mainActivity.setActionBarTitle(R.string.title_fragment_league_event, true);
mainActivity.setFloatingActionButtonState(R.drawable.ic_add_black_24dp, 0);
mainActivity.setDrawerState(false);
boolean averageAsDecimal = PreferenceManager.getDefaultSharedPreferences(getContext())
.getBoolean(Constants.KEY_AVERAGE_AS_DECIMAL, false);
mAdapterLeagueEvents.setDisplayAverageAsDecimal(averageAsDecimal);
}
new LoadLeaguesEventsTask(this).execute();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.menu_leagues_events, menu);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
boolean drawerOpen = ((MainActivity) getActivity()).isDrawerOpen();
MenuItem menuItem = menu.findItem(R.id.action_stats).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_stats:
// Displays stats of current bowler in a new StatsFragment
if (mLeagueCallback != null)
mLeagueCallback.onBowlerStatsOpened();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onNAItemClick(final int position) {
// When league name is clicked, its data is opened in a new SeriesFragment
new OpenLeagueEventSeriesTask(this).execute(position);
}
@Override
public void onNAItemLongClick(final int position) {
// When league name is long clicked, a dialog is opened to change or delete the item
// Ignores long clicks on the "Open" league
if (!Constants.NAME_OPEN_LEAGUE.equals(mListLeaguesEvents.get(position).getLeagueEventName()))
showLongClickLeagueDialog(position);
}
@Override
public void onNAItemDelete(long id) {
for (int i = 0; i < mListLeaguesEvents.size(); i++) {
if (mListLeaguesEvents.get(i).getLeagueEventId() == id) {
LeagueEvent leagueEvent = mListLeaguesEvents.remove(i);
mAdapterLeagueEvents.notifyItemRemoved(i);
deleteLeagueEvent(leagueEvent.getLeagueEventId());
}
}
}
@Override
public void onNAItemUndoDelete(long id) {
for (int i = 0; i < mListLeaguesEvents.size(); i++) {
if (mListLeaguesEvents.get(i).getLeagueEventId() == id) {
mListLeaguesEvents.get(i).setIsDeleted(false);
mAdapterLeagueEvents.notifyItemChanged(i);
}
}
}
@Override
public void onUpdateLeagueEvent(final LeagueEvent leagueEventToChange,
String name,
short baseAverage,
int baseGames) {
boolean validInput = true;
int invalidInputMessage = -1;
int incorrectAverage = (baseAverage != leagueEventToChange.getBaseAverage()
|| baseGames != leagueEventToChange.getBaseGames())
? -1
: 1;
final LeagueEvent updatedLeagueEvent = new LeagueEvent(leagueEventToChange.getId(),
name,
leagueEventToChange.isEvent(),
leagueEventToChange.getAverage() * incorrectAverage,
baseAverage,
baseGames,
leagueEventToChange.getLeagueEventNumberOfGames());
if (name.equalsIgnoreCase(Constants.NAME_OPEN_LEAGUE)) {
/*
* User has attempted to create a league or event entitled "Open"
* which is a reserved name
*/
validInput = false;
invalidInputMessage = R.string.dialog_default_name;
} else if (hasNameBeenUsed(mListLeaguesEvents, name) && !leagueEventToChange.equals(updatedLeagueEvent)) {
// User has provided a name which is already in use for a league or event
validInput = false;
invalidInputMessage = R.string.dialog_name_exists;
} else if (!name.matches(Constants.REGEX_NAME)) {
// Name is not made up of valid characters
validInput = false;
invalidInputMessage = R.string.dialog_name_letters_spaces_numbers;
} else if (baseAverage > 0 && (baseGames < 1 || baseGames > Constants.MAXIMUM_BASE_GAMES)) {
// Base average was added with an invalid number of base games
validInput = false;
invalidInputMessage = R.string.dialog_invalid_base_games;
}
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 = mListLeaguesEvents.indexOf(leagueEventToChange);
mListLeaguesEvents.set(position, updatedLeagueEvent);
mAdapterLeagueEvents.notifyItemChanged(position);
((MainActivity) getActivity()).addSavingThread(new Thread(new Runnable() {
@Override
public void run() {
SQLiteDatabase database = DatabaseHelper.getInstance(getContext()).getWritableDatabase();
String[] whereArgs = {String.valueOf(updatedLeagueEvent.getId())};
ContentValues values = new ContentValues();
values.put(LeagueEntry.COLUMN_LEAGUE_NAME, updatedLeagueEvent.getLeagueEventName());
values.put(LeagueEntry.COLUMN_BASE_AVERAGE, updatedLeagueEvent.getBaseAverage());
values.put(LeagueEntry.COLUMN_BASE_GAMES, updatedLeagueEvent.getBaseGames());
database.beginTransaction();
try {
database.update(LeagueEntry.TABLE_NAME, values, LeagueEntry._ID + "=?", whereArgs);
database.setTransactionSuccessful();
} catch (Exception ex) {
Log.e(TAG, "Error updating league/event name.", ex);
} finally {
database.endTransaction();
}
}
}));
}
}
@Override
public void onAddNewLeagueEvent(boolean isEvent,
String leagueEventName,
byte numberOfGames,
short baseAverage,
int baseGames) {
boolean validInput = true;
int invalidInputMessageVal = -1;
String invalidInputMessage = null;
LeagueEvent leagueEvent = new LeagueEvent(0,
leagueEventName,
isEvent,
(short) 0,
baseAverage,
baseGames,
numberOfGames);
if (numberOfGames < 1 || (isEvent && numberOfGames > Constants.MAX_NUMBER_EVENT_GAMES)
|| (!isEvent && numberOfGames > Constants.MAX_NUMBER_LEAGUE_GAMES)) {
// User has provided an invalid number of games
validInput = false;
invalidInputMessage = "The number of games must be between 1 and "
+ (isEvent
? Constants.MAX_NUMBER_EVENT_GAMES
: Constants.MAX_NUMBER_LEAGUE_GAMES) + " (inclusive).";
} else if (leagueEventName.equalsIgnoreCase(Constants.NAME_OPEN_LEAGUE)) {
/*
* User has attempted to create a league or event entitled "Open"
* which is a reserved name
*/
validInput = false;
invalidInputMessageVal = R.string.dialog_default_name;
} else if (hasNameBeenUsed(mListLeaguesEvents, leagueEventName)) {
// User has provided a name which is already in use for a league or event
validInput = false;
invalidInputMessageVal = R.string.dialog_name_exists;
} else if (!leagueEventName.matches(Constants.REGEX_NAME)) {
// Name is not made up of valid characters
validInput = false;
invalidInputMessageVal = R.string.dialog_name_letters_spaces_numbers;
} else if (baseAverage > 0 && (baseGames < 1 || baseGames > Constants.MAXIMUM_BASE_GAMES)) {
// Base average was added with an invalid number of base games
validInput = false;
invalidInputMessageVal = R.string.dialog_invalid_base_games;
}
if (!validInput) {
// Displays an error dialog if the input was not valid and exits the method
AlertDialog.Builder invalidInputBuilder = new AlertDialog.Builder(getContext());
if (invalidInputMessageVal == -1)
invalidInputBuilder.setMessage(invalidInputMessage);
else
invalidInputBuilder.setMessage(invalidInputMessageVal);
invalidInputBuilder
.setPositiveButton(R.string.dialog_okay, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})
.create()
.show();
} else {
new AddNewLeagueEventTask(this).execute(leagueEvent);
}
}
@Override
public void onFabClick() {
showLeagueOrEventDialog();
}
@Override
public void onSecondaryFabClick() {
// does nothing - secondary fab has no function here
}
/**
* Invoked when a league 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 showLongClickLeagueDialog(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 LeagueEvent leagueEvent = mListLeaguesEvents.get(position);
new AlertDialog.Builder(getContext())
.setTitle(leagueEvent.getLeagueEventName())
.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 league or event.
*
* @param position position of the league / event to change the name of
*/
private void promptChangeItemName(final int position) {
DialogFragment dialogFragment = NameLeagueEventDialog.newInstance(this,
false,
true,
mListLeaguesEvents.get(position));
dialogFragment.show(getFragmentManager(), "ChangeNameLeagueEventDialog");
}
/**
* Prompts the user to delete a league or event.
*
* @param position position of the item to delete
*/
private void promptDeleteItem(final int position) {
final LeagueEvent leagueEvent = mListLeaguesEvents.get(position);
DialogInterface.OnClickListener onClickListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_POSITIVE) {
mListLeaguesEvents.remove(position);
mAdapterLeagueEvents.notifyItemRemoved(position);
deleteLeagueEvent(leagueEvent.getId());
}
dialog.dismiss();
}
};
new AlertDialog.Builder(getContext())
.setTitle("Warning!")
.setMessage("Are you sure you want to delete " + leagueEvent.getLeagueEventName() + "?"
+ " This cannot be undone!")
.setPositiveButton(R.string.dialog_delete, onClickListener)
.setNegativeButton(R.string.dialog_cancel, onClickListener)
.create()
.show();
}
/**
* Deletes all data regarding a certain league id in the database.
*
* @param leagueEventId id of league/event whose data will be deleted
*/
private void deleteLeagueEvent(final long leagueEventId) {
SharedPreferences prefs = getActivity().getSharedPreferences(Constants.PREFS, Context.MODE_PRIVATE);
SharedPreferences.Editor prefsEditor = prefs.edit();
long recentId = prefs.getLong(Constants.PREF_RECENT_LEAGUE_ID, -1);
long quickId = prefs.getLong(Constants.PREF_QUICK_LEAGUE_ID, -1);
// Clears recent/quick ids if they match the deleted league
if (recentId == leagueEventId) {
prefsEditor.putLong(Constants.PREF_RECENT_BOWLER_ID, -1)
.putLong(Constants.PREF_RECENT_LEAGUE_ID, -1);
}
if (quickId == leagueEventId) {
prefsEditor.putLong(Constants.PREF_QUICK_BOWLER_ID, -1)
.putLong(Constants.PREF_QUICK_LEAGUE_ID, -1);
}
prefsEditor.apply();
// Deletion occurs on separate thread so UI does not hang
new Thread(new Runnable() {
@Override
public void run() {
// Deletes data from all tables corresponding to leagueEventId
SQLiteDatabase database =
DatabaseHelper.getInstance(getContext()).getWritableDatabase();
String[] whereArgs = {String.valueOf(leagueEventId)};
database.beginTransaction();
try {
database.delete(LeagueEntry.TABLE_NAME,
LeagueEntry._ID + "=?",
whereArgs);
database.setTransactionSuccessful();
} catch (Exception ex) {
Log.e(TAG, "Error deleting from database", ex);
} finally {
database.endTransaction();
}
}
}).start();
}
/**
* Prompts user to select either league or event to add.
*/
private void showLeagueOrEventDialog() {
AlertDialog.Builder leagueOrEventBuilder = new AlertDialog.Builder(getContext());
leagueOrEventBuilder.setTitle("New league or event?")
.setSingleChoiceItems(new CharSequence[]{"League", "Event"}, 0, null)
.setPositiveButton(R.string.dialog_add, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
showNewLeagueEventDialog(((AlertDialog) dialog)
.getListView().getCheckedItemPosition() == 1);
dialog.dismiss();
}
})
.setNegativeButton(R.string.dialog_cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})
.create()
.show();
}
/**
* Prompts the user to add a new league or event.
*
* @param newEvent if true, a new event will be made. If false, a new league will be made.
*/
private void showNewLeagueEventDialog(boolean newEvent) {
DialogFragment dialogFragment = NameLeagueEventDialog.newInstance(this, newEvent, false, null);
dialogFragment.show(getFragmentManager(), "NewLeagueEventDialog");
}
/**
* Displays a dialog to inform the user of a bug fixed from v2.1.3. The fix allows renamed leagues and events to
* be renamed without an extra "L" or "E" appearing.
*/
private void promptToUpdateIncorrectName() {
new AlertDialog.Builder(getContext())
.setTitle(R.string.text_bugfix_league_event_extra_letter_title)
.setMessage(R.string.text_bugfix_league_event_extra_letter_msg)
.setPositiveButton(R.string.dialog_okay, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})
.create()
.show();
}
/**
* Iterates through the existing list of leagues and events to check if the new name has already been used.
*
* @param leagueEvents list of leagues and events
* @param newName a name to check for in the list
* @return {@code true} if any of the instances in {@code leagueEvents} has a name that is equal to {@code newName},
* or {@code false} otherwise
*/
private static boolean hasNameBeenUsed(List<LeagueEvent> leagueEvents, String newName) {
for (LeagueEvent item : leagueEvents) {
if (item.getName().equals(newName))
return true;
}
return false;
}
/**
* Creates a new instance of this fragment to display.
*
* @return a new instance of LeagueEventFragment
*/
public static LeagueEventFragment newInstance() {
return new LeagueEventFragment();
}
/**
* Loads/updates data for the league/event from the database and creates a new SeriesFragment or GameFragment to
* display selected league or event, respectively.
*/
private static final class OpenLeagueEventSeriesTask
extends AsyncTask<Integer, Void, Pair<LeagueEvent, Series>> {
/** Weak reference to the parent fragment. */
private final WeakReference<LeagueEventFragment> mFragment;
/**
* Assigns a weak reference to the parent fragment.
*
* @param fragment parent fragment
*/
private OpenLeagueEventSeriesTask(LeagueEventFragment fragment) {
mFragment = new WeakReference<>(fragment);
}
@Override
protected Pair<LeagueEvent, Series> doInBackground(Integer... position) {
LeagueEventFragment fragment = mFragment.get();
if (fragment == null || !fragment.isAdded())
return null;
MainActivity mainActivity = (MainActivity) fragment.getActivity();
if (mainActivity == null)
return null;
LeagueEvent selectedLeagueEvent = fragment.mListLeaguesEvents.get(position[0]);
boolean isEvent = selectedLeagueEvent.isEvent();
SQLiteDatabase db = DatabaseHelper.getInstance(mainActivity).getWritableDatabase();
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CANADA);
String currentDate = df.format(new Date());
ContentValues values = new ContentValues();
values.put(LeagueEntry.COLUMN_DATE_MODIFIED, currentDate);
// Updates the date modified in the database of the selected league
db.beginTransaction();
try {
db.update(LeagueEntry.TABLE_NAME,
values,
LeagueEntry._ID + "=?",
new String[]{String.valueOf(selectedLeagueEvent.getLeagueEventId())});
db.setTransactionSuccessful();
} catch (Exception ex) {
// does nothing - error updating league date - non-fatal
} finally {
db.endTransaction();
}
/*
* If an event was selected by the user the corresponding series of the event is
* loaded from the database so an instance of GameFragment can be created, since
* creating a SeriesFragment would be redundant for a single series.
*/
if (isEvent) {
String rawSeriesQuery = "SELECT "
+ SeriesEntry._ID + ", "
+ SeriesEntry.COLUMN_SERIES_DATE
+ " FROM " + SeriesEntry.TABLE_NAME
+ " WHERE " + SeriesEntry.COLUMN_LEAGUE_ID + "=?";
Cursor cursor = db.rawQuery(rawSeriesQuery,
new String[]{String.valueOf(selectedLeagueEvent.getLeagueEventId())});
if (cursor.moveToFirst()) {
long seriesId = cursor.getLong(cursor.getColumnIndex(SeriesEntry._ID));
String seriesDate = cursor.getString(cursor.getColumnIndex(SeriesEntry.COLUMN_SERIES_DATE));
cursor.close();
return Pair.create(selectedLeagueEvent,
new Series(seriesId, selectedLeagueEvent.getLeagueEventId(), seriesDate, null, null));
} else
cursor.close();
throw new RuntimeException("Event series id could not be loaded from database");
} else
return Pair.create(selectedLeagueEvent, new Series(-1, -1, null, null, null));
}
@Override
protected void onPostExecute(Pair<LeagueEvent, Series> result) {
LeagueEventFragment fragment = mFragment.get();
if (fragment == null || result == null)
return;
MainActivity mainActivity = (MainActivity) fragment.getActivity();
if (mainActivity == null)
return;
boolean isEvent = result.second.getSeriesId() >= 0;
if (isEvent) {
/*
* If an event was selected, creates an instance of GameFragment
* displaying the event's corresponding series
*/
if (fragment.mLeagueCallback != null
&& fragment.mSeriesCallback != null) {
fragment.mLeagueCallback.onLeagueSelected(result.first, false);
fragment.mSeriesCallback.onSeriesSelected(result.second, true);
}
} else {
/*
* If a league was selected, creates an instance of SeriesActivity
* to display all available series in the league
*/
long bowlerId = mainActivity.getBowlerId();
if (!result.first.getLeagueEventName().equals(Constants.NAME_OPEN_LEAGUE)) {
mainActivity
.getSharedPreferences(Constants.PREFS, Context.MODE_PRIVATE)
.edit()
.putLong(Constants.PREF_RECENT_LEAGUE_ID,
result.first.getLeagueEventId())
.putLong(Constants.PREF_RECENT_BOWLER_ID, bowlerId)
.apply();
}
if (fragment.mLeagueCallback != null)
fragment.mLeagueCallback.onLeagueSelected(result.first, true);
}
}
}
/**
* Loads the names of relevant leagues or events and adds them to the lists to be displayed to the user.
*/
private static final class LoadLeaguesEventsTask
extends AsyncTask<Void, Void, List<LeagueEvent>> {
/** Weak reference to the parent fragment. */
private final WeakReference<LeagueEventFragment> mFragment;
/**
* Assigns a weak reference to the parent fragment.
*
* @param fragment parent fragment
*/
private LoadLeaguesEventsTask(LeagueEventFragment fragment) {
mFragment = new WeakReference<>(fragment);
}
@Override
protected void onPreExecute() {
LeagueEventFragment fragment = mFragment.get();
if (fragment == null)
return;
fragment.mListLeaguesEvents.clear();
fragment.mAdapterLeagueEvents.notifyDataSetChanged();
}
@Override
protected List<LeagueEvent> doInBackground(Void... params) {
LeagueEventFragment fragment = mFragment.get();
if (fragment == null || !fragment.isAdded())
return null;
MainActivity mainActivity = (MainActivity) fragment.getActivity();
if (mainActivity == null)
return null;
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mainActivity);
boolean promptToFixNames = preferences.getBoolean(Constants.PREF_PROMPT_LEAGUE_EVENT_NAME_FIX, true);
MainActivity.waitForSaveThreads(new WeakReference<>(mainActivity));
SQLiteDatabase database = DatabaseHelper.getInstance(mainActivity).getReadableDatabase();
List<LeagueEvent> listLeagueEvents = new ArrayList<>();
String rawLeagueEventQuery = "SELECT "
+ "league." + LeagueEntry._ID + " AS lid, "
+ LeagueEntry.COLUMN_LEAGUE_NAME + ", "
+ LeagueEntry.COLUMN_IS_EVENT + ", "
+ LeagueEntry.COLUMN_BASE_AVERAGE + ", "
+ LeagueEntry.COLUMN_BASE_GAMES + ", "
+ LeagueEntry.COLUMN_NUMBER_OF_GAMES + ", "
+ GameEntry.COLUMN_SCORE
+ " FROM " + LeagueEntry.TABLE_NAME + " AS league"
+ " LEFT JOIN " + SeriesEntry.TABLE_NAME + " AS series"
+ " ON league." + LeagueEntry._ID + "=series." + SeriesEntry.COLUMN_LEAGUE_ID
+ " LEFT JOIN " + GameEntry.TABLE_NAME + " AS game"
+ " ON series." + SeriesEntry._ID + "=game." + GameEntry.COLUMN_SERIES_ID
+ " WHERE " + LeagueEntry.COLUMN_BOWLER_ID + "=?"
+ " ORDER BY " + LeagueEntry.COLUMN_DATE_MODIFIED + " DESC";
long bowlerId = mainActivity.getBowlerId();
Cursor cursor = database.rawQuery(rawLeagueEventQuery, new String[]{String.valueOf(bowlerId)});
long lastLeagueEventId = -1;
int leagueTotal = 0;
int leagueNumberOfGames = 0;
if (cursor.moveToFirst()) {
while (!cursor.isAfterLast()) {
long leagueEventId = cursor.getLong(cursor.getColumnIndex("lid"));
if (leagueEventId != lastLeagueEventId && lastLeagueEventId != -1) {
cursor.moveToPrevious();
String name = cursor.getString(cursor.getColumnIndex(LeagueEntry.COLUMN_LEAGUE_NAME));
boolean isEvent = cursor.getInt(cursor.getColumnIndex(LeagueEntry.COLUMN_IS_EVENT)) == 1;
short baseAverage = cursor.getShort(cursor.getColumnIndex(LeagueEntry.COLUMN_BASE_AVERAGE));
int baseGames = cursor.getInt(cursor.getColumnIndex(LeagueEntry.COLUMN_BASE_GAMES));
float average = Score.getAdjustedAverage(leagueTotal,
leagueNumberOfGames,
baseAverage,
baseGames);
LeagueEvent leagueEvent = new LeagueEvent(cursor.getLong(cursor.getColumnIndex("lid")),
name,
isEvent,
average,
baseAverage,
baseGames,
(byte) cursor.getInt(cursor.getColumnIndex(LeagueEntry.COLUMN_NUMBER_OF_GAMES)));
listLeagueEvents.add(leagueEvent);
leagueTotal = 0;
leagueNumberOfGames = 0;
if (!fragment.mPromptToUpdateIncorrectName && name.matches("^[LE][A-Z].*")
&& promptToFixNames) {
preferences.edit().putBoolean(Constants.PREF_PROMPT_LEAGUE_EVENT_NAME_FIX, false).apply();
fragment.mPromptToUpdateIncorrectName = true;
}
cursor.moveToNext();
}
short score = cursor.getShort(cursor.getColumnIndex(GameEntry.COLUMN_SCORE));
if (score > 0) {
leagueTotal += score;
leagueNumberOfGames++;
}
lastLeagueEventId = leagueEventId;
cursor.moveToNext();
}
cursor.moveToPrevious();
boolean isEvent = cursor.getInt(cursor.getColumnIndex(LeagueEntry.COLUMN_IS_EVENT)) == 1;
short baseAverage = cursor.getShort(cursor.getColumnIndex(LeagueEntry.COLUMN_BASE_AVERAGE));
int baseGames = cursor.getInt(cursor.getColumnIndex(LeagueEntry.COLUMN_BASE_GAMES));
float average = Score.getAdjustedAverage(leagueTotal, leagueNumberOfGames, baseAverage, baseGames);
LeagueEvent leagueEvent = new LeagueEvent(cursor.getLong(cursor.getColumnIndex("lid")),
cursor.getString(cursor.getColumnIndex(LeagueEntry.COLUMN_LEAGUE_NAME)),
isEvent,
average,
baseAverage,
baseGames,
(byte) cursor.getInt(cursor.getColumnIndex(LeagueEntry.COLUMN_NUMBER_OF_GAMES)));
listLeagueEvents.add(leagueEvent);
}
cursor.close();
return listLeagueEvents;
}
@Override
@SuppressWarnings("unchecked")
protected void onPostExecute(List<LeagueEvent> listLeagueEvents) {
LeagueEventFragment fragment = mFragment.get();
if (fragment == null || listLeagueEvents == null)
return;
fragment.mListLeaguesEvents.addAll(listLeagueEvents);
fragment.mAdapterLeagueEvents.notifyDataSetChanged();
if (fragment.mPromptToUpdateIncorrectName)
fragment.promptToUpdateIncorrectName();
}
}
/**
* Creates a new entry in the database for a league or event which is then added to the list of data to be displayed
* to the user.
*/
private static final class AddNewLeagueEventTask
extends AsyncTask<LeagueEvent, Void, LeagueEvent> {
/** Weak reference to the parent fragment. */
private final WeakReference<LeagueEventFragment> mFragment;
/**
* Assigns a weak reference to the parent fragment.
*
* @param fragment parent fragment
*/
private AddNewLeagueEventTask(LeagueEventFragment fragment) {
mFragment = new WeakReference<>(fragment);
}
@Override
protected LeagueEvent doInBackground(LeagueEvent... league) {
LeagueEventFragment fragment = mFragment.get();
if (fragment == null || !fragment.isAdded())
return null;
MainActivity mainActivity = (MainActivity) fragment.getActivity();
if (mainActivity == null)
return null;
league[0].setLeagueEventId(-1);
long bowlerId = mainActivity.getBowlerId();
SQLiteDatabase db = DatabaseHelper.getInstance(mainActivity).getWritableDatabase();
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CANADA);
String currentDate = df.format(new Date());
ContentValues values = new ContentValues();
values.put(LeagueEntry.COLUMN_LEAGUE_NAME, league[0].getLeagueEventName());
values.put(LeagueEntry.COLUMN_DATE_MODIFIED, currentDate);
values.put(LeagueEntry.COLUMN_BOWLER_ID, bowlerId);
values.put(LeagueEntry.COLUMN_BASE_AVERAGE, league[0].getBaseAverage());
values.put(LeagueEntry.COLUMN_BASE_GAMES, league[0].getBaseGames());
values.put(LeagueEntry.COLUMN_NUMBER_OF_GAMES, league[0].getLeagueEventNumberOfGames());
values.put(LeagueEntry.COLUMN_IS_EVENT, league[0].isEvent()
? 1
: 0);
db.beginTransaction();
try {
// Creates the entry for the league or event in the "league" database
league[0].setLeagueEventId(db.insert(LeagueEntry.TABLE_NAME, null, values));
if (league[0].isEvent()) {
/*
* If the new entry is an event, its series is also created at this time
* since there is only a single series to an event
*/
values = new ContentValues();
values.put(SeriesEntry.COLUMN_SERIES_DATE, currentDate);
values.put(SeriesEntry.COLUMN_LEAGUE_ID, league[0].getLeagueEventId());
long seriesId = db.insert(SeriesEntry.TABLE_NAME, null, values);
for (int i = 0; i < league[0].getLeagueEventNumberOfGames(); i++) {
values = new ContentValues();
values.put(GameEntry.COLUMN_GAME_NUMBER, i + 1);
values.put(GameEntry.COLUMN_SCORE, 0);
values.put(GameEntry.COLUMN_SERIES_ID, seriesId);
long gameId = db.insert(GameEntry.TABLE_NAME, null, values);
for (int j = 0; j < Constants.NUMBER_OF_FRAMES; j++) {
values = new ContentValues();
values.put(FrameEntry.COLUMN_FRAME_NUMBER, j + 1);
values.put(FrameEntry.COLUMN_GAME_ID, gameId);
db.insert(FrameEntry.TABLE_NAME, null, values);
}
}
}
db.setTransactionSuccessful();
} catch (Exception ex) {
Log.e(TAG, "Error adding new league", ex);
} finally {
db.endTransaction();
}
return league[0];
}
@Override
protected void onPostExecute(LeagueEvent result) {
LeagueEventFragment fragment = mFragment.get();
if (result != null && fragment != null && result.getLeagueEventId() != -1) {
fragment.mListLeaguesEvents.add(0, result);
fragment.mAdapterLeagueEvents.notifyItemInserted(0);
fragment.mRecyclerViewLeagueEvents.scrollToPosition(0);
}
}
}
/**
* Container Activity must implement this interface to allow SeriesFragment/GameFragment to be loaded when a
* league/event is selected.
*/
public interface LeagueEventCallback {
/**
* Should be overridden to create a SeriesFragment with the series belonging to the league represented by
* leagueId.
*
* @param leagueEvent league / event whose series will be displayed
* @param openSeriesFragment indicates if series fragment should be opened
*/
void onLeagueSelected(LeagueEvent leagueEvent, boolean openSeriesFragment);
/**
* Used to open StatsFragment to display bowler stats.
*/
void onBowlerStatsOpened();
}
}