package pk.contender.earmouse;
import android.app.Activity;
import android.app.Fragment;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.List;
/**
* Implements a selected {@link pk.contender.earmouse.Module} by presenting the user with random exercises
* from said {@link pk.contender.earmouse.Module} and processing the answers.
*
* @author Paul Klinkenberg <pklinken.development@gmail.com>
*/
public class ExerciseFragment extends Fragment {
/**
* State in which an exercise is prepared and has been played at least once, the buttons in the
* ButtonGrid are enabled. Exercise can be played as often as the user wants.
*/
private static final int EXERCISE_READY = 0;
/**
* State in which an exercise has been correctly answered and a click on the ButtonGrid or
* Play button will load the next exercise.
* Clicking on the Play button will, in addition to preparing the next exercise, also play
* the media as soon as it is available.
* @see pk.contender.earmouse.MediaFragment#requestPlayback()
*/
private static final int EXERCISE_CONTINUE = 1;
/**
* State in which an exercise is prepared but has not been played, the buttons in the ButtonGrid
* are disabled as we do not want the user to (inadvertently) give an answer before listening to the
* exercise.
*/
private static final int EXERCISE_READY_NOTPLAYED = 2;
/* SharedPreferences constants */
private static final String PREFERENCES_ISEMPTY = "preferences_isEmpty";
private static final String PREFERENCES_MODINDEX = "preferences_modIndex";
private static final String PREFERENCES_MODID = "preferences_modId";
private static final String PREFERENCES_CURRENTEXERCISE = "preferences_currentExercise";
private static final String PREFERENCES_EXERCISESTATE = "preferences_exerciseState";
static final String PREFERENCES_ISFRESHINTENT = "preferences_isFreshIntent";
static final String PREFERENCES_PRACTICEMODE = "preferences_practiceMode";
/**
* The current state.
*/
private int exerciseState;
/**
* The {@link pk.contender.earmouse.Module} we are currently doing exercises from.
*/
private Module mod = null;
/**
* Index of the {@link pk.contender.earmouse.Module} in {@link Main#mModules}
*/
private int modIndex = -1;
/**
* ID of the {@link pk.contender.earmouse.Module} we are currently doing exercises from.
*/
private int modId = -1;
/**
* The index of the {@link pk.contender.earmouse.Exercise} in {@link #mod} that we are currently doing.
*/
private int currentExercise = -1;
/**
* If true the instance should request the mediaFragment to play the selected answer
*/
private boolean practiceMode = false;
/** Set to true if we are currently hiding the UI
* The UI is hidden when there is no {@link pk.contender.earmouse.Module} selected. */
private boolean isEmpty;
private Activity mCtx;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_exercise, container, false);
mCtx = getActivity();
if(mCtx == null)
Log.d("DEBUG", "Context is null in ExerciseFragment onCreateView()");
return view;
}
/**
* Initializes or restores the Fragment's state. If the parent activity was started from an {@link android.content.Intent}, set up the UI
* from the given position value, otherwise restore the state the fragment was left in.
*/
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
SharedPreferences settings = mCtx.getSharedPreferences(Main.PREFS_NAME, Activity.MODE_PRIVATE);
Bundle extras = mCtx.getIntent().getExtras();
// Restore state
practiceMode = settings.getBoolean(PREFERENCES_PRACTICEMODE, false);
// If started from an Intent and isFreshIntent, set that up and set isFreshIntent to false.
if(extras != null && settings.getBoolean(PREFERENCES_ISFRESHINTENT, false)) {
int position = extras.getInt(ExerciseActivity.EXTRA_POSITION);
setModule(position);
settings.edit().putBoolean(PREFERENCES_ISFRESHINTENT, false).apply();
} else {
// If not started from a fresh intent, just restore the UI and fragments how we left them.
isEmpty = settings.getBoolean(PREFERENCES_ISEMPTY, true);
if (isEmpty)
setModule(-1);
else {
exerciseState = settings.getInt(PREFERENCES_EXERCISESTATE, EXERCISE_READY);
modId = settings.getInt(PREFERENCES_MODID, -1);
if(modId < 0) {
setModule(-1);
return;
}
else {
for (Module m : Main.getModuleList()) {
if(m.getId() == modId) {
mod = m;
modIndex = Main.getModuleList().indexOf(mod);
}
}
currentExercise = settings.getInt(PREFERENCES_CURRENTEXERCISE, 0);
prepareExercise(false);
}
ModuleDetailsFragment detailFragment = (ModuleDetailsFragment) getFragmentManager().findFragmentById(R.id.moduledetail);
if (detailFragment != null) {
detailFragment.setTitle(mod.getTitle());
detailFragment.setDescription(mod.getDescription());
} else
Log.d("DEBUG", "ModuleDetailsFragment is null");
updateFeedbackStatistics();
}
}
}
/**
* Hide the main UI of the fragment and display a single TextView instead.
* Used on tablets when there is no {@link pk.contender.earmouse.Module} selected, instead of showing an empty layout
* we show this TextView saying 'No Module Selected'
*/
void setEmpty() {
View v;
ModuleDetailsFragment detailFragment = (ModuleDetailsFragment) getFragmentManager().findFragmentById(R.id.moduledetail);
if(detailFragment != null) {
detailFragment.setTitle("");
detailFragment.setDescription("");
v = detailFragment.getView();
if(v != null)
v.setVisibility(View.GONE);
} else
Log.d("DEBUG", "ModuleDetailsFragment is null");
ButtonGridFragment buttonFragment = (ButtonGridFragment) getFragmentManager().findFragmentById(R.id.buttongrid);
if(buttonFragment != null) {
buttonFragment.initGrid(null);
v = buttonFragment.getView();
if(v != null)
v.setVisibility(View.GONE);
} else
Log.d("DEBUG", "ButtonGridFragment is null");
FeedbackBarFragment feedbackFragment = (FeedbackBarFragment) getFragmentManager().findFragmentById(R.id.feedbackbar);
if(feedbackFragment != null) {
feedbackFragment.setEmpty();
v = feedbackFragment.getView();
if(v != null)
v.setVisibility(View.GONE);
} else {
Log.d("DEBUG", "FeedbackBarFragment is null");
}
MediaFragment mediaFragment = (MediaFragment) getFragmentManager().findFragmentById(R.id.media);
if(mediaFragment != null) {
//mediaFragment.setEmpty();
v = mediaFragment.getView();
if(v != null)
v.setVisibility(View.GONE);
} else
Log.d("DEBUG", "MediaFragment is null");
View fragmentView = getView();
if(fragmentView != null) {
v = fragmentView.findViewById(R.id.feedbackbar_divider);
if (v != null)
v.setVisibility(View.GONE);
v = fragmentView.findViewById(R.id.buttongrid_divider);
if (v != null)
v.setVisibility(View.GONE);
v = fragmentView.findViewById(R.id.media_divider);
if (v != null)
v.setVisibility(View.GONE);
v = fragmentView.findViewById(R.id.message_text);
if(v != null)
v.setVisibility(View.VISIBLE);
}
isEmpty = true;
}
/**
* Unhide the main UI, does the exact opposite of setEmpty().
*/
void setNotEmpty() {
View v;
ModuleDetailsFragment detailFragment = (ModuleDetailsFragment) getFragmentManager().findFragmentById(R.id.moduledetail);
if(detailFragment != null) {
v = detailFragment.getView();
if(v != null)
v.setVisibility(View.VISIBLE);
} else
Log.d("DEBUG", "ModuleDetailsFragment is null");
ButtonGridFragment buttonFragment = (ButtonGridFragment) getFragmentManager().findFragmentById(R.id.buttongrid);
if(buttonFragment != null) {
v = buttonFragment.getView();
if(v != null)
v.setVisibility(View.VISIBLE);
} else
Log.d("DEBUG", "ButtonGridFragment is null");
FeedbackBarFragment feedbackFragment = (FeedbackBarFragment) getFragmentManager().findFragmentById(R.id.feedbackbar);
if(feedbackFragment != null) {
v = feedbackFragment.getView();
if(v != null)
v.setVisibility(View.VISIBLE);
} else {
Log.d("DEBUG", "FeedbackBarFragment is null");
}
MediaFragment mediaFragment = (MediaFragment) getFragmentManager().findFragmentById(R.id.media);
if(mediaFragment != null) {
v = mediaFragment.getView();
if(v != null)
v.setVisibility(View.VISIBLE);
} else
Log.d("DEBUG", "MediaFragment is null");
View fragmentView = getView();
if(fragmentView != null) {
v = fragmentView.findViewById(R.id.feedbackbar_divider);
if (v != null)
v.setVisibility(View.VISIBLE);
v = fragmentView.findViewById(R.id.buttongrid_divider);
if (v != null)
v.setVisibility(View.VISIBLE);
v = fragmentView.findViewById(R.id.media_divider);
if (v != null)
v.setVisibility(View.VISIBLE);
v = fragmentView.findViewById(R.id.message_text);
if(v != null)
v.setVisibility(View.GONE);
}
isEmpty = false;
}
/**
* Display the Module at position in {@link pk.contender.earmouse.Main#getModuleList()} <p>
* If position is out of bounds set the UI to 'empty', otherwise load the selected
* Module, set up the UI and prepare an exercise.
* @param position The position of the Module in {@link pk.contender.earmouse.Main#getModuleList()} to display
*/
public void setModule(int position) {
List<Module> moduleList = Main.getModuleList();
if(position >= moduleList.size() || position < 0) {
mod = null;
modIndex = -1;
modId = -1;
setEmpty();
return;
} else if (isEmpty)
setNotEmpty();
mod = moduleList.get(position);
modIndex = position;
modId = mod.getId();
ModuleDetailsFragment detailFragment = (ModuleDetailsFragment) getFragmentManager().findFragmentById(R.id.moduledetail);
if(detailFragment != null) {
detailFragment.setTitle(mod.getTitle());
detailFragment.setDescription(mod.getDescription());
} else
Log.d("DEBUG", "ModuleDetailsFragment is null");
// Set up grid of buttons with the Module's answerList
ButtonGridFragment buttonFragment = (ButtonGridFragment) getFragmentManager().findFragmentById(R.id.buttongrid);
if(buttonFragment != null) {
buttonFragment.initGrid(mod.getAnswerList());
} else
Log.d("DEBUG", "ButtonGridFragment is null");
mod.refreshState();
prepareExercise(false);
updateFeedbackStatistics();
}
public int getModuleIndex() {
return modIndex;
}
/**
* Save complete state.
*/
@Override
public void onPause() {
super.onPause();
// Possibly superfluous to write to disk here.
if(mod != null)
mod.saveState();
SharedPreferences settings = mCtx.getSharedPreferences(Main.PREFS_NAME, Activity.MODE_PRIVATE);
settings.edit().putBoolean(PREFERENCES_ISEMPTY, isEmpty).putInt(PREFERENCES_MODINDEX, modIndex)
.putInt(PREFERENCES_CURRENTEXERCISE, currentExercise).putInt(PREFERENCES_EXERCISESTATE, exerciseState)
.putInt(PREFERENCES_MODID, modId).putBoolean(PREFERENCES_PRACTICEMODE, practiceMode).apply();
}
@Override
public void onResume() {
super.onResume();
if(mod != null)
mod.refreshState();
}
/**
* Prepare an exercise for this activity.
* <p>
* This function will receive an Exercise index from the loaded Module and use that to:<br>
* - Set up the ButtonGrid with the answers<br>
* - Start the MediaFragment to prepare and load the required WAV file<br>
* - Set up the FeedbackBarFragment to reflect the current state.
*/
private void prepareExercise(boolean playNow){
currentExercise = mod.getWeightedExerciseIndex();
ButtonGridFragment buttonFragment = (ButtonGridFragment) getFragmentManager().findFragmentById(R.id.buttongrid);
if(buttonFragment != null) {
buttonFragment.resetGridButtonState();
} else
Log.d("DEBUG", "ButtonGridFragment is null");
MediaFragment mediaFragment = (MediaFragment) getFragmentManager().findFragmentById(R.id.media);
if(mediaFragment != null) {
mediaFragment.prepareExercise(mod.getExercise(currentExercise), playNow);
} else
Log.d("DEBUG", "MediaFragment is null");
setFeedbackText((String) this.getResources().getText(R.string.feedback_ready));
exerciseState = EXERCISE_READY_NOTPLAYED;
}
/**
* On receiving a click event on the Play button, relay it to the MediaFragment and:
* If the current state is {@link #EXERCISE_READY_NOTPLAYED}, move the state to {@link #EXERCISE_READY}
* If the current state is {@link #EXERCISE_CONTINUE}, prepare an exercise and request the MediaFragment
* to play it as soon as it is ready, and set the state to {@link #EXERCISE_READY}
*
* @param view The parent view where the click event was received.
*/
public void onClickPlay(@SuppressWarnings("UnusedParameters") View view) {
MediaFragment mediaFragment = (MediaFragment) getFragmentManager().findFragmentById(R.id.media);
if(mediaFragment != null) {
if(exerciseState == EXERCISE_CONTINUE) {
prepareExercise(true);
//mediaFragment.requestPlayback();
exerciseState = EXERCISE_READY;
} else {
mediaFragment.clickPlay();
exerciseState = (exerciseState == EXERCISE_READY_NOTPLAYED ? EXERCISE_READY : exerciseState);
}
} else
Log.d("DEBUG", "MediaFragment is null");
}
/**
* Handler for click events in the ButtonGridFragment
* <p>
* Depending on the state of the Activity, handle user input, see code for details.
*
* @param position The position of the Button in the ButtonGridFragment that was clicked.
*/
public void onAnswerSelected(int position) {
if(practiceMode) {
playSelectedAnswer(position);
} else if(exerciseState == EXERCISE_READY) {
// Ready to receive answer
if(position == currentExercise) {
// Correct answer, register with statistics, give UI feedback and change state.
mod.registerAnswer(currentExercise, true);
setFeedbackText((String) this.getResources().getText(R.string.feedback_correct));
updateFeedbackStatistics();
mod.saveState();
exerciseState = EXERCISE_CONTINUE;
} else {
// Wrong answer, register with statistics and give UI feedback
mod.registerAnswer(currentExercise, false);
fadeButton(position);
setFeedbackText((String) this.getResources().getText(R.string.feedback_incorrect));
updateFeedbackStatistics();
mod.saveState();
}
} else if(exerciseState == EXERCISE_CONTINUE) {
// Prepare the next exercise
prepareExercise(false);
}
// Activity not ready to receive answer events, discard input.
}
/**
* In practice mode, when a user selects an answer it is to be played immediately by
* the mediaFragment.
* @param pos is the index of the button clicked on the grid, which translates directly
* to an Exercise returned by mod.getExercise(pos)
*/
private void playSelectedAnswer(int pos) {
// Get an exercise for the user selection
Exercise ex = mod.getExercise(pos);
MediaFragment mediaFragment = (MediaFragment) getFragmentManager().findFragmentById(R.id.media);
if(mediaFragment != null) {
mediaFragment.playPractice(ex);
} else
Log.d("DEBUG", "MediaFragment is null");
}
/**
* Set the feedback text in {@link pk.contender.earmouse.FeedbackBarFragment}
* @param text The text to set in the FeedbackBarFragment text field.
*/
private void setFeedbackText(String text) {
FeedbackBarFragment feedbackFragment = (FeedbackBarFragment) getFragmentManager().findFragmentById(R.id.feedbackbar);
if(feedbackFragment != null) {
feedbackFragment.setFeedback(text);
} else {
Log.d("DEBUG", "FeedbackBarFragment is null");
}
}
/**
* Update {@link pk.contender.earmouse.FeedbackBarFragment} statistics
*/
public void updateFeedbackStatistics() {
FeedbackBarFragment feedbackFragment = (FeedbackBarFragment) getFragmentManager().findFragmentById(R.id.feedbackbar);
if(feedbackFragment != null) {
feedbackFragment.setStatistics(mod.getSuccessRate());
} else {
Log.d("DEBUG", "FeedbackBarFragment is null");
}
}
/**
* Fade out the Button in {@link pk.contender.earmouse.ButtonGridFragment} at the given position
* @param position The position of the Button in {@link pk.contender.earmouse.ButtonGridFragment} to fade out.
*/
private void fadeButton(int position) {
ButtonGridFragment buttonFragment = (ButtonGridFragment) getFragmentManager().findFragmentById(R.id.buttongrid);
if(buttonFragment != null) {
buttonFragment.fadeButton(position);
} else
Log.d("DEBUG", "ButtonGridFragment is null");
}
public void onPracticeModeToggle() {
practiceMode = !practiceMode;
Log.d("debug", "practiceMode is set to: " + (practiceMode ? "true" : "false"));
}
}