Java tutorial
package com.wellsandwhistles.android.redditsp.reddit.prepared; /** * This file was either copied or modified from https://github.com/QuantumBadger/RedReader * under the Free Software Foundation General Public License version 3 */ import android.Manifest; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; import android.net.Uri; import android.preference.PreferenceManager; import android.support.v7.app.AppCompatActivity; import android.text.ClipboardManager; import android.text.SpannableStringBuilder; import android.util.Log; import android.widget.TextView; import android.widget.Toast; import com.wellsandwhistles.android.redditsp.R; import com.wellsandwhistles.android.redditsp.account.RedditAccount; import com.wellsandwhistles.android.redditsp.account.RedditAccountManager; import com.wellsandwhistles.android.redditsp.activities.BaseActivity; import com.wellsandwhistles.android.redditsp.activities.BugReportActivity; import com.wellsandwhistles.android.redditsp.activities.CommentEditActivity; import com.wellsandwhistles.android.redditsp.activities.CommentReplyActivity; import com.wellsandwhistles.android.redditsp.activities.MainActivity; import com.wellsandwhistles.android.redditsp.activities.PostListingActivity; import com.wellsandwhistles.android.redditsp.activities.WebViewActivity; import com.wellsandwhistles.android.redditsp.cache.CacheManager; import com.wellsandwhistles.android.redditsp.cache.CacheRequest; import com.wellsandwhistles.android.redditsp.cache.downloadstrategy.DownloadStrategyIfNotCached; import com.wellsandwhistles.android.redditsp.common.BetterSSB; import com.wellsandwhistles.android.redditsp.common.Constants; import com.wellsandwhistles.android.redditsp.common.General; import com.wellsandwhistles.android.redditsp.common.LinkHandler; import com.wellsandwhistles.android.redditsp.common.PrefsUtility; import com.wellsandwhistles.android.redditsp.common.SRError; import com.wellsandwhistles.android.redditsp.common.SRTime; import com.wellsandwhistles.android.redditsp.fragments.PostPropertiesDialog; import com.wellsandwhistles.android.redditsp.image.SaveImageCallback; import com.wellsandwhistles.android.redditsp.image.ShareImageCallback; import com.wellsandwhistles.android.redditsp.image.ThumbnailScaler; import com.wellsandwhistles.android.redditsp.reddit.APIResponseHandler; import com.wellsandwhistles.android.redditsp.reddit.RedditAPI; import com.wellsandwhistles.android.redditsp.reddit.api.RedditSubredditSubscriptionManager; import com.wellsandwhistles.android.redditsp.reddit.things.RedditSubreddit; import com.wellsandwhistles.android.redditsp.reddit.url.SubredditPostListURL; import com.wellsandwhistles.android.redditsp.reddit.url.UserProfileURL; import com.wellsandwhistles.android.redditsp.views.RedditPostView; import org.apache.commons.lang3.StringEscapeUtils; import java.net.URI; import java.util.ArrayList; import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.UUID; public final class RedditPreparedPost { public final RedditParsedPost src; private final RedditChangeDataManager mChangeDataManager; public SpannableStringBuilder postListDescription; public SpannableStringBuilder postKarma; public final boolean isArchived; public final boolean hasThumbnail; public final boolean mIsProbablyAnImage; // TODO make it possible to turn off in-memory caching when out of memory private volatile Bitmap thumbnailCache = null; private static final Object singleImageDecodeLock = new Object(); private ThumbnailLoadedCallback thumbnailCallback; private int usageId = -1; public long lastChange = Long.MIN_VALUE; private final boolean showSubreddit; private RedditPostView boundView = null; public enum Action { UPVOTE(R.string.action_upvote), UNVOTE(R.string.action_vote_remove), DOWNVOTE( R.string.action_downvote), SAVE(R.string.action_save), HIDE(R.string.action_hide), UNSAVE( R.string.action_unsave), UNHIDE(R.string.action_unhide), EDIT(R.string.action_edit), DELETE( R.string.action_delete), REPORT(R.string.action_report), SHARE( R.string.action_share), REPLY(R.string.action_reply), USER_PROFILE( R.string.action_user_profile), EXTERNAL( R.string.action_external), PROPERTIES( R.string.action_properties), COMMENTS( R.string.action_comments), LINK( R.string.action_link), COMMENTS_SWITCH( R.string.action_comments_switch), LINK_SWITCH( R.string.action_link_switch), SHARE_COMMENTS( R.string.action_share_comments), SHARE_IMAGE( R.string.action_share_image), GOTO_SUBREDDIT( R.string.action_gotosubreddit), ACTION_MENU( R.string.action_actionmenu), SAVE_IMAGE( R.string.action_save_image), COPY( R.string.action_copy), SELFTEXT_LINKS( R.string.action_selftext_links), BACK( R.string.action_back), BLOCK( R.string.action_block_subreddit), UNBLOCK( R.string.action_unblock_subreddit), PIN( R.string.action_pin_subreddit), UNPIN( R.string.action_unpin_subreddit), SUBSCRIBE( R.string.action_subscribe_subreddit), UNSUBSCRIBE( R.string.action_unsubscribe_subreddit); public final int descriptionResId; Action(final int descriptionResId) { this.descriptionResId = descriptionResId; } } // TODO too many parameters public RedditPreparedPost(final Context context, final CacheManager cm, final int listId, final RedditParsedPost post, final long timestamp, final boolean showSubreddit, final boolean showThumbnails) { this.src = post; this.showSubreddit = showSubreddit; final RedditAccount user = RedditAccountManager.getInstance(context).getDefaultAccount(); mChangeDataManager = RedditChangeDataManager.getInstance(user); isArchived = post.isArchived(); mIsProbablyAnImage = LinkHandler.isProbablyAnImage(post.getUrl()); hasThumbnail = showThumbnails && hasThumbnail(post); // TODO parameterise final int thumbnailWidth = General.dpToPixels(context, 64); if (hasThumbnail && hasThumbnail(post)) { downloadThumbnail(context, thumbnailWidth, cm, listId); } lastChange = timestamp; mChangeDataManager.update(timestamp, post.getSrc()); rebuildSubtitle(context); } public static void showActionMenu(final AppCompatActivity activity, final RedditPreparedPost post) { final EnumSet<Action> itemPref = PrefsUtility.pref_menus_post_context_items(activity, PreferenceManager.getDefaultSharedPreferences(activity)); if (itemPref.isEmpty()) return; final RedditAccount user = RedditAccountManager.getInstance(activity).getDefaultAccount(); final ArrayList<RPVMenuItem> menu = new ArrayList<>(); if (!RedditAccountManager.getInstance(activity).getDefaultAccount().isAnonymous()) { if (itemPref.contains(Action.UPVOTE)) { if (!post.isUpvoted()) { menu.add(new RPVMenuItem(activity, R.string.action_upvote, Action.UPVOTE)); } else { menu.add(new RPVMenuItem(activity, R.string.action_upvote_remove, Action.UNVOTE)); } } if (itemPref.contains(Action.DOWNVOTE)) { if (!post.isDownvoted()) { menu.add(new RPVMenuItem(activity, R.string.action_downvote, Action.DOWNVOTE)); } else { menu.add(new RPVMenuItem(activity, R.string.action_downvote_remove, Action.UNVOTE)); } } if (itemPref.contains(Action.SAVE)) { if (!post.isSaved()) { menu.add(new RPVMenuItem(activity, R.string.action_save, Action.SAVE)); } else { menu.add(new RPVMenuItem(activity, R.string.action_unsave, Action.UNSAVE)); } } if (itemPref.contains(Action.HIDE)) { if (!post.isHidden()) { menu.add(new RPVMenuItem(activity, R.string.action_hide, Action.HIDE)); } else { menu.add(new RPVMenuItem(activity, R.string.action_unhide, Action.UNHIDE)); } } if (itemPref.contains(Action.EDIT) && post.isSelf() && user.username.equalsIgnoreCase(post.src.getAuthor())) { menu.add(new RPVMenuItem(activity, R.string.action_edit, Action.EDIT)); } if (itemPref.contains(Action.DELETE) && user.username.equalsIgnoreCase(post.src.getAuthor())) { menu.add(new RPVMenuItem(activity, R.string.action_delete, Action.DELETE)); } if (itemPref.contains(Action.REPORT)) menu.add(new RPVMenuItem(activity, R.string.action_report, Action.REPORT)); } if (itemPref.contains(Action.EXTERNAL)) menu.add(new RPVMenuItem(activity, R.string.action_external, Action.EXTERNAL)); if (itemPref.contains(Action.SELFTEXT_LINKS) && post.src.getRawSelfText() != null && post.src.getRawSelfText().length() > 1) menu.add(new RPVMenuItem(activity, R.string.action_selftext_links, Action.SELFTEXT_LINKS)); if (itemPref.contains(Action.SAVE_IMAGE) && post.mIsProbablyAnImage) menu.add(new RPVMenuItem(activity, R.string.action_save_image, Action.SAVE_IMAGE)); if (itemPref.contains(Action.GOTO_SUBREDDIT)) menu.add(new RPVMenuItem(activity, R.string.action_gotosubreddit, Action.GOTO_SUBREDDIT)); if (post.showSubreddit) { try { String subredditCanonicalName = RedditSubreddit.getCanonicalName(post.src.getSubreddit()); if (itemPref.contains(Action.BLOCK) && post.showSubreddit) { final List<String> blockedSubreddits = PrefsUtility.pref_blocked_subreddits(activity, PreferenceManager.getDefaultSharedPreferences(activity)); if (blockedSubreddits.contains(subredditCanonicalName)) { menu.add(new RPVMenuItem(activity, R.string.action_unblock_subreddit, Action.UNBLOCK)); } else { menu.add(new RPVMenuItem(activity, R.string.action_block_subreddit, Action.BLOCK)); } } if (itemPref.contains(Action.PIN) && post.showSubreddit) { List<String> pinnedSubreddits = PrefsUtility.pref_pinned_subreddits(activity, PreferenceManager.getDefaultSharedPreferences(activity)); if (pinnedSubreddits.contains(subredditCanonicalName)) { menu.add(new RPVMenuItem(activity, R.string.action_unpin_subreddit, Action.UNPIN)); } else { menu.add(new RPVMenuItem(activity, R.string.action_pin_subreddit, Action.PIN)); } } if (!RedditAccountManager.getInstance(activity).getDefaultAccount().isAnonymous()) { if (itemPref.contains(Action.SUBSCRIBE)) { if (RedditSubredditSubscriptionManager .getSingleton(activity, RedditAccountManager.getInstance(activity).getDefaultAccount()) .getSubscriptionState( subredditCanonicalName) == RedditSubredditSubscriptionManager.SubredditSubscriptionState.SUBSCRIBED) { menu.add(new RPVMenuItem(activity, R.string.action_unsubscribe_subreddit, Action.UNSUBSCRIBE)); } else { menu.add(new RPVMenuItem(activity, R.string.action_subscribe_subreddit, Action.SUBSCRIBE)); } } } } catch (RedditSubreddit.InvalidSubredditNameException ex) { throw new RuntimeException(ex); } } if (itemPref.contains(Action.SHARE)) menu.add(new RPVMenuItem(activity, R.string.action_share, Action.SHARE)); if (itemPref.contains(Action.SHARE_COMMENTS)) menu.add(new RPVMenuItem(activity, R.string.action_share_comments, Action.SHARE_COMMENTS)); if (itemPref.contains(Action.SHARE_IMAGE) && post.mIsProbablyAnImage) menu.add(new RPVMenuItem(activity, R.string.action_share_image, Action.SHARE_IMAGE)); if (itemPref.contains(Action.COPY)) menu.add(new RPVMenuItem(activity, R.string.action_copy, Action.COPY)); if (itemPref.contains(Action.USER_PROFILE)) menu.add(new RPVMenuItem(activity, R.string.action_user_profile, Action.USER_PROFILE)); if (itemPref.contains(Action.PROPERTIES)) menu.add(new RPVMenuItem(activity, R.string.action_properties, Action.PROPERTIES)); final String[] menuText = new String[menu.size()]; for (int i = 0; i < menuText.length; i++) { menuText[i] = menu.get(i).title; } final AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setItems(menuText, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { onActionMenuItemSelected(post, activity, menu.get(which).action); } }); //builder.setNeutralButton(R.string.dialog_cancel, null); final AlertDialog alert = builder.create(); alert.setCanceledOnTouchOutside(true); alert.show(); } public static void onActionMenuItemSelected(final RedditPreparedPost post, final AppCompatActivity activity, final Action action) { switch (action) { case UPVOTE: post.action(activity, RedditAPI.ACTION_UPVOTE); break; case DOWNVOTE: post.action(activity, RedditAPI.ACTION_DOWNVOTE); break; case UNVOTE: post.action(activity, RedditAPI.ACTION_UNVOTE); break; case SAVE: post.action(activity, RedditAPI.ACTION_SAVE); break; case UNSAVE: post.action(activity, RedditAPI.ACTION_UNSAVE); break; case HIDE: post.action(activity, RedditAPI.ACTION_HIDE); break; case UNHIDE: post.action(activity, RedditAPI.ACTION_UNHIDE); break; case EDIT: final Intent editIntent = new Intent(activity, CommentEditActivity.class); editIntent.putExtra("commentIdAndType", post.src.getIdAndType()); editIntent.putExtra("commentText", StringEscapeUtils.unescapeHtml4(post.src.getRawSelfText())); editIntent.putExtra("isSelfPost", true); activity.startActivity(editIntent); break; case DELETE: new AlertDialog.Builder(activity).setTitle(R.string.accounts_delete).setMessage(R.string.delete_confirm) .setPositiveButton(R.string.action_delete, new DialogInterface.OnClickListener() { @Override public void onClick(final DialogInterface dialog, final int which) { post.action(activity, RedditAPI.ACTION_DELETE); } }).setNegativeButton(R.string.dialog_cancel, null).show(); break; case REPORT: new AlertDialog.Builder(activity).setTitle(R.string.action_report) .setMessage(R.string.action_report_sure) .setPositiveButton(R.string.action_report, new DialogInterface.OnClickListener() { @Override public void onClick(final DialogInterface dialog, final int which) { post.action(activity, RedditAPI.ACTION_REPORT); // TODO update the view to show the result // TODO don't forget, this also hides } }).setNegativeButton(R.string.dialog_cancel, null).show(); break; case EXTERNAL: { final Intent intent = new Intent(Intent.ACTION_VIEW); String url = (activity instanceof WebViewActivity) ? ((WebViewActivity) activity).getCurrentUrl() : post.src.getUrl(); intent.setData(Uri.parse(url)); activity.startActivity(intent); break; } case SELFTEXT_LINKS: { final HashSet<String> linksInComment = LinkHandler .computeAllLinks(StringEscapeUtils.unescapeHtml4(post.src.getRawSelfText())); if (linksInComment.isEmpty()) { General.quickToast(activity, R.string.error_toast_no_urls_in_self); } else { final String[] linksArr = linksInComment.toArray(new String[linksInComment.size()]); final AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setItems(linksArr, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { LinkHandler.onLinkClicked(activity, linksArr[which], false, post.src.getSrc()); dialog.dismiss(); } }); final AlertDialog alert = builder.create(); alert.setTitle(R.string.action_selftext_links); alert.setCanceledOnTouchOutside(true); alert.show(); } break; } case SAVE_IMAGE: { ((BaseActivity) activity).requestPermissionWithCallback(Manifest.permission.WRITE_EXTERNAL_STORAGE, new SaveImageCallback(activity, post.src.getUrl())); break; } case SHARE: { final Intent mailer = new Intent(Intent.ACTION_SEND); mailer.setType("text/plain"); mailer.putExtra(Intent.EXTRA_SUBJECT, post.src.getTitle()); mailer.putExtra(Intent.EXTRA_TEXT, post.src.getUrl()); activity.startActivity(Intent.createChooser(mailer, activity.getString(R.string.action_share))); break; } case SHARE_COMMENTS: { final boolean shareAsPermalink = PrefsUtility.pref_behaviour_share_permalink(activity, PreferenceManager.getDefaultSharedPreferences(activity)); final Intent mailer = new Intent(Intent.ACTION_SEND); mailer.setType("text/plain"); mailer.putExtra(Intent.EXTRA_SUBJECT, "Comments for " + post.src.getTitle()); if (shareAsPermalink) { mailer.putExtra(Intent.EXTRA_TEXT, Constants.Reddit.getNonAPIUri(post.src.getPermalink()).toString()); } else { mailer.putExtra(Intent.EXTRA_TEXT, Constants.Reddit .getNonAPIUri(Constants.Reddit.PATH_COMMENTS + post.src.getIdAlone()).toString()); } activity.startActivity( Intent.createChooser(mailer, activity.getString(R.string.action_share_comments))); break; } case SHARE_IMAGE: { ((BaseActivity) activity).requestPermissionWithCallback(Manifest.permission.WRITE_EXTERNAL_STORAGE, new ShareImageCallback(activity, post.src.getUrl())); break; } case COPY: { ClipboardManager manager = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); manager.setText(post.src.getUrl()); break; } case GOTO_SUBREDDIT: { try { final Intent intent = new Intent(activity, PostListingActivity.class); intent.setData(SubredditPostListURL.getSubreddit(post.src.getSubreddit()).generateJsonUri()); activity.startActivityForResult(intent, 1); } catch (RedditSubreddit.InvalidSubredditNameException e) { Toast.makeText(activity, R.string.invalid_subreddit_name, Toast.LENGTH_LONG).show(); } break; } case USER_PROFILE: LinkHandler.onLinkClicked(activity, new UserProfileURL(post.src.getAuthor()).toString()); break; case PROPERTIES: PostPropertiesDialog.newInstance(post.src.getSrc()).show(activity.getSupportFragmentManager(), null); break; case COMMENTS: ((PostSelectionListener) activity).onPostCommentsSelected(post); new Thread() { @Override public void run() { post.markAsRead(activity); } }.start(); break; case LINK: ((PostSelectionListener) activity).onPostSelected(post); break; case COMMENTS_SWITCH: if (!(activity instanceof MainActivity)) activity.finish(); ((PostSelectionListener) activity).onPostCommentsSelected(post); break; case LINK_SWITCH: if (!(activity instanceof MainActivity)) activity.finish(); ((PostSelectionListener) activity).onPostSelected(post); break; case ACTION_MENU: showActionMenu(activity, post); break; case REPLY: final Intent intent = new Intent(activity, CommentReplyActivity.class); intent.putExtra(CommentReplyActivity.PARENT_ID_AND_TYPE_KEY, post.src.getIdAndType()); intent.putExtra(CommentReplyActivity.PARENT_MARKDOWN_KEY, post.src.getUnescapedSelfText()); activity.startActivity(intent); break; case BACK: activity.onBackPressed(); break; case PIN: try { String subredditCanonicalName = RedditSubreddit.getCanonicalName(post.src.getSubreddit()); List<String> pinnedSubreddits = PrefsUtility.pref_pinned_subreddits(activity, PreferenceManager.getDefaultSharedPreferences(activity)); if (!pinnedSubreddits.contains(subredditCanonicalName)) { PrefsUtility.pref_pinned_subreddits_add(activity, PreferenceManager.getDefaultSharedPreferences(activity), subredditCanonicalName); } else { Toast.makeText(activity, R.string.mainmenu_toast_pinned, Toast.LENGTH_SHORT).show(); } } catch (RedditSubreddit.InvalidSubredditNameException e) { throw new RuntimeException(e); } break; case UNPIN: try { String subredditCanonicalName = RedditSubreddit.getCanonicalName(post.src.getSubreddit()); List<String> pinnedSubreddits = PrefsUtility.pref_pinned_subreddits(activity, PreferenceManager.getDefaultSharedPreferences(activity)); if (pinnedSubreddits.contains(subredditCanonicalName)) { PrefsUtility.pref_pinned_subreddits_remove(activity, PreferenceManager.getDefaultSharedPreferences(activity), subredditCanonicalName); } else { Toast.makeText(activity, R.string.mainmenu_toast_not_pinned, Toast.LENGTH_SHORT).show(); } } catch (RedditSubreddit.InvalidSubredditNameException e) { throw new RuntimeException(e); } break; case BLOCK: try { String subredditCanonicalName = RedditSubreddit.getCanonicalName(post.src.getSubreddit()); List<String> blockedSubreddits = PrefsUtility.pref_blocked_subreddits(activity, PreferenceManager.getDefaultSharedPreferences(activity)); if (!blockedSubreddits.contains(subredditCanonicalName)) { PrefsUtility.pref_blocked_subreddits_add(activity, PreferenceManager.getDefaultSharedPreferences(activity), subredditCanonicalName); } else { Toast.makeText(activity, R.string.mainmenu_toast_blocked, Toast.LENGTH_SHORT).show(); } } catch (RedditSubreddit.InvalidSubredditNameException e) { throw new RuntimeException(e); } break; case UNBLOCK: try { String subredditCanonicalName = RedditSubreddit.getCanonicalName(post.src.getSubreddit()); List<String> blockedSubreddits = PrefsUtility.pref_blocked_subreddits(activity, PreferenceManager.getDefaultSharedPreferences(activity)); if (blockedSubreddits.contains(subredditCanonicalName)) { PrefsUtility.pref_blocked_subreddits_remove(activity, PreferenceManager.getDefaultSharedPreferences(activity), subredditCanonicalName); } else { Toast.makeText(activity, R.string.mainmenu_toast_not_blocked, Toast.LENGTH_SHORT).show(); } } catch (RedditSubreddit.InvalidSubredditNameException e) { throw new RuntimeException(e); } break; case SUBSCRIBE: try { String subredditCanonicalName = RedditSubreddit.getCanonicalName(post.src.getSubreddit()); RedditSubredditSubscriptionManager subMan = RedditSubredditSubscriptionManager .getSingleton(activity, RedditAccountManager.getInstance(activity).getDefaultAccount()); if (subMan.getSubscriptionState( subredditCanonicalName) == RedditSubredditSubscriptionManager.SubredditSubscriptionState.NOT_SUBSCRIBED) { subMan.subscribe(subredditCanonicalName, activity); Toast.makeText(activity, R.string.options_subscribing, Toast.LENGTH_SHORT).show(); } else { Toast.makeText(activity, R.string.mainmenu_toast_subscribed, Toast.LENGTH_SHORT).show(); } } catch (RedditSubreddit.InvalidSubredditNameException e) { throw new RuntimeException(e); } break; case UNSUBSCRIBE: try { String subredditCanonicalName = RedditSubreddit.getCanonicalName(post.src.getSubreddit()); RedditSubredditSubscriptionManager subMan = RedditSubredditSubscriptionManager .getSingleton(activity, RedditAccountManager.getInstance(activity).getDefaultAccount()); if (subMan.getSubscriptionState( subredditCanonicalName) == RedditSubredditSubscriptionManager.SubredditSubscriptionState.SUBSCRIBED) { subMan.unsubscribe(subredditCanonicalName, activity); Toast.makeText(activity, R.string.options_unsubscribing, Toast.LENGTH_SHORT).show(); } else { Toast.makeText(activity, R.string.mainmenu_toast_not_subscribed, Toast.LENGTH_SHORT).show(); } } catch (RedditSubreddit.InvalidSubredditNameException e) { throw new RuntimeException(e); } break; } } public int computeScore() { int score = src.getScoreExcludingOwnVote(); if (isUpvoted()) { score++; } else if (isDownvoted()) { score--; } return score; } private void rebuildSubtitle(Context context) { // TODO customise display // TODO preference for the X days, X hours thing final TypedArray appearance = context .obtainStyledAttributes(new int[] { R.attr.srPostSubtitleBoldCol, R.attr.srPostSubtitleUpvoteCol, R.attr.srPostSubtitleDownvoteCol, R.attr.srFlairBackCol, R.attr.srFlairTextCol }); final int boldCol = appearance.getColor(0, 255), rrPostSubtitleUpvoteCol = appearance.getColor(1, 255), rrPostSubtitleDownvoteCol = appearance.getColor(2, 255), rrFlairBackCol = appearance.getColor(3, 255), rrFlairTextCol = appearance.getColor(4, 255); appearance.recycle(); final BetterSSB postListDescSb = new BetterSSB(); // SpannableStringBuilder for the Karma that goes between our up/downvote arrows final BetterSSB karmaSb = new BetterSSB(); final int pointsCol; final int score = computeScore(); if (isUpvoted()) { pointsCol = rrPostSubtitleUpvoteCol; } else if (isDownvoted()) { pointsCol = rrPostSubtitleDownvoteCol; } else { pointsCol = boldCol; } if (src.isSpoiler()) { postListDescSb.append(" SPOILER ", BetterSSB.BOLD | BetterSSB.FOREGROUND_COLOR | BetterSSB.BACKGROUND_COLOR, Color.WHITE, Color.rgb(50, 50, 50), 1f); postListDescSb.append(" ", 0); } if (src.isStickied()) { postListDescSb.append(" STICKY ", BetterSSB.BOLD | BetterSSB.FOREGROUND_COLOR | BetterSSB.BACKGROUND_COLOR, Color.WHITE, Color.rgb(0, 170, 0), 1f); // TODO color? postListDescSb.append(" ", 0); } if (src.isNsfw()) { postListDescSb.append(" NSFW ", BetterSSB.BOLD | BetterSSB.FOREGROUND_COLOR | BetterSSB.BACKGROUND_COLOR, Color.WHITE, Color.RED, 1f); // TODO color? postListDescSb.append(" ", 0); } if (src.getFlairText() != null) { postListDescSb.append(" " + src.getFlairText() + " ", BetterSSB.BOLD | BetterSSB.FOREGROUND_COLOR | BetterSSB.BACKGROUND_COLOR, rrFlairTextCol, rrFlairBackCol, 1f); postListDescSb.append(" ", 0); } postListDescSb.append(String.valueOf(score), BetterSSB.BOLD | BetterSSB.FOREGROUND_COLOR, pointsCol, 0, 1f); postListDescSb.append(" " + context.getString(R.string.subtitle_points) + " ", 0); postListDescSb.append(SRTime.formatDurationFrom(context, src.getCreatedTimeSecsUTC() * 1000), BetterSSB.BOLD | BetterSSB.FOREGROUND_COLOR, boldCol, 0, 1f); postListDescSb.append(" " + context.getString(R.string.subtitle_by) + " ", 0); postListDescSb.append(src.getAuthor(), BetterSSB.BOLD | BetterSSB.FOREGROUND_COLOR, boldCol, 0, 1f); if (showSubreddit) { postListDescSb.append(" " + context.getString(R.string.subtitle_to) + " ", 0); postListDescSb.append(src.getSubreddit(), BetterSSB.BOLD | BetterSSB.FOREGROUND_COLOR, boldCol, 0, 1f); } postListDescSb.append(" (" + src.getDomain() + ")", 0); karmaSb.append(String.valueOf(score), BetterSSB.BOLD | BetterSSB.FOREGROUND_COLOR, pointsCol, 0, 1f); postListDescription = postListDescSb.get(); postKarma = karmaSb.get(); } private void colorKarma(Context context) { final int boldCol; final int rrPostSubtitleUpvoteCol; final int rrPostSubtitleDownvoteCol; final TypedArray appearance = context.obtainStyledAttributes(new int[] { R.attr.srPostSubtitleBoldCol, R.attr.srPostSubtitleUpvoteCol, R.attr.srPostSubtitleDownvoteCol }); boldCol = appearance.getColor(0, 255); rrPostSubtitleUpvoteCol = appearance.getColor(1, 255); rrPostSubtitleDownvoteCol = appearance.getColor(2, 255); appearance.recycle(); final BetterSSB karmaSB = new BetterSSB(); final int pointsCol; final int score = computeScore(); if (isUpvoted()) { pointsCol = rrPostSubtitleUpvoteCol; } else if (isDownvoted()) { pointsCol = rrPostSubtitleDownvoteCol; } else { pointsCol = boldCol; } karmaSB.append(String.valueOf(score), BetterSSB.BOLD | BetterSSB.FOREGROUND_COLOR, pointsCol, 0, 1f); postKarma = karmaSB.get(); } private void colorArrowUpvote() { } private void colorArrowDownvote() { } // lol, reddit api private static boolean hasThumbnail(final RedditParsedPost post) { final String url = post.getThumbnailUrl(); return url != null && url.length() != 0 && !url.equalsIgnoreCase("nsfw") && !url.equalsIgnoreCase("self") && !url.equalsIgnoreCase("default"); } private void downloadThumbnail(final Context context, final int widthPixels, final CacheManager cm, final int listId) { final String uriStr = src.getThumbnailUrl(); final URI uri = General.uriFromString(uriStr); final int priority = Constants.Priority.THUMBNAIL; final int fileType = Constants.FileType.THUMBNAIL; final RedditAccount anon = RedditAccountManager.getAnon(); cm.makeRequest(new CacheRequest(uri, anon, null, priority, listId, DownloadStrategyIfNotCached.INSTANCE, fileType, CacheRequest.DOWNLOAD_QUEUE_IMMEDIATE, false, false, context) { @Override protected void onDownloadNecessary() { } @Override protected void onDownloadStarted() { } @Override protected void onCallbackException(final Throwable t) { // TODO handle -- internal error throw new RuntimeException(t); } @Override protected void onFailure(final @CacheRequest.RequestFailureType int type, final Throwable t, final Integer status, final String readableMessage) { } @Override protected void onProgress(final boolean authorizationInProgress, final long bytesRead, final long totalBytes) { } @Override protected void onSuccess(final CacheManager.ReadableCacheFile cacheFile, final long timestamp, final UUID session, final boolean fromCache, final String mimetype) { try { synchronized (singleImageDecodeLock) { BitmapFactory.Options justDecodeBounds = new BitmapFactory.Options(); justDecodeBounds.inJustDecodeBounds = true; BitmapFactory.decodeStream(cacheFile.getInputStream(), null, justDecodeBounds); final int width = justDecodeBounds.outWidth; final int height = justDecodeBounds.outHeight; int factor = 1; while (width / (factor + 1) > widthPixels && height / (factor + 1) > widthPixels) factor *= 2; BitmapFactory.Options scaledOptions = new BitmapFactory.Options(); scaledOptions.inSampleSize = factor; final Bitmap data = BitmapFactory.decodeStream(cacheFile.getInputStream(), null, scaledOptions); if (data == null) return; thumbnailCache = ThumbnailScaler.scale(data, widthPixels); if (thumbnailCache != data) data.recycle(); } if (thumbnailCallback != null) thumbnailCallback.betterThumbnailAvailable(thumbnailCache, usageId); } catch (OutOfMemoryError e) { // TODO handle this better - disable caching of images Log.e("RedditPreparedPost", "Out of memory trying to download image"); e.printStackTrace(); } catch (Throwable t) { // Just ignore it. } } }); } // These operations are ordered so as to avoid race conditions public Bitmap getThumbnail(final ThumbnailLoadedCallback callback, final int usageId) { this.thumbnailCallback = callback; this.usageId = usageId; return thumbnailCache; } public boolean isSelf() { return src.isSelfPost(); } public boolean isRead() { return mChangeDataManager.isRead(src); } public void bind(RedditPostView boundView) { this.boundView = boundView; } public void unbind(RedditPostView boundView) { if (this.boundView == boundView) this.boundView = null; } // TODO handle download failure - show red "X" or something public interface ThumbnailLoadedCallback { void betterThumbnailAvailable(Bitmap thumbnail, int usageId); } public void markAsRead(final Context context) { final RedditAccount user = RedditAccountManager.getInstance(context).getDefaultAccount(); RedditChangeDataManager.getInstance(user).markRead(SRTime.utcCurrentTimeMillis(), src); refreshView(context); } public void refreshView(final Context context) { General.UI_THREAD_HANDLER.post(new Runnable() { @Override public void run() { rebuildSubtitle(context); if (boundView != null) { boundView.updateAppearance(); } } }); } public void action(final AppCompatActivity activity, final @RedditAPI.RedditAction int action) { final RedditAccount user = RedditAccountManager.getInstance(activity).getDefaultAccount(); if (user.isAnonymous()) { General.UI_THREAD_HANDLER.post(new Runnable() { @Override public void run() { Toast.makeText(activity, activity.getString(R.string.error_toast_notloggedin), Toast.LENGTH_SHORT).show(); } }); return; } final int lastVoteDirection = getVoteDirection(); final boolean archived = src.isArchived(); final long now = SRTime.utcCurrentTimeMillis(); switch (action) { case RedditAPI.ACTION_DOWNVOTE: if (!archived) { mChangeDataManager.markDownvoted(now, src); } break; case RedditAPI.ACTION_UNVOTE: if (!archived) { mChangeDataManager.markUnvoted(now, src); } break; case RedditAPI.ACTION_UPVOTE: if (!archived) { mChangeDataManager.markUpvoted(now, src); } break; case RedditAPI.ACTION_SAVE: mChangeDataManager.markSaved(now, src, true); break; case RedditAPI.ACTION_UNSAVE: mChangeDataManager.markSaved(now, src, false); break; case RedditAPI.ACTION_HIDE: mChangeDataManager.markHidden(now, src, true); break; case RedditAPI.ACTION_UNHIDE: mChangeDataManager.markHidden(now, src, false); break; case RedditAPI.ACTION_REPORT: break; case RedditAPI.ACTION_DELETE: break; default: throw new RuntimeException("Unknown post action"); } refreshView(activity); boolean vote = (action == RedditAPI.ACTION_DOWNVOTE | action == RedditAPI.ACTION_UPVOTE | action == RedditAPI.ACTION_UNVOTE); if (archived && vote) { Toast.makeText(activity, R.string.error_archived_vote, Toast.LENGTH_SHORT).show(); return; } RedditAPI.action(CacheManager.getInstance(activity), new APIResponseHandler.ActionResponseHandler(activity) { @Override protected void onCallbackException(final Throwable t) { BugReportActivity.handleGlobalError(context, t); } @Override protected void onFailure(final @CacheRequest.RequestFailureType int type, final Throwable t, final Integer status, final String readableMessage) { revertOnFailure(); if (t != null) t.printStackTrace(); final SRError error = General.getGeneralErrorForFailure(context, type, t, status, "Reddit API action code: " + action + " " + src.getIdAndType()); General.UI_THREAD_HANDLER.post(new Runnable() { @Override public void run() { General.showResultDialog(activity, error); } }); } @Override protected void onFailure(final APIFailureType type) { revertOnFailure(); final SRError error = General.getGeneralErrorForFailure(context, type); General.UI_THREAD_HANDLER.post(new Runnable() { @Override public void run() { General.showResultDialog(activity, error); } }); } @Override protected void onSuccess() { final long now = SRTime.utcCurrentTimeMillis(); switch (action) { case RedditAPI.ACTION_DOWNVOTE: mChangeDataManager.markDownvoted(now, src); break; case RedditAPI.ACTION_UNVOTE: mChangeDataManager.markUnvoted(now, src); break; case RedditAPI.ACTION_UPVOTE: mChangeDataManager.markUpvoted(now, src); break; case RedditAPI.ACTION_SAVE: mChangeDataManager.markSaved(now, src, true); break; case RedditAPI.ACTION_UNSAVE: mChangeDataManager.markSaved(now, src, false); break; case RedditAPI.ACTION_HIDE: mChangeDataManager.markHidden(now, src, true); break; case RedditAPI.ACTION_UNHIDE: mChangeDataManager.markHidden(now, src, false); break; case RedditAPI.ACTION_REPORT: break; case RedditAPI.ACTION_DELETE: General.quickToast(activity, R.string.delete_success); break; default: throw new RuntimeException("Unknown post action"); } refreshView(context); } private void revertOnFailure() { final long now = SRTime.utcCurrentTimeMillis(); switch (action) { case RedditAPI.ACTION_DOWNVOTE: case RedditAPI.ACTION_UNVOTE: case RedditAPI.ACTION_UPVOTE: switch (lastVoteDirection) { case RedditAPI.ACTION_DOWNVOTE: mChangeDataManager.markDownvoted(now, src); break; case RedditAPI.ACTION_UNVOTE: mChangeDataManager.markUnvoted(now, src); break; case RedditAPI.ACTION_UPVOTE: mChangeDataManager.markUpvoted(now, src); break; } case RedditAPI.ACTION_SAVE: mChangeDataManager.markSaved(now, src, false); break; case RedditAPI.ACTION_UNSAVE: mChangeDataManager.markSaved(now, src, true); break; case RedditAPI.ACTION_HIDE: mChangeDataManager.markHidden(now, src, false); break; case RedditAPI.ACTION_UNHIDE: mChangeDataManager.markHidden(now, src, true); break; case RedditAPI.ACTION_REPORT: break; case RedditAPI.ACTION_DELETE: break; default: throw new RuntimeException("Unknown post action"); } refreshView(context); } }, user, src.getIdAndType(), action, activity); } public boolean isUpvoted() { return mChangeDataManager.isUpvoted(src); } public boolean isDownvoted() { return mChangeDataManager.isDownvoted(src); } public boolean isUnvoted() { return mChangeDataManager.isUnvoted(src); } public int getVoteDirection() { return isUpvoted() ? RedditAPI.ACTION_UPVOTE : (isDownvoted() ? RedditAPI.ACTION_DOWNVOTE : RedditAPI.ACTION_UNVOTE); } public void handleVote(final AppCompatActivity activity, final @RedditAPI.RedditAction int action) { // If our post is upvoted/downvoted and our user presses the same button, change our action to UNVOTE if (getVoteDirection() == action) { action(activity, RedditAPI.ACTION_UNVOTE); } else { action(activity, action); } } public boolean isSaved() { return mChangeDataManager.isSaved(src); } public boolean isHidden() { return Boolean.TRUE.equals(mChangeDataManager.isHidden(src)); } private static class RPVMenuItem { public final String title; public final Action action; private RPVMenuItem(Context context, int titleRes, Action action) { this.title = context.getString(titleRes); this.action = action; } } public interface PostSelectionListener { void onPostSelected(RedditPreparedPost post); void onPostCommentsSelected(RedditPreparedPost post); } }