diff options
Diffstat (limited to 'libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/LegacyEditorFragment.java')
-rw-r--r-- | libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/LegacyEditorFragment.java | 1194 |
1 files changed, 1194 insertions, 0 deletions
diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/LegacyEditorFragment.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/LegacyEditorFragment.java new file mode 100644 index 000000000..75febb3fa --- /dev/null +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/LegacyEditorFragment.java @@ -0,0 +1,1194 @@ +package org.wordpress.android.editor; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Typeface; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Parcelable; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.text.Editable; +import android.text.Layout; +import android.text.Selection; +import android.text.Spannable; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.text.method.ArrowKeyMovementMethod; +import android.text.style.AlignmentSpan; +import android.text.style.QuoteSpan; +import android.text.style.StrikethroughSpan; +import android.text.style.StyleSpan; +import android.text.style.URLSpan; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.WindowManager; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.webkit.URLUtil; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.SeekBar; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.ToggleButton; + +import com.android.volley.VolleyError; +import com.android.volley.toolbox.ImageLoader; + +import org.wordpress.android.editor.legacy.EditLinkActivity; +import org.wordpress.android.editor.legacy.WPEditImageSpan; +import org.wordpress.android.util.AppLog; +import org.wordpress.android.util.AppLog.T; +import org.wordpress.android.util.DisplayUtils; +import org.wordpress.android.util.ImageUtils; +import org.wordpress.android.util.MediaUtils; +import org.wordpress.android.util.ToastUtils; +import org.wordpress.android.util.ToastUtils.Duration; +import org.wordpress.android.util.helpers.MediaFile; +import org.wordpress.android.util.helpers.MediaGallery; +import org.wordpress.android.util.helpers.MediaGalleryImageSpan; +import org.wordpress.android.util.helpers.WPImageSpan; +import org.wordpress.android.util.helpers.WPUnderlineSpan; +import org.wordpress.android.util.widgets.WPEditText; + +import java.util.Locale; + +public class LegacyEditorFragment extends EditorFragmentAbstract implements TextWatcher, + WPEditText.OnSelectionChangedListener, View.OnTouchListener { + public static final int ACTIVITY_REQUEST_CODE_CREATE_LINK = 4; + public static final String ACTION_MEDIA_GALLERY_TOUCHED = "MEDIA_GALLERY_TOUCHED"; + public static final String EXTRA_MEDIA_GALLERY = "EXTRA_MEDIA_GALLERY"; + + private static final int MIN_THUMBNAIL_WIDTH = 200; + private static final int CONTENT_ANIMATION_DURATION = 250; + private static final String KEY_IMAGE_SPANS = "image-spans"; + private static final String KEY_START = "start"; + private static final String KEY_END = "end"; + private static final String KEY_CONTENT = "content"; + private static final String TAG_FORMAT_BAR_BUTTON_STRONG = "strong"; + private static final String TAG_FORMAT_BAR_BUTTON_EM = "em"; + private static final String TAG_FORMAT_BAR_BUTTON_UNDERLINE = "u"; + private static final String TAG_FORMAT_BAR_BUTTON_STRIKE = "strike"; + private static final String TAG_FORMAT_BAR_BUTTON_QUOTE = "blockquote"; + + private View mRootView; + private WPEditText mContentEditText; + private EditText mTitleEditText; + private ToggleButton mBoldToggleButton, mEmToggleButton, mBquoteToggleButton; + private ToggleButton mUnderlineToggleButton, mStrikeToggleButton; + private LinearLayout mFormatBar, mPostContentLinearLayout, mPostSettingsLinearLayout; + private Button mAddPictureButton; + private boolean mIsBackspace; + private boolean mScrollDetected; + private boolean mIsLocalDraft; + + private int mStyleStart, mSelectionStart, mSelectionEnd, mFullViewBottom; + private int mLastPosition = -1; + private CharSequence mTitle; + private CharSequence mContent; + + private float mLastYPos = 0; + + @Override + public boolean onBackPressed() { + // leave full screen mode back button is pressed + if (getActionBar() != null && !getActionBar().isShowing()) { + setContentEditingModeVisible(false); + return true; + } + return false; + } + + @Override + public CharSequence getTitle() { + if (mTitleEditText != null) { + return mTitleEditText.getText().toString(); + } + return mTitle; + } + + @Override + public CharSequence getContent() { + if (mContentEditText != null) { + return mContentEditText.getText().toString(); + } + return mContent; + } + + @Override + public void setTitle(CharSequence text) { + mTitle = text; + if (mTitleEditText != null) { + mTitleEditText.setText(text); + } else { + // TODO + } + } + + @Override + public void setContent(CharSequence text) { + mContent = text; + if (mContentEditText != null) { + mContentEditText.setText(text); + mContentEditText.setSelection(mSelectionStart, mSelectionEnd); + } else { + // TODO + } + } + + @Override + public Spanned getSpannedContent() { + return mContentEditText.getText(); + } + + public void setLocalDraft(boolean isLocalDraft) { + mIsLocalDraft = isLocalDraft; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + final ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_edit_post_content, container, false); + + mFormatBar = (LinearLayout) rootView.findViewById(R.id.format_bar); + mTitleEditText = (EditText) rootView.findViewById(R.id.post_title); + mTitleEditText.setText(mTitle); + mTitleEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + // Go to full screen editor when 'next' button is tapped on soft keyboard + ActionBar actionBar = getActionBar(); + if (actionId == EditorInfo.IME_ACTION_NEXT && actionBar != null && actionBar.isShowing()) { + setContentEditingModeVisible(true); + } + return false; + } + }); + + mContentEditText = (WPEditText) rootView.findViewById(R.id.post_content); + mContentEditText.setText(mContent); + + mPostContentLinearLayout = (LinearLayout) rootView.findViewById(R.id.post_content_wrapper); + mPostSettingsLinearLayout = (LinearLayout) rootView.findViewById(R.id.post_settings_wrapper); + Button postSettingsButton = (Button) rootView.findViewById(R.id.post_settings_button); + postSettingsButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mEditorFragmentListener.onSettingsClicked(); + } + }); + mBoldToggleButton = (ToggleButton) rootView.findViewById(R.id.bold); + mEmToggleButton = (ToggleButton) rootView.findViewById(R.id.em); + mBquoteToggleButton = (ToggleButton) rootView.findViewById(R.id.bquote); + mUnderlineToggleButton = (ToggleButton) rootView.findViewById(R.id.underline); + mStrikeToggleButton = (ToggleButton) rootView.findViewById(R.id.strike); + mAddPictureButton = (Button) rootView.findViewById(R.id.addPictureButton); + Button linkButton = (Button) rootView.findViewById(R.id.link); + Button moreButton = (Button) rootView.findViewById(R.id.more); + + registerForContextMenu(mAddPictureButton); + mContentEditText = (WPEditText) rootView.findViewById(R.id.post_content); + mContentEditText.setOnSelectionChangedListener(this); + mContentEditText.setOnTouchListener(this); + mContentEditText.addTextChangedListener(this); + mContentEditText.setOnEditTextImeBackListener(new WPEditText.EditTextImeBackListener() { + @Override + public void onImeBack(WPEditText ctrl, String text) { + // Go back to regular editor if IME keyboard is dismissed + // Bottom comparison is there to ensure that the keyboard is actually showing + ActionBar actionBar = getActionBar(); + if (mRootView.getBottom() < mFullViewBottom && actionBar != null && !actionBar.isShowing()) { + setContentEditingModeVisible(false); + } + } + }); + mAddPictureButton.setOnClickListener(mFormatBarButtonClickListener); + mBoldToggleButton.setOnClickListener(mFormatBarButtonClickListener); + linkButton.setOnClickListener(mFormatBarButtonClickListener); + mEmToggleButton.setOnClickListener(mFormatBarButtonClickListener); + mUnderlineToggleButton.setOnClickListener(mFormatBarButtonClickListener); + mStrikeToggleButton.setOnClickListener(mFormatBarButtonClickListener); + mBquoteToggleButton.setOnClickListener(mFormatBarButtonClickListener); + moreButton.setOnClickListener(mFormatBarButtonClickListener); + mEditorFragmentListener.onEditorFragmentInitialized(); + + if (savedInstanceState != null) { + Parcelable[] spans = savedInstanceState.getParcelableArray(KEY_IMAGE_SPANS); + + mContent = savedInstanceState.getString(KEY_CONTENT, ""); + mContentEditText.setText(mContent); + mContentEditText.setSelection(savedInstanceState.getInt(KEY_START, 0), + savedInstanceState.getInt(KEY_END, 0)); + + if (spans != null && spans.length > 0) { + for (Parcelable s : spans) { + WPImageSpan editSpan = (WPImageSpan)s; + addMediaFile(editSpan.getMediaFile(), editSpan.getMediaFile().getFilePath(), + mImageLoader, editSpan.getStartPosition(), editSpan.getEndPosition()); + } + } + } + + return rootView; + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mRootView = view; + mRootView.getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener); + } + + private ViewTreeObserver.OnGlobalLayoutListener mGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() { + public void onGlobalLayout() { + mRootView.getViewTreeObserver().removeGlobalOnLayoutListener(this); + mFullViewBottom = mRootView.getBottom(); + } + }; + + private ActionBar getActionBar() { + if (!isAdded()) { + return null; + } + if (getActivity() instanceof AppCompatActivity) { + return ((AppCompatActivity) getActivity()).getSupportActionBar(); + } else { + return null; + } + } + + public void setContentEditingModeVisible(boolean isVisible) { + if (!isAdded()) { + return; + } + ActionBar actionBar = getActionBar(); + if (isVisible) { + Animation fadeAnimation = new AlphaAnimation(1, 0); + fadeAnimation.setDuration(CONTENT_ANIMATION_DURATION); + fadeAnimation.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + mTitleEditText.setVisibility(View.GONE); + } + + @Override + public void onAnimationEnd(Animation animation) { + mPostSettingsLinearLayout.setVisibility(View.GONE); + mFormatBar.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + }); + + mPostContentLinearLayout.startAnimation(fadeAnimation); + if (actionBar != null) { + actionBar.hide(); + } + } else { + mTitleEditText.setVisibility(View.VISIBLE); + mFormatBar.setVisibility(View.GONE); + Animation fadeAnimation = new AlphaAnimation(0, 1); + fadeAnimation.setDuration(CONTENT_ANIMATION_DURATION); + fadeAnimation.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + } + + @Override + public void onAnimationEnd(Animation animation) { + mPostSettingsLinearLayout.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + }); + mPostContentLinearLayout.startAnimation(fadeAnimation); + getActivity().invalidateOptionsMenu(); + if (actionBar != null) { + actionBar.show(); + } + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (requestCode == LegacyEditorFragment.ACTIVITY_REQUEST_CODE_CREATE_LINK && data != null) { + Bundle extras = data.getExtras(); + if (extras == null) { + return; + } + String linkURL = extras.getString("linkURL"); + String linkText = extras.getString("linkText"); + createLinkFromSelection(linkURL, linkText); + } + } + + public boolean hasEmptyContentFields() { + return TextUtils.isEmpty(mTitleEditText.getText()) && TextUtils.isEmpty(mContentEditText.getText()); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + mFullViewBottom = mRootView.getBottom(); + } + + private void createLinkFromSelection(String linkURL, String linkText) { + try { + if (linkURL != null && !linkURL.equals("http://") && !linkURL.equals("")) { + if (mSelectionStart > mSelectionEnd) { + int temp = mSelectionEnd; + mSelectionEnd = mSelectionStart; + mSelectionStart = temp; + } + Editable editable = mContentEditText.getText(); + if (editable == null) { + return; + } + if (mIsLocalDraft) { + if (linkText == null) { + if (mSelectionStart < mSelectionEnd) { + editable.delete(mSelectionStart, mSelectionEnd); + } + editable.insert(mSelectionStart, linkURL); + editable.setSpan(new URLSpan(linkURL), mSelectionStart, mSelectionStart + linkURL.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + mContentEditText.setSelection(mSelectionStart + linkURL.length()); + } else { + if (mSelectionStart < mSelectionEnd) { + editable.delete(mSelectionStart, mSelectionEnd); + } + editable.insert(mSelectionStart, linkText); + editable.setSpan(new URLSpan(linkURL), mSelectionStart, mSelectionStart + linkText.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + mContentEditText.setSelection(mSelectionStart + linkText.length()); + } + } else { + if (linkText == null) { + if (mSelectionStart < mSelectionEnd) { + editable.delete(mSelectionStart, mSelectionEnd); + } + String urlHTML = "<a href=\"" + linkURL + "\">" + linkURL + "</a>"; + editable.insert(mSelectionStart, urlHTML); + mContentEditText.setSelection(mSelectionStart + urlHTML.length()); + } else { + if (mSelectionStart < mSelectionEnd) { + editable.delete(mSelectionStart, mSelectionEnd); + } + String urlHTML = "<a href=\"" + linkURL + "\">" + linkText + "</a>"; + editable.insert(mSelectionStart, urlHTML); + mContentEditText.setSelection(mSelectionStart + urlHTML.length()); + } + } + } + } catch (RuntimeException e) { + AppLog.e(T.POSTS, e); + } + } + + /** + * Formatting bar + */ + private View.OnClickListener mFormatBarButtonClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + int id = v.getId(); + if (id == R.id.bold) { + mEditorFragmentListener.onTrackableEvent(TrackableEvent.BOLD_BUTTON_TAPPED); + onFormatButtonClick(mBoldToggleButton, TAG_FORMAT_BAR_BUTTON_STRONG); + } else if (id == R.id.em) { + mEditorFragmentListener.onTrackableEvent(TrackableEvent.ITALIC_BUTTON_TAPPED); + onFormatButtonClick(mEmToggleButton, TAG_FORMAT_BAR_BUTTON_EM); + } else if (id == R.id.underline) { + mEditorFragmentListener.onTrackableEvent(TrackableEvent.UNDERLINE_BUTTON_TAPPED); + onFormatButtonClick(mUnderlineToggleButton, TAG_FORMAT_BAR_BUTTON_UNDERLINE); + } else if (id == R.id.strike) { + mEditorFragmentListener.onTrackableEvent(TrackableEvent.STRIKETHROUGH_BUTTON_TAPPED); + onFormatButtonClick(mStrikeToggleButton, TAG_FORMAT_BAR_BUTTON_STRIKE); + } else if (id == R.id.bquote) { + mEditorFragmentListener.onTrackableEvent(TrackableEvent.BLOCKQUOTE_BUTTON_TAPPED); + onFormatButtonClick(mBquoteToggleButton, TAG_FORMAT_BAR_BUTTON_QUOTE); + } else if (id == R.id.more) { + mEditorFragmentListener.onTrackableEvent(TrackableEvent.MORE_BUTTON_TAPPED); + mSelectionEnd = mContentEditText.getSelectionEnd(); + Editable str = mContentEditText.getText(); + if (str != null) { + if (mSelectionEnd > str.length()) + mSelectionEnd = str.length(); + str.insert(mSelectionEnd, "\n<!--more-->\n"); + } + } else if (id == R.id.link) { + mEditorFragmentListener.onTrackableEvent(TrackableEvent.LINK_BUTTON_TAPPED); + mSelectionStart = mContentEditText.getSelectionStart(); + mStyleStart = mSelectionStart; + mSelectionEnd = mContentEditText.getSelectionEnd(); + if (mSelectionStart > mSelectionEnd) { + int temp = mSelectionEnd; + mSelectionEnd = mSelectionStart; + mSelectionStart = temp; + } + Intent i = new Intent(getActivity(), EditLinkActivity.class); + if (mSelectionEnd > mSelectionStart) { + if (mContentEditText.getText() != null) { + String selectedText = mContentEditText.getText().subSequence(mSelectionStart, mSelectionEnd).toString(); + i.putExtra("selectedText", selectedText); + } + } + startActivityForResult(i, ACTIVITY_REQUEST_CODE_CREATE_LINK); + } else if (id == R.id.addPictureButton) { + mEditorFragmentListener.onTrackableEvent(TrackableEvent.MEDIA_BUTTON_TAPPED); + mEditorFragmentListener.onAddMediaClicked(); + if (isAdded()) { + getActivity().openContextMenu(mAddPictureButton); + } + } + } + }; + + private WPEditImageSpan createWPEditImageSpanLocal(Context context, MediaFile mediaFile) { + if (context == null || mediaFile == null || mediaFile.getFilePath() == null) { + return null; + } + Uri imageUri = Uri.parse(mediaFile.getFilePath()); + Bitmap thumbnailBitmap; + if (MediaUtils.isVideo(imageUri.toString())) { + thumbnailBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.media_movieclip); + } else { + thumbnailBitmap = ImageUtils.getWPImageSpanThumbnailFromFilePath(context, imageUri.getEncodedPath(), + ImageUtils.getMaximumThumbnailWidthForEditor(context)); + if (thumbnailBitmap == null) { + // Use a placeholder in case thumbnail can't be decoded (OOM for instance) + thumbnailBitmap = BitmapFactory.decodeResource(context.getResources(), + R.drawable.legacy_dashicon_format_image_big_grey); + } + } + WPEditImageSpan imageSpan = new WPEditImageSpan(context, thumbnailBitmap, imageUri); + mediaFile.setWidth(MediaUtils.getMaximumImageWidth(context, imageUri, mBlogSettingMaxImageWidth)); + imageSpan.setMediaFile(mediaFile); + return imageSpan; + } + + private WPEditImageSpan createWPEditImageSpanRemote(Context context, MediaFile mediaFile) { + if (context == null || mediaFile == null || mediaFile.getFileURL() == null) { + return null; + } + int drawable = mediaFile.isVideo() ? R.drawable.media_movieclip : R.drawable.legacy_dashicon_format_image_big_grey; + Uri uri = Uri.parse(mediaFile.getFileURL()); + WPEditImageSpan imageSpan = new WPEditImageSpan(context, drawable, uri); + imageSpan.setMediaFile(mediaFile); + return imageSpan; + } + + private WPEditImageSpan createWPEditImageSpan(Context context, MediaFile mediaFile) { + if (!URLUtil.isNetworkUrl(mediaFile.getFileURL())) { + return createWPEditImageSpanLocal(context, mediaFile); + } else { + return createWPEditImageSpanRemote(context, mediaFile); + } + } + + /** + * Applies formatting to selected text, or marks the entry for a new text style + * at the current cursor position + * @param toggleButton button from formatting bar + * @param tag HTML tag name for text style + */ + private void onFormatButtonClick(ToggleButton toggleButton, String tag) { + Spannable s = mContentEditText.getText(); + if (s == null) + return; + int selectionStart = mContentEditText.getSelectionStart(); + mStyleStart = selectionStart; + int selectionEnd = mContentEditText.getSelectionEnd(); + + if (selectionStart > selectionEnd) { + int temp = selectionEnd; + selectionEnd = selectionStart; + selectionStart = temp; + } + + Class styleClass = null; + if (tag.equals(TAG_FORMAT_BAR_BUTTON_STRONG) || tag.equals(TAG_FORMAT_BAR_BUTTON_EM)) + styleClass = StyleSpan.class; + else if (tag.equals(TAG_FORMAT_BAR_BUTTON_UNDERLINE)) + styleClass = WPUnderlineSpan.class; + else if (tag.equals(TAG_FORMAT_BAR_BUTTON_STRIKE)) + styleClass = StrikethroughSpan.class; + else if (tag.equals(TAG_FORMAT_BAR_BUTTON_QUOTE)) + styleClass = QuoteSpan.class; + + if (styleClass == null) + return; + + Object[] allSpans = s.getSpans(selectionStart, selectionEnd, styleClass); + boolean textIsSelected = selectionEnd > selectionStart; + if (mIsLocalDraft) { + // Local drafts can use the rich text editor. Yay! + boolean shouldAddSpan = true; + for (Object span : allSpans) { + if (span instanceof StyleSpan) { + StyleSpan styleSpan = (StyleSpan)span; + if ((styleSpan.getStyle() == Typeface.BOLD && !tag.equals(TAG_FORMAT_BAR_BUTTON_STRONG)) + || (styleSpan.getStyle() == Typeface.ITALIC && !tag.equals(TAG_FORMAT_BAR_BUTTON_EM))) { + continue; + } + } + if (!toggleButton.isChecked() && textIsSelected) { + // If span exists and text is selected, remove the span + s.removeSpan(span); + shouldAddSpan = false; + break; + } else if (!toggleButton.isChecked()) { + // Remove span at cursor point if button isn't checked + Object[] spans = s.getSpans(mStyleStart - 1, mStyleStart, styleClass); + for (Object removeSpan : spans) { + selectionStart = s.getSpanStart(removeSpan); + selectionEnd = s.getSpanEnd(removeSpan); + s.removeSpan(removeSpan); + } + } + } + + if (shouldAddSpan) { + if (tag.equals(TAG_FORMAT_BAR_BUTTON_STRONG)) { + s.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), selectionStart, selectionEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } else if (tag.equals(TAG_FORMAT_BAR_BUTTON_EM)) { + s.setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), selectionStart, selectionEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } else { + try { + s.setSpan(styleClass.newInstance(), selectionStart, selectionEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } catch (java.lang.InstantiationException e) { + AppLog.e(T.POSTS, e); + } catch (IllegalAccessException e) { + AppLog.e(T.POSTS, e); + } + } + } + } else { + // Add HTML tags when editing an existing post + String startTag = "<" + tag + ">"; + String endTag = "</" + tag + ">"; + Editable content = mContentEditText.getText(); + if (textIsSelected) { + content.insert(selectionStart, startTag); + content.insert(selectionEnd + startTag.length(), endTag); + toggleButton.setChecked(false); + mContentEditText.setSelection(selectionEnd + startTag.length() + endTag.length()); + } else if (toggleButton.isChecked()) { + content.insert(selectionStart, startTag); + mContentEditText.setSelection(selectionEnd + startTag.length()); + } else if (!toggleButton.isChecked()) { + content.insert(selectionEnd, endTag); + mContentEditText.setSelection(selectionEnd + endTag.length()); + } + } + } + + /** + * Rich Text Editor + */ + public void showImageSettings(final View alertView, final EditText titleText, + final EditText caption, final EditText imageWidthText, + final CheckBox featuredCheckBox, final CheckBox featuredInPostCheckBox, + final int maxWidth, final Spinner alignmentSpinner, final WPImageSpan imageSpan) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setTitle(getString(R.string.image_settings)); + builder.setView(alertView); + builder.setPositiveButton(getString(android.R.string.ok), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + String title = (titleText.getText() != null) ? titleText.getText().toString() : ""; + MediaFile mediaFile = imageSpan.getMediaFile(); + if (mediaFile == null) { + return; + } + mediaFile.setTitle(title); + mediaFile.setHorizontalAlignment(alignmentSpinner.getSelectedItemPosition()); + mediaFile.setWidth(getEditTextIntegerClamped(imageWidthText, 10, maxWidth)); + String captionText = (caption.getText() != null) ? caption.getText().toString() : ""; + mediaFile.setCaption(captionText); + mediaFile.setFeatured(featuredCheckBox.isChecked()); + if (featuredCheckBox.isChecked()) { + // remove featured flag from all other images + Spannable contentSpannable = mContentEditText.getText(); + WPImageSpan[] imageSpans = + contentSpannable.getSpans(0, contentSpannable.length(), WPImageSpan.class); + if (imageSpans.length > 1) { + for (WPImageSpan postImageSpan : imageSpans) { + if (postImageSpan != imageSpan) { + MediaFile postMediaFile = postImageSpan.getMediaFile(); + postMediaFile.setFeatured(false); + postMediaFile.setFeaturedInPost(false); + // TODO: remove this + mEditorFragmentListener.saveMediaFile(postMediaFile); + } + } + } + } + mediaFile.setFeaturedInPost(featuredInPostCheckBox.isChecked()); + // TODO: remove this + mEditorFragmentListener.saveMediaFile(mediaFile); + } + }); + builder.setNegativeButton(getString(android.R.string.cancel), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + dialog.dismiss(); + } + }); + AlertDialog alertDialog = builder.create(); + alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); + alertDialog.show(); + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + float pos = event.getY(); + + if (event.getAction() == 0) + mLastYPos = pos; + + if (event.getAction() > 1) { + int scrollThreshold = DisplayUtils.dpToPx(getActivity(), 2); + if (((mLastYPos - pos) > scrollThreshold) || ((pos - mLastYPos) > scrollThreshold)) + mScrollDetected = true; + } + + mLastYPos = pos; + + if (event.getAction() == MotionEvent.ACTION_UP) { + ActionBar actionBar = getActionBar(); + if (actionBar != null && actionBar.isShowing()) { + setContentEditingModeVisible(true); + return false; + } + } + + if (event.getAction() == MotionEvent.ACTION_UP && !mScrollDetected) { + Layout layout = ((TextView) v).getLayout(); + int x = (int) event.getX(); + int y = (int) event.getY(); + + x += v.getScrollX(); + y += v.getScrollY(); + if (layout != null) { + int line = layout.getLineForVertical(y); + int charPosition = layout.getOffsetForHorizontal(line, x); + + Spannable spannable = mContentEditText.getText(); + if (spannable == null) { + return false; + } + // check if image span was tapped + WPImageSpan[] imageSpans = spannable.getSpans(charPosition, charPosition, WPImageSpan.class); + + if (imageSpans.length != 0) { + final WPImageSpan imageSpan = imageSpans[0]; + MediaFile mediaFile = imageSpan.getMediaFile(); + if (mediaFile == null) + return false; + if (!mediaFile.isVideo()) { + LayoutInflater factory = LayoutInflater.from(getActivity()); + final View alertView = factory.inflate(R.layout.alert_image_options, null); + if (alertView == null) + return false; + final EditText imageWidthText = (EditText) alertView.findViewById(R.id.imageWidthText); + final EditText titleText = (EditText) alertView.findViewById(R.id.title); + final EditText caption = (EditText) alertView.findViewById(R.id.caption); + final CheckBox featuredCheckBox = (CheckBox) alertView.findViewById(R.id.featuredImage); + final CheckBox featuredInPostCheckBox = (CheckBox) alertView.findViewById(R.id.featuredInPost); + + // show featured image checkboxes if supported + if (mFeaturedImageSupported) { + featuredCheckBox.setVisibility(View.VISIBLE); + featuredInPostCheckBox.setVisibility(View.VISIBLE); + } + + featuredCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) { + featuredInPostCheckBox.setVisibility(View.VISIBLE); + } else { + featuredInPostCheckBox.setVisibility(View.GONE); + } + + } + }); + + final SeekBar seekBar = (SeekBar) alertView.findViewById(R.id.imageWidth); + final Spinner alignmentSpinner = (Spinner) alertView.findViewById(R.id.alignment_spinner); + ArrayAdapter<CharSequence> adapter = + ArrayAdapter.createFromResource(getActivity(), R.array.alignment_array, + android.R.layout.simple_spinner_item); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + alignmentSpinner.setAdapter(adapter); + + seekBar.setProgress(mediaFile.getWidth()); + titleText.setText(mediaFile.getTitle()); + caption.setText(mediaFile.getCaption()); + featuredCheckBox.setChecked(mediaFile.isFeatured()); + + if (mediaFile.isFeatured()) { + featuredInPostCheckBox.setVisibility(View.VISIBLE); + } else { + featuredInPostCheckBox.setVisibility(View.GONE); + } + + featuredInPostCheckBox.setChecked(mediaFile.isFeaturedInPost()); + + alignmentSpinner.setSelection(mediaFile.getHorizontalAlignment(), true); + + final int maxWidth = MediaUtils.getMaximumImageWidth(getActivity(), + imageSpan.getImageSource(), mBlogSettingMaxImageWidth); + seekBar.setMax(maxWidth / 10); + imageWidthText.setText(String.format(Locale.US, "%dpx", maxWidth)); + if (mediaFile.getWidth() != 0) { + seekBar.setProgress(mediaFile.getWidth() / 10); + } + seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (progress == 0) { + progress = 1; + } + imageWidthText.setText(String.format(Locale.US, "%dpx", progress * 10)); + } + }); + + imageWidthText.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (hasFocus) { + imageWidthText.setText(""); + } + } + }); + + imageWidthText.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + int width = getEditTextIntegerClamped(imageWidthText, 10, maxWidth); + seekBar.setProgress(width / 10); + imageWidthText.setSelection((String.valueOf(width).length())); + + InputMethodManager imm = (InputMethodManager) getActivity() + .getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(imageWidthText.getWindowToken(), + InputMethodManager.RESULT_UNCHANGED_SHOWN); + + return true; + } + }); + + showImageSettings(alertView, titleText, caption, imageWidthText, featuredCheckBox, + featuredInPostCheckBox, maxWidth, alignmentSpinner, imageSpan); + mScrollDetected = false; + return true; + } + + } else { + mContentEditText.setMovementMethod(ArrowKeyMovementMethod.getInstance()); + int selectionStart = mContentEditText.getSelectionStart(); + if (selectionStart >= 0 && mContentEditText.getSelectionEnd() >= selectionStart) + mContentEditText.setSelection(selectionStart, mContentEditText.getSelectionEnd()); + } + + // get media gallery spans + MediaGalleryImageSpan[] gallerySpans = spannable.getSpans(charPosition, charPosition, MediaGalleryImageSpan.class); + if (gallerySpans.length > 0) { + final MediaGalleryImageSpan gallerySpan = gallerySpans[0]; + Intent intent = new Intent(ACTION_MEDIA_GALLERY_TOUCHED); + intent.putExtra(EXTRA_MEDIA_GALLERY, gallerySpan.getMediaGallery()); + getActivity().sendBroadcast(intent); + } + } + } else if (event.getAction() == 1) { + mScrollDetected = false; + } + return false; + } + + @Override + public void afterTextChanged(Editable s) { + int position = Selection.getSelectionStart(mContentEditText.getText()); + if ((mIsBackspace && position != 1) || mLastPosition == position || !mIsLocalDraft) + return; + + if (position < 0) { + position = 0; + } + mLastPosition = position; + if (position > 0) { + if (mStyleStart > position) { + mStyleStart = position - 1; + } + + boolean shouldBold = mBoldToggleButton.isChecked(); + boolean shouldEm = mEmToggleButton.isChecked(); + boolean shouldUnderline = mUnderlineToggleButton.isChecked(); + boolean shouldStrike = mStrikeToggleButton.isChecked(); + boolean shouldQuote = mBquoteToggleButton.isChecked(); + + Object[] allSpans = s.getSpans(mStyleStart, position, Object.class); + for (Object span : allSpans) { + if (span instanceof StyleSpan) { + StyleSpan styleSpan = (StyleSpan) span; + if (styleSpan.getStyle() == Typeface.BOLD) + shouldBold = false; + else if (styleSpan.getStyle() == Typeface.ITALIC) + shouldEm = false; + } else if (span instanceof WPUnderlineSpan) { + shouldUnderline = false; + } else if (span instanceof StrikethroughSpan) { + shouldStrike = false; + } else if (span instanceof QuoteSpan) { + shouldQuote = false; + } + } + + if (shouldBold) + s.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), mStyleStart, position, Spannable.SPAN_INCLUSIVE_INCLUSIVE); + if (shouldEm) + s.setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), mStyleStart, position, Spannable.SPAN_INCLUSIVE_INCLUSIVE); + if (shouldUnderline) + s.setSpan(new WPUnderlineSpan(), mStyleStart, position, Spannable.SPAN_INCLUSIVE_INCLUSIVE); + if (shouldStrike) + s.setSpan(new StrikethroughSpan(), mStyleStart, position, Spannable.SPAN_INCLUSIVE_INCLUSIVE); + if (shouldQuote) + s.setSpan(new QuoteSpan(), mStyleStart, position, Spannable.SPAN_INCLUSIVE_INCLUSIVE); + } + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + mIsBackspace = (count - after == 1) || (s.length() == 0); + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void onSelectionChanged() { + if (!mIsLocalDraft) { + return; + } + + final Spannable s = mContentEditText.getText(); + if (s == null) + return; + // set toggle buttons if cursor is inside of a matching span + mStyleStart = mContentEditText.getSelectionStart(); + Object[] spans = s.getSpans(mContentEditText.getSelectionStart(), mContentEditText.getSelectionStart(), Object.class); + + mBoldToggleButton.setChecked(false); + mEmToggleButton.setChecked(false); + mBquoteToggleButton.setChecked(false); + mUnderlineToggleButton.setChecked(false); + mStrikeToggleButton.setChecked(false); + for (Object span : spans) { + if (span instanceof StyleSpan) { + StyleSpan ss = (StyleSpan) span; + if (ss.getStyle() == android.graphics.Typeface.BOLD) { + mBoldToggleButton.setChecked(true); + } + if (ss.getStyle() == android.graphics.Typeface.ITALIC) { + mEmToggleButton.setChecked(true); + } + } + if (span instanceof QuoteSpan) { + mBquoteToggleButton.setChecked(true); + } + if (span instanceof WPUnderlineSpan) { + mUnderlineToggleButton.setChecked(true); + } + if (span instanceof StrikethroughSpan) { + mStrikeToggleButton.setChecked(true); + } + } + } + + private int getEditTextIntegerClamped(EditText editText, int min, int max) { + int width = 10; + try { + if (editText.getText() != null) + width = Integer.parseInt(editText.getText().toString().replace("px", "")); + } catch (NumberFormatException e) { + AppLog.e(T.POSTS, e); + } + width = Math.min(max, Math.max(width, min)); + return width; + } + + private void loadWPImageSpanThumbnail(MediaFile mediaFile, String imageURL, ImageLoader imageLoader) { + if (mediaFile == null || imageURL == null) { + return; + } + final String mediaId = mediaFile.getMediaId(); + if (mediaId == null) { + return; + } + + final int maxThumbWidth = ImageUtils.getMaximumThumbnailWidthForEditor(getActivity()); + + imageLoader.get(imageURL, new ImageLoader.ImageListener() { + @Override + public void onErrorResponse(VolleyError arg0) { + } + + @Override + public void onResponse(ImageLoader.ImageContainer container, boolean arg1) { + Bitmap downloadedBitmap = container.getBitmap(); + if (downloadedBitmap == null) { + // no bitmap downloaded from the server. + return; + } + + if (downloadedBitmap.getWidth() < MIN_THUMBNAIL_WIDTH) { + // Picture is too small. Show the placeholder in this case. + return; + } + + Bitmap resizedBitmap; + // resize the downloaded bitmap + resizedBitmap = ImageUtils.getScaledBitmapAtLongestSide(downloadedBitmap, maxThumbWidth); + + if (resizedBitmap == null) { + return; + } + + final EditText editText = mContentEditText; + Editable s = editText.getText(); + if (s == null) { + return; + } + WPImageSpan[] spans = s.getSpans(0, s.length(), WPImageSpan.class); + if (spans.length != 0 && getActivity() != null) { + for (WPImageSpan is : spans) { + MediaFile mediaFile = is.getMediaFile(); + if (mediaFile == null) { + continue; + } + if (mediaId.equals(mediaFile.getMediaId()) && !is.isNetworkImageLoaded()) { + // replace the existing span with a new one with the correct image, re-add + // it to the same position. + int spanStart = is.getStartPosition(); + int spanEnd = is.getEndPosition(); + WPEditImageSpan imageSpan = new WPEditImageSpan(getActivity(), resizedBitmap, + is.getImageSource()); + imageSpan.setMediaFile(is.getMediaFile()); + imageSpan.setNetworkImageLoaded(true); + imageSpan.setPosition(spanStart, spanEnd); + s.removeSpan(is); + s.setSpan(imageSpan, spanStart, spanEnd + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + } + } + } + } + }, 0, 0); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + WPImageSpan[] spans = mContentEditText.getText().getSpans(0, mContentEditText.getText().length(), WPEditImageSpan.class); + + if (spans != null && spans.length > 0) { + outState.putParcelableArray(KEY_IMAGE_SPANS, spans); + } + + outState.putInt(KEY_START, mContentEditText.getSelectionStart()); + outState.putInt(KEY_END, mContentEditText.getSelectionEnd()); + outState.putString(KEY_CONTENT, mContentEditText.getText().toString()); + } + + private class AddMediaFileTask extends AsyncTask<Void, Void, WPEditImageSpan> { + private MediaFile mMediaFile; + private String mImageUrl; + private ImageLoader mImageLoader; + private int mStart; + private int mEnd; + + public AddMediaFileTask(MediaFile mediaFile, String imageUrl, ImageLoader imageLoader, int start, int end) { + mMediaFile = mediaFile; + mImageUrl = imageUrl; + mImageLoader = imageLoader; + mStart = start; + mEnd = end; + } + + protected WPEditImageSpan doInBackground(Void... voids) { + mMediaFile.setFileURL(mImageUrl); + mMediaFile.setFilePath(mImageUrl); + WPEditImageSpan imageSpan = createWPEditImageSpan(getActivity(), mMediaFile); + mEditorFragmentListener.saveMediaFile(mMediaFile); + return imageSpan; + } + + protected void onPostExecute(WPEditImageSpan imageSpan) { + if (imageSpan == null) { + if (isAdded()) { + ToastUtils.showToast(getActivity(), R.string.alert_error_adding_media, Duration.LONG); + } + return ; + } + // Insert the WPImageSpan in the content field + int selectionStart = mStart; + int selectionEnd = mEnd; + + if (selectionStart > selectionEnd) { + int temp = selectionEnd; + selectionEnd = selectionStart; + selectionStart = temp; + } + + imageSpan.setPosition(selectionStart, selectionEnd); + + int line, column = 0; + if (mContentEditText.getLayout() != null) { + line = mContentEditText.getLayout().getLineForOffset(selectionStart); + column = selectionStart - mContentEditText.getLayout().getLineStart(line); + } + + Editable s = mContentEditText.getText(); + if (s == null) { + return; + } + + WPImageSpan[] imageSpans = s.getSpans(selectionStart, selectionEnd, WPImageSpan.class); + if (imageSpans.length != 0) { + // insert a few line breaks if the cursor is already on an image + s.insert(selectionEnd, "\n\n"); + selectionStart = selectionStart + 2; + selectionEnd = selectionEnd + 2; + } else if (column != 0) { + // insert one line break if the cursor is not at the first column + s.insert(selectionEnd, "\n"); + selectionStart = selectionStart + 1; + selectionEnd = selectionEnd + 1; + } + + s.insert(selectionStart, " "); + s.setSpan(imageSpan, selectionStart, selectionEnd + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + AlignmentSpan.Standard as = new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER); + s.setSpan(as, selectionStart, selectionEnd + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + s.insert(selectionEnd + 1, "\n\n"); + + // Fetch and replace the WPImageSpan if it's a remote media + if (mImageLoader != null && URLUtil.isNetworkUrl(mImageUrl)) { + loadWPImageSpanThumbnail(mMediaFile, mImageUrl, mImageLoader); + } + } + } + + public void addMediaFile(final MediaFile mediaFile, final String imageUrl, final ImageLoader imageLoader, + final int start, final int end) { + AddMediaFileTask addMediaFileTask = new AddMediaFileTask(mediaFile, imageUrl, imageLoader, start, end); + addMediaFileTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + @Override + public void appendMediaFile(final MediaFile mediaFile, final String imageUrl, final ImageLoader imageLoader) { + addMediaFile(mediaFile, imageUrl, imageLoader, mContentEditText.getSelectionStart(), mContentEditText.getSelectionEnd()); + } + + @Override + public void appendGallery(MediaGallery mediaGallery) { + Editable editableText = mContentEditText.getText(); + if (editableText == null) { + return; + } + + int selectionStart = mContentEditText.getSelectionStart(); + int selectionEnd = mContentEditText.getSelectionEnd(); + + if (selectionStart > selectionEnd) { + int temp = selectionEnd; + selectionEnd = selectionStart; + selectionStart = temp; + } + + int line, column = 0; + if (mContentEditText.getLayout() != null) { + line = mContentEditText.getLayout().getLineForOffset(selectionStart); + column = mContentEditText.getSelectionStart() - mContentEditText.getLayout().getLineStart(line); + } + + if (column != 0) { + // insert one line break if the cursor is not at the first column + editableText.insert(selectionEnd, "\n"); + selectionStart = selectionStart + 1; + selectionEnd = selectionEnd + 1; + } + + editableText.insert(selectionStart, " "); + MediaGalleryImageSpan is = new MediaGalleryImageSpan(getActivity(), mediaGallery, + R.drawable.legacy_icon_mediagallery_placeholder); + editableText.setSpan(is, selectionStart, selectionEnd + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + AlignmentSpan.Standard as = new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER); + editableText.setSpan(as, selectionStart, selectionEnd + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + editableText.insert(selectionEnd + 1, "\n\n"); + } + + @Override + public void setUrlForVideoPressId(String videoPressId, String url, String posterUrl) { + + } + + @Override + public boolean isUploadingMedia() { + return false; + } + + @Override + public boolean hasFailedMediaUploads() { + return false; + } + + @Override + public void removeAllFailedMediaUploads() {} + + @Override + public void setTitlePlaceholder(CharSequence text) { + } + + @Override + public void setContentPlaceholder(CharSequence text) { + } + + @Override + public boolean isActionInProgress() { + return false; + } +} |