package com.dozuki.ifixit.ui.guide.create; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.os.Handler; import android.speech.RecognizerIntent; import android.support.v4.app.DialogFragment; import android.support.v4.app.FixedFragmentStatePagerAdapter; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.CompoundButton; import android.widget.ImageButton; import android.widget.Toast; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuItem; import com.dozuki.ifixit.App; import com.dozuki.ifixit.R; import com.dozuki.ifixit.model.Image; import com.dozuki.ifixit.model.guide.Guide; import com.dozuki.ifixit.model.guide.GuideStep; import com.dozuki.ifixit.model.guide.StepLine; import com.dozuki.ifixit.ui.BaseMenuDrawerActivity; import com.dozuki.ifixit.ui.gallery.GalleryActivity; import com.dozuki.ifixit.ui.guide.view.GuideViewActivity; import com.dozuki.ifixit.util.JSONHelper; import com.dozuki.ifixit.util.api.Api; import com.dozuki.ifixit.util.api.ApiCall; import com.dozuki.ifixit.util.api.ApiError; import com.dozuki.ifixit.util.api.ApiEvent; import com.squareup.otto.Subscribe; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class StepEditActivity extends BaseMenuDrawerActivity implements OnClickListener, ViewPager.OnPageChangeListener { private static final int STEP_VIEW = 1; private static final int FOR_RESULT = 2; private static final int HOME_UP = 3; private static final String TAG = "StepEditActivity"; private enum ConfirmSave { NEW_STEP, NEXT_STEP } public static final int GALLERY_REQUEST_CODE = 1; public static final int CAMERA_REQUEST_CODE = 1888; public static final String TEMP_FILE_NAME_KEY = "TEMP_FILE_NAME_KEY"; public static final String EXIT_CODE = "EXIT_CODE_KEY"; public static final String GUIDE_PUBLIC_KEY = "GUIDE_PUBLIC_KEY"; public static final String GUIDE_STEP_NUM_KEY = "GUIDE_STEP_NUM_KEY"; public static final String DELETE_GUIDE_DIALOG_KEY = "DeleteGuideDialog"; public static final String GUIDE_ID_KEY = "GUIDE_ID_KEY"; public static final String GUIDE_STEP_ID = "GUIDE_STEP_ID"; public static final String PARENT_GUIDE_ID_KEY = "PARENT_GUIDE_ID_KEY"; public static final int NO_PARENT_GUIDE = -1; private static final String SHOWING_HELP = "SHOWING_HELP"; private static final String IS_GUIDE_DIRTY_KEY = "IS_GUIDE_DIRTY_KEY"; private static final String SHOWING_SAVE = "SHOWING_SAVE"; private static final String LOCK_SAVE = "LOCK_SAVE"; private Guide mGuide; private ImageButton mAddStepButton; private Button mSaveStep; private ImageButton mDeleteStepButton; private StepAdapter mStepAdapter; private LockableViewPager mPager; private LockableTitlePageIndicator mTitleIndicator; private int mPagePosition = 0; private int mSavePosition; /** * Necessary for editing prerequisite guides from the view interface in order * to navigate back to the parent guide. */ private int mParentGuideId = NO_PARENT_GUIDE; // Used to navigate to the correct step when coming from GuideViewActivity. private int mInboundStepId; private boolean mConfirmDelete = false; private boolean mIsStepDirty = false; private boolean mShowingHelp = false; private boolean mShowingSave = false; private boolean mIsLoading = false; // Should a new step be created after a step POST response (creating a new step) private boolean mAddStepAfterSave = false; // Flag to prevent saving a guide while we're waiting for an image to upload and return private boolean mLockSave = false; private int mExitCode; private static int mLoadingContainer = R.id.step_edit_loading_screen; private SharedPreferences mSharedPreferences; ///////////////////////////////////////////////////// // LIFECYCLE ///////////////////////////////////////////////////// @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.guide_create_step_edit); mSharedPreferences = getSharedPreferences("com.dozuki.ifixit", Context.MODE_PRIVATE); if (savedInstanceState != null) { mGuide = (Guide) savedInstanceState.getSerializable(StepsActivity.GUIDE_KEY); mPagePosition = savedInstanceState.getInt(GUIDE_STEP_NUM_KEY); mConfirmDelete = savedInstanceState.getBoolean(DELETE_GUIDE_DIALOG_KEY); mIsStepDirty = savedInstanceState.getBoolean(IS_GUIDE_DIRTY_KEY); mShowingHelp = savedInstanceState.getBoolean(SHOWING_HELP); mShowingSave = savedInstanceState.getBoolean(SHOWING_SAVE); mLockSave = savedInstanceState.getBoolean(LOCK_SAVE); mIsLoading = savedInstanceState.getBoolean(LOADING); mExitCode = savedInstanceState.getInt(EXIT_CODE); if (mShowingHelp) { createHelpDialog().show(); } if (mShowingSave) { createExitWarningDialog(mExitCode).show(); } if (mConfirmDelete) { createDeleteDialog(this).show(); } } else if (getIntent().getExtras() != null) { extractExtras(getIntent().getExtras()); } else { // Creating a new guide initializeNewGuide(); } if (App.get().getSite().mGuideTypes == null) { Api.call(this, ApiCall.siteInfo()); } mSaveStep = (Button) findViewById(R.id.step_edit_save); toggleSave(mIsStepDirty); if (mGuide != null) { initPage(mPagePosition); } } private void initializeNewGuide() { // Set the page title to "New Guide" getSupportActionBar().setTitle(getString(R.string.new_guide)); // Require the user to be logged in to create a new guide. Existing guides // are covered by the get guide API call. openLoginDialogIfLoggedOut(); mGuide = new Guide(); GuideStep step = new GuideStep(); step.addLine(new StepLine()); mGuide.addStep(step); mPagePosition = 0; } private void initPage(int startPage) { String guideTitle = mGuide.getTitle(); // Only set the page title if the guide actually has a title. if (guideTitle != null) { getSupportActionBar().setTitle(guideTitle); } mAddStepButton = (ImageButton) findViewById(R.id.step_edit_add_step); mDeleteStepButton = (ImageButton) findViewById(R.id.step_edit_delete_step); mPager = (LockableViewPager) findViewById(R.id.guide_edit_body_pager); initPager(); mPager.setCurrentItem(startPage); mTitleIndicator = (LockableTitlePageIndicator) findViewById(R.id.step_edit_top_bar); mTitleIndicator.setViewPager(mPager); mTitleIndicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int i, float v, int i2) {} @Override public void onPageSelected(int currentPage) { String label = mStepAdapter.getFragmentScreenLabel(currentPage); App.sendScreenView(label); } @Override public void onPageScrollStateChanged(int i) {} }); mSaveStep.setOnClickListener(this); mAddStepButton.setOnClickListener(this); mDeleteStepButton.setOnClickListener(this); if (mIsLoading) { mPager.setVisibility(View.GONE); } // Must be after mPager and mTitleIndicator are initialized, otherwise they aren't locked if (mLockSave) { lockSave(); } // Finally, reload the action bar to update action states (view guide and public/private toggle) supportInvalidateOptionsMenu(); } private void initPager() { mStepAdapter = new StepAdapter(getSupportFragmentManager()); mPager.setAdapter(mStepAdapter); } private void extractExtras(Bundle extras) { if (extras != null) { mGuide = (Guide) extras.getSerializable(GuideCreateActivity.GUIDE_KEY); mPagePosition = extras.getInt(GUIDE_STEP_NUM_KEY, 0); if (mGuide == null) { mParentGuideId = extras.getInt(PARENT_GUIDE_ID_KEY, NO_PARENT_GUIDE); int guideid = extras.getInt(GUIDE_ID_KEY); mInboundStepId = extras.getInt(GUIDE_STEP_ID); showLoading(mLoadingContainer); Api.call(StepEditActivity.this, ApiCall.unpatrolledGuide(guideid)); } } } @Override public void onNewIntent(Intent intent) { super.onNewIntent(intent); Bundle extras = intent.getExtras(); if (extras != null) { mGuide = null; mPagePosition = 0; extractExtras(intent.getExtras()); } else { initializeNewGuide(); } if (mGuide != null) { initPage(mPagePosition); } } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { App.getBus().register(this); Image newThumb; switch (requestCode) { case GALLERY_REQUEST_CODE: if (data != null) { newThumb = (Image) data.getSerializableExtra(GalleryActivity.MEDIA_RETURN_KEY); mGuide.getStep(mPagePosition).addImage(newThumb); refreshView(mPagePosition); onGuideChanged(null); } else { Log.e("StepEditActivity", "Error data is null!"); return; } break; case CAMERA_REQUEST_CODE: if (resultCode == Activity.RESULT_OK) { String tempFileName = mSharedPreferences.getString(TEMP_FILE_NAME_KEY, null); if (tempFileName == null) { Log.e("StepEditActivity", "Error cameraTempFile is null!"); return; } // Prevent a save from being called until the image uploads and returns with the imageid lockSave(); newThumb = new Image(); newThumb.setLocalImage(tempFileName); mGuide.getStep(mPagePosition).addImage(newThumb); refreshView(mPagePosition); Api.call(this, ApiCall.uploadImageToStep(tempFileName)); } break; case StepEditLinesFragment.MIC_REQUEST_CODE: if (resultCode == Activity.RESULT_OK) { // Populate the wordsList with the String values the recognition engine thought it heard final ArrayList<String> matches = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS); if (matches == null) { return; } if (App.inDebug()) { String debug = ""; for (String match : matches) { debug += " " + match + "\n"; } } if (matches.size() > 0) { Handler handler = new Handler(); /** * We have to delay posting the event because this activities * onActivityResult method is called just before the fragments onResume. * Delaying 1/10 of a second gives the fragment enough time to * register its' event bus listener so it can receive the event. */ handler.postDelayed(new Runnable() { @Override public void run() { App.getBus().post(new StepMicCompleteEvent(matches, mGuide.getStep(mPagePosition).getStepid())); } }, 100); } else { Log.d("StepEditActivity", "No mic matches; try again"); // TODO: Relaunch mic and try again. } } break; default: super.onActivityResult(requestCode, resultCode, data); } } @Override public void onSaveInstanceState(Bundle savedInstanceState) { super.onSaveInstanceState(savedInstanceState); savedInstanceState.putSerializable(StepsActivity.GUIDE_KEY, mGuide); savedInstanceState.putBoolean(DELETE_GUIDE_DIALOG_KEY, mConfirmDelete); savedInstanceState.putInt(StepEditActivity.GUIDE_STEP_NUM_KEY, mPagePosition); savedInstanceState.putBoolean(IS_GUIDE_DIRTY_KEY, mIsStepDirty); savedInstanceState.putBoolean(SHOWING_HELP, mShowingHelp); savedInstanceState.putBoolean(SHOWING_SAVE, mShowingSave); savedInstanceState.putBoolean(LOADING, mIsLoading); savedInstanceState.putBoolean(LOCK_SAVE, mLockSave); savedInstanceState.putInt(EXIT_CODE, mExitCode); } private void navigateBack() { Intent returnIntent = new Intent(); returnIntent.putExtra(GuideCreateActivity.GUIDE_KEY, mGuide); setResult(RESULT_OK, returnIntent); finish(); } @Override public boolean finishActivityIfLoggedOut() { return true; } @Override public AlertDialog getNavigationAlertDialog(final NavigationItem item) { if (!mIsStepDirty) { // Don't warn the user if the step is clean. return null; } mShowingSave = true; AlertDialog.Builder builder = new AlertDialog.Builder(this); builder .setTitle(getString(R.string.guide_create_confirm_leave_without_save_title)) .setMessage(getString(R.string.guide_create_confirm_leave_without_save_body)) .setNegativeButton(getString(R.string.save), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { mIsStepDirty = true; save(mPagePosition); dialog.dismiss(); navigateMenuDrawer(item); } }) .setPositiveButton(R.string.guide_create_confirm_leave_without_save_cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { mIsStepDirty = false; dialog.dismiss(); navigateMenuDrawer(item); } }); AlertDialog dialog = builder.create(); dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { mShowingSave = false; } }); return dialog; } @Override public boolean onCreateOptionsMenu(Menu menu) { getSupportMenuInflater().inflate(R.menu.step_edit_menu, menu); MenuItem item = menu.findItem(R.id.publish_guide); CompoundButton toggle = (CompoundButton)item.getActionView().findViewById(R.id.publish_toggle); toggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (mGuide != null && !mGuide.isNewGuide() && isChecked != mGuide.isPublic()) { // Disable the toggle so we don't have multiple presses. buttonView.setEnabled(false); // Disable the switch / checkbox until the publish response comes back. //buttonView.setEnabled(false); showLoading(mLoadingContainer, getString(isChecked ? R.string.publishing : R.string.unpublishing)); if (isChecked) { Api.call(StepEditActivity.this, ApiCall.publishGuide(mGuide.getGuideid(), mGuide.getRevisionid())); } else { Api.call(StepEditActivity.this, ApiCall.unpublishGuide(mGuide.getGuideid(), mGuide.getRevisionid())); } } } }); return super.onCreateOptionsMenu(menu); } @Override public boolean onPrepareOptionsMenu(Menu menu) { MenuItem viewGuide = menu.findItem(R.id.view_guide); MenuItem visibilityToggle = menu.findItem(R.id.publish_guide); CompoundButton toggle = ((CompoundButton) visibilityToggle.getActionView().findViewById(R.id.publish_toggle)); if (mGuide != null) { if (mGuide.getRevisionid() == null) { viewGuide.setIcon(R.drawable.ic_action_book_dark); viewGuide.setEnabled(false); toggle.setEnabled(false); } else { viewGuide.setIcon(R.drawable.ic_action_book); viewGuide.setEnabled(true); toggle.setEnabled(true); if (toggle.isChecked() != mGuide.isPublic()) toggle.setChecked(mGuide.isPublic()); } } else { viewGuide.setIcon(R.drawable.ic_action_book_dark); viewGuide.setEnabled(false); toggle.setEnabled(false); } return super.onPrepareOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.view_guide: finishEdit(STEP_VIEW); return true; case R.id.discard_changes: if (mIsStepDirty) { toggleSave(false); mIsStepDirty = false; // Set the inbound stepid so the Step pager will navigate to the current step after updating mInboundStepId = mGuide.getStep(mPagePosition).getStepid(); Api.call(StepEditActivity.this, ApiCall.unpatrolledGuide( mGuide.getGuideid())); } return true; default: return super.onOptionsItemSelected(item); } } ///////////////////////////////////////////////////// // NOTIFICATION LISTENERS ///////////////////////////////////////////////////// @Subscribe public void onSiteInfo(ApiEvent.SiteInfo event) { if (!event.hasError()) { App.get().setSite(event.getResult()); } } @Subscribe public void onPublishStatus(ApiEvent.PublishStatus event) { hideLoading(); // Re-enable the toggle view View publishToggle = findViewById(R.id.publish_toggle); if (publishToggle != null) { publishToggle.setEnabled(true); } // Update guide even if there is a conflict. if (!event.hasError() || event.getError().mType == ApiError.Type.CONFLICT) { Guide result = event.getResult(); mGuide.setRevisionid(result.getRevisionid()); mGuide.setPublic(result.isPublic()); } if (event.hasError()) { Api.getErrorDialog(this, event).show(); } // Reload the options menu to reenable the button, regardless of success or failure because we need to update // the state if the request failed so the toggle is reset to it's correct position. supportInvalidateOptionsMenu(); } @Subscribe public void onGuideGet(ApiEvent.GuideForEdit event) { hideLoading(); if (!event.hasError()) { int startPagePosition = 0; mGuide = event.getResult(); for (int i = 0; i < mGuide.getSteps().size(); i++) { if (mGuide.getStep(i).getStepid() == mInboundStepId) { startPagePosition = i; break; } } initPage(startPagePosition); } else { Api.getErrorDialog(this, event).show(); } } @Subscribe public void onStepSave(ApiEvent.StepSave event) { hideLoading(); if (!event.hasError() || event.getError().mType == ApiError.Type.CONFLICT) { updateCurrentStep(event.getResult()); } if (event.hasError()) { mIsStepDirty = true; toggleSave(mIsStepDirty); final ApiError error = event.getError(); if (error.mType == ApiError.Type.VALIDATION) { int positiveButton = R.string.error_confirm; AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(error.mTitle) .setMessage(error.mMessage) .setPositiveButton(positiveButton, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.dismiss(); if (error.mIndex != -1) { App.getBus().post(new StepLineValidationEvent( mGuide.getStep(mSavePosition).getStepid(), error.mIndex)); } } }); builder.create().show(); } else { Api.getErrorDialog(this, event).show(); } } } @Subscribe public void onGuideDetailsChanged(GuideDetailsChangedEvent event) { showLoading(mLoadingContainer, getString(R.string.creating_new_guide)); toggleSave(false); mGuide = event.guide; Api.call(StepEditActivity.this, ApiCall.createGuide(mGuide)); } @Subscribe public void onGuideCreated(ApiEvent.CreateGuide event) { hideLoading(); if (!event.hasError()) { mPagePosition = 0; Guide guide = event.getResult(); mGuide.setGuideid(guide.getGuideid()); mGuide.setRevisionid(guide.getRevisionid()); mGuide.setAuthor(guide.getAuthor()); mGuide.setPublic(false); mGuide.setTitle(guide.getTitle()); setTitle(guide.getTitle()); supportInvalidateOptionsMenu(); save(mPagePosition); } else { mIsStepDirty = true; toggleSave(mIsStepDirty); Api.getErrorDialog(this, event).show(); } } @Subscribe public void onUploadStepImage(ApiEvent.UploadStepImage event) { int position = mPagePosition; if (!event.hasError()) { Image newThumb = event.getResult(); // Find the temporarily stored image object to update the filename to // the image path and imageid. if (newThumb != null) { ArrayList<Image> images = new ArrayList<Image>(mGuide.getStep(position).getImages()); int i = 0; for (Image image : images) { if (image.isLocal()) { newThumb.setLocalPath(image.getPath()); images.set(i, newThumb); break; } i++; } mGuide.getStep(position).setImages(images); refreshView(position); } if (!mGuide.getStep(position).hasLocalImages()) { unlockSave(); // Set guide dirty after the image is uploaded so the user can't // save the guide before we have the imageid. onGuideChanged(null); } } else { Api.getErrorDialog(this, event).show(); } } @Subscribe public void onStepImageDelete(StepImageDeleteEvent event) { mGuide.getStep(mPagePosition).getImages().remove(event.image); refreshView(mPagePosition); } @Subscribe public void onStepAdd(ApiEvent.StepAdd event) { hideLoading(); if (!event.hasError()) { mGuide = event.getResult(); refreshView(mSavePosition); if (mAddStepAfterSave) { addNewStep(mSavePosition + 1); mAddStepAfterSave = false; } } else { mAddStepAfterSave = false; mIsStepDirty = true; toggleSave(mIsStepDirty); Api.getErrorDialog(this, event).show(); } } @Subscribe public void onGuideStepDeleted(ApiEvent.StepRemove event) { hideLoading(); if (!event.hasError()) { mGuide.setRevisionid(event.getResult().getRevisionid()); deleteStep(); } else { // Try to update the step on a conflict. if (event.getError().mType == ApiError.Type.CONFLICT) { try { updateCurrentStep(JSONHelper.parseStep( new JSONObject(event.getResponse()), 0)); } catch (JSONException e) { Log.e("StepEditActivity", "Error parsing step delete conflict", e); } } Api.getErrorDialog(this, event).show(); } } @Subscribe public void onStepLinesChanged(StepLinesChangedEvent event) { mGuide.getStepById(event.stepid).setLines(event.lines); onGuideChanged(null); } @Subscribe public void onStepTitleChanged(StepTitleChangedEvent event) { mGuide.getStepById(event.stepid).setTitle(event.title); onGuideChanged(null); } @Subscribe public void onImageCopy(ApiEvent.CopyImage event) { if (!event.hasError()) { Toast.makeText(this, getString(R.string.image_saved_to_media_manager_toast), Toast.LENGTH_LONG).show(); } else { Api.getErrorDialog(this, event).show(); } } @Subscribe public void onGuideChanged(StepChangedEvent event) { mIsStepDirty = true; toggleSave(mIsStepDirty); } ///////////////////////////////////////////////////// // UI EVENT LISTENERS ///////////////////////////////////////////////////// @Override public void onClick(View v) { String label = null; switch (v.getId()) { case R.id.step_edit_delete_step: label = "step_edit_delete_step"; if (!mGuide.getSteps().isEmpty()) { createDeleteDialog(StepEditActivity.this).show(); } break; case R.id.step_edit_save: label = "step_edit_save_step"; save(mPagePosition); break; case R.id.step_edit_add_step: label = "step_edit_add_step"; int newPosition = mPagePosition + 1; // If the step has changes, prompt the user to save or continue editing. if (mIsStepDirty) { createSaveChangesDialog(ConfirmSave.NEW_STEP).show(); // If the step doesn't have any bullet content, prompt them to add some. } else if (!stepHasLineContent(mGuide.getStep(mPagePosition))) { Toast.makeText(this, getResources().getString(R.string.guide_create_edit_step_media_cannot_add_step), Toast.LENGTH_SHORT).show(); return; } else { addNewStep(newPosition); } break; } if (label != null) { App.sendEvent("ui_action", "button_press", label, (long)mGuide.getStep(mPagePosition).getStepid()); } } private void addNewStep(int newPosition) { if (!mGuide.hasNewStep()) { GuideStep step = new GuideStep(newPosition); step.addLine(new StepLine()); mGuide.addStep(step, newPosition); refreshView(newPosition); } else { // Show "Must add content to step" toast Toast.makeText(this, getResources().getString(R.string.guide_create_edit_step_media_cannot_add_step), Toast.LENGTH_SHORT).show(); } } @Override public void onBackPressed() { if (mGuide != null && mGuide.getRevisionid() == null) { super.onBackPressed(); } else { finishEdit(HOME_UP); } } ///////////////////////////////////////////////////// // ADAPTERS and PRIVATE CLASSES ///////////////////////////////////////////////////// private void refreshView(int position) { // The view pager does not recreate the item in the current position unless we force it to. initPager(); mTitleIndicator.notifyDataSetChanged(); mStepAdapter.notifyDataSetChanged(); mPager.setCurrentItem(position, false); mPagePosition = position; } private class StepAdapter extends FixedFragmentStatePagerAdapter { private Map<Integer, String> mPageLabelMap; public StepAdapter(FragmentManager fm) { super(fm); mPageLabelMap = new HashMap<Integer, String>(); } @Override public int getCount() { return mGuide.getSteps().size(); } @Override public CharSequence getPageTitle(int position) { return getString(R.string.step_number, position + 1); } @Override public Fragment getItem(int position) { String label = "/guide/edit/" + mGuide.getGuideid() + "/" + (position + 1); // Step title # should be 1 indexed mPageLabelMap.put(position, label); return StepEditFragment.newInstance(mGuide.getStep(position)); } /** * When you call notifyDataSetChanged(), if it's set to POSITION_NONE, the view pager will remove all views and * reload them all. */ @Override public int getItemPosition(Object object) { /*StepEditFragment page = (StepEditFragment)object; GuideStep step = page.getStepObject(); int position = mGuide.getSteps().indexOf(step); if (position >= 0) { return position; } else {*/ return PagerAdapter.POSITION_NONE; // } } public String getFragmentScreenLabel(int key) { return mPageLabelMap.get(key); } @Override public void destroyItem(View container, int position, Object object) { super.destroyItem(container, position, object); mPageLabelMap.remove(position); } @Override public void setPrimaryItem(ViewGroup container, int position, Object object) { super.setPrimaryItem(container, position, object); mPagePosition = position; } } public void onPageScrollStateChanged(int arg0) { } public void onPageScrolled(int arg0, float arg1, int arg2) { } public void onPageSelected(int currentPage) { } ///////////////////////////////////////////////////// // DIALOGS ///////////////////////////////////////////////////// protected AlertDialog createDeleteDialog(final Context context) { mConfirmDelete = true; AlertDialog.Builder builder = new AlertDialog.Builder(context); builder .setTitle(context.getString(R.string.step_edit_confirm_delete_title)) .setMessage(context.getString(R.string.step_edit_confirm_delete_message, mPagePosition + 1)) .setPositiveButton(context.getString(R.string.yes), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { mConfirmDelete = false; mIsStepDirty = false; //step is at end of list if (mPagePosition >= mGuide.getSteps().size() // or it's a new step || mGuide.getStep(mPagePosition).getRevisionid() == null) { deleteStep(); } else { showLoading(mLoadingContainer, getString(R.string.deleting)); Api.call(StepEditActivity.this, ApiCall.deleteStep( mGuide.getGuideid(), mGuide.getSteps().get(mPagePosition))); } dialog.cancel(); } }).setNegativeButton(R.string.no, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { mConfirmDelete = false; dialog.cancel(); } }); AlertDialog dialog = builder.create(); dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { mConfirmDelete = false; } }); return dialog; } protected AlertDialog createHelpDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(getString(R.string.media_help_title)) .setMessage(getString(R.string.guide_create_edit_steps_help)) .setPositiveButton(getString(R.string.media_help_confirm), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }); return builder.create(); } protected AlertDialog createSaveChangesDialog(final ConfirmSave dialogType) { mShowingSave = true; AlertDialog.Builder builder = new AlertDialog.Builder(this); builder .setTitle(getString(R.string.save_changes_to_step)) .setPositiveButton(getString(R.string.yes), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { save(mPagePosition); dialog.dismiss(); switch (dialogType) { case NEW_STEP: mAddStepAfterSave = true; break; case NEXT_STEP: break; } } }) .setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { mIsStepDirty = true; dialog.dismiss(); } }); AlertDialog dialog = builder.create(); dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { mShowingSave = false; } }); return dialog; } protected AlertDialog createExitWarningDialog(final int exitCode) { mShowingSave = true; AlertDialog.Builder builder = new AlertDialog.Builder(this); builder .setTitle(getString(R.string.guide_create_confirm_leave_without_save_title)) .setMessage(getString(R.string.guide_create_confirm_leave_without_save_body)) .setPositiveButton(getString(R.string.save), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { mIsStepDirty = true; save(mPagePosition); dialog.dismiss(); if (mExitCode == STEP_VIEW) { navigateToStepView(); } else { navigateBack(); } } }) .setNegativeButton(R.string.guide_create_confirm_leave_without_save_cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { mIsStepDirty = false; dialog.dismiss(); finishEdit(exitCode); } }); AlertDialog dialog = builder.create(); dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { mShowingSave = false; } }); return dialog; } ///////////////////////////////////////////////////// // HELPERS ///////////////////////////////////////////////////// protected void save(int savePosition) { if (mGuide.isNewGuide()) { createGuide(); } else { saveStep(savePosition); } } private void createGuide() { if (!stepHasLineContent(mGuide.getStep(mPagePosition).getLines())) { Toast.makeText(this, getString( R.string.guide_create_edit_step_media_cannot_add_step), Toast.LENGTH_SHORT).show(); return; } // DialogFragment.show() will take care of adding the fragment // in a transaction. We also want to remove any currently showing // dialog, so make our own transaction and take care of that here. FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); Fragment prev = getSupportFragmentManager().findFragmentByTag("dialog"); if (prev != null) { ft.remove(prev); } ft.addToBackStack(null); // Create and show the dialog. DialogFragment newFragment = NewGuideDialogFragment.newInstance(mGuide); newFragment.show(ft, "dialog"); } private void saveStep(int savePosition) { GuideStep step = mGuide.getStep(savePosition); if (!stepHasLineContent(step)) { Toast.makeText(this, getResources().getString(R.string.guide_create_edit_must_add_line_content), Toast.LENGTH_SHORT).show(); return; } if (!mIsStepDirty || mLockSave) { return; } mSavePosition = savePosition; mIsStepDirty = false; InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0); showLoading(mLoadingContainer, getString(R.string.saving)); toggleSave(mIsStepDirty); if (step.getRevisionid() != null) { Api.call(this, ApiCall.editStep(step, mGuide.getGuideid())); } else { Api.call(this, ApiCall.createStep(step, mGuide.getGuideid(), mPagePosition + 1, mGuide.getRevisionid())); } } private boolean stepHasLineContent(GuideStep obj) { return stepHasLineContent(obj.getLines()); } private boolean stepHasLineContent(ArrayList<StepLine> lines) { if (lines.size() == 0) { return false; } for (StepLine l : lines) { if (l.getTextRaw().length() == 0) { return false; } } return true; } @Override public void showLoading(int container) { this.showLoading(container, getString(R.string.loading)); } @Override public void showLoading(int container, String message) { if (mPager != null) { mPager.setVisibility(View.GONE); } mIsLoading = true; super.showLoading(container, message); } @Override public void hideLoading() { if (mPager != null) { mPager.setVisibility(View.VISIBLE); } mIsLoading = false; super.hideLoading(); } public int getGuideId() { if (mGuide != null) { return mGuide.getGuideid(); } else { return 0; } } protected void navigateToStepView() { // Bail early if somehow the user is able to click view guide before the guide is retrieved. if (mGuide == null) { return; } App.sendEvent("menu_action", "button_press", "view_guide", (long)mGuide.getGuideid()); Intent intent = new Intent(this, GuideViewActivity.class); if (mParentGuideId != NO_PARENT_GUIDE) { intent.putExtra(GuideViewActivity.GUIDEID, mParentGuideId); } else { intent.putExtra(GuideViewActivity.GUIDEID, mGuide.getGuideid()); } intent.putExtra(GuideViewActivity.CURRENT_PAGE, mPagePosition + 1); intent.putExtra(GuideViewActivity.INBOUND_STEP_ID, mGuide.getStep(mPagePosition).getStepid()); intent.putExtra(StepEditActivity.GUIDE_PUBLIC_KEY, mGuide.isPublic()); intent.putExtra(GuideViewActivity.FROM_EDIT, true); startActivity(intent); } protected void finishEdit(int exitCode) { mExitCode = exitCode; if (mIsStepDirty || mLockSave) { createExitWarningDialog(exitCode).show(); } else { // Clean out unsaved, new steps. for (Iterator<GuideStep> it = mGuide.getSteps().iterator(); it.hasNext(); ) { if (it.next().getRevisionid() == null) { it.remove(); } } int guideSize = mGuide.getSteps().size(); // Make sure the step numbers are correct after removing steps. for (int i = 1; i <= guideSize; i++) { mGuide.getStep(i - 1).setStepNum(i); } // Necessary because if there were any new steps that were deleted, we need to let the adapters know about // it. Otherwise we get IllegalStateExceptions. mStepAdapter.notifyDataSetChanged(); mTitleIndicator.notifyDataSetChanged(); // If the current position is equal to or greater than the number of steps in the guide, // there was a new step at the end of the guide and that position no longer exists. Set the page position // to the new last step. if (mPagePosition >= guideSize) { mPagePosition = guideSize - 1; } Intent data; switch (exitCode) { case HOME_UP: data = new Intent(this, StepsActivity.class); data.putExtra(StepsActivity.GUIDE_ID_KEY, mGuide.getGuideid()); data.putExtra(GuideCreateActivity.GUIDE_KEY, mGuide); data.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION); startActivity(data); finish(); break; case FOR_RESULT: data = new Intent(); data.putExtra(GuideCreateActivity.GUIDE_KEY, mGuide); if (getParent() == null) { setResult(Activity.RESULT_OK, data); } else { getParent().setResult(Activity.RESULT_OK, data); } finish(); break; case STEP_VIEW: mIsStepDirty = false; toggleSave(false); navigateToStepView(); break; } } } private void updateCurrentStep(GuideStep step) { // Update the guide on successful save or conflict. mGuide.getSteps().set(mSavePosition, step); refreshView(mSavePosition); } protected void deleteStep() { mGuide.getSteps().remove(mPagePosition); // If it's the last step in the guide, finish the activity. if (mGuide.getSteps().size() == 0) { mStepAdapter.notifyDataSetChanged(); finishEdit(HOME_UP); return; } // Disable the save button. toggleSave(false); int guideSize = mGuide.getSteps().size(); for (int i = 0; i < guideSize; i++) { mGuide.getStep(i).setStepNum(i); } int newPosition = mPagePosition - 1; // The view pager does not recreate the item in the current position unless we force it to. refreshView(newPosition); } /** * Toggle the save button state * * @param toggle true to enable, false to disable */ public void toggleSave(boolean toggle) { if (!mLockSave) { int buttonBackgroundColor = toggle ? R.color.emphasis : R.color.disabled_grey_bg; int buttonTextColor = toggle ? R.color.white : R.color.disabled_grey_text; mSaveStep.setBackgroundColor(getResources().getColor(buttonBackgroundColor)); mSaveStep.setTextColor(getResources().getColor(buttonTextColor)); mSaveStep.setText(R.string.save); mSaveStep.setEnabled(toggle); // Lock the pager if save is enabled enableViewPager(!toggle); } } protected void enableViewPager(boolean unlocked) { if (mPager != null) { mPager.setPagingEnabled(unlocked); } if (mTitleIndicator != null) { mTitleIndicator.setPagingEnabled(unlocked); } } public void lockSave() { mLockSave = true; mSaveStep.setText(getString(R.string.loading_image)); mSaveStep.setBackgroundColor(getResources().getColor(R.color.disabled_grey_bg)); mSaveStep.setTextColor(getResources().getColor(R.color.disabled_grey_text)); enableViewPager(false); } public void unlockSave() { mLockSave = false; enableViewPager(true); } }