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.Build; import android.os.Bundle; import android.speech.RecognizerIntent; import android.support.v4.app.FragmentManager; import android.text.Editable; import android.text.Spannable; import android.text.SpannableString; import android.text.TextWatcher; import android.text.style.SuggestionSpan; import android.view.KeyEvent; import android.view.LayoutInflater; 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.EditText; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RelativeLayout.LayoutParams; import android.widget.Toast; import com.dozuki.ifixit.App; import com.dozuki.ifixit.R; import com.dozuki.ifixit.model.guide.StepLine; import com.dozuki.ifixit.ui.BaseFragment; import com.dozuki.ifixit.ui.guide.create.BulletReorderFragment.BulletRearrangeListener; import com.dozuki.ifixit.ui.guide.create.ChooseBulletDialog.BulletDialogListener; import com.dozuki.ifixit.util.Utils; import com.squareup.otto.Subscribe; import java.util.ArrayList; public class StepEditLinesFragment extends BaseFragment implements BulletDialogListener, BulletRearrangeListener { private static final int BULLET_LIMIT = 8; private static final int BULLET_INDENT = 25; private static final int INDENT_LIMIT = 2; private static final String STEP_LIST_KEY = "STEP_LIST_KEY"; private static final String STEP_ORDERBY = "STEP_ORDERBY"; private static final String SHOWING_BULLET_FRAG = "SHOWING_BULLET_FRAG"; private static final String BULLET_FRAG_ID = "BULLET_FRAG_ID"; private static final String SHOWING_REORDER_FRAG = "SHOWING_REORDER_FRAG"; private static final String REORDER_FRAG_ID = "REORDER_FRAG_ID"; private static final String MIC_TEXT_LINE_ID = "MIC_TEXT_LINE_ID_KEY"; protected static final int MIC_REQUEST_CODE = 2342; private LinearLayout mBulletContainer; private Button mNewBulletButton; private ArrayList<StepLine> mLines = new ArrayList<StepLine>(); private int mOrderby; private ChooseBulletDialog mChooseBulletDialog; private boolean mShowingChooseBulletDialog; private boolean mReorderModeActive; private BulletReorderFragment mReorderFragment; private static String NO_TITLE = ""; private static final String TITLE_KEY = "TITLE_KEY"; private String mTitle = NO_TITLE; private EditText mStepTitle; private int mStepId; ///////////////////////////////////////////////////// // LIFECYCLE ///////////////////////////////////////////////////// @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.guide_create_step_edit_lines, container, false); mStepTitle = (EditText) v.findViewById(R.id.step_edit_title_text); mNewBulletButton = (Button) v.findViewById(R.id.add_new_bullet_button); mBulletContainer = (LinearLayout) v.findViewById(R.id.edit_step_bullet_container); // Hide the step title input on the first step because it shouldn't ever be titled. if (mOrderby == 1) { mStepTitle.setVisibility(View.GONE); } mStepTitle.addTextChangedListener(new TextWatcher() { @Override public void afterTextChanged(Editable s) { if (mTitle.equals(s.toString())) { return; } mTitle = s.toString(); App.getBus().post(new StepTitleChangedEvent(mStepId, mTitle)); } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } }); if (mTitle.length() > 0) { mStepTitle.setText(mTitle); } if (savedInstanceState != null) { mTitle = savedInstanceState.getString(TITLE_KEY); FragmentManager fm = getSherlockActivity().getSupportFragmentManager(); mLines = (ArrayList<StepLine>) savedInstanceState.getSerializable(STEP_LIST_KEY); mOrderby = savedInstanceState.getInt(STEP_ORDERBY); mChooseBulletDialog = (ChooseBulletDialog) fm.getFragment(savedInstanceState, BULLET_FRAG_ID); mReorderFragment = (BulletReorderFragment) fm.getFragment(savedInstanceState, REORDER_FRAG_ID); mShowingChooseBulletDialog = savedInstanceState.getBoolean(SHOWING_BULLET_FRAG, false); if (mChooseBulletDialog != null && mShowingChooseBulletDialog) { mChooseBulletDialog.setTargetFragment(this, 0); } mReorderModeActive = savedInstanceState.getBoolean(SHOWING_REORDER_FRAG, false); if (mReorderFragment != null && mReorderModeActive) { mReorderFragment.setBulletRearrangeListener(this); } } mNewBulletButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { App.sendEvent("ui_action", "button_press", "new_bullet_button", null); mLines.add(new StepLine()); View view = getView(mLines.get(mLines.size() - 1), mLines.size() - 1); mBulletContainer.addView(view, mLines.size() - 1); mNewBulletButton.setVisibility(View.GONE); view.findViewById(mLines.size() - 1).requestFocus(); ((InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE)).showSoftInput( view.findViewById(mLines.size() - 1), InputMethodManager.SHOW_FORCED); } }); initilizeBulletContainer(); return v; } @Override public void onSaveInstanceState(Bundle savedInstanceState) { super.onSaveInstanceState(savedInstanceState); savedInstanceState.putSerializable(STEP_LIST_KEY, mLines); savedInstanceState.putSerializable(STEP_ORDERBY, mOrderby); savedInstanceState.putString(TITLE_KEY, mTitle); FragmentManager fm = getSherlockActivity().getSupportFragmentManager(); if (mChooseBulletDialog != null && mShowingChooseBulletDialog) { fm.putFragment(savedInstanceState, BULLET_FRAG_ID, mChooseBulletDialog); savedInstanceState.putBoolean(SHOWING_BULLET_FRAG, mShowingChooseBulletDialog); } savedInstanceState.putBoolean(SHOWING_REORDER_FRAG, mReorderModeActive); if (mReorderFragment != null && mReorderModeActive) { fm.putFragment(savedInstanceState, REORDER_FRAG_ID, mReorderFragment); savedInstanceState.putBoolean(SHOWING_REORDER_FRAG, mReorderModeActive); } } @Override public void onFinishBulletDialog(int index, String color) { mShowingChooseBulletDialog = false; StepLine curStep = mLines.get(index); if (color.equals("action_indent")) { if (curStep.getLevel() == INDENT_LIMIT) { Toast.makeText((getActivity()), R.string.indent_limit_above, Toast.LENGTH_SHORT).show(); return; } indentBullet(index); setGuideDirty(); } else if (color.equals("action_unindent")) { if (curStep.getLevel() == 0) { Toast.makeText((getActivity()), R.string.indent_limit_below, Toast.LENGTH_SHORT).show(); return; } unIndentBullet(index); setGuideDirty(); } else if (color.equals("action_reorder")) { launchBulletReorder(); } else if (color.equals("action_delete")) { createDeleteDialog(getActivity(), index).show(); } else if (!color.equals("action_cancel")) { curStep.setColor(color); mBulletContainer.removeViewAt(index); mBulletContainer.addView(getView(mLines.get(index), index), index); setGuideDirty(); } } @Override public void onReorderComplete(boolean canceled, ArrayList<StepLine> list) { mReorderModeActive = false; if (!canceled) { mLines.clear(); mLines.addAll(list); removeBullets(); initilizeBulletContainer(); if (!isIndentionStateValid()) { fixIndentionState(); } setGuideDirty(); } } @Subscribe public void onLineValidationFailed(StepLineValidationEvent event) { if (event.stepid == mStepId) { // Set the cursor to focus on the line that failed. EditText et = (EditText) mBulletContainer.findViewById(event.index); et.setError(getString(R.string.line_length_exceeded_error)); int textLength = et.getText().length(); et.setSelection(textLength, textLength); } } @Subscribe public void onStepMicComplete(StepMicCompleteEvent event) { if (event.stepid == mStepId) { SharedPreferences sp = getActivity().getPreferences(Activity.MODE_PRIVATE); int lineid = sp.getInt(MIC_TEXT_LINE_ID, 0); EditText lineText = (EditText) mBulletContainer.findViewWithTag(lineid + "text"); if (lineText != null) { String[] suggestions = new String[event.results.size()]; suggestions = event.results.toArray(suggestions); String newText = suggestions[0]; Editable oldText = lineText.getText(); int start = oldText.length(); // If there wasn't any old text (new line), then don't add an extra space between the old and new text. String combinedText = oldText + ((start == 0) ? "" : " ") + newText; int end = combinedText.length(); Spannable span = new SpannableString(combinedText); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { SuggestionSpan suggestionsSpan = new SuggestionSpan(getActivity(), suggestions, SuggestionSpan.FLAG_EASY_CORRECT); span.setSpan(suggestionsSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } lineText.setText(span); lineText.setSelection(end); } } } ///////////////////////////////////////////////////// // DIALOGS ///////////////////////////////////////////////////// public AlertDialog createDeleteDialog(final Context context, final int index) { AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(context.getString(R.string.step_delete_dialog_title)) .setMessage(context.getString(R.string.step_delete_dialog_body)) .setPositiveButton(context.getString(R.string.yes), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { removeBullet(index); if (!isIndentionStateValid()) { fixIndentionState(); } setGuideDirty(); dialog.dismiss(); } }).setNegativeButton(R.string.no, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); return builder.create(); } ///////////////////////////////////////////////////// // HELPERS ///////////////////////////////////////////////////// public void setStepTitle(String title) { mTitle = title; if (mStepTitle != null && title.length() > 0) { mStepTitle.setText(mTitle); } } public String getTitle() { return mTitle; } public void setSteps(ArrayList<StepLine> lines) { mLines.addAll(lines); } public void setStepOrderby(int orderby) { mOrderby = orderby; } public void setStepId(int stepid) { mStepId = stepid; } private void initilizeBulletContainer() { if (mLines.size() == 0 || (mLines.get(mLines.size() - 1).getTextRaw().length() != 0 && mLines.size() != BULLET_LIMIT)) { mNewBulletButton.setVisibility(View.VISIBLE); } else { mNewBulletButton.setVisibility(View.GONE); } for (int i = 0; i < mLines.size(); i++) { mBulletContainer.addView(getView(mLines.get(i), i), i); } } public View getView(final StepLine line, int index) { LayoutInflater vi = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); View v = vi.inflate(R.layout.guide_create_step_edit_line, null); ImageView bullet = (ImageView) v.findViewById(R.id.guide_step_item_bullet_thumbnail); bullet.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { FragmentManager fm = getActivity().getSupportFragmentManager(); mChooseBulletDialog = new ChooseBulletDialog(); mChooseBulletDialog.setTargetFragment(StepEditLinesFragment.this, 0); mChooseBulletDialog.setStepIndex(mLines.indexOf(line)); mChooseBulletDialog.show(fm, "fragment_choose_bullet"); restrictDialogOptions(mChooseBulletDialog, line); mShowingChooseBulletDialog = true; } }); LayoutParams params = (LayoutParams) bullet.getLayoutParams(); bullet.setImageResource(getBulletResource(line.getColor())); params.setMargins(BULLET_INDENT * line.getLevel(), 0, 0, 0); bullet.setLayoutParams(params); final EditText text = (EditText) v.findViewById(R.id.step_line_text_view); text.setText(line.getTextRaw()); text.setId(index); text.addTextChangedListener(new TextWatcher() { @Override public void afterTextChanged(Editable s) { if (s.length() > 0) { // Strip out newlines s = Utils.stripNewlines(s); String lineText = s.toString(); if (lineText.equals(line.getTextRaw())) { return; } int position = mLines.indexOf(line); mLines.get(position).setTextRaw(lineText); if (mLines.size() != BULLET_LIMIT && mLines.indexOf(line) == mLines.size() - 1) { mNewBulletButton.setVisibility(View.VISIBLE); } App.getBus().post(new StepLinesChangedEvent(mStepId, mLines)); } else if (mLines.indexOf(line) == mLines.size() - 1) { mNewBulletButton.setVisibility(View.GONE); } } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @Override public void onTextChanged(CharSequence s, int start, int before, int count) {} }); // Override ENTER key on hardware keyboards to prevent newlines in steps text.setOnKeyListener(new View.OnKeyListener() { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { return keyCode == KeyEvent.KEYCODE_ENTER; } }); ImageButton mic = (ImageButton) v.findViewById(R.id.step_line_mic_button); mic.setTag(index); text.setTag(index + "text"); mic.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM); SharedPreferences sp = getActivity().getPreferences(Activity.MODE_PRIVATE); sp.edit().putInt(MIC_TEXT_LINE_ID, (Integer) v.getTag()).commit(); intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM); intent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true); getActivity().startActivityForResult(intent, MIC_REQUEST_CODE); } }); return v; } protected void restrictDialogOptions(ChooseBulletDialog dialog, StepLine line) { if (!canIndent(line)) { dialog.disableIndent(); } if (!canUnIndent(line)) { dialog.disableUnIndent(); } if (!canDelete()) { dialog.disableDelete(); } if (!canRearrange()) { dialog.disableRearrange(); } } private boolean canRearrange() { return (mLines.size() > 1); } private boolean canDelete() { return (mLines.size() > 1); } private boolean canUnIndent(StepLine line) { return line.getLevel() != 0; } private boolean canIndent(StepLine line) { if (!(line.getLevel() < 2)) { return false; } if (mLines.indexOf(line) == 0) { return false; } if (line.getLevel() == 0) { return true; } if (line.getLevel() == 1) { if (mLines.get(mLines.indexOf(line) - 1).getLevel() >= 1) { return true; } } return false; } public int getBulletResource(String color) { int iconRes; if (color.equals("black")) { iconRes = R.drawable.bullet_black; } else if (color.equals("orange")) { iconRes = R.drawable.bullet_orange; } else if (color.equals("blue")) { iconRes = R.drawable.bullet_blue; } else if (color.equals("light_blue")) { iconRes = R.drawable.bullet_light_blue; } else if (color.equals("violet")) { iconRes = R.drawable.bullet_violet; } else if (color.equals("red")) { iconRes = R.drawable.bullet_red; } else if (color.equals("green")) { iconRes = R.drawable.bullet_green; } else if (color.equals("yellow")) { iconRes = R.drawable.bullet_yellow; } else if (color.equals("icon_reminder")) { iconRes = R.drawable.ic_dialog_bullet_reminder_dark; } else if (color.equals("icon_caution")) { iconRes = R.drawable.ic_dialog_bullet_caution; } else if (color.equals("icon_note")) { iconRes = R.drawable.ic_dialog_bullet_note_dark; } else { iconRes = R.drawable.bullet_black; } return iconRes; } private void unIndentBullet(int index) { StepLine curStep = mLines.get(index); if (curStep.getLevel() == 0) { return; } for (int i = index + 1; i < mLines.size(); i++) { if (mLines.get(i).getLevel() == (curStep.getLevel() + 1)) { unIndentBullet(i); } else if (mLines.get(i).getLevel() <= (curStep.getLevel())) { break; } } curStep.setLevel(curStep.getLevel() - 1); mBulletContainer.removeViewAt(index); mBulletContainer.addView(getView(mLines.get(index), index), index); } private void indentBullet(int index) { StepLine curStep = mLines.get(index); if (curStep.getLevel() == INDENT_LIMIT) { return; } for (int i = index + 1; i < mLines.size(); i++) { if (mLines.get(i).getLevel() == (curStep.getLevel() + 1)) { indentBullet(i); } else if (mLines.get(i).getLevel() <= (curStep.getLevel())) { break; } } curStep.setLevel(curStep.getLevel() + 1); mBulletContainer.removeViewAt(index); mBulletContainer.addView(getView(mLines.get(index), index), index); } private boolean isIndentionStateValid() { if (mLines.get(0).getLevel() != 0) { return false; } for (int i = 1; i < mLines.size(); i++) { if (mLines.get(i).getLevel() > mLines.get(i - 1).getLevel() + 1) { return false; } } return true; } private void fixIndentionState() { mLines.get(0).setLevel(0); for (int i = 1; i < mLines.size(); i++) { if (mLines.get(i).getLevel() > mLines.get(i - 1).getLevel() + 1) { mLines.get(i).setLevel(mLines.get(i - 1).getLevel() + 1); } } removeBullets(); initilizeBulletContainer(); } public ArrayList<StepLine> getLines() { return mLines; } private void launchBulletReorder() { App.sendScreenView("/guide/edit/" + ((StepEditActivity)getActivity()).getGuideId() + "/" + mStepId + "/line_reorder"); FragmentManager fm = getActivity().getSupportFragmentManager(); mReorderFragment = new BulletReorderFragment(); mReorderFragment.setLines(mLines); mReorderFragment.setBulletRearrangeListener(this); mReorderFragment.show(fm, "fragment_reorder_bullet"); mReorderModeActive = true; } private void setGuideDirty() { App.getBus().post(new StepLinesChangedEvent(mStepId, mLines)); } public void removeBullets() { mBulletContainer.removeViewsInLayout(0, mLines.size()); } private void removeBullet(int index) { mLines.remove(index); mBulletContainer.removeViewAt(index); mNewBulletButton.setVisibility(View.VISIBLE); } }