diff --git a/WordPress/src/main/assets/ic_reader_video_overlay.png b/WordPress/src/main/assets/ic_reader_video_overlay.png new file mode 120000 index 000000000000..a58cc199483f --- /dev/null +++ b/WordPress/src/main/assets/ic_reader_video_overlay.png @@ -0,0 +1 @@ +../res/drawable-xhdpi/ic_reader_video_overlay.png \ No newline at end of file diff --git a/WordPress/src/main/java/org/wordpress/android/datasets/ReaderDatabase.java b/WordPress/src/main/java/org/wordpress/android/datasets/ReaderDatabase.java index e94203ba982d..abe69084ec24 100644 --- a/WordPress/src/main/java/org/wordpress/android/datasets/ReaderDatabase.java +++ b/WordPress/src/main/java/org/wordpress/android/datasets/ReaderDatabase.java @@ -19,7 +19,7 @@ */ public class ReaderDatabase extends SQLiteOpenHelper { protected static final String DB_NAME = "wpreader.db"; - private static final int DB_VERSION = 85; + private static final int DB_VERSION = 83; /* * version history @@ -40,8 +40,6 @@ public class ReaderDatabase extends SQLiteOpenHelper { * 81 - added image_url to tbl_blog_info * 82 - added idx_posts_timestamp to tbl_posts * 83 - removed tag_list from tbl_posts - * 84 - added tbl_attachments - * 85 - removed tbl_attachments, added attachments_json to tbl_posts */ /* diff --git a/WordPress/src/main/java/org/wordpress/android/datasets/ReaderPostTable.java b/WordPress/src/main/java/org/wordpress/android/datasets/ReaderPostTable.java index c1d9bc01414e..6bc66eafdee9 100644 --- a/WordPress/src/main/java/org/wordpress/android/datasets/ReaderPostTable.java +++ b/WordPress/src/main/java/org/wordpress/android/datasets/ReaderPostTable.java @@ -47,8 +47,7 @@ public class ReaderPostTable { + "primary_tag," // 26 + "secondary_tag," // 27 + "is_likes_enabled," // 28 - + "is_sharing_enabled," // 29 - + "attachments_json"; // 30 + + "is_sharing_enabled"; // 29 protected static void createTables(SQLiteDatabase db) { @@ -82,7 +81,6 @@ protected static void createTables(SQLiteDatabase db) { + " secondary_tag TEXT," + " is_likes_enabled INTEGER DEFAULT 0," + " is_sharing_enabled INTEGER DEFAULT 0," - + " attachments_json TEXT," + " PRIMARY KEY (post_id, blog_id)" + ")"); db.execSQL("CREATE INDEX idx_posts_timestamp ON tbl_posts(timestamp)"); @@ -375,7 +373,7 @@ public static void addOrUpdatePosts(final ReaderTag tag, ReaderPostList posts) { SQLiteStatement stmtPosts = db.compileStatement( "INSERT OR REPLACE INTO tbl_posts (" + COLUMN_NAMES - + ") VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14,?15,?16,?17,?18,?19,?20,?21,?22,?23,?24,?25,?26,?27,?28,?29,?30)"); + + ") VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14,?15,?16,?17,?18,?19,?20,?21,?22,?23,?24,?25,?26,?27,?28,?29)"); SQLiteStatement stmtTags = db.compileStatement( "INSERT OR REPLACE INTO tbl_post_tags (post_id, blog_id, pseudo_id, tag_name, tag_type) VALUES (?1,?2,?3,?4,?5)"); @@ -412,7 +410,6 @@ public static void addOrUpdatePosts(final ReaderTag tag, ReaderPostList posts) { stmtPosts.bindString(27, post.getSecondaryTag()); stmtPosts.bindLong (28, SqlUtils.boolToSql(post.isLikesEnabled)); stmtPosts.bindLong (29, SqlUtils.boolToSql(post.isSharingEnabled)); - stmtPosts.bindString(30, post.getAttachmentsJson()); stmtPosts.execute(); } @@ -563,8 +560,6 @@ private static class PostColumnIndexes { private final int idx_is_likes_enabled; private final int idx_is_sharing_enabled; - private final int idx_attachments_json; - private PostColumnIndexes(Cursor c) { if (c == null) throw new IllegalArgumentException("PostColumnIndexes > null cursor"); @@ -605,8 +600,6 @@ private PostColumnIndexes(Cursor c) { idx_is_likes_enabled = c.getColumnIndex("is_likes_enabled"); idx_is_sharing_enabled = c.getColumnIndex("is_sharing_enabled"); - - idx_attachments_json = c.getColumnIndex("attachments_json"); } } @@ -659,8 +652,6 @@ private static ReaderPost getPostFromCursor(Cursor c, PostColumnIndexes cols) { post.isLikesEnabled = SqlUtils.sqlToBool(c.getInt(cols.idx_is_likes_enabled)); post.isSharingEnabled = SqlUtils.sqlToBool(c.getInt(cols.idx_is_sharing_enabled)); - post.setAttachmentsJson(c.getString(cols.idx_attachments_json)); - return post; } } diff --git a/WordPress/src/main/java/org/wordpress/android/models/ReaderPost.java b/WordPress/src/main/java/org/wordpress/android/models/ReaderPost.java index 313298d7ff28..0290b69484ce 100644 --- a/WordPress/src/main/java/org/wordpress/android/models/ReaderPost.java +++ b/WordPress/src/main/java/org/wordpress/android/models/ReaderPost.java @@ -52,8 +52,6 @@ public class ReaderPost { public boolean isLikesEnabled; public boolean isSharingEnabled; // currently unused - private String attachmentsJson; - public static ReaderPost fromJson(JSONObject json) { if (json == null) { throw new IllegalArgumentException("null json post"); @@ -157,12 +155,6 @@ public static ReaderPost fromJson(JSONObject json) { // parse the tags section assignTagsFromJson(post, json.optJSONObject("tags")); - // parse the attachments - JSONObject jsonAttachments = json.optJSONObject("attachments"); - if (jsonAttachments != null) { - post.attachmentsJson = jsonAttachments.toString(); - } - // the single-post sites/$site/posts/$post endpoint returns all site metadata // under meta/data/site (assuming ?meta=site was added to the request) JSONObject jsonSite = JSONUtil.getJSONChild(json, "meta/data/site"); @@ -351,6 +343,8 @@ public void setPublished(String published) { this.published = StringUtils.notNullStr(published); } + // -------------------------------------------------------------------------------------------- + public String getPrimaryTag() { return StringUtils.notNullStr(primaryTag); } @@ -374,17 +368,7 @@ public void setSecondaryTag(String tagName) { } } - /* - * attachments are stored as the actual JSON to avoid having a separate table for - * them, may need to revisit this if/when attachments become more important - */ - public String getAttachmentsJson() { - return StringUtils.notNullStr(attachmentsJson); - } - public void setAttachmentsJson(String json) { - attachmentsJson = StringUtils.notNullStr(json); - } - + // -------------------------------------------------------------------------------------------- public boolean hasText() { return !TextUtils.isEmpty(text); @@ -450,8 +434,12 @@ public String getFeaturedImageForDisplay(int width, int height) { if (featuredImageForDisplay == null) { if (!hasFeaturedImage()) { featuredImageForDisplay = ""; + } else if (isPrivate) { + // images in private posts can't use photon, so handle separately + featuredImageForDisplay = ReaderUtils.getPrivateImageForDisplay(featuredImage, width, height); } else { - featuredImageForDisplay = ReaderUtils.getResizedImageUrl(featuredImage, width, height, isPrivate); + // not private, so set to correctly sized photon url + featuredImageForDisplay = PhotonUtils.getPhotonImageUrl(featuredImage, width, height); } } return featuredImageForDisplay; diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPhotoView.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPhotoView.java index 34a4437b1585..e50c12ea19f5 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPhotoView.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPhotoView.java @@ -21,6 +21,7 @@ import org.wordpress.android.ui.reader.utils.ReaderUtils; import org.wordpress.android.util.AppLog; import org.wordpress.android.util.DisplayUtils; +import org.wordpress.android.util.PhotonUtils; import uk.co.senab.photoview.PhotoViewAttacher; @@ -76,8 +77,13 @@ public void setImageUrl(String imageUrl, boolean isPrivate, PhotoViewListener listener) { int loResWidth = (int) (hiResWidth * 0.10f); - mLoResImageUrl = ReaderUtils.getResizedImageUrl(imageUrl, loResWidth, 0, isPrivate); - mHiResImageUrl = ReaderUtils.getResizedImageUrl(imageUrl, hiResWidth, 0, isPrivate); + if (isPrivate) { + mLoResImageUrl = ReaderUtils.getPrivateImageForDisplay(imageUrl, loResWidth, 0); + mHiResImageUrl = ReaderUtils.getPrivateImageForDisplay(imageUrl, hiResWidth, 0); + } else { + mLoResImageUrl = PhotonUtils.getPhotonImageUrl(imageUrl, loResWidth, 0); + mHiResImageUrl = PhotonUtils.getPhotonImageUrl(imageUrl, hiResWidth, 0); + } mPhotoViewListener = listener; loadLoResImage(); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPhotoViewerActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPhotoViewerActivity.java index 814c195a77ef..6451112c7c36 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPhotoViewerActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPhotoViewerActivity.java @@ -16,7 +16,6 @@ import org.wordpress.android.ui.reader.ReaderViewPagerTransformer.TransformType; import org.wordpress.android.ui.reader.models.ReaderImageList; import org.wordpress.android.ui.reader.utils.ReaderImageScanner; -import org.wordpress.android.util.AppLog; import javax.annotation.Nonnull; @@ -69,35 +68,26 @@ public void onPageSelected(int position) { } private void loadImageList() { - // content will be empty unless this was called by ReaderPostDetailFragment to show - // a list of images in a post (in which case, it's the content of the post) - if (TextUtils.isEmpty(mContent)) { - final ReaderImageList imageList = new ReaderImageList(mIsPrivate); - if (!TextUtils.isEmpty(mInitialImageUrl)) { - imageList.add(mInitialImageUrl); - } - setImageList(imageList, mInitialImageUrl); - } else { - // parse images from content and make sure the list includes the passed url - new Thread() { - @Override - public void run() { - final ReaderImageList imageList = new ReaderImageScanner(mContent, mIsPrivate).getImageList(); - if (!TextUtils.isEmpty(mInitialImageUrl) && !imageList.hasImageUrl(mInitialImageUrl)) { - AppLog.w(AppLog.T.READER, "reader photo viewer > initial image not in list"); - imageList.addImageUrl(0, mInitialImageUrl); - } - runOnUiThread(new Runnable() { - @Override - public void run() { - if (!isFinishing()) { - setImageList(imageList, mInitialImageUrl); - } - } - }); + new Thread() { + @Override + public void run() { + // parse list of images from content that was (optionally) passed to + // this activity, and make sure the list includes the passed url + ReaderImageScanner scanner = new ReaderImageScanner(mContent, mIsPrivate); + final ReaderImageList imageList = scanner.getImageList(); + if (!TextUtils.isEmpty(mInitialImageUrl) && !imageList.hasImageUrl(mInitialImageUrl)) { + imageList.addImageUrl(0, mInitialImageUrl); } - }.start(); - } + runOnUiThread(new Runnable() { + @Override + public void run() { + if (!isFinishing()) { + setImageList(imageList, mInitialImageUrl); + } + } + }); + } + }.start(); } private void setImageList(ReaderImageList imageList, String initialImageUrl) { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostDetailFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostDetailFragment.java index 0d77842d9dc4..c7e44f8742e7 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostDetailFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostDetailFragment.java @@ -3,7 +3,10 @@ import android.app.ActionBar; import android.app.Activity; import android.app.Fragment; +import android.content.Context; import android.content.Intent; +import android.content.res.Resources; +import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; @@ -33,6 +36,7 @@ import org.wordpress.android.datasets.ReaderPostTable; import org.wordpress.android.models.ReaderComment; import org.wordpress.android.models.ReaderPost; +import org.wordpress.android.ui.WPActionBarActivity; import org.wordpress.android.ui.reader.ReaderActivityLauncher.OpenUrlType; import org.wordpress.android.ui.reader.ReaderTypes.ReaderPostListType; import org.wordpress.android.ui.reader.ReaderWebView.ReaderCustomViewListener; @@ -49,7 +53,11 @@ import org.wordpress.android.util.AppLog; import org.wordpress.android.util.AppLog.T; import org.wordpress.android.util.DateTimeUtils; +import org.wordpress.android.util.DisplayUtils; import org.wordpress.android.util.EditTextUtils; +import org.wordpress.android.util.HtmlUtils; +import org.wordpress.android.util.PhotonUtils; +import org.wordpress.android.util.StringUtils; import org.wordpress.android.util.ToastUtils; import org.wordpress.android.util.UrlUtils; import org.wordpress.android.widgets.WPListView; @@ -69,7 +77,7 @@ public class ReaderPostDetailFragment extends Fragment private long mPostId; private long mBlogId; private ReaderPost mPost; - private ReaderPostRenderer mRenderer; + private ReaderPostListType mPostListType; private ViewGroup mLayoutIcons; @@ -94,7 +102,7 @@ public class ReaderPostDetailFragment extends Fragment private int mPrevScrollState = SCROLL_STATE_IDLE; private Parcelable mListState; - private ReaderResourceVars mResourceVars; + private ResourceVars mResourceVars; private ReaderUtils.FullScreenListener mFullScreenListener; @@ -122,6 +130,60 @@ public static ReaderPostDetailFragment newInstance(long blogId, return fragment; } + /* + * class which holds all resource-based variables used by this fragment + */ + private static class ResourceVars { + final int displayWidth; + final int actionBarHeight; + final int likeAvatarSize; + final int videoOverlaySize; + + final int marginLarge; + final int marginSmall; + final int marginExtraSmall; + final int listMarginWidth; + final int fullSizeImageWidth; + + final int colorGreyExtraLight; + final int mediumAnimTime; + + final String linkColorStr; + final String greyLightStr; + final String greyExtraLightStr; + + private ResourceVars(Context context) { + Resources resources = context.getResources(); + + displayWidth = DisplayUtils.getDisplayPixelWidth(context); + actionBarHeight = DisplayUtils.getActionBarHeight(context); + likeAvatarSize = resources.getDimensionPixelSize(R.dimen.avatar_sz_small); + videoOverlaySize = resources.getDimensionPixelSize(R.dimen.reader_video_overlay_size); + + marginLarge = resources.getDimensionPixelSize(R.dimen.margin_large); + marginSmall = resources.getDimensionPixelSize(R.dimen.margin_small); + marginExtraSmall = resources.getDimensionPixelSize(R.dimen.margin_extra_small); + listMarginWidth = resources.getDimensionPixelOffset(R.dimen.reader_list_margin); + + colorGreyExtraLight = resources.getColor(R.color.grey_extra_light); + mediumAnimTime = resources.getInteger(android.R.integer.config_mediumAnimTime); + + linkColorStr = HtmlUtils.colorResToHtmlColor(context, R.color.reader_hyperlink); + greyLightStr = HtmlUtils.colorResToHtmlColor(context, R.color.grey_light); + greyExtraLightStr = HtmlUtils.colorResToHtmlColor(context, R.color.grey_extra_light); + + int imageWidth = displayWidth - (listMarginWidth * 2); + boolean hasStaticMenuDrawer = + (context instanceof WPActionBarActivity) + && (((WPActionBarActivity) context).isStaticMenuDrawer()); + if (hasStaticMenuDrawer) { + int drawerWidth = resources.getDimensionPixelOffset(R.dimen.menu_drawer_width); + imageWidth -= drawerWidth; + } + fullSizeImageWidth = imageWidth; + } + } + /* * adapter containing comments for this post */ @@ -224,7 +286,7 @@ public void setArguments(Bundle args) { public void onAttach(Activity activity) { super.onAttach(activity); - mResourceVars = new ReaderResourceVars(activity); + mResourceVars = new ResourceVars(activity); if (activity instanceof ReaderUtils.FullScreenListener) { mFullScreenListener = (ReaderUtils.FullScreenListener) activity; @@ -238,13 +300,14 @@ public void onAttach(Activity activity) { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View view = inflater.inflate(R.layout.reader_fragment_post_detail, container, false); + final Context context = container.getContext(); // locate & init listView mListView = (WPListView) view.findViewById(android.R.id.list); if (isFullScreenSupported()) { mListView.setOnScrollDirectionListener(this); mListView.setOnScrollListener(this); - ReaderUtils.addListViewHeader(mListView, mResourceVars.actionBarHeightPx); + ReaderUtils.addListViewHeader(mListView, mResourceVars.actionBarHeight); } // add post detail as header to listView - must be done before setting adapter @@ -943,7 +1006,33 @@ private void refreshFollowed() { ReaderUtils.showFollowStatus(txtFollow, isFollowed); } - private boolean showPhotoViewer(String imageUrl, View sourceView, int startX, int startY) { + /* + * creates formatted div for passed video with passed (optional) thumbnail + */ + private static final String OVERLAY_IMG = "file:///android_asset/ic_reader_video_overlay.png"; + + private String makeVideoDiv(String videoUrl, String thumbnailUrl) { + if (TextUtils.isEmpty(videoUrl)) { + return ""; + } + + // sometimes we get src values like "//player.vimeo.com/video/70534716" - prefix these with http: + if (videoUrl.startsWith("//")) { + videoUrl = "http:" + videoUrl; + } + + int overlaySz = mResourceVars.videoOverlaySize / 2; + if (TextUtils.isEmpty(thumbnailUrl)) { + return String.format("
", videoUrl, overlaySz, overlaySz, OVERLAY_IMG); + } else { + return "
" + + String.format("", videoUrl, thumbnailUrl) + + String.format("", videoUrl, OVERLAY_IMG, overlaySz, overlaySz) + + "
"; + } + } + + private boolean showPhotoViewer(String imageUrl, View source, int startX, int startY) { if (!isAdded() || TextUtils.isEmpty(imageUrl)) { return false; } @@ -953,14 +1042,13 @@ private boolean showPhotoViewer(String imageUrl, View sourceView, int startX, in return false; } - String postContent = (mRenderer != null ? mRenderer.getRenderedHtml() : null); boolean isPrivatePost = (mPost != null && mPost.isPrivate); ReaderActivityLauncher.showReaderPhotoViewer( getActivity(), imageUrl, - postContent, - sourceView, + getPostContent(), + source, isPrivatePost, startX, startY); @@ -968,6 +1056,134 @@ private boolean showPhotoViewer(String imageUrl, View sourceView, int startX, in return true; } + /* + * returns the basic content of the post tweaked for use here + */ + private String getPostContent() { + if (mPost == null) { + return ""; + } else if (mPost.hasText()) { + // some content (such as Vimeo embeds) don't have "http:" before links, correct this here + String content = mPost.getText().replace("src=\"//", "src=\"http://"); + // insert video div before content if this is a VideoPress post (video otherwise won't appear) + if (mPost.isVideoPress) { + content = makeVideoDiv(mPost.getFeaturedVideo(), mPost.getFeaturedImage()) + content; + } else if (mPost.hasFeaturedImage() && !PhotonUtils.isMshotsUrl(mPost.getFeaturedImage())) { + // if the post has a featured image other than an mshot that's not in the content, + // add it to the content + Uri uri = Uri.parse(mPost.getFeaturedImage()); + String path = StringUtils.notNullStr(uri.getLastPathSegment()); + if (!content.contains(path)) { + AppLog.d(T.READER, "reader post detail > added featured image to content"); + content = String.format("

