package se.johan.wendler.activity; import android.annotation.TargetApi; import android.app.ActionBar.Tab; import android.app.ActionBar.TabListener; import android.app.Activity; import android.app.FragmentTransaction; import android.content.res.Resources; import android.os.Build; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager.OnPageChangeListener; import android.text.format.Time; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import com.astuetz.PagerSlidingTabStrip; import com.doomonafireball.betterpickers.calendardatepicker.CalendarDatePickerDialog; import com.getbase.floatingactionbutton.FloatingActionsMenu; import com.nispok.snackbar.Snackbar; import com.nispok.snackbar.listeners.EventListener; import java.sql.SQLException; import se.johan.wendler.R; import se.johan.wendler.activity.base.BaseActivity; import se.johan.wendler.animation.ZoomOutPageTransformer; import se.johan.wendler.fragment.WorkoutAdditionalFragment; import se.johan.wendler.fragment.WorkoutMainFragment; import se.johan.wendler.fragment.base.WorkoutFragment; import se.johan.wendler.model.Action; import se.johan.wendler.model.Workout; import se.johan.wendler.sql.SqlHandler; import se.johan.wendler.ui.dialog.ConfirmationDialog; import se.johan.wendler.ui.dialog.EditTextDialog; import se.johan.wendler.ui.dialog.StopwatchDialog; import se.johan.wendler.util.Constants; import se.johan.wendler.util.WendlerizedLog; import se.johan.wendler.util.WorkoutHolder; /** * Activity for handling the workout fragments */ public class WorkoutActivity extends BaseActivity implements TabListener, OnPageChangeListener, EditTextDialog.EditTextListener, StopwatchDialog.StopWatchListener, ConfirmationDialog.ConfirmationDialogListener, EventListener { private static final String EXTRA_ELAPSED_TIME = "elapsedTime"; private static final String EXTRA_TIMER_IS_RUNNING = "timerIsRunning"; private static final String EXTRA_CURRENT_PAGE = "mCurrentPage"; private ViewPager mViewPager; private Workout mWorkout; private int mCurrentPage; private long mTimeElapsed = -1; private boolean mTimerIsRunning; /** * Called when our activity is created. */ @TargetApi(Build.VERSION_CODES.KITKAT) @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState, R.layout.activity_workout); overrideElevation(getResources().getDimension(R.dimen.toolbar_elevation)); if (savedInstanceState != null) { mWorkout = savedInstanceState.getParcelable(Constants.BUNDLE_EXERCISE_ITEM); mTimerIsRunning = savedInstanceState.getBoolean(EXTRA_TIMER_IS_RUNNING); mCurrentPage = savedInstanceState.getInt(EXTRA_CURRENT_PAGE, 0); mTimeElapsed = savedInstanceState.getLong(EXTRA_ELAPSED_TIME, -1); } else { WorkoutHolder.WorkoutItem item = WorkoutHolder.getInstance().getWorkout(); mWorkout = item.getWorkout(); mTimerIsRunning = item.isTimerRunning(); mCurrentPage = item.getCurrentPage(); mTimeElapsed = item.getElapsedTime(); } FragmentManager fragmentManager = initFragmentManagement(); CalendarDatePickerDialog dialog = (CalendarDatePickerDialog) fragmentManager.findFragmentByTag(CalendarDatePickerDialog.class.getName()); if (dialog != null) { dialog.setOnDateSetListener(mDateSetListener); } String text = String.format( getString(R.string.actionbar_title_workout), mWorkout.getDisplayName()); updateTitle(text); String subtitle = getString( R.string.actionbar_subtitle_workout, mWorkout.getWeek(), mWorkout.getCycle(), mWorkout.getMainExercise().getWeight()); updateHelpMessage(subtitle); initActionButton(); } /** * Called when the application is paused. */ @Override protected void onPause() { super.onPause(); int pos = mViewPager != null ? mViewPager.getCurrentItem() : mCurrentPage; WorkoutHolder.getInstance().putWorkout( new WorkoutHolder.WorkoutItem( mWorkout, pos, mTimerIsRunning, mTimeElapsed)); } @Override protected int getNavigationResource() { return R.drawable.ic_arrow_back_black_24dp; } @Override protected String getToolbarTitle() { return ""; } @Override protected int getToolbarHelpMessage() { return 0; } /** * Called when an option item is selected. */ @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: finish(); overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); checkWorkoutForDeload(false); return true; case R.id.action_done: checkWorkoutForDeload(true); return true; case R.id.action_timer: StopwatchDialog.newInstance( getString(R.string.stopwatch_title), mTimeElapsed, mTimerIsRunning) .show(getSupportFragmentManager(), StopwatchDialog.TAG); return true; case R.id.action_calendar: Time time = mWorkout.getWorkoutTime(); CalendarDatePickerDialog.newInstance(mDateSetListener, time.year, time.month, time.monthDay) .show(getSupportFragmentManager(), CalendarDatePickerDialog.class.getName()); return true; } return super.onOptionsItemSelected(item); } /** * Override on back press to store the workout. */ @Override public void onBackPressed() { super.onBackPressed(); checkWorkoutForDeload(false); } /** * Save our instance when needed. */ @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); int pos = mViewPager != null ? mViewPager.getCurrentItem() : mCurrentPage; outState.putParcelable(Constants.BUNDLE_EXERCISE_ITEM, mWorkout); outState.putBoolean(EXTRA_TIMER_IS_RUNNING, mTimerIsRunning); outState.putInt(EXTRA_CURRENT_PAGE, pos); outState.putLong(EXTRA_ELAPSED_TIME, mTimeElapsed); } /** * Called before our option menu is created. */ @Override public boolean onPrepareOptionsMenu(Menu menu) { menu.findItem(R.id.action_calendar).setVisible( mWorkout != null && mWorkout.isComplete()); menu.findItem(R.id.action_done).setVisible( mWorkout != null && mWorkout.getMainExercise().getLastSetProgress() > -1); return super.onPrepareOptionsMenu(menu); } /** * Called to create our options menu. */ @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_workout, menu); return true; } /** * Called when a page in our ViewPager is scrolled */ @Override public void onPageScrolled(int i, float v, int i2) { // Not used here } /** * Called when a page is selected in our ViewPager. */ @Override public void onPageSelected(int i) { // Not used } /** * Called when a page in our ViewPager has changed state. */ @Override public void onPageScrollStateChanged(int i) { // Not used here } /** * Called when a tab is selected. */ @Override public void onTabSelected(Tab tab, FragmentTransaction ft) { if (mViewPager != null) { mCurrentPage = tab.getPosition(); mViewPager.setCurrentItem(tab.getPosition()); } } /** * Called when a tab is unselected. */ @Override public void onTabUnselected(Tab tab, FragmentTransaction ft) { // Not used here } /** * Called when a tab is reselected. */ @Override public void onTabReselected(Tab tab, FragmentTransaction ft) { // Not used here } /** * Called when we confirm our text in the EditTextDialog. */ @Override public void getTextFromDialog(String text) { mWorkout.updateNotes(text); } /** * Get callback information for time elapsed in the stopwatch */ @Override public void onStopwatchDismissed(long timeElapsed, boolean isRunning) { mTimeElapsed = timeElapsed; mTimerIsRunning = isRunning; } /** * Called when our confirmation dialog is closed. */ @Override public void onDialogConfirmed(boolean confirmed) { FragmentManager fragmentManager = getSupportFragmentManager(); WorkoutFragment main = (WorkoutMainFragment) fragmentManager.findFragmentByTag(WorkoutMainFragment.TAG); WorkoutFragment extra = (WorkoutAdditionalFragment) fragmentManager.findFragmentByTag(WorkoutAdditionalFragment.TAG); SqlHandler handler = new SqlHandler(this); try { handler.open(); if (!handler.doDeload(mWorkout)) { int workoutId = mWorkout.getWorkoutId() == -1 ? handler.getNextWorkoutId() : mWorkout.getWorkoutId(); mWorkout.setWorkoutId(workoutId); int code = Activity.RESULT_CANCELED; if (main.storeWorkout(true, mWorkout, handler, !confirmed) && extra.storeWorkout(true, mWorkout, handler, !confirmed)) { code = Activity.RESULT_OK; } setResult(code); finish(); } } catch (SQLException e) { WendlerizedLog.e("Failed to store mWorkout on deload", e); } finally { handler.close(); } } /** * Called when snack bar is shown. */ @Override public void onShow(Snackbar snackbar) { final View view = findViewById(R.id.multiple_actions); view.animate() .translationY(-snackbar.getHeight()) .setInterpolator(new DecelerateInterpolator(1.5f)) .setDuration(300) .start(); } /** * Called when snack bar is in place. */ @Override public void onShown(Snackbar snackbar) { } /** * Called when the snack bar is dismissed. */ @Override public void onDismiss(Snackbar snackbar) { final View view = findViewById(R.id.multiple_actions); view.animate() .setInterpolator(new AccelerateInterpolator(1.5f)) .translationY(view.getTranslationY() + snackbar.getHeight()) .setDuration(300) .start(); } /** * Called when snack bar is gone. */ @Override public void onDismissed(Snackbar snackbar) { } /** * Check if we should deload the workout. */ private void checkWorkoutForDeload(boolean complete) { SqlHandler handler = new SqlHandler(this); try { handler.open(); if (!handler.doDeload(mWorkout)) { WendlerizedLog.d("No need to deload, store the workout"); storeWorkout(handler, complete); } else if (complete) { WendlerizedLog.d("Workout is completed, but we should deload"); ConfirmationDialog.newInstance( getString(R.string.delayed_deload_message), getString(R.string.delayed_deload_title), getString(R.string.do_continue), getString(R.string.do_deload), null).show(getSupportFragmentManager(), ConfirmationDialog.TAG); } } catch (SQLException e) { WendlerizedLog.e("Failed to store mWorkout", e); } finally { handler.close(); } } /** * Store our workout in the database. */ private void storeWorkout(SqlHandler handler, boolean isComplete) { FragmentManager fragmentManager = getSupportFragmentManager(); WorkoutFragment main = (WorkoutMainFragment) fragmentManager.findFragmentByTag(WorkoutMainFragment.TAG); WorkoutFragment extra = (WorkoutAdditionalFragment) fragmentManager.findFragmentByTag(WorkoutAdditionalFragment.TAG); int workoutId = mWorkout.getWorkoutId() == -1 ? handler.getNextWorkoutId() : mWorkout.getWorkoutId(); mWorkout.setWorkoutId(workoutId); int code = Activity.RESULT_CANCELED; if (main.storeWorkout(isComplete, mWorkout, handler, false) && extra.storeWorkout(isComplete, mWorkout, handler, false)) { code = Activity.RESULT_OK; } WendlerizedLog.d("Stored workout and we were successful: " + (code == Activity.RESULT_OK)); setResult(code); WorkoutHolder.getInstance().destroy(); finish(); } /** * Listener for the CalenderPicker. */ private final CalendarDatePickerDialog.OnDateSetListener mDateSetListener = new CalendarDatePickerDialog.OnDateSetListener() { @Override public void onDateSet(CalendarDatePickerDialog calendarDatePickerDialog, int year, int month, int day) { mWorkout.updateWorkoutTime(year, month, day); } }; /** * Initialize the Floating Action Button. */ private void initActionButton() { final FloatingActionsMenu menu = (FloatingActionsMenu) findViewById(R.id.multiple_actions); findViewById(R.id.action_notes).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { EditTextDialog.newInstance( getString(R.string.my_notes), mWorkout.getNotes()) .show(getSupportFragmentManager(), EditTextDialog.TAG); menu.collapse(); } }); findViewById(R.id.action_add_additional).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Fragment fragment = getSupportFragmentManager(). findFragmentByTag(WorkoutAdditionalFragment.TAG); if (fragment instanceof WorkoutAdditionalFragment) { ((WorkoutAdditionalFragment) fragment).onActionTaken(Action.ADD_EXERCISE); } menu.collapse(); } }); findViewById(R.id.action_enter_reps).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Fragment fragment = getSupportFragmentManager(). findFragmentByTag(WorkoutMainFragment.TAG); if (fragment instanceof WorkoutMainFragment) { ((WorkoutMainFragment) fragment).onActionTaken(Action.SET_REPS); } menu.collapse(); } }); } /** * Initialize the fragment handling. */ private FragmentManager initFragmentManagement() { FragmentManager fragmentManager = getSupportFragmentManager(); WorkoutMainFragment main = (WorkoutMainFragment) fragmentManager.findFragmentByTag(WorkoutMainFragment.TAG); WorkoutAdditionalFragment extra = (WorkoutAdditionalFragment) fragmentManager.findFragmentByTag(WorkoutAdditionalFragment.TAG); android.support.v4.app.FragmentTransaction remove = fragmentManager.beginTransaction(); if (main == null) { main = WorkoutMainFragment.newInstance( mWorkout.getMainExercise(), mWorkout.getWeek(), mWorkout.isComplete()); } else { remove.remove(main); } if (extra == null) { extra = WorkoutAdditionalFragment.newInstance( mWorkout.getAdditionalExercises(), mWorkout.getName()); } else { remove.remove(extra); } if (!remove.isEmpty()) { remove.commit(); fragmentManager.executePendingTransactions(); } mViewPager = (ViewPager) findViewById(R.id.pager); TwoFragmentAdapter adapter = new TwoFragmentAdapter(fragmentManager, main, extra, getResources()); if (mViewPager != null) { mViewPager.setAdapter(adapter); mViewPager.setCurrentItem(mCurrentPage); mViewPager.setPageTransformer(true, new ZoomOutPageTransformer()); mViewPager.setOnPageChangeListener(this); PagerSlidingTabStrip mSlidingTabLayout = (PagerSlidingTabStrip) findViewById(R.id.sliding_tabs); mSlidingTabLayout.setViewPager(mViewPager); } else { fragmentManager.beginTransaction() .add(R.id.left_pane, main, WorkoutMainFragment.TAG) .add(R.id.right_pane, extra, WorkoutAdditionalFragment.TAG) .commit(); } return fragmentManager; } /** * Custom TwoFragmentAdapter */ private static class TwoFragmentAdapter extends PagerAdapter { private final FragmentManager fragmentManager; private final Fragment one; private final Fragment two; private android.support.v4.app.FragmentTransaction currentTransaction = null; private Fragment currentPrimaryItem = null; private final Resources res; public TwoFragmentAdapter( FragmentManager fragmentManager, Fragment one, Fragment two, Resources res) { this.fragmentManager = fragmentManager; this.one = one; this.two = two; this.res = res; } @Override public int getCount() { return 2; } @Override public Object instantiateItem(ViewGroup container, int position) { if (currentTransaction == null) { currentTransaction = fragmentManager.beginTransaction(); } String tag = (position == 0) ? WorkoutMainFragment.TAG : WorkoutAdditionalFragment.TAG; Fragment fragment = (position == 0) ? one : two; currentTransaction.add(container.getId(), fragment, tag); if (fragment != currentPrimaryItem) { fragment.setMenuVisibility(false); fragment.setUserVisibleHint(false); } return fragment; } @Override public void destroyItem(ViewGroup container, int position, Object object) { // With two pages, fragments should never be destroyed. throw new AssertionError(); } @Override public void setPrimaryItem(ViewGroup container, int position, Object object) { Fragment fragment = (Fragment) object; if (fragment != currentPrimaryItem) { if (currentPrimaryItem != null) { currentPrimaryItem.setMenuVisibility(false); currentPrimaryItem.setUserVisibleHint(false); } if (fragment != null) { fragment.setMenuVisibility(true); fragment.setUserVisibleHint(true); } currentPrimaryItem = fragment; } } @Override public void finishUpdate(ViewGroup container) { if (currentTransaction != null) { currentTransaction.commitAllowingStateLoss(); currentTransaction = null; fragmentManager.executePendingTransactions(); } } @Override public CharSequence getPageTitle(int position) { switch (position) { case 0: return res.getString(R.string.main_exercise).toUpperCase(); default: return res.getString(R.string.additional_exercise).toUpperCase(); } } @Override public boolean isViewFromObject(View view, Object object) { return ((Fragment) object).getView() == view; } } }