diff --git a/README.md b/README.md new file mode 100644 index 000000000000..5bfc36560535 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# WordPress-Utils-Android + +Part of the [WordPress-Android] project. + +[WordPress-Android]: https://github.com/wordpress-mobile/WordPress-Android diff --git a/WordPressUtils/src/main/java/org/wordpress/android/util/README.md b/WordPressUtils/README.md similarity index 100% rename from WordPressUtils/src/main/java/org/wordpress/android/util/README.md rename to WordPressUtils/README.md diff --git a/WordPressUtils/build.gradle b/WordPressUtils/build.gradle index da68c80de30e..1b1bd5a4457f 100644 --- a/WordPressUtils/build.gradle +++ b/WordPressUtils/build.gradle @@ -32,7 +32,7 @@ android { defaultConfig { applicationId "org.wordpress.android.util" - versionName "1.0.2" + versionName "1.1.0" versionCode 1 minSdkVersion 14 targetSdkVersion 19 diff --git a/WordPressUtils/src/main/java/org/wordpress/android/util/AppLog.java b/WordPressUtils/src/main/java/org/wordpress/android/util/AppLog.java index f2fff1b2ef4a..292c974ec7fa 100644 --- a/WordPressUtils/src/main/java/org/wordpress/android/util/AppLog.java +++ b/WordPressUtils/src/main/java/org/wordpress/android/util/AppLog.java @@ -17,6 +17,7 @@ public class AppLog { // T for Tag public enum T {READER, EDITOR, MEDIA, NUX, API, STATS, UTILS, NOTIFS, DB, POSTS, COMMENTS, THEMES, TESTS, PROFILING, SIMPERIUM} public static final String TAG = "WordPress"; + public static final int HEADER_LINE_COUNT = 2; private static boolean mEnableRecording = false; @@ -170,24 +171,18 @@ private static String getHTMLStringStackTrace(Throwable throwable) { /* * returns entire log as html for display (see AppLogViewerActivity) */ - public static String toHtml(Context context) { - StringBuilder sb = new StringBuilder(); + public static ArrayList toHtmlList(Context context) { + ArrayList items = new ArrayList(); - // add version & device info - sb.append("WordPress Android version: " + ProfilingUtils.getVersionName(context)).append("
") - .append("Android device name: " + DeviceUtils.getInstance().getDeviceName(context)).append("
"); + // add version & device info - be sure to change HEADER_LINE_COUNT if additional lines are added + items.add("WordPress Android version: " + ProfilingUtils.getVersionName(context) + ""); + items.add("Android device name: " + DeviceUtils.getInstance().getDeviceName(context) + ""); Iterator it = mLogEntries.iterator(); - int lineNum = 1; while (it.hasNext()) { - sb.append("") - .append(String.format("%02d", lineNum)) - .append(" ") - .append(it.next().toHtml()) - .append("
"); - lineNum++; + items.add(it.next().toHtml()); } - return sb.toString(); + return items; } diff --git a/WordPressUtils/src/main/java/org/wordpress/android/util/AutoResizeTextView.java b/WordPressUtils/src/main/java/org/wordpress/android/util/AutoResizeTextView.java new file mode 100644 index 000000000000..5f55f6058a97 --- /dev/null +++ b/WordPressUtils/src/main/java/org/wordpress/android/util/AutoResizeTextView.java @@ -0,0 +1,299 @@ +package org.wordpress.android.util; + +import android.content.Context; +import android.text.Layout; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.widget.TextView; + +/** + * Text view that auto adjusts text size to fit within the view. + * If the text size equals the minimum text size and still does not + * fit, append with an ellipsis. + * + * See http://stackoverflow.com/a/5535672 + * + */ +public class AutoResizeTextView extends TextView { + // Minimum text size for this text view + private static final float MIN_TEXT_SIZE = 20; + + // Interface for resize notifications + public interface OnTextResizeListener { + void onTextResize(TextView textView, float oldSize, float newSize); + } + + // Our ellipse string - Unicode Character 'HORIZONTAL ELLIPSIS' + private static final String M_ELLIPSIS = "\u2026"; + + // Registered resize listener + private OnTextResizeListener mTextResizeListener; + + // Flag for text and/or size changes to force a resize + private boolean mNeedsResize = false; + + // Text size that is set from code. This acts as a starting point for resizing + private float mTextSize; + + // Temporary upper bounds on the starting text size + private float mMaxTextSize = 0; + + // Lower bounds for text size + private float mMinTextSize = MIN_TEXT_SIZE; + + // Text view line spacing multiplier + private float mSpacingMult = 1.0f; + + // Text view additional line spacing + private float mSpacingAdd = 0.0f; + + // Add ellipsis to text that overflows at the smallest text size + private boolean mAddEllipsis = true; + + // Default constructor override + public AutoResizeTextView(Context context) { + this(context, null); + } + + // Default constructor when inflating from XML file + public AutoResizeTextView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + // Default constructor override + public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mTextSize = getTextSize(); + } + + /** + * When text changes, set the force resize flag to true and reset the text size. + */ + @Override + protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) { + mNeedsResize = true; + // Since this view may be reused, it is good to reset the text size + resetTextSize(); + } + + /** + * If the text view size changed, set the force resize flag to true + */ + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + if (w != oldw || h != oldh) { + mNeedsResize = true; + } + } + + /** + * Register listener to receive resize notifications + * @param listener + */ + public void setOnResizeListener(OnTextResizeListener listener) { + mTextResizeListener = listener; + } + + /** + * Override the set text size to update our internal reference values + */ + @Override + public void setTextSize(float size) { + super.setTextSize(size); + mTextSize = getTextSize(); + } + + /** + * Override the set text size to update our internal reference values + */ + @Override + public void setTextSize(int unit, float size) { + super.setTextSize(unit, size); + mTextSize = getTextSize(); + } + + /** + * Override the set line spacing to update our internal reference values + */ + @Override + public void setLineSpacing(float add, float mult) { + super.setLineSpacing(add, mult); + mSpacingMult = mult; + mSpacingAdd = add; + } + + /** + * Set the upper text size limit and invalidate the view + * @param maxTextSize + */ + public void setMaxTextSize(float maxTextSize) { + mMaxTextSize = maxTextSize; + requestLayout(); + invalidate(); + } + + /** + * Return upper text size limit + * @return + */ + public float getMaxTextSize() { + return mMaxTextSize; + } + + /** + * Set the lower text size limit and invalidate the view + * @param minTextSize + */ + public void setMinTextSize(float minTextSize) { + mMinTextSize = minTextSize; + requestLayout(); + invalidate(); + } + + /** + * Return lower text size limit + * @return + */ + public float getMinTextSize() { + return mMinTextSize; + } + + /** + * Set flag to add ellipsis to text that overflows at the smallest text size + * @param addEllipsis + */ + public void setAddEllipsis(boolean addEllipsis) { + mAddEllipsis = addEllipsis; + } + + /** + * Return flag to add ellipsis to text that overflows at the smallest text size + * @return + */ + public boolean getAddEllipsis() { + return mAddEllipsis; + } + + /** + * Reset the text to the original size + */ + private void resetTextSize() { + if (mTextSize > 0) { + super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize); + mMaxTextSize = mTextSize; + } + } + + /** + * Resize text after measuring + */ + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + if (changed || mNeedsResize) { + int widthLimit = (right - left) - getCompoundPaddingLeft() - getCompoundPaddingRight(); + int heightLimit = (bottom - top) - getCompoundPaddingBottom() - getCompoundPaddingTop(); + resizeText(widthLimit, heightLimit); + } + super.onLayout(changed, left, top, right, bottom); + } + + /** + * Resize the text size with default width and height + */ + public void resizeText() { + int heightLimit = getHeight() - getPaddingBottom() - getPaddingTop(); + int widthLimit = getWidth() - getPaddingLeft() - getPaddingRight(); + resizeText(widthLimit, heightLimit); + } + + /** + * Resize the text size with specified width and height + * @param width + * @param height + */ + public void resizeText(int width, int height) { + CharSequence text = getText(); + // Do not resize if the view does not have dimensions or there is no text + if (text == null || text.length() == 0 || height <= 0 || width <= 0 || mTextSize == 0) { + return; + } + + // Get the text view's paint object + TextPaint textPaint = getPaint(); + + // Store the current text size + float oldTextSize = textPaint.getTextSize(); + // If there is a max text size set, use the lesser of that and the default text size + float targetTextSize = mMaxTextSize > 0 ? Math.min(mTextSize, mMaxTextSize) : mTextSize; + + // Get the required text height + int textHeight = getTextHeight(text, textPaint, width, targetTextSize); + + // Until we either fit within our text view or we had reached our min text size, incrementally try smaller sizes + while (textHeight > height && targetTextSize > mMinTextSize) { + targetTextSize = Math.max(targetTextSize - 2, mMinTextSize); + textHeight = getTextHeight(text, textPaint, width, targetTextSize); + } + + // If we had reached our minimum text size and still don't fit, append an ellipsis + if (mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) { + // Draw using a static layout + // modified: use a copy of TextPaint for measuring + TextPaint paint = new TextPaint(textPaint); + // Draw using a static layout + StaticLayout layout = new StaticLayout(text, paint, width, Layout.Alignment.ALIGN_NORMAL, + mSpacingMult, mSpacingAdd, false); + // Check that we have a least one line of rendered text + if (layout.getLineCount() > 0) { + // Since the line at the specific vertical position would be cut off, + // we must trim up to the previous line + int lastLine = layout.getLineForVertical(height) - 1; + // If the text would not even fit on a single line, clear it + if (lastLine < 0) { + setText(""); + } else { + // Otherwise, trim to the previous line and add an ellipsis + int start = layout.getLineStart(lastLine); + int end = layout.getLineEnd(lastLine); + float lineWidth = layout.getLineWidth(lastLine); + float ellipseWidth = paint.measureText(M_ELLIPSIS); + + // Trim characters off until we have enough room to draw the ellipsis + while (width < lineWidth + ellipseWidth) { + lineWidth = paint.measureText(text.subSequence(start, --end + 1).toString()); + } + setText(text.subSequence(0, end) + M_ELLIPSIS); + } + } + } + + // Some devices try to auto adjust line spacing, so force default line spacing + // and invalidate the layout as a side effect + setTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize); + setLineSpacing(mSpacingAdd, mSpacingMult); + + // Notify the listener if registered + if (mTextResizeListener != null) { + mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize); + } + + // Reset force resize flag + mNeedsResize = false; + } + + // Set the text size of the text paint object and use a static layout to render text off screen before measuring + private int getTextHeight(CharSequence source, TextPaint paint, int width, float textSize) { + // modified: make a copy of the original TextPaint object for measuring + // (apparently the object gets modified while measuring, see also the + // docs for TextView.getPaint() (which states to access it read-only) + TextPaint paintCopy = new TextPaint(paint); + // Update the text paint object + paintCopy.setTextSize(textSize); + // Measure using a static layout + StaticLayout layout = new StaticLayout(source, paintCopy, width, Layout.Alignment.ALIGN_NORMAL, + mSpacingMult, mSpacingAdd, true); + return layout.getHeight(); + } +}