diff options
Diffstat (limited to 'libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers')
13 files changed, 1267 insertions, 0 deletions
diff --git a/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/ListScrollPositionManager.java b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/ListScrollPositionManager.java new file mode 100644 index 000000000..914373c8f --- /dev/null +++ b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/ListScrollPositionManager.java @@ -0,0 +1,58 @@ +package org.wordpress.android.util.helpers; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.preference.PreferenceManager; +import android.view.View; +import android.widget.ListView; + +public class ListScrollPositionManager { + private int mSelectedPosition; + private int mListViewScrollStateIndex; + private int mListViewScrollStateOffset; + private ListView mListView; + private boolean mSetSelection; + + public ListScrollPositionManager(ListView listView, boolean setSelection) { + mListView = listView; + mSetSelection = setSelection; + } + + public void saveScrollOffset() { + mListViewScrollStateIndex = mListView.getFirstVisiblePosition(); + View view = mListView.getChildAt(0); + mListViewScrollStateOffset = 0; + if (view != null) { + mListViewScrollStateOffset = view.getTop(); + } + if (mSetSelection) { + mSelectedPosition = mListView.getCheckedItemPosition(); + } + } + + public void restoreScrollOffset() { + mListView.setSelectionFromTop(mListViewScrollStateIndex, mListViewScrollStateOffset); + if (mSetSelection) { + mListView.setItemChecked(mSelectedPosition, true); + } + } + + public void saveToPreferences(Context context, String uniqueId) { + saveScrollOffset(); + SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context); + Editor editor = settings.edit(); + editor.putInt("scroll-position-manager-index-" + uniqueId, mListViewScrollStateIndex); + editor.putInt("scroll-position-manager-offset-" + uniqueId, mListViewScrollStateOffset); + editor.putInt("scroll-position-manager-selected-position-" + uniqueId, mSelectedPosition); + editor.apply(); + } + + public void restoreFromPreferences(Context context, String uniqueId) { + SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context); + mListViewScrollStateIndex = settings.getInt("scroll-position-manager-index-" + uniqueId, 0); + mListViewScrollStateOffset = settings.getInt("scroll-position-manager-offset-" + uniqueId, 0); + mSelectedPosition = settings.getInt("scroll-position-manager-selected-position-" + uniqueId, 0); + restoreScrollOffset(); + } +} diff --git a/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/LocationHelper.java b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/LocationHelper.java new file mode 100644 index 000000000..ff472c2ed --- /dev/null +++ b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/LocationHelper.java @@ -0,0 +1,144 @@ +//This Handy-Dandy class acquired and tweaked from http://stackoverflow.com/a/3145655/309558 +package org.wordpress.android.util.helpers; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Bundle; + +import java.util.Timer; +import java.util.TimerTask; + +public class LocationHelper { + Timer mTimer; + LocationManager mLocationManager; + LocationResult mLocationResult; + boolean mGpsEnabled = false; + boolean mNetworkEnabled = false; + + @SuppressLint("MissingPermission") + public boolean getLocation(Activity activity, LocationResult result) { + mLocationResult = result; + if (mLocationManager == null) { + mLocationManager = (LocationManager) activity.getSystemService(Context.LOCATION_SERVICE); + } + + // exceptions will be thrown if provider is not permitted. + try { + mGpsEnabled = mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); + } catch (Exception ex) { + } + try { + mNetworkEnabled = mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER); + } catch (Exception ex) { + } + + // don't start listeners if no provider is enabled + if (!mGpsEnabled && !mNetworkEnabled) { + return false; + } + + if (mGpsEnabled) { + mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListenerGps); + } + + if (mNetworkEnabled) { + mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListenerNetwork); + } + + mTimer = new Timer(); + mTimer.schedule(new GetLastLocation(), 30000); + return true; + } + + LocationListener locationListenerGps = new LocationListener() { + @SuppressLint("MissingPermission") + public void onLocationChanged(Location location) { + mTimer.cancel(); + mLocationResult.gotLocation(location); + mLocationManager.removeUpdates(this); + mLocationManager.removeUpdates(locationListenerNetwork); + } + + public void onProviderDisabled(String provider) { + } + + public void onProviderEnabled(String provider) { + } + + public void onStatusChanged(String provider, int status, Bundle extras) { + } + }; + + LocationListener locationListenerNetwork = new LocationListener() { + @SuppressLint("MissingPermission") + public void onLocationChanged(Location location) { + mTimer.cancel(); + mLocationResult.gotLocation(location); + mLocationManager.removeUpdates(this); + mLocationManager.removeUpdates(locationListenerGps); + } + + public void onProviderDisabled(String provider) { + } + + public void onProviderEnabled(String provider) { + } + + public void onStatusChanged(String provider, int status, Bundle extras) { + } + }; + + class GetLastLocation extends TimerTask { + @Override + @SuppressLint("MissingPermission") + public void run() { + mLocationManager.removeUpdates(locationListenerGps); + mLocationManager.removeUpdates(locationListenerNetwork); + + Location net_loc = null, gps_loc = null; + if (mGpsEnabled) { + gps_loc = mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); + } + if (mNetworkEnabled) { + net_loc = mLocationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); + } + + // if there are both values use the latest one + if (gps_loc != null && net_loc != null) { + if (gps_loc.getTime() > net_loc.getTime()) { + mLocationResult.gotLocation(gps_loc); + } else { + mLocationResult.gotLocation(net_loc); + } + return; + } + + if (gps_loc != null) { + mLocationResult.gotLocation(gps_loc); + return; + } + if (net_loc != null) { + mLocationResult.gotLocation(net_loc); + return; + } + mLocationResult.gotLocation(null); + } + } + + public static abstract class LocationResult { + public abstract void gotLocation(Location location); + } + + @SuppressLint("MissingPermission") + public void cancelTimer() { + if (mTimer != null) { + mTimer.cancel(); + mLocationManager.removeUpdates(locationListenerGps); + mLocationManager.removeUpdates(locationListenerNetwork); + } + } +} diff --git a/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/MediaFile.java b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/MediaFile.java new file mode 100644 index 000000000..b57ad0165 --- /dev/null +++ b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/MediaFile.java @@ -0,0 +1,339 @@ +package org.wordpress.android.util.helpers; + +import android.text.TextUtils; +import android.webkit.MimeTypeMap; + +import org.wordpress.android.util.MapUtils; +import org.wordpress.android.util.StringUtils; + +import java.util.Date; +import java.util.Map; + +public class MediaFile { + protected int id; + protected long postID; + protected String filePath = null; //path of the file into disk + protected String fileName = null; //name of the file into the server + protected String title = null; + protected String description = null; + protected String caption = null; + protected int horizontalAlignment; //0 = none, 1 = left, 2 = center, 3 = right + protected boolean verticalAligment = false; //false = bottom, true = top + protected int width = 500, height; + protected String mimeType = ""; + protected String videoPressShortCode = null; + protected boolean featured = false; + protected boolean isVideo = false; + protected boolean featuredInPost; + protected String fileURL = null; // url of the file to download + protected String thumbnailURL = null; // url of the thumbnail to download + private String blogId; + private long dateCreatedGmt; + private String uploadState = null; + private String mediaId; + + public static String VIDEOPRESS_SHORTCODE_ID = "videopress_shortcode"; + + public MediaFile(String blogId, Map<?, ?> resultMap, boolean isDotCom) { + setBlogId(blogId); + setMediaId(MapUtils.getMapStr(resultMap, "attachment_id")); + setPostID(MapUtils.getMapLong(resultMap, "parent")); + setTitle(MapUtils.getMapStr(resultMap, "title")); + setCaption(MapUtils.getMapStr(resultMap, "caption")); + setDescription(MapUtils.getMapStr(resultMap, "description")); + setVideoPressShortCode(MapUtils.getMapStr(resultMap, VIDEOPRESS_SHORTCODE_ID)); + + // get the file name from the link + String link = MapUtils.getMapStr(resultMap, "link"); + setFileName(new String(link).replaceAll("^.*/([A-Za-z0-9_-]+)\\.\\w+$", "$1")); + + String fileType = new String(link).replaceAll(".*\\.(\\w+)$", "$1").toLowerCase(); + String fileMimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileType); + setMimeType(fileMimeType); + + // make the file urls be https://... so that we can get these images with oauth when the blogs are private + // assume no https for images in self-hosted blogs + String fileUrl = MapUtils.getMapStr(resultMap, "link"); + if (isDotCom) { + fileUrl = fileUrl.replace("http:", "https:"); + } + setFileURL(fileUrl); + + String thumbnailURL = MapUtils.getMapStr(resultMap, "thumbnail"); + if (thumbnailURL.startsWith("http")) { + if (isDotCom) { + thumbnailURL = thumbnailURL.replace("http:", "https:"); + } + setThumbnailURL(thumbnailURL); + } + + Date date = MapUtils.getMapDate(resultMap, "date_created_gmt"); + if (date != null) { + setDateCreatedGMT(date.getTime()); + } + + Object meta = resultMap.get("metadata"); + if (meta != null && meta instanceof Map) { + Map<?, ?> metadata = (Map<?, ?>) meta; + setWidth(MapUtils.getMapInt(metadata, "width")); + setHeight(MapUtils.getMapInt(metadata, "height")); + } + } + + public MediaFile() { + // default constructor + } + + public MediaFile(MediaFile mediaFile) { + this.id = mediaFile.id; + this.postID = mediaFile.postID; + this.filePath = mediaFile.filePath; + this.fileName = mediaFile.fileName; + this.title = mediaFile.title; + this.description = mediaFile.description; + this.caption = mediaFile.caption; + this.horizontalAlignment = mediaFile.horizontalAlignment; + this.verticalAligment = mediaFile.verticalAligment; + this.width = mediaFile.width; + this.height = mediaFile.height; + this.mimeType = mediaFile.mimeType; + this.videoPressShortCode = mediaFile.videoPressShortCode; + this.featured = mediaFile.featured; + this.isVideo = mediaFile.isVideo; + this.featuredInPost = mediaFile.featuredInPost; + this.fileURL = mediaFile.fileURL; + this.thumbnailURL = mediaFile.thumbnailURL; + this.blogId = mediaFile.blogId; + this.dateCreatedGmt = mediaFile.dateCreatedGmt; + this.uploadState = mediaFile.uploadState; + this.mediaId = mediaFile.mediaId; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getMediaId() { + return mediaId; + } + + public void setMediaId(String id) { + mediaId = id; + } + + public boolean isFeatured() { + return featured; + } + + public void setFeatured(boolean featured) { + this.featured = featured; + } + + public long getPostID() { + return postID; + } + + public void setPostID(long postID) { + this.postID = postID; + } + + public String getFilePath() { + return filePath; + } + + public void setFilePath(String filePath) { + this.filePath = filePath; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getCaption() { + return caption; + } + + public void setCaption(String caption) { + this.caption = caption; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getFileURL() { + return fileURL; + } + + public void setFileURL(String fileURL) { + this.fileURL = fileURL; + } + + public String getThumbnailURL() { + return thumbnailURL; + } + + public void setThumbnailURL(String thumbnailURL) { + this.thumbnailURL = thumbnailURL; + } + + public boolean isVerticalAlignmentOnTop() { + return verticalAligment; + } + + public void setVerticalAlignmentOnTop(boolean verticalAligment) { + this.verticalAligment = verticalAligment; + } + + public int getWidth() { + return width; + } + + public void setWidth(int width) { + this.width = width; + } + + public int getHeight() { + return height; + } + + public void setHeight(int height) { + this.height = height; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public String getMimeType() { + return StringUtils.notNullStr(mimeType); + } + + public void setMimeType(String type) { + mimeType = StringUtils.notNullStr(type); + } + + public String getVideoPressShortCode() { + return videoPressShortCode; + } + + public void setVideoPressShortCode(String videoPressShortCode) { + this.videoPressShortCode = videoPressShortCode; + } + + public int getHorizontalAlignment() { + return horizontalAlignment; + } + + public void setHorizontalAlignment(int horizontalAlignment) { + this.horizontalAlignment = horizontalAlignment; + } + + public boolean isVideo() { + return isVideo; + } + + public void setVideo(boolean isVideo) { + this.isVideo = isVideo; + } + + public boolean isFeaturedInPost() { + return featuredInPost; + } + + public void setFeaturedInPost(boolean featuredInPost) { + this.featuredInPost = featuredInPost; + } + + public String getBlogId() { + return blogId; + } + + public void setBlogId(String blogId) { + this.blogId = blogId; + } + + public void setDateCreatedGMT(long date_created_gmt) { + this.dateCreatedGmt = date_created_gmt; + } + + public long getDateCreatedGMT() { + return dateCreatedGmt; + } + + public void setUploadState(String uploadState) { + this.uploadState = uploadState; + } + + public String getUploadState() { + return uploadState; + } + + /** + * Outputs the Html for an image + * If a fullSizeUrl exists, a link will be created to it from the resizedPictureUrl + */ + public String getImageHtmlForUrls(String fullSizeUrl, String resizedPictureURL, boolean shouldAddImageWidthCSS) { + String alignment = ""; + switch (getHorizontalAlignment()) { + case 0: + alignment = "alignnone"; + break; + case 1: + alignment = "alignleft"; + break; + case 2: + alignment = "aligncenter"; + break; + case 3: + alignment = "alignright"; + break; + } + + String alignmentCSS = "class=\"" + alignment + " size-full\" "; + + if (shouldAddImageWidthCSS) { + alignmentCSS += "style=\"max-width: " + getWidth() + "px\" "; + } + + // Check if we uploaded a featured picture that is not added to the Post content (normal case) + if ((fullSizeUrl != null && fullSizeUrl.equalsIgnoreCase("")) || + (resizedPictureURL != null && resizedPictureURL.equalsIgnoreCase(""))) { + return ""; // Not featured in Post. Do not add to the content. + } + + if (fullSizeUrl == null && resizedPictureURL != null) { + fullSizeUrl = resizedPictureURL; + } else if (fullSizeUrl != null && resizedPictureURL == null) { + resizedPictureURL = fullSizeUrl; + } + + String mediaTitle = StringUtils.notNullStr(getTitle()); + + String content = String.format("<a href=\"%s\"><img title=\"%s\" %s alt=\"image\" src=\"%s\" /></a>", + fullSizeUrl, mediaTitle, alignmentCSS, resizedPictureURL); + + if (!TextUtils.isEmpty(getCaption())) { + content = String.format("[caption id=\"\" align=\"%s\" width=\"%d\"]%s%s[/caption]", + alignment, getWidth(), content, TextUtils.htmlEncode(getCaption())); + } + + return content; + } +} diff --git a/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/MediaGallery.java b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/MediaGallery.java new file mode 100644 index 000000000..ab7326a17 --- /dev/null +++ b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/MediaGallery.java @@ -0,0 +1,87 @@ + +package org.wordpress.android.util.helpers; + +import java.io.Serializable; +import java.util.ArrayList; + +/** + * A model representing a Media Gallery. + * A unique id is not used on the website, but only in this app. + * It is used to uniquely determining the instance of the object, as it is + * passed between post and media gallery editor. + */ +public class MediaGallery implements Serializable { + private static final long serialVersionUID = 2359176987182027508L; + + private long uniqueId; + private boolean isRandom; + private String type; + private int numColumns; + private ArrayList<String> ids; + + public MediaGallery(boolean isRandom, String type, int numColumns, ArrayList<String> ids) { + this.isRandom = isRandom; + this.type = type; + this.numColumns = numColumns; + this.ids = ids; + this.uniqueId = System.currentTimeMillis(); + } + + public MediaGallery() { + isRandom = false; + type = ""; + numColumns = 3; + ids = new ArrayList<String>(); + this.uniqueId = System.currentTimeMillis(); + } + + public boolean isRandom() { + return isRandom; + } + + public void setRandom(boolean isRandom) { + this.isRandom = isRandom; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public int getNumColumns() { + return numColumns; + } + + public void setNumColumns(int numColumns) { + this.numColumns = numColumns; + } + + public ArrayList<String> getIds() { + return ids; + } + + public String getIdsStr() { + String ids_str = ""; + if (ids.size() > 0) { + for (String id : ids) { + ids_str += id + ","; + } + ids_str = ids_str.substring(0, ids_str.length() - 1); + } + return ids_str; + } + + public void setIds(ArrayList<String> ids) { + this.ids = ids; + } + + /** + * An id to uniquely identify a media gallery object, so that the same object can be edited in the post editor + */ + public long getUniqueId() { + return uniqueId; + } +} diff --git a/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/MediaGalleryImageSpan.java b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/MediaGalleryImageSpan.java new file mode 100644 index 000000000..588b98141 --- /dev/null +++ b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/MediaGalleryImageSpan.java @@ -0,0 +1,21 @@ +package org.wordpress.android.util.helpers; + +import android.content.Context; +import android.text.style.ImageSpan; + +public class MediaGalleryImageSpan extends ImageSpan { + private MediaGallery mMediaGallery; + + public MediaGalleryImageSpan(Context context, MediaGallery mediaGallery, int placeHolder) { + super(context, placeHolder); + setMediaGallery(mediaGallery); + } + + public MediaGallery getMediaGallery() { + return mMediaGallery; + } + + public void setMediaGallery(MediaGallery mediaGallery) { + this.mMediaGallery = mediaGallery; + } +} diff --git a/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/SwipeToRefreshHelper.java b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/SwipeToRefreshHelper.java new file mode 100644 index 000000000..e6f4bf323 --- /dev/null +++ b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/SwipeToRefreshHelper.java @@ -0,0 +1,72 @@ +package org.wordpress.android.util.helpers; + +import android.app.Activity; +import android.content.Context; +import android.content.res.TypedArray; +import android.support.v4.widget.SwipeRefreshLayout.OnRefreshListener; +import android.util.TypedValue; + +import org.wordpress.android.util.R; +import org.wordpress.android.util.widgets.CustomSwipeRefreshLayout; + +public class SwipeToRefreshHelper implements OnRefreshListener { + private CustomSwipeRefreshLayout mSwipeRefreshLayout; + private RefreshListener mRefreshListener; + private boolean mRefreshing; + + public interface RefreshListener { + public void onRefreshStarted(); + } + + public SwipeToRefreshHelper(Context context, CustomSwipeRefreshLayout swipeRefreshLayout, RefreshListener listener) { + init(context, swipeRefreshLayout, listener); + } + + public void init(Context context, CustomSwipeRefreshLayout swipeRefreshLayout, RefreshListener listener) { + mRefreshListener = listener; + mSwipeRefreshLayout = swipeRefreshLayout; + mSwipeRefreshLayout.setOnRefreshListener(this); + final TypedArray styleAttrs = obtainStyledAttrsFromThemeAttr(context, R.attr.swipeToRefreshStyle, + R.styleable.RefreshIndicator); + int color = styleAttrs.getColor(R.styleable.RefreshIndicator_refreshIndicatorColor, context.getResources() + .getColor(android.R.color.holo_blue_dark)); + mSwipeRefreshLayout.setColorSchemeColors(color, color, color, color); + } + + public void setRefreshing(boolean refreshing) { + mRefreshing = refreshing; + // Delayed refresh, it fixes https://code.google.com/p/android/issues/detail?id=77712 + // 50ms seems a good compromise (always worked during tests) and fast enough so user can't notice the delay + if (refreshing) { + mSwipeRefreshLayout.postDelayed(new Runnable() { + @Override + public void run() { + // use mRefreshing so if the refresh takes less than 50ms, loading indicator won't show up. + mSwipeRefreshLayout.setRefreshing(mRefreshing); + } + }, 50); + } else { + mSwipeRefreshLayout.setRefreshing(false); + } + } + + public boolean isRefreshing() { + return mSwipeRefreshLayout.isRefreshing(); + } + + @Override + public void onRefresh() { + mRefreshListener.onRefreshStarted(); + } + + public void setEnabled(boolean enabled) { + mSwipeRefreshLayout.setEnabled(enabled); + } + + public static TypedArray obtainStyledAttrsFromThemeAttr(Context context, int themeAttr, int[] styleAttrs) { + TypedValue outValue = new TypedValue(); + context.getTheme().resolveAttribute(themeAttr, outValue, true); + int styleResId = outValue.resourceId; + return context.obtainStyledAttributes(styleResId, styleAttrs); + } +} diff --git a/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/Version.java b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/Version.java new file mode 100644 index 000000000..b35f84757 --- /dev/null +++ b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/Version.java @@ -0,0 +1,47 @@ +package org.wordpress.android.util.helpers; + +//See: http://stackoverflow.com/a/11024200 +public class Version implements Comparable<Version> { + private String version; + + public final String get() { + return this.version; + } + + public Version(String version) { + if(version == null) + throw new IllegalArgumentException("Version can not be null"); + if(!version.matches("[0-9]+(\\.[0-9]+)*")) + throw new IllegalArgumentException("Invalid version format"); + this.version = version; + } + + @Override public int compareTo(Version that) { + if(that == null) + return 1; + String[] thisParts = this.get().split("\\."); + String[] thatParts = that.get().split("\\."); + int length = Math.max(thisParts.length, thatParts.length); + for(int i = 0; i < length; i++) { + int thisPart = i < thisParts.length ? + Integer.parseInt(thisParts[i]) : 0; + int thatPart = i < thatParts.length ? + Integer.parseInt(thatParts[i]) : 0; + if(thisPart < thatPart) + return -1; + if(thisPart > thatPart) + return 1; + } + return 0; + } + + @Override public boolean equals(Object that) { + if(this == that) + return true; + if(that == null) + return false; + if(this.getClass() != that.getClass()) + return false; + return this.compareTo((Version) that) == 0; + } +} diff --git a/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/WPHtmlTagHandler.java b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/WPHtmlTagHandler.java new file mode 100644 index 000000000..da333b24e --- /dev/null +++ b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/WPHtmlTagHandler.java @@ -0,0 +1,59 @@ +package org.wordpress.android.util.helpers; + +import android.text.Editable; +import android.text.Html; +import android.text.style.BulletSpan; +import android.text.style.LeadingMarginSpan; + +import org.xml.sax.XMLReader; + +import java.util.Vector; + +/** + * Handle tags that the Html class doesn't understand + * Tweaked from source at http://stackoverflow.com/questions/4044509/android-how-to-use-the-html-taghandler + */ +public class WPHtmlTagHandler implements Html.TagHandler { + private int mListItemCount = 0; + private Vector<String> mListParents = new Vector<String>(); + + @Override + public void handleTag(final boolean opening, final String tag, Editable output, + final XMLReader xmlReader) { + if (tag.equals("ul") || tag.equals("ol") || tag.equals("dd")) { + if (opening) { + mListParents.add(tag); + } else { + mListParents.remove(tag); + } + mListItemCount = 0; + } else if (tag.equals("li") && !opening) { + handleListTag(output); + } + } + + private void handleListTag(Editable output) { + if (mListParents.lastElement().equals("ul")) { + output.append("\n"); + String[] split = output.toString().split("\n"); + int start = 0; + if (split.length != 1) { + int lastIndex = split.length - 1; + start = output.length() - split[lastIndex].length() - 1; + } + output.setSpan(new BulletSpan(15 * mListParents.size()), start, output.length(), 0); + } else if (mListParents.lastElement().equals("ol")) { + mListItemCount++; + output.append("\n"); + String[] split = output.toString().split("\n"); + int start = 0; + if (split.length != 1) { + int lastIndex = split.length - 1; + start = output.length() - split[lastIndex].length() - 1; + } + output.insert(start, mListItemCount + ". "); + output.setSpan(new LeadingMarginSpan.Standard(15 * mListParents.size()), start, + output.length(), 0); + } + } +} diff --git a/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/WPImageGetter.java b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/WPImageGetter.java new file mode 100644 index 000000000..b03d74045 --- /dev/null +++ b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/WPImageGetter.java @@ -0,0 +1,177 @@ +package org.wordpress.android.util.helpers; + +import android.graphics.Canvas; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.text.Html; +import android.text.TextUtils; +import android.widget.TextView; + +import com.android.volley.VolleyError; +import com.android.volley.toolbox.ImageLoader; + +import org.wordpress.android.util.AppLog; +import org.wordpress.android.util.AppLog.T; +import org.wordpress.android.util.PhotonUtils; + +import java.lang.ref.WeakReference; + +/** + * ImageGetter for Html.fromHtml() + * adapted from existing ImageGetter code in NoteCommentFragment + */ +public class WPImageGetter implements Html.ImageGetter { + private final WeakReference<TextView> mWeakView; + private final int mMaxSize; + private ImageLoader mImageLoader; + private Drawable mLoadingDrawable; + private Drawable mFailedDrawable; + + public WPImageGetter(TextView view) { + this(view, 0); + } + + public WPImageGetter(TextView view, int maxSize) { + mWeakView = new WeakReference<TextView>(view); + mMaxSize = maxSize; + } + + public WPImageGetter(TextView view, int maxSize, ImageLoader imageLoader, Drawable loadingDrawable, + Drawable failedDrawable) { + mWeakView = new WeakReference<TextView>(view); + mMaxSize = maxSize; + mImageLoader = imageLoader; + mLoadingDrawable = loadingDrawable; + mFailedDrawable = failedDrawable; + } + + private TextView getView() { + return mWeakView.get(); + } + + @Override + public Drawable getDrawable(String source) { + if (mImageLoader == null || mLoadingDrawable == null || mFailedDrawable == null) { + throw new RuntimeException("Developer, you need to call setImageLoader, setLoadingDrawable and setFailedDrawable"); + } + + if (TextUtils.isEmpty(source)) { + return null; + } + + // images in reader comments may skip "http:" (no idea why) so make sure to add protocol here + if (source.startsWith("//")) { + source = "http:" + source; + } + + // use Photon if a max size is requested (otherwise the full-sized image will be downloaded + // and then resized) + if (mMaxSize > 0) { + source = PhotonUtils.getPhotonImageUrl(source, mMaxSize, 0); + } + + final RemoteDrawable remote = new RemoteDrawable(mLoadingDrawable, mFailedDrawable); + + mImageLoader.get(source, new ImageLoader.ImageListener() { + @Override + public void onErrorResponse(VolleyError error) { + remote.displayFailed(); + TextView view = getView(); + if (view != null) { + view.invalidate(); + } + } + + @Override + public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) { + if (response.getBitmap() == null) { + AppLog.w(T.UTILS, "WPImageGetter null bitmap"); + } + + TextView view = getView(); + if (view == null) { + AppLog.w(T.UTILS, "WPImageGetter view is invalid"); + return; + } + + int maxWidth = view.getWidth() - view.getPaddingLeft() - view.getPaddingRight(); + if (mMaxSize > 0 && (maxWidth > mMaxSize || maxWidth == 0)) { + maxWidth = mMaxSize; + } + + Drawable drawable = new BitmapDrawable(view.getContext().getResources(), response.getBitmap()); + remote.setRemoteDrawable(drawable, maxWidth); + + // force textView to resize correctly if image isn't cached by resetting the content + // to itself - this way the textView will use the cached image, and resizing to + // accommodate the image isn't necessary + if (!isImmediate) { + view.setText(view.getText()); + } + } + }); + + return remote; + } + + public static class RemoteDrawable extends BitmapDrawable { + Drawable mRemoteDrawable; + final Drawable mLoadingDrawable; + final Drawable mFailedDrawable; + private boolean mDidFail = false; + + public RemoteDrawable(Drawable loadingDrawable, Drawable failedDrawable) { + mLoadingDrawable = loadingDrawable; + mFailedDrawable = failedDrawable; + setBounds(0, 0, mLoadingDrawable.getIntrinsicWidth(), mLoadingDrawable.getIntrinsicHeight()); + } + + public void displayFailed() { + mDidFail = true; + } + + public void setBounds(int x, int y, int width, int height) { + super.setBounds(x, y, width, height); + if (mRemoteDrawable != null) { + mRemoteDrawable.setBounds(x, y, width, height); + return; + } + if (mLoadingDrawable != null) { + mLoadingDrawable.setBounds(x, y, width, height); + mFailedDrawable.setBounds(x, y, width, height); + } + } + + public void setRemoteDrawable(Drawable remote, int maxWidth) { + // null sentinel for now + if (remote == null) { + // throw error + return; + } + mRemoteDrawable = remote; + // determine if we need to scale the image to fit in view + int imgWidth = remote.getIntrinsicWidth(); + int imgHeight = remote.getIntrinsicHeight(); + float xScale = (float) imgWidth / (float) maxWidth; + if (xScale > 1.0f) { + setBounds(0, 0, Math.round(imgWidth / xScale), Math.round(imgHeight / xScale)); + } else { + setBounds(0, 0, imgWidth, imgHeight); + } + } + + public boolean didFail() { + return mDidFail; + } + + public void draw(Canvas canvas) { + if (mRemoteDrawable != null) { + mRemoteDrawable.draw(canvas); + } else if (didFail()) { + mFailedDrawable.draw(canvas); + } else { + mLoadingDrawable.draw(canvas); + } + } + } +} diff --git a/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/WPImageSpan.java b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/WPImageSpan.java new file mode 100644 index 000000000..fa0a0b4aa --- /dev/null +++ b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/WPImageSpan.java @@ -0,0 +1,140 @@ +//Add WordPress image fields to ImageSpan object + +package org.wordpress.android.util.helpers; + +import android.content.Context; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.style.ImageSpan; + +public class WPImageSpan extends ImageSpan implements Parcelable { + protected Uri mImageSource = null; + protected boolean mNetworkImageLoaded = false; + protected MediaFile mMediaFile; + protected int mStartPosition, mEndPosition; + + protected WPImageSpan() { + super((Bitmap) null); + } + + public WPImageSpan(Context context, Bitmap b, Uri src) { + super(context, b); + this.mImageSource = src; + mMediaFile = new MediaFile(); + } + + public WPImageSpan(Context context, int resId, Uri src) { + super(context, resId); + this.mImageSource = src; + mMediaFile = new MediaFile(); + } + + public void setPosition(int start, int end) { + mStartPosition = start; + mEndPosition = end; + } + + public int getStartPosition() { + return mStartPosition >= 0 ? mStartPosition : 0; + } + + public int getEndPosition() { + return mEndPosition < getStartPosition() ? getStartPosition() : mEndPosition; + } + + public MediaFile getMediaFile() { + return mMediaFile; + } + + public void setMediaFile(MediaFile mMediaFile) { + this.mMediaFile = mMediaFile; + } + + public void setImageSource(Uri mImageSource) { + this.mImageSource = mImageSource; + } + + public Uri getImageSource() { + return mImageSource; + } + + public boolean isNetworkImageLoaded() { + return mNetworkImageLoaded; + } + + public void setNetworkImageLoaded(boolean networkImageLoaded) { + this.mNetworkImageLoaded = networkImageLoaded; + } + + protected void setupFromParcel(Parcel in) { + MediaFile mediaFile = new MediaFile(); + + boolean[] booleans = new boolean[2]; + in.readBooleanArray(booleans); + setNetworkImageLoaded(booleans[0]); + mediaFile.setVideo(booleans[1]); + + setImageSource(Uri.parse(in.readString())); + mediaFile.setMediaId(in.readString()); + mediaFile.setBlogId(in.readString()); + mediaFile.setPostID(in.readLong()); + mediaFile.setCaption(in.readString()); + mediaFile.setDescription(in.readString()); + mediaFile.setTitle(in.readString()); + mediaFile.setMimeType(in.readString()); + mediaFile.setFileName(in.readString()); + mediaFile.setThumbnailURL(in.readString()); + mediaFile.setVideoPressShortCode(in.readString()); + mediaFile.setFileURL(in.readString()); + mediaFile.setFilePath(in.readString()); + mediaFile.setDateCreatedGMT(in.readLong()); + mediaFile.setWidth(in.readInt()); + mediaFile.setHeight(in.readInt()); + setPosition(in.readInt(), in.readInt()); + + setMediaFile(mediaFile); + } + + public static final Parcelable.Creator<WPImageSpan> CREATOR + = new Parcelable.Creator<WPImageSpan>() { + public WPImageSpan createFromParcel(Parcel in) { + WPImageSpan imageSpan = new WPImageSpan(); + imageSpan.setupFromParcel(in); + return imageSpan; + } + + public WPImageSpan[] newArray(int size) { + return new WPImageSpan[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int i) { + parcel.writeBooleanArray(new boolean[] {mNetworkImageLoaded, mMediaFile.isVideo()}); + parcel.writeString(mImageSource.toString()); + parcel.writeString(mMediaFile.getMediaId()); + parcel.writeString(mMediaFile.getBlogId()); + parcel.writeLong(mMediaFile.getPostID()); + parcel.writeString(mMediaFile.getCaption()); + parcel.writeString(mMediaFile.getDescription()); + parcel.writeString(mMediaFile.getTitle()); + parcel.writeString(mMediaFile.getMimeType()); + parcel.writeString(mMediaFile.getFileName()); + parcel.writeString(mMediaFile.getThumbnailURL()); + parcel.writeString(mMediaFile.getVideoPressShortCode()); + parcel.writeString(mMediaFile.getFileURL()); + parcel.writeString(mMediaFile.getFilePath()); + parcel.writeLong(mMediaFile.getDateCreatedGMT()); + parcel.writeInt(mMediaFile.getWidth()); + parcel.writeInt(mMediaFile.getHeight()); + parcel.writeInt(getStartPosition()); + parcel.writeInt(getEndPosition()); + } +} diff --git a/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/WPQuoteSpan.java b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/WPQuoteSpan.java new file mode 100644 index 000000000..33cdc0093 --- /dev/null +++ b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/WPQuoteSpan.java @@ -0,0 +1,44 @@ +package org.wordpress.android.util.helpers; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.text.Layout; +import android.text.style.QuoteSpan; + +/** + * Customzed QuoteSpan for use in SpannableString's + */ +public class WPQuoteSpan extends QuoteSpan { + public static final int STRIPE_COLOR = 0xFF21759B; + private static final int STRIPE_WIDTH = 5; + private static final int GAP_WIDTH = 20; + + public WPQuoteSpan(){ + super(STRIPE_COLOR); + } + + @Override + public int getLeadingMargin(boolean first) { + int margin = GAP_WIDTH * 2 + STRIPE_WIDTH; + return margin; + } + + /** + * Draw a nice thick gray bar if Ice Cream Sandwhich or newer. There's a + * bug on older devices that does not respect the increased margin. + */ + @Override + public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, + CharSequence text, int start, int end, boolean first, Layout layout) { + Paint.Style style = p.getStyle(); + int color = p.getColor(); + + p.setStyle(Paint.Style.FILL); + p.setColor(STRIPE_COLOR); + + c.drawRect(GAP_WIDTH + x, top, x + dir * STRIPE_WIDTH, bottom, p); + + p.setStyle(style); + p.setColor(color); + } +} diff --git a/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/WPUnderlineSpan.java b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/WPUnderlineSpan.java new file mode 100644 index 000000000..4b6805ccf --- /dev/null +++ b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/WPUnderlineSpan.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wordpress.android.util.helpers; + +import android.os.Parcel; +import android.text.style.UnderlineSpan; + +/** + * WPUnderlineSpan is used as an alternative class to UnderlineSpan. UnderlineSpan is used by EditText auto + * correct, so it can get mixed up with our formatting. + */ +public class WPUnderlineSpan extends UnderlineSpan { + public WPUnderlineSpan() { + super(); + } + + public WPUnderlineSpan(Parcel src) { + super(src); + } +} diff --git a/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/WPWebChromeClient.java b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/WPWebChromeClient.java new file mode 100644 index 000000000..1418e79ea --- /dev/null +++ b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/WPWebChromeClient.java @@ -0,0 +1,45 @@ +package org.wordpress.android.util.helpers; + +import android.app.Activity; +import android.text.TextUtils; +import android.view.View; +import android.webkit.WebChromeClient; +import android.webkit.WebView; +import android.widget.ProgressBar; + +public class WPWebChromeClient extends WebChromeClient { + private final ProgressBar mProgressBar; + private final Activity mActivity; + private final boolean mAutoUpdateActivityTitle; + + public WPWebChromeClient(Activity activity, ProgressBar progressBar) { + mActivity = activity; + mProgressBar = progressBar; + mAutoUpdateActivityTitle = true; + } + + public WPWebChromeClient(Activity activity, + ProgressBar progressBar, + boolean autoUpdateActivityTitle) { + mActivity = activity; + mProgressBar = progressBar; + mAutoUpdateActivityTitle = autoUpdateActivityTitle; + } + + public void onProgressChanged(WebView webView, int progress) { + if (mActivity != null + && !mActivity.isFinishing() + && mAutoUpdateActivityTitle + && !TextUtils.isEmpty(webView.getTitle())) { + mActivity.setTitle(webView.getTitle()); + } + if (mProgressBar != null) { + if (progress == 100) { + mProgressBar.setVisibility(View.GONE); + } else { + mProgressBar.setVisibility(View.VISIBLE); + mProgressBar.setProgress(progress); + } + } + } +} |