", mPost.getFeaturedImage()) + + content; + } + } + return content; + } else if (mPost.hasFeaturedImage()) { + // some photo blogs have posts with empty content but still have a featured image, so + // use the featured image as the content + return String.format("

", mPost.getFeaturedImage()); + } else { + return ""; + } + } + + /* + * returns the full content, including CSS, that will be shown in the WebView for this post + */ + private String getPostContentForWebView(Context context) { + if (mPost == null || context == null) { + return ""; + } + + String content = getPostContent(); + + StringBuilder sbHtml = new StringBuilder(""); + + // title isn't strictly necessary, but source is invalid html5 without one + sbHtml.append("Reader Post"); + + // https://developers.google.com/chrome/mobile/docs/webview/pixelperfect + sbHtml.append(""); + + // use "Open Sans" Google font + sbHtml.append(""); + + sbHtml.append("") + .append(content) + .append(""); + + return sbHtml.toString(); + } + /* * called when the post doesn't exist in local db, need to get it from server */ @@ -1001,10 +1217,16 @@ private void postFailed() { } } + /* + * javascript should only be enabled for wp blogs (not external feeds) + */ + private boolean canEnableJavaScript() { + return (mPost != null && mPost.isWP()); + } + private void showPost() { if (mIsPostTaskRunning) { AppLog.w(T.READER, "reader post detail > show post task already running"); - return; } new ShowPostTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); @@ -1028,6 +1250,8 @@ private class ShowPostTask extends AsyncTask { WPNetworkImageView imgAvatar; ViewGroup layoutDetailHeader; + String postHtml; + @Override protected void onPreExecute() { mIsPostTaskRunning = true; @@ -1064,6 +1288,8 @@ protected Boolean doInBackground(Void... params) { layoutDetailHeader = (ViewGroup) container.findViewById(R.id.layout_detail_header); + postHtml = getPostContentForWebView(container.getContext()); + return true; } @@ -1080,15 +1306,17 @@ protected void onPostExecute(Boolean result) { // the server if it hasn't already been requested if (!mHasAlreadyRequestedPost) { mHasAlreadyRequestedPost = true; - AppLog.i(T.READER, "reader post detail > post not found, requesting it"); requestPost(); } return; } - // render the post in the webView - mRenderer = new ReaderPostRenderer(mReaderWebView, mPost); - mRenderer.beginRender(); + // enable JavaScript in the webView if it's safe to do so + mReaderWebView.getSettings().setJavaScriptEnabled(canEnableJavaScript()); + + // IMPORTANT: use loadDataWithBaseURL() since loadData() may fail + // https://code.google.com/p/android/issues/detail?id=4401 + mReaderWebView.loadDataWithBaseURL(null, postHtml, "text/html", "UTF-8", null); txtTitle.setText(mPost.hasTitle() ? mPost.getTitle() : getString(R.string.reader_untitled_post)); @@ -1121,13 +1349,13 @@ public void onClick(View view) { if (mPost.hasPostAvatar()) { imgAvatar.setImageUrl(mPost.getPostAvatarForDisplay( - mResourceVars.likeAvatarSizePx), WPNetworkImageView.ImageType.AVATAR); + mResourceVars.likeAvatarSize), WPNetworkImageView.ImageType.AVATAR); imgAvatar.setVisibility(View.VISIBLE); } else { imgAvatar.setVisibility(View.GONE); } - // hide header if this fragment was shown from blog preview + // hide blog name, avatar & follow button if this fragment was shown from blog preview if (isBlogPreview()) { layoutDetailHeader.setVisibility(View.GONE); } @@ -1204,6 +1432,7 @@ public void onClick(View view) { } } + /* * called by the web view when the content finishes loading - likes & comments aren't displayed * until this is triggered, to avoid having them appear before the webView content diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListActivity.java index 2d0884d392c6..641140877d03 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListActivity.java @@ -17,6 +17,7 @@ import org.wordpress.android.datasets.ReaderTagTable; import org.wordpress.android.models.ReaderTag; import org.wordpress.android.models.ReaderTagType; +import org.wordpress.android.util.NetworkUtils; import org.wordpress.android.ui.WPActionBarActivity; import org.wordpress.android.ui.accounts.WPComLoginActivity; import org.wordpress.android.ui.prefs.AppPrefs; @@ -29,7 +30,6 @@ import org.wordpress.android.ui.reader.services.ReaderUpdateService.UpdateTask; import org.wordpress.android.util.AppLog; import org.wordpress.android.util.AppLog.T; -import org.wordpress.android.util.NetworkUtils; import org.wordpress.android.util.StringUtils; import java.util.EnumSet; @@ -412,11 +412,6 @@ public void onReceive(Context context, Intent intent) { } else if (listFragment.getPostListType() == ReaderTypes.ReaderPostListType.TAG_FOLLOWED) { // list fragment is viewing followed tags, tell it to refresh the list of tags listFragment.refreshTags(); - // update the current tag if the list fragment is empty - this will happen if - // the tag table was previously empty (ie: first run) - if (listFragment.isPostAdapterEmpty()) { - listFragment.updateCurrentTag(); - } } } else if (action.equals(ReaderUpdateService.ACTION_FOLLOWED_BLOGS_CHANGED)) { // followed blogs have changed, so remove posts in blogs that are diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java index 832605d939c3..d85df3f9af96 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java @@ -872,10 +872,6 @@ public void onActionResult(boolean succeeded) { ReaderPostActions.requestPostsForBlog(mCurrentBlogId, mCurrentBlogUrl, updateAction, listener); } - void updateCurrentTag() { - updatePostsWithTag(getCurrentTag(), RequestDataAction.LOAD_NEWER, ReaderTypes.RefreshType.AUTOMATIC); - } - /* * get latest posts for this tag from the server */ diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostPagerActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostPagerActivity.java index 7d2f500dfa7a..6d883f454118 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostPagerActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostPagerActivity.java @@ -25,6 +25,7 @@ import org.wordpress.android.models.ReaderPost; import org.wordpress.android.models.ReaderPostList; import org.wordpress.android.models.ReaderTag; +import org.wordpress.android.util.NetworkUtils; import org.wordpress.android.ui.reader.ReaderAnim.AnimationEndListener; import org.wordpress.android.ui.reader.ReaderAnim.Duration; import org.wordpress.android.ui.reader.ReaderPostPagerEndFragment.EndFragmentType; @@ -39,7 +40,6 @@ import org.wordpress.android.ui.reader.models.ReaderBlogIdPostIdList; import org.wordpress.android.ui.reader.utils.ReaderUtils; import org.wordpress.android.util.AppLog; -import org.wordpress.android.util.NetworkUtils; import org.wordpress.android.util.ToastUtils; import javax.annotation.Nonnull; @@ -232,12 +232,15 @@ private void loadPosts(final long blogId, new Thread() { @Override public void run() { - final ReaderBlogIdPostIdList idList; + final ReaderPostList postList; if (mIsSinglePostView) { - idList = new ReaderBlogIdPostIdList(); - idList.add(new ReaderBlogIdPostId(blogId, postId)); + ReaderPost post = ReaderPostTable.getPost(blogId, postId); + if (post == null) { + return; + } + postList = new ReaderPostList(); + postList.add(post); } else { - final ReaderPostList postList; int maxPosts = ReaderConstants.READER_MAX_POSTS_TO_DISPLAY; switch (getPostListType()) { case TAG_FOLLOWED: @@ -250,22 +253,22 @@ public void run() { default: return; } - idList = postList.getBlogIdPostIdList(); } + final ReaderBlogIdPostIdList ids = postList.getBlogIdPostIdList(); final int currentPosition = mViewPager.getCurrentItem(); final int newPosition; if (gotoNext) { - newPosition = idList.indexOf(blogId, postId) + 1; + newPosition = ids.indexOf(blogId, postId) + 1; } else { - newPosition = idList.indexOf(blogId, postId); + newPosition = ids.indexOf(blogId, postId); } runOnUiThread(new Runnable() { @Override public void run() { mPagerAdapter = new PostPagerAdapter(getFragmentManager()); - mPagerAdapter.showPosts(idList); + mPagerAdapter.showPosts(ids); mViewPager.setAdapter(mPagerAdapter); if (mPagerAdapter.isValidPosition(newPosition)) { mViewPager.setCurrentItem(newPosition); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostRenderer.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostRenderer.java deleted file mode 100644 index f7c41302226e..000000000000 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostRenderer.java +++ /dev/null @@ -1,427 +0,0 @@ -package org.wordpress.android.ui.reader; - -import android.annotation.SuppressLint; -import android.net.Uri; -import android.os.Handler; -import android.text.TextUtils; - -import org.json.JSONException; -import org.json.JSONObject; -import org.wordpress.android.WordPress; -import org.wordpress.android.models.ReaderPost; -import org.wordpress.android.ui.reader.utils.ReaderImageScanner; -import org.wordpress.android.ui.reader.utils.ReaderUtils; -import org.wordpress.android.util.AppLog; -import org.wordpress.android.util.DisplayUtils; -import org.wordpress.android.util.JSONUtil; -import org.wordpress.android.util.PhotonUtils; -import org.wordpress.android.util.StringUtils; -import org.wordpress.android.util.UrlUtils; - -import java.lang.ref.WeakReference; -import java.util.HashMap; -import java.util.Iterator; - -/** - * generates and displays the HTML for post detail content - main purpose is to assign the - * height/width attributes on image tags to (1) avoid the webView resizing as images are - * loaded, and (2) avoid requesting images at a size larger than the display - * - * important to note that displayed images rely on dp rather than px sizes due to the - * fact that WebView "converts CSS pixel values to density-independent pixel values" - * http://developer.android.com/guide/webapps/targeting.html - */ -class ReaderPostRenderer { - - private final ReaderResourceVars mResourceVars; - private final ReaderPost mPost; - private final int mMinFullSizeWidthDp; - private final int mMinMidSizeWidthDp; - private final WeakReference mWeakWebView; - - private StringBuilder mRenderBuilder; - private String mRenderedHtml; - private ImageSizeMap mAttachmentSizes; - - @SuppressLint("SetJavaScriptEnabled") - ReaderPostRenderer(ReaderWebView webView, ReaderPost post) { - if (webView == null) { - throw new IllegalArgumentException("ReaderPostRenderer requires a webView"); - } - if (post == null) { - throw new IllegalArgumentException("ReaderPostRenderer requires a post"); - } - - mPost = post; - mWeakWebView = new WeakReference(webView); - mResourceVars = new ReaderResourceVars(webView.getContext()); - - mMinFullSizeWidthDp = pxToDp(mResourceVars.fullSizeImageWidthPx / 3); - mMinMidSizeWidthDp = mMinFullSizeWidthDp / 2; - - // enable JavaScript in the webView if it's safe to do so, otherwise videos - // and other embedded content won't work - webView.getSettings().setJavaScriptEnabled(canEnableJavaScript()); - } - - void beginRender() { - mRenderBuilder = new StringBuilder(getPostContent()); - - // start image scanner to find images so we can replace them with ones that have h/w set - final Handler handler = new Handler(); - new Thread() { - @Override - public void run() { - ReaderImageScanner.ImageScanListener imageListener = new ReaderImageScanner.ImageScanListener() { - @Override - public void onImageFound(String imageTag, String imageUrl, int start, int end) { - replaceImageTag(imageTag, imageUrl); - } - - @Override - public void onScanCompleted() { - final String htmlContent = formatPostContentForWebView(mRenderBuilder.toString()); - mRenderBuilder = null; - handler.post(new Runnable() { - @Override - public void run() { - renderHtmlContent(htmlContent); - } - }); - } - }; - ReaderImageScanner scanner = new ReaderImageScanner(mRenderBuilder.toString(), mPost.isPrivate); - scanner.beginScan(imageListener); - } - }.start(); - } - - /* - * called once the content is ready to be rendered in the webView - */ - private void renderHtmlContent(final String htmlContent) { - mRenderedHtml = htmlContent; - - // make sure webView is still valid (containing fragment may have been detached) - ReaderWebView webView = mWeakWebView.get(); - if (webView == null) { - AppLog.w(AppLog.T.READER, "reader renderer > null webView"); - return; - } - - // IMPORTANT: use loadDataWithBaseURL() since loadData() may fail - // https://code.google.com/p/android/issues/detail?id=4401 - // also important to use null as the baseUrl since onPageFinished - // doesn't appear to fire when it's set to an actual url - webView.loadDataWithBaseURL(null, htmlContent, "text/html", "UTF-8", null); - } - - /* - * called when image scanner finds an image, tries to replace the image tag with one that - * has height & width attributes set correctly for the current display, if that fails - * replaces it with one that has our 'size-none' class - */ - private void replaceImageTag(final String imageTag, final String imageUrl) { - ImageSize origSize = getImageSize(imageTag, imageUrl); - boolean hasWidth = (origSize != null && origSize.width > 0); - boolean isFullSize = hasWidth && (origSize.width >= mMinFullSizeWidthDp); - boolean isMidSize = hasWidth - && (origSize.width >= mMinMidSizeWidthDp) - && (origSize.width < mMinFullSizeWidthDp); - - final String newImageTag; - if (isFullSize) { - newImageTag = makeFullSizeImageTag(imageUrl, origSize.width, origSize.height); - } else if (isMidSize) { - newImageTag = makeImageTag(imageUrl, origSize.width, origSize.height, "size-medium"); - } else if (hasWidth) { - newImageTag = makeImageTag(imageUrl, origSize.width, origSize.height, "size-none"); - } else { - newImageTag = ""; - } - - int start = mRenderBuilder.indexOf(imageTag); - if (start == -1) { - AppLog.w(AppLog.T.READER, "reader renderer > image not found in builder"); - return; - } - - mRenderBuilder.replace(start, start + imageTag.length(), newImageTag); - } - - private String makeImageTag(final String imageUrl, int width, int height, final String imageClass) { - String newImageUrl = ReaderUtils.getResizedImageUrl(imageUrl, width, height, mPost.isPrivate); - if (height > 0) { - return new StringBuilder("") - .toString(); - } else { - return new StringBuilder("") - .toString(); - } - } - - private String makeFullSizeImageTag(final String imageUrl, int width, int height) { - int newWidth; - int newHeight; - if (width > 0 && height > 0) { - if (height > width) { - newHeight = mResourceVars.fullSizeImageWidthPx; - float ratio = ((float) width / (float) height); - newWidth = (int) (newHeight * ratio); - } else { - float ratio = ((float) height / (float) width); - newWidth = mResourceVars.fullSizeImageWidthPx; - newHeight = (int) (newWidth * ratio); - } - } else { - newWidth = mResourceVars.fullSizeImageWidthPx; - newHeight = 0; - } - - return makeImageTag(imageUrl, newWidth, newHeight, "size-full"); - } - - /* - * returns true if the post has a featured image and there are no images in the - * post's content - when this is the case, the featured image is inserted at - * the top of the content - */ - private boolean shouldAddFeaturedImage() { - return mPost.hasFeaturedImage() - && !mPost.getText().contains(" added featured image"); - content = getFeaturedImageHtml() + content; - } - return content; - } - - /* - * returns the HTML that was last rendered, will be null prior to rendering - */ - String getRenderedHtml() { - return mRenderedHtml; - } - - /* - * returns the HTML to use when inserting a featured image into the rendered content - */ - private String getFeaturedImageHtml() { - String imageUrl = ReaderUtils.getResizedImageUrl( - mPost.getFeaturedImage(), - mResourceVars.fullSizeImageWidthPx, - mResourceVars.featuredImageHeightPx, - mPost.isPrivate); - - return ""; - } - - /* - * returns the full content, including CSS, that will be shown in the WebView for this post - */ - private String formatPostContentForWebView(final String content) { - StringBuilder sbHtml = new StringBuilder(""); - - // title isn't necessary, but it's invalid html5 without one - sbHtml.append("Reader Post") - - // https://developers.google.com/chrome/mobile/docs/webview/pixelperfect - .append("") - - // use "Open Sans" Google font - .append("") - - .append("") - .append("") - .append(content) - .append(""); - - return sbHtml.toString(); - } - - private ImageSize getImageSize(final String imageTag, final String imageUrl) { - ImageSize size = getImageSizeFromAttachments(imageUrl); - if (size == null && imageUrl.contains("?")) { - size = getImageSizeFromQueryParams(imageUrl); - } - if (size == null && imageTag.contains("width=")) { - size = getImageSizeFromAttributes(imageTag); - } - return size; - } - - private ImageSize getImageSizeFromAttachments(final String imageUrl) { - if (mAttachmentSizes == null) { - mAttachmentSizes = new ImageSizeMap(mPost.getAttachmentsJson()); - } - return mAttachmentSizes.getAttachmentSize(imageUrl); - } - - private ImageSize getImageSizeFromQueryParams(final String imageUrl) { - if (imageUrl.contains("w=")) { - Uri uri = Uri.parse(imageUrl.replace("&", "&")); - return new ImageSize( - StringUtils.stringToInt(uri.getQueryParameter("w")), - StringUtils.stringToInt(uri.getQueryParameter("h"))); - } else if (imageUrl.contains("resize=")) { - Uri uri = Uri.parse(imageUrl.replace("&", "&")); - String param = uri.getQueryParameter("resize"); - if (param != null) { - String[] sizes = param.split(","); - if (sizes.length == 2) { - return new ImageSize( - StringUtils.stringToInt(sizes[0]), - StringUtils.stringToInt(sizes[1])); - } - } - } - - return null; - } - - private ImageSize getImageSizeFromAttributes(final String imageTag) { - return new ImageSize( - ReaderImageScanner.getWidthAttrValue(imageTag), - ReaderImageScanner.getHeightAttrValue(imageTag)); - } - - private int pxToDp(int px) { - if (px == 0) { - return 0; - } - return DisplayUtils.pxToDp(WordPress.getContext(), px); - } - - /* - * javascript should only be enabled for wp blogs (not external feeds) - */ - private boolean canEnableJavaScript() { - return mPost.isWP(); - } - - /* - * hash map of sizes of attachments in the current post for quick lookup - created from - * the json "attachments" section of the post endpoints - */ - class ImageSizeMap extends HashMap { - ImageSizeMap(String jsonString) { - if (TextUtils.isEmpty(jsonString)) { - return; - } - - try { - JSONObject json = new JSONObject(jsonString); - Iterator it = json.keys(); - if (!it.hasNext()) { - return; - } - - while (it.hasNext()) { - JSONObject jsonAttach = json.optJSONObject(it.next()); - if (jsonAttach != null && JSONUtil.getString(jsonAttach, "mime_type").startsWith("image")) { - String normUrl = UrlUtils.normalizeUrl(UrlUtils.removeQuery(JSONUtil.getString(json, "URL"))); - int width = jsonAttach.optInt("width"); - int height = jsonAttach.optInt("height"); - this.put(normUrl, new ImageSize(width, height)); - } - } - } catch (JSONException e) { - AppLog.e(AppLog.T.READER, e); - } - } - - ImageSize getAttachmentSize(final String imageUrl) { - if (imageUrl == null) { - return null; - } else { - return super.get(UrlUtils.normalizeUrl(UrlUtils.removeQuery(imageUrl))); - } - } - } - - static class ImageSize { - final int width; - final int height; - ImageSize(int width, int height) { - this.width = width; - this.height = height; - } - } -} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderResourceVars.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderResourceVars.java deleted file mode 100644 index 03ac68be85b4..000000000000 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderResourceVars.java +++ /dev/null @@ -1,75 +0,0 @@ -package org.wordpress.android.ui.reader; - -import android.content.Context; -import android.content.res.Resources; - -import org.wordpress.android.R; -import org.wordpress.android.ui.WPActionBarActivity; -import org.wordpress.android.util.DisplayUtils; -import org.wordpress.android.util.HtmlUtils; - -/* - * class which holds all resource-based variables used when rendering post detail - */ -class ReaderResourceVars { - final int displayWidthPx; - final int actionBarHeightPx; - final int likeAvatarSizePx; - - final int marginLargePx; - final int marginSmallPx; - final int marginExtraSmallPx; - final int listMarginWidthPx; - - final int fullSizeImageWidthPx; - final int featuredImageHeightPx; - - final int videoWidthPx; - final int videoHeightPx; - - final int colorGreyExtraLight; - final int mediumAnimTime; - - final String linkColorStr; - final String greyMediumDarkStr; - final String greyLightStr; - final String greyExtraLightStr; - - ReaderResourceVars(Context context) { - Resources resources = context.getResources(); - - displayWidthPx = DisplayUtils.getDisplayPixelWidth(context); - actionBarHeightPx = DisplayUtils.getActionBarHeight(context); - likeAvatarSizePx = resources.getDimensionPixelSize(R.dimen.avatar_sz_small); - featuredImageHeightPx = resources.getDimensionPixelSize(R.dimen.reader_featured_image_height); - - marginLargePx = resources.getDimensionPixelSize(R.dimen.margin_large); - marginSmallPx = resources.getDimensionPixelSize(R.dimen.margin_small); - marginExtraSmallPx = resources.getDimensionPixelSize(R.dimen.margin_extra_small); - listMarginWidthPx = resources.getDimensionPixelOffset(R.dimen.reader_list_margin); - - colorGreyExtraLight = resources.getColor(R.color.grey_extra_light); - mediumAnimTime = resources.getInteger(android.R.integer.config_mediumAnimTime); - - linkColorStr = HtmlUtils.colorResToHtmlColor(context, R.color.reader_hyperlink); - greyMediumDarkStr = HtmlUtils.colorResToHtmlColor(context, R.color.grey_medium_dark); - greyLightStr = HtmlUtils.colorResToHtmlColor(context, R.color.grey_light); - greyExtraLightStr = HtmlUtils.colorResToHtmlColor(context, R.color.grey_extra_light); - - // full-size image width must take list margin and padding into account - int listPadding = resources.getDimensionPixelOffset(R.dimen.margin_large); - int imageWidth = displayWidthPx - (listMarginWidthPx * 2) - (listPadding * 2); - boolean hasStaticMenuDrawer = - (context instanceof WPActionBarActivity) - && (((WPActionBarActivity) context).isStaticMenuDrawer()); - if (hasStaticMenuDrawer) { - int drawerWidth = resources.getDimensionPixelOffset(R.dimen.menu_drawer_width); - imageWidth -= drawerWidth; - } - fullSizeImageWidthPx = imageWidth; - - // 16:9 ratio (YouTube standard) - videoWidthPx = fullSizeImageWidthPx - (marginLargePx * 2); - videoHeightPx = (int) (videoWidthPx * 0.5625f); - } -} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/utils/ReaderImageScanner.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/utils/ReaderImageScanner.java index 08173de03623..3549e6f2930d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/utils/ReaderImageScanner.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/utils/ReaderImageScanner.java @@ -1,7 +1,6 @@ package org.wordpress.android.ui.reader.utils; import android.net.Uri; -import android.text.TextUtils; import org.wordpress.android.ui.reader.models.ReaderImageList; import org.wordpress.android.util.PhotonUtils; @@ -13,18 +12,13 @@ public class ReaderImageScanner { - public static interface ImageScanListener { - public void onImageFound(String imageTag, String imageUrl, int start, int end); - public void onScanCompleted(); - } private final String mContent; private final boolean mIsPrivate; - private final boolean mContentContainsImages; private static final int MIN_FEATURED_IMAGE_WIDTH = 500; // regex for matching img tags in html content private static final Pattern IMG_TAG_PATTERN = Pattern.compile( - "", + "", Pattern.DOTALL| Pattern.CASE_INSENSITIVE); // regex for matching width attributes in tags @@ -32,11 +26,6 @@ public static interface ImageScanListener { "width\\s*=\\s*(?:'|\")(.*?)(?:'|\")", Pattern.DOTALL|Pattern.CASE_INSENSITIVE); - // regex for matching height attributes in tags - private static final Pattern HEIGHT_ATTR_PATTERN = Pattern.compile( - "height\\s*=\\s*(?:'|\")(.*?)(?:'|\")", - Pattern.DOTALL|Pattern.CASE_INSENSITIVE); - // regex for matching src attributes in tags private static final Pattern SRC_ATTR_PATTERN = Pattern.compile( "src\\s*=\\s*(?:'|\")(.*?)(?:'|\")", @@ -45,41 +34,15 @@ public static interface ImageScanListener { public ReaderImageScanner(String contentOfPost, boolean isPrivate) { mContent = contentOfPost; mIsPrivate = isPrivate; - mContentContainsImages = mContent != null && mContent.contains(" 0 && height > 0) { - query = "?w=" + width + "&h=" + height; + query = String.format("?w=%d&h=%d", width, height); } else if (width > 0) { - query = "?w=" + width; + query = String.format("?w=%d", width); } else if (height > 0) { - query = "?h=" + height; + query = String.format("?h=%d", height); } else { query = ""; } diff --git a/WordPress/src/main/res/layout/reader_listitem_post_detail.xml b/WordPress/src/main/res/layout/reader_listitem_post_detail.xml index e346246e84d9..3642f6314220 100644 --- a/WordPress/src/main/res/layout/reader_listitem_post_detail.xml +++ b/WordPress/src/main/res/layout/reader_listitem_post_detail.xml @@ -40,7 +40,7 @@ android:layout_marginBottom="@dimen/margin_small" android:ellipsize="end" android:maxLines="2" - android:textColor="@color/grey_dark" + android:textColor="@color/reader_hyperlink" android:textSize="@dimen/text_sz_medium" tools:text="text_blog_name" /> diff --git a/WordPress/src/main/res/values/dimens.xml b/WordPress/src/main/res/values/dimens.xml index 7fee4b237e47..943037c2ca23 100644 --- a/WordPress/src/main/res/values/dimens.xml +++ b/WordPress/src/main/res/values/dimens.xml @@ -68,6 +68,7 @@ 340dp @dimen/reader_featured_image_height_default + 48dp 12dp @dimen/margin_large diff --git a/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/PhotonUtils.java b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/PhotonUtils.java index bf275e56abdc..497d756ee377 100644 --- a/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/PhotonUtils.java +++ b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/PhotonUtils.java @@ -24,7 +24,7 @@ public static String fixAvatar(final String imageUrl, int avatarSz) { return getPhotonImageUrl(imageUrl, avatarSz, avatarSz); // remove all other params, then add query string for size and "mystery man" default - return UrlUtils.removeQuery(imageUrl) + "?s=" + avatarSz + "&d=mm"; + return UrlUtils.removeQuery(imageUrl) + String.format("?s=%d&d=mm", avatarSz); } /* @@ -64,18 +64,18 @@ public static String getPhotonImageUrl(String imageUrl, int width, int height) { // see http://wp.tutsplus.com/tutorials/how-to-generate-website-screenshots-for-your-wordpress-site/ // ex: http://s.wordpress.com/mshots/v1/http%3A%2F%2Fnickbradbury.com?w=600 if (isMshotsUrl(imageUrl)) { - return imageUrl + "?w=" + width + "&h=" + height; + return imageUrl + String.format("?w=%d&h=%d", width, height); } // if both width & height are passed use the "resize" param, use only "w" or "h" if just // one of them is set, otherwise no query string final String query; if (width > 0 && height > 0) { - query = "?resize=" + width + "," + height; + query = String.format("?resize=%d,%d", width, height); } else if (width > 0) { - query = "?w=" + width; + query = String.format("?w=%d", width); } else if (height > 0) { - query = "?h=" + height; + query = String.format("?h=%d", height); } else { query = ""; }