diff --git a/Clover/app/src/main/java/org/floens/chan/core/loader/ChanParser.java b/Clover/app/src/main/java/org/floens/chan/core/loader/ChanParser.java index 770807cc..7d6d34bf 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/loader/ChanParser.java +++ b/Clover/app/src/main/java/org/floens/chan/core/loader/ChanParser.java @@ -18,6 +18,7 @@ package org.floens.chan.core.loader; +import android.content.Context; import android.content.res.TypedArray; import android.graphics.Typeface; import android.text.SpannableString; @@ -34,6 +35,7 @@ import org.floens.chan.R; import org.floens.chan.core.settings.ChanSettings; import org.floens.chan.core.model.Post; import org.floens.chan.core.model.PostLinkable; +import org.floens.chan.utils.AndroidUtils; import org.floens.chan.utils.ThemeHelper; import org.jsoup.Jsoup; import org.jsoup.helper.StringUtil; @@ -50,6 +52,8 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import static org.floens.chan.utils.AndroidUtils.sp; + public class ChanParser { private static final Pattern colorPattern = Pattern.compile("color:#([0-9a-fA-F]*)"); @@ -76,10 +80,8 @@ public class ChanParser { } if (!post.parsedSpans) { - TypedArray ta = ThemeHelper.getInstance().getThemedContext().obtainStyledAttributes(null, R.styleable.PostView, R.attr.post_style, 0); post.parsedSpans = true; - parseSpans(post, ta); - ta.recycle(); + parseSpans(post); } if (post.rawComment != null) { @@ -87,7 +89,7 @@ public class ChanParser { } } - private void parseSpans(Post post, TypedArray ta) { + private void parseSpans(Post post) { boolean anonymize = ChanSettings.getAnonymize(); boolean anonymizeIds = ChanSettings.getAnonymizeIds(); @@ -100,22 +102,42 @@ public class ChanParser { post.id = ""; } - int detailSize = ta.getDimensionPixelSize(R.styleable.PostView_detail_size, 0); + int detailsSizePx = sp(Integer.parseInt(ChanSettings.fontSize.get()) - 4); + Context context = ThemeHelper.getInstance().getThemedContext(); + if (context == null) { + context = AndroidUtils.getAppRes(); + } + + TypedArray ta = context.obtainStyledAttributes(new int[]{ + R.attr.post_subject_color, + R.attr.post_name_color, + R.attr.post_id_background_light, + R.attr.post_id_background_dark, + R.attr.post_capcode_color + }); + + int subjectColor = ta.getColor(0, 0); + int nameColor = ta.getColor(1, 0); + int idBackgroundLight = ta.getColor(2, 0); + int idBackgroundDark = ta.getColor(3, 0); + int capcodeColor = ta.getColor(4, 0); + + ta.recycle(); if (!TextUtils.isEmpty(post.subject)) { post.subjectSpan = new SpannableString(post.subject); - post.subjectSpan.setSpan(new ForegroundColorSpan(ta.getColor(R.styleable.PostView_subject_color, 0)), 0, post.subjectSpan.length(), 0); + post.subjectSpan.setSpan(new ForegroundColorSpan(subjectColor), 0, post.subjectSpan.length(), 0); } if (!TextUtils.isEmpty(post.name)) { post.nameSpan = new SpannableString(post.name); - post.nameSpan.setSpan(new ForegroundColorSpan(ta.getColor(R.styleable.PostView_name_color, 0)), 0, post.nameSpan.length(), 0); + post.nameSpan.setSpan(new ForegroundColorSpan(nameColor), 0, post.nameSpan.length(), 0); } if (!TextUtils.isEmpty(post.tripcode)) { post.tripcodeSpan = new SpannableString(post.tripcode); - post.tripcodeSpan.setSpan(new ForegroundColorSpan(ta.getColor(R.styleable.PostView_name_color, 0)), 0, post.tripcodeSpan.length(), 0); - post.tripcodeSpan.setSpan(new AbsoluteSizeSpan(detailSize), 0, post.tripcodeSpan.length(), 0); + post.tripcodeSpan.setSpan(new ForegroundColorSpan(nameColor), 0, post.tripcodeSpan.length(), 0); + post.tripcodeSpan.setSpan(new AbsoluteSizeSpan(detailsSizePx), 0, post.tripcodeSpan.length(), 0); } if (!TextUtils.isEmpty(post.id)) { @@ -130,17 +152,17 @@ public class ChanParser { int idColor = (0xff << 24) + (r << 16) + (g << 8) + b; boolean lightColor = (r * 0.299f) + (g * 0.587f) + (b * 0.114f) > 125f; - int idBgColor = lightColor ? ta.getColor(R.styleable.PostView_id_background_light, 0) : ta.getColor(R.styleable.PostView_id_background_dark, 0); + int idBgColor = lightColor ? idBackgroundLight : idBackgroundDark; post.idSpan.setSpan(new ForegroundColorSpan(idColor), 0, post.idSpan.length(), 0); post.idSpan.setSpan(new BackgroundColorSpan(idBgColor), 0, post.idSpan.length(), 0); - post.idSpan.setSpan(new AbsoluteSizeSpan(detailSize), 0, post.idSpan.length(), 0); + post.idSpan.setSpan(new AbsoluteSizeSpan(detailsSizePx), 0, post.idSpan.length(), 0); } if (!TextUtils.isEmpty(post.capcode)) { post.capcodeSpan = new SpannableString("Capcode: " + post.capcode); - post.capcodeSpan.setSpan(new ForegroundColorSpan(ta.getColor(R.styleable.PostView_capcode_color, 0)), 0, post.capcodeSpan.length(), 0); - post.capcodeSpan.setSpan(new AbsoluteSizeSpan(detailSize), 0, post.capcodeSpan.length(), 0); + post.capcodeSpan.setSpan(new ForegroundColorSpan(capcodeColor), 0, post.capcodeSpan.length(), 0); + post.capcodeSpan.setSpan(new AbsoluteSizeSpan(detailsSizePx), 0, post.capcodeSpan.length(), 0); } post.nameTripcodeIdCapcodeSpan = new SpannableString(""); @@ -279,7 +301,7 @@ public class ChanParser { String text = getNodeText(pre); SpannableString monospace = new SpannableString(text); monospace.setSpan(new TypefaceSpan("monospace"), 0, monospace.length(), 0); - monospace.setSpan(new AbsoluteSizeSpan(ThemeHelper.getInstance().getCodeTagSize()), 0, monospace.length(), 0); + monospace.setSpan(new AbsoluteSizeSpan(sp(12f)), 0, monospace.length(), 0); return monospace; } else { return pre.text(); diff --git a/Clover/app/src/main/java/org/floens/chan/core/model/PostLinkable.java b/Clover/app/src/main/java/org/floens/chan/core/model/PostLinkable.java index 70b54366..e13d2028 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/model/PostLinkable.java +++ b/Clover/app/src/main/java/org/floens/chan/core/model/PostLinkable.java @@ -76,7 +76,7 @@ public class PostLinkable extends ClickableSpan { if (type == Type.QUOTE || type == Type.LINK || type == Type.THREAD) { if (type == Type.QUOTE) { Callback top = topCallback(); - if (value instanceof Integer && top != null && (Integer) value == top.getHighlightQuotesWithNo(this)) { + if (value instanceof Integer && top != null && (Integer) value == top.getMarkedNo(this)) { ds.setColor(ThemeHelper.getInstance().getHighlightQuoteColor()); } else { ds.setColor(ThemeHelper.getInstance().getQuoteColor()); @@ -116,6 +116,6 @@ public class PostLinkable extends ClickableSpan { public interface Callback { void onLinkableClick(PostLinkable postLinkable); - int getHighlightQuotesWithNo(PostLinkable postLinkable); + int getMarkedNo(PostLinkable postLinkable); } } diff --git a/Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java b/Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java index a5f4afd8..bf4556b1 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java +++ b/Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java @@ -37,7 +37,9 @@ import org.floens.chan.core.model.PostLinkable; import org.floens.chan.core.model.SavedReply; import org.floens.chan.core.settings.ChanSettings; import org.floens.chan.ui.adapter.PostAdapter; +import org.floens.chan.ui.cell.PostCell; import org.floens.chan.ui.cell.ThreadStatusCell; +import org.floens.chan.ui.view.FloatingMenuItem; import org.floens.chan.ui.view.PostView; import org.floens.chan.ui.view.ThumbnailView; import org.floens.chan.utils.AndroidUtils; @@ -46,7 +48,21 @@ import java.util.ArrayList; import java.util.List; import java.util.Locale; -public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapter.PostAdapterCallback, PostView.PostViewCallback, ThreadStatusCell.Callback { +import static org.floens.chan.utils.AndroidUtils.getString; + +public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapter.PostAdapterCallback, PostCell.PostCellCallback, ThreadStatusCell.Callback { + private static final int POST_OPTION_QUOTE = 0; + private static final int POST_OPTION_QUOTE_TEXT = 1; + private static final int POST_OPTION_INFO = 2; + private static final int POST_OPTION_LINKS = 3; + private static final int POST_OPTION_COPY_TEXT = 4; + private static final int POST_OPTION_REPORT = 5; + private static final int POST_OPTION_HIGHLIGHT_ID = 6; + private static final int POST_OPTION_DELETE = 7; + private static final int POST_OPTION_SAVE = 8; + private static final int POST_OPTION_PIN = 9; + private static final int POST_OPTION_QUICK_REPLY = 10; + private ThreadPresenterCallback threadPresenterCallback; private Loadable loadable; @@ -220,6 +236,8 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt threadPresenterCallback.filterList(null, null, true, false, true); highlightPost(post); scrollToPost(post, false); + } else { + threadPresenterCallback.postClicked(post); } } } @@ -242,69 +260,71 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt } @Override - public void onPopulatePostOptions(Post post, Menu menu) { - if (chanLoader.getLoadable().isBoardMode() || chanLoader.getLoadable().isCatalogMode()) { - menu.add(Menu.NONE, 9, Menu.NONE, AndroidUtils.getRes().getString(R.string.action_pin)); + public void onPopulatePostOptions(Post post, List menu) { + if (loadable.isBoardMode() || loadable.isCatalogMode()) { + menu.add(new FloatingMenuItem(POST_OPTION_PIN, R.string.action_pin)); } - if (chanLoader.getLoadable().isThreadMode()) { - menu.add(Menu.NONE, 10, Menu.NONE, AndroidUtils.getRes().getString(R.string.post_quick_reply)); + if (loadable.isThreadMode()) { + menu.add(new FloatingMenuItem(POST_OPTION_QUICK_REPLY, R.string.post_quick_reply)); } - String[] baseOptions = AndroidUtils.getRes().getStringArray(R.array.post_options); - for (int i = 0; i < baseOptions.length; i++) { - menu.add(Menu.NONE, i, Menu.NONE, baseOptions[i]); - } + menu.add(new FloatingMenuItem(POST_OPTION_QUOTE, R.string.post_quote)); + menu.add(new FloatingMenuItem(POST_OPTION_QUOTE_TEXT, R.string.post_quote_text)); + menu.add(new FloatingMenuItem(POST_OPTION_INFO, R.string.post_info)); + menu.add(new FloatingMenuItem(POST_OPTION_LINKS, R.string.post_show_links)); + menu.add(new FloatingMenuItem(POST_OPTION_COPY_TEXT, R.string.post_copy_text)); + menu.add(new FloatingMenuItem(POST_OPTION_REPORT, R.string.post_report)); if (!TextUtils.isEmpty(post.id)) { - menu.add(Menu.NONE, 6, Menu.NONE, AndroidUtils.getRes().getString(R.string.post_highlight_id)); + menu.add(new FloatingMenuItem(POST_OPTION_HIGHLIGHT_ID, R.string.post_highlight_id)); } // Only add the delete option when the post is a saved reply if (ChanApplication.getDatabaseManager().isSavedReply(post.board, post.no)) { - menu.add(Menu.NONE, 7, Menu.NONE, AndroidUtils.getRes().getString(R.string.delete)); + menu.add(new FloatingMenuItem(POST_OPTION_DELETE, R.string.delete)); } if (ChanSettings.getDeveloper()) { - menu.add(Menu.NONE, 8, Menu.NONE, "Make this a saved reply"); + menu.add(new FloatingMenuItem(POST_OPTION_SAVE, "Save")); } } - public void onPostOptionClicked(Post post, int id) { - switch (id) { - case 10: // Quick reply + public void onPostOptionClicked(Post post, Object id) { + switch ((Integer) id) { + case POST_OPTION_QUICK_REPLY: // openReply(false); TODO // Pass through - case 0: // Quote + case POST_OPTION_QUOTE: ChanApplication.getReplyManager().quote(post.no); break; - case 1: // Quote inline + case POST_OPTION_QUOTE_TEXT: ChanApplication.getReplyManager().quoteInline(post.no, post.comment.toString()); break; - case 2: // Info + case POST_OPTION_INFO: showPostInfo(post); break; - case 3: // Show clickables + case POST_OPTION_LINKS: if (post.linkables.size() > 0) { threadPresenterCallback.showPostLinkables(post.linkables); } break; - case 4: // Copy text + case POST_OPTION_COPY_TEXT: threadPresenterCallback.clipboardPost(post); break; - case 5: // Report + case POST_OPTION_REPORT: threadPresenterCallback.openWebView("Report /" + post.board + "/" + post.no, ChanUrls.getReportUrl(post.board, post.no)); break; - case 6: // Id + case POST_OPTION_HIGHLIGHT_ID: threadPresenterCallback.highlightPostId(post.id); break; - case 7: // Delete + case POST_OPTION_DELETE: // deletePost(post); TODO break; - case 8: // Save reply (debug) + case POST_OPTION_SAVE: ChanApplication.getDatabaseManager().saveReply(new SavedReply(post.board, post.no, "foo")); break; - case 9: // Pin + case POST_OPTION_PIN: ChanApplication.getWatchManager().addPin(post); break; } @@ -343,11 +363,6 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt } } - @Override - public boolean isPostLastSeen(Post post) { - return false; - } - /* * ThreadStatusCell callbacks */ @@ -437,6 +452,8 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt public interface ThreadPresenterCallback { void showPosts(ChanThread thread); + void postClicked(Post post); + void showError(VolleyError error); void showLoading(); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java b/Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java index 9e9b5fc2..46ed91ed 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java @@ -6,6 +6,7 @@ import android.support.v7.app.AppCompatActivity; import android.view.ViewGroup; import org.floens.chan.ChanApplication; +import org.floens.chan.R; import org.floens.chan.controller.Controller; import org.floens.chan.ui.controller.BrowseController; import org.floens.chan.ui.controller.RootNavigationController; @@ -24,6 +25,7 @@ public class StartActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + setTheme(R.style.Chan_Theme); ThemeHelper.getInstance().reloadPostViewColors(this); contentView = (ViewGroup) findViewById(android.R.id.content); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/adapter/PinAdapter.java b/Clover/app/src/main/java/org/floens/chan/ui/adapter/PinAdapter.java index a84f6fd5..ab20dde1 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/adapter/PinAdapter.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/adapter/PinAdapter.java @@ -243,7 +243,7 @@ public class PinAdapter extends RecyclerView.Adapter im if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { watchCountText.setBackground(getAttrDrawable(itemView.getContext(), android.R.attr.selectableItemBackgroundBorderless)); } else { - watchCountText.setBackgroundResource(R.drawable.gray_background_selector); + watchCountText.setBackgroundResource(R.drawable.item_background); } itemView.setOnClickListener(new View.OnClickListener() { @@ -274,7 +274,7 @@ public class PinAdapter extends RecyclerView.Adapter im if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { image.setBackground(getAttrDrawable(itemView.getContext(), android.R.attr.selectableItemBackgroundBorderless)); } else { - image.setBackgroundResource(R.drawable.gray_background_selector); + image.setBackgroundResource(R.drawable.item_background); } image.setOnClickListener(new View.OnClickListener() { @Override diff --git a/Clover/app/src/main/java/org/floens/chan/ui/adapter/PostAdapter.java b/Clover/app/src/main/java/org/floens/chan/ui/adapter/PostAdapter.java index aee26df7..ec9fa782 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/adapter/PostAdapter.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/adapter/PostAdapter.java @@ -25,6 +25,7 @@ import org.floens.chan.R; import org.floens.chan.core.model.ChanThread; import org.floens.chan.core.model.Loadable; import org.floens.chan.core.model.Post; +import org.floens.chan.ui.cell.PostCell; import org.floens.chan.ui.cell.ThreadStatusCell; import org.floens.chan.ui.view.PostView; @@ -36,7 +37,7 @@ public class PostAdapter extends RecyclerView.Adapter { private static final int TYPE_STATUS = 1; private final PostAdapterCallback postAdapterCallback; - private final PostView.PostViewCallback postViewCallback; + private final PostCell.PostCellCallback postCellCallback; private final ThreadStatusCell.Callback statusCellCallback; private RecyclerView recyclerView; @@ -48,10 +49,10 @@ public class PostAdapter extends RecyclerView.Adapter { private String highlightedPostId; private boolean filtering = false; - public PostAdapter(RecyclerView recyclerView, PostAdapterCallback postAdapterCallback, PostView.PostViewCallback postViewCallback, ThreadStatusCell.Callback statusCellCallback) { + public PostAdapter(RecyclerView recyclerView, PostAdapterCallback postAdapterCallback, PostCell.PostCellCallback postCellCallback, ThreadStatusCell.Callback statusCellCallback) { this.recyclerView = recyclerView; this.postAdapterCallback = postAdapterCallback; - this.postViewCallback = postViewCallback; + this.postCellCallback = postCellCallback; this.statusCellCallback = statusCellCallback; setHasStableIds(true); @@ -60,8 +61,8 @@ public class PostAdapter extends RecyclerView.Adapter { @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == TYPE_POST) { - PostView postView = new PostView(parent.getContext()); - return new PostViewHolder(postView); + PostCell postCell = (PostCell) LayoutInflater.from(parent.getContext()).inflate(R.layout.cell_post, parent, false); + return new PostViewHolder(postCell); } else { StatusViewHolder statusViewHolder = new StatusViewHolder((ThreadStatusCell) LayoutInflater.from(parent.getContext()).inflate(R.layout.cell_thread_status, parent, false)); statusViewHolder.threadStatusCell.setCallback(statusCellCallback); @@ -76,7 +77,7 @@ public class PostAdapter extends RecyclerView.Adapter { PostViewHolder postViewHolder = (PostViewHolder) holder; Post post = displayList.get(position); boolean highlight = post == highlightedPost || post.id.equals(highlightedPostId); - postViewHolder.postView.setPost(post, postViewCallback, highlight); + postViewHolder.postView.setPost(post, postCellCallback, highlight, -1); } else if (getItemViewType(position) == TYPE_STATUS) { ((StatusViewHolder) holder).threadStatusCell.update(); onScrolledToBottom(); @@ -200,9 +201,9 @@ public class PostAdapter extends RecyclerView.Adapter { } public static class PostViewHolder extends RecyclerView.ViewHolder { - private PostView postView; + private PostCell postView; - public PostViewHolder(PostView postView) { + public PostViewHolder(PostCell postView) { super(postView); this.postView = postView; } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/cell/PostCell.java b/Clover/app/src/main/java/org/floens/chan/ui/cell/PostCell.java new file mode 100644 index 00000000..6c32f9bc --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/ui/cell/PostCell.java @@ -0,0 +1,473 @@ +package org.floens.chan.ui.cell; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Typeface; +import android.graphics.drawable.BitmapDrawable; +import android.os.Build; +import android.support.annotation.NonNull; +import android.text.Layout; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.SpannedString; +import android.text.TextUtils; +import android.text.format.DateUtils; +import android.text.method.LinkMovementMethod; +import android.text.style.AbsoluteSizeSpan; +import android.text.style.BackgroundColorSpan; +import android.text.style.ClickableSpan; +import android.text.style.ForegroundColorSpan; +import android.text.style.StyleSpan; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.android.volley.VolleyError; +import com.android.volley.toolbox.ImageLoader; + +import org.floens.chan.ChanApplication; +import org.floens.chan.R; +import org.floens.chan.core.model.Loadable; +import org.floens.chan.core.model.Post; +import org.floens.chan.core.model.PostLinkable; +import org.floens.chan.core.settings.ChanSettings; +import org.floens.chan.ui.helper.PostHelper; +import org.floens.chan.ui.view.FloatingMenu; +import org.floens.chan.ui.view.FloatingMenuItem; +import org.floens.chan.ui.view.ThumbnailView; +import org.floens.chan.utils.ThemeHelper; +import org.floens.chan.utils.Time; + +import java.util.ArrayList; +import java.util.List; + +import static org.floens.chan.utils.AndroidUtils.dp; +import static org.floens.chan.utils.AndroidUtils.getAttrDrawable; +import static org.floens.chan.utils.AndroidUtils.getRes; +import static org.floens.chan.utils.AndroidUtils.sp; + +public class PostCell extends RelativeLayout implements PostLinkable.Callback { + private static final int COMMENT_MAX_LENGTH_BOARD = 500; + + private Post post; + private boolean threadMode; + + private FrameLayout thumbnailViewContainer; + private ThumbnailView thumbnailView; + private TextView title; + private TextView icons; + private TextView comment; + private TextView replies; + private ImageView options; + + private boolean commentClickable = false; + private CharSequence iconsSpannable; + private int detailsSizePx; + private int detailsColor; + private int iconsTextSize; + private int countrySizePx; + private boolean ignoreNextOnClick; + private int highlightColor; + private int savedColor; + + private int paddingPx; + private PostCellCallback callback; + private boolean highlighted; + private int markedNo; + + private OnClickListener selfClicked = new OnClickListener() { + @Override + public void onClick(View v) { + if (ignoreNextOnClick) { + ignoreNextOnClick = false; + } else { + callback.onPostClicked(post); + } + } + }; + + public PostCell(Context context) { + super(context); + } + + public PostCell(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public PostCell(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + thumbnailViewContainer = (FrameLayout) findViewById(R.id.thumbnail_container); + thumbnailView = (ThumbnailView) findViewById(R.id.thumbnail_view); + title = (TextView) findViewById(R.id.title); + icons = (TextView) findViewById(R.id.icons); + comment = (TextView) findViewById(R.id.comment); + replies = (TextView) findViewById(R.id.replies); + options = (ImageView) findViewById(R.id.options); + + int textSizeSp = Integer.parseInt(ChanSettings.fontSize.get()); + paddingPx = dp(textSizeSp - 6); + detailsSizePx = sp(textSizeSp - 4); + title.setTextSize(textSizeSp); + title.setPadding(paddingPx, paddingPx, dp(52), 0); + + iconsTextSize = sp(textSizeSp); + countrySizePx = sp(textSizeSp - 3); + icons.setTextSize(textSizeSp); + icons.setPadding(paddingPx, dp(4), paddingPx, 0); + + comment.setTextSize(textSizeSp); + comment.setPadding(paddingPx, paddingPx, paddingPx, 0); + + replies.setTextSize(textSizeSp); + replies.setPadding(paddingPx, 0, paddingPx, paddingPx); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + replies.setBackground(getAttrDrawable(getContext(), android.R.attr.selectableItemBackgroundBorderless)); + } else { + replies.setBackgroundResource(R.drawable.item_background); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + options.setBackground(getAttrDrawable(getContext(), android.R.attr.selectableItemBackgroundBorderless)); + } else { + options.setBackgroundResource(R.drawable.item_background); + } + + TypedArray ta = getContext().obtainStyledAttributes(new int[]{ + R.attr.post_details_color, + R.attr.post_highlighted_color, + R.attr.post_saved_reply_color + }); + + detailsColor = ta.getColor(0, 0); + highlightColor = ta.getColor(1, 0); + savedColor = ta.getColor(2, 0); + + ta.recycle(); + + thumbnailView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + callback.onThumbnailClicked(post, thumbnailView); + } + }); + + replies.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (threadMode) { + if (post.repliesFrom.size() > 0) { + callback.onShowPostReplies(post); + } + } + } + }); + + options.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (ThemeHelper.getInstance().getTheme().isLightTheme) { + options.setImageResource(R.drawable.ic_overflow_black); + } + + List items = new ArrayList<>(); + + callback.onPopulatePostOptions(post, items); + + FloatingMenu menu = new FloatingMenu(getContext(), v, items); + menu.setCallback(new FloatingMenu.FloatingMenuCallback() { + @Override + public void onFloatingMenuItemClicked(FloatingMenu menu, FloatingMenuItem item) { + callback.onPostOptionClicked(post, item.getId()); + } + + @Override + public void onFloatingMenuDismissed(FloatingMenu menu) { + options.setImageResource(R.drawable.ic_overflow); + } + }); + menu.show(); + } + }); + + setOnClickListener(selfClicked); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + if (post != null) { + unbindPost(post); + } + } + + public void setPost(final Post post, PostCellCallback callback, boolean highlighted, int markedNo) { + if (this.post != null) { + unbindPost(this.post); + } + + this.post = post; + this.callback = callback; + this.highlighted = highlighted; + this.markedNo = markedNo; + + bindPost(post); + } + + public Post getPost() { + return post; + } + + public ThumbnailView getThumbnailView() { + return thumbnailView; + } + + private void bindPost(Post post) { + threadMode = callback.getLoadable().isThreadMode(); + + setPostLinkableListener(post, this); + + replies.setClickable(threadMode); + + if (!threadMode) { + replies.setBackgroundResource(0); + } + + if (highlighted) { + setBackgroundColor(highlightColor); + } else if (post.isSavedReply) { + setBackgroundColor(savedColor); + } else if (threadMode) { + setBackgroundResource(0); + } else { + setBackgroundResource(R.drawable.item_background); + } + + if (post.hasImage) { + thumbnailViewContainer.setVisibility(View.VISIBLE); + thumbnailView.setUrl(post.thumbnailUrl, thumbnailView.getLayoutParams().width, thumbnailView.getLayoutParams().height); + } else { + thumbnailViewContainer.setVisibility(View.GONE); + thumbnailView.setUrl(null, 0, 0); + } + + CharSequence[] titleParts = new CharSequence[post.subjectSpan == null ? 2 : 4]; + int titlePartsCount = 0; + + if (post.subjectSpan != null) { + titleParts[titlePartsCount++] = post.subjectSpan; + titleParts[titlePartsCount++] = "\n"; + } + + titleParts[titlePartsCount++] = post.nameTripcodeIdCapcodeSpan; + + CharSequence relativeTime = DateUtils.getRelativeTimeSpanString(post.time * 1000L, Time.get(), DateUtils.SECOND_IN_MILLIS, 0); + SpannableString date = new SpannableString("No." + post.no + " " + relativeTime); + date.setSpan(new ForegroundColorSpan(detailsColor), 0, date.length(), 0); + date.setSpan(new AbsoluteSizeSpan(detailsSizePx), 0, date.length(), 0); + + titleParts[titlePartsCount] = date; + + title.setText(TextUtils.concat(titleParts)); + + iconsSpannable = new SpannableString(""); + + if (post.sticky) { + iconsSpannable = PostHelper.addIcon(iconsSpannable, PostHelper.stickyIcon, iconsTextSize); + } + + if (post.closed) { + iconsSpannable = PostHelper.addIcon(iconsSpannable, PostHelper.closedIcon, iconsTextSize); + } + + if (post.deleted) { + iconsSpannable = PostHelper.addIcon(iconsSpannable, PostHelper.trashIcon, iconsTextSize); + } + + if (post.archived) { + iconsSpannable = PostHelper.addIcon(iconsSpannable, PostHelper.archivedIcon, iconsTextSize); + } + + boolean waitingForCountry = false; + if (!TextUtils.isEmpty(post.country)) { + loadCountryIcon(); + waitingForCountry = true; + } + + if (iconsSpannable.length() > 0 || waitingForCountry) { + icons.setVisibility(VISIBLE); + icons.setText(iconsSpannable); + } else { + icons.setVisibility(GONE); + icons.setText(""); + } + + CharSequence commentText; + if (post.comment.length() > COMMENT_MAX_LENGTH_BOARD && !threadMode) { + commentText = post.comment.subSequence(0, COMMENT_MAX_LENGTH_BOARD); + } else { + commentText = post.comment; + } + + comment.setText(commentText); + + if (commentClickable != threadMode) { + commentClickable = threadMode; + if (commentClickable) { + comment.setMovementMethod(new PostViewMovementMethod()); + comment.setOnClickListener(selfClicked); + } else { + comment.setOnClickListener(null); + comment.setClickable(false); + comment.setMovementMethod(null); + } + } + + if ((!threadMode && post.replies > 0) || (post.repliesFrom.size() > 0)) { + replies.setVisibility(View.VISIBLE); + + int replyCount = threadMode ? post.repliesFrom.size() : post.replies; + String text = getResources().getQuantityString(R.plurals.reply, replyCount, replyCount); + + if (!threadMode && post.images > 0) { + text += ", " + getResources().getQuantityString(R.plurals.image, post.images, post.images); + } + + replies.setText(text); + comment.setPadding(comment.getPaddingLeft(), comment.getPaddingTop(), comment.getPaddingRight(), 0); + replies.setPadding(replies.getPaddingLeft(), paddingPx, replies.getPaddingRight(), replies.getPaddingBottom()); + } else { + replies.setVisibility(View.GONE); + comment.setPadding(comment.getPaddingLeft(), comment.getPaddingTop(), comment.getPaddingRight(), paddingPx); + replies.setPadding(replies.getPaddingLeft(), 0, replies.getPaddingRight(), replies.getPaddingBottom()); + } + } + + private void unbindPost(Post post) { + setPostLinkableListener(post, null); + } + + private void setPostLinkableListener(Post post, PostLinkable.Callback callback) { + if (post.comment instanceof SpannedString) { + SpannedString commentSpannable = (SpannedString) post.comment; + PostLinkable[] linkables = commentSpannable.getSpans(0, commentSpannable.length(), PostLinkable.class); + for (PostLinkable linkable : linkables) { + if (callback == null) { + if (linkable.hasCallback(this)) { + linkable.removeCallback(this); + } + } else { + linkable.addCallback(callback); + } + } + } + } + + private void loadCountryIcon() { + final Post requestedPost = post; + ChanApplication.getVolleyImageLoader().get(post.countryUrl, new ImageLoader.ImageListener() { + @Override + public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) { + if (response.getBitmap() != null && PostCell.this.post == requestedPost) { + CharSequence countryIcon = PostHelper.addIcon(new BitmapDrawable(getRes(), response.getBitmap()), iconsTextSize); + + SpannableString countryText = new SpannableString(post.countryName); + countryText.setSpan(new StyleSpan(Typeface.ITALIC), 0, countryText.length(), 0); + countryText.setSpan(new ForegroundColorSpan(detailsColor), 0, countryText.length(), 0); + countryText.setSpan(new AbsoluteSizeSpan(countrySizePx), 0, countryText.length(), 0); + + iconsSpannable = TextUtils.concat(iconsSpannable, countryIcon, countryText); + + if (!isImmediate) { + icons.setVisibility(VISIBLE); + icons.setText(iconsSpannable); + } + } + } + + @Override + public void onErrorResponse(VolleyError error) { + } + }); + } + + @Override + public void onLinkableClick(PostLinkable postLinkable) { + callback.onPostLinkableClicked(postLinkable); + } + + @Override + public int getMarkedNo(PostLinkable postLinkable) { + return markedNo; + } + + public interface PostCellCallback { + Loadable getLoadable(); + + void onPostClicked(Post post); + + void onThumbnailClicked(Post post, ThumbnailView thumbnail); + + void onShowPostReplies(Post post); + + void onPopulatePostOptions(Post post, List menu); + + void onPostOptionClicked(Post post, Object id); + + void onPostLinkableClicked(PostLinkable linkable); + } + + private static BackgroundColorSpan BACKGROUND_SPAN = new BackgroundColorSpan(0x6633B5E5); + + private class PostViewMovementMethod extends LinkMovementMethod { + @Override + public boolean onTouchEvent(@NonNull TextView widget, @NonNull Spannable buffer, @NonNull MotionEvent event) { + int action = event.getActionMasked(); + + if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_DOWN) { + int x = (int) event.getX(); + int y = (int) event.getY(); + + x -= widget.getTotalPaddingLeft(); + y -= widget.getTotalPaddingTop(); + + x += widget.getScrollX(); + y += widget.getScrollY(); + + Layout layout = widget.getLayout(); + int line = layout.getLineForVertical(y); + int off = layout.getOffsetForHorizontal(line, x); + + ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class); + + if (link.length != 0) { + if (action == MotionEvent.ACTION_UP) { + ignoreNextOnClick = true; + link[0].onClick(widget); + buffer.removeSpan(BACKGROUND_SPAN); + } else if (action == MotionEvent.ACTION_DOWN) { + buffer.setSpan(BACKGROUND_SPAN, buffer.getSpanStart(link[0]), buffer.getSpanEnd(link[0]), 0); + } else if (action == MotionEvent.ACTION_CANCEL) { + buffer.removeSpan(BACKGROUND_SPAN); + } + + return true; + } else { + buffer.removeSpan(BACKGROUND_SPAN); + } + } + + return true; + } + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/ui/cell/ThreadStatusCell.java b/Clover/app/src/main/java/org/floens/chan/ui/cell/ThreadStatusCell.java index fe653a0d..162879df 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/cell/ThreadStatusCell.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/cell/ThreadStatusCell.java @@ -45,11 +45,7 @@ public class ThreadStatusCell extends LinearLayout implements View.OnClickListen public ThreadStatusCell(Context context, AttributeSet attrs) { super(context, attrs); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - setBackground(getAttrDrawable(context, android.R.attr.selectableItemBackground)); - } else { - setBackgroundResource(R.drawable.gray_background_selector); - } + setBackgroundResource(R.drawable.item_background); } @Override diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java index 79295cac..bd962ed8 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java @@ -125,6 +125,10 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte } } + @Override + public void onFloatingMenuDismissed(FloatingMenu menu) { + } + @Override public void showThread(Loadable threadLoadable) { ViewThreadController viewThreadController = new ViewThreadController(context); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerController.java index 23ab2d15..3e504c79 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerController.java @@ -158,6 +158,10 @@ public class ImageViewerController extends Controller implements View.OnClickLis } } } + + @Override + public void onFloatingMenuDismissed(FloatingMenu menu) { + } }); menu.show(); break; diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/PostRepliesController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/PostRepliesController.java index 55f14071..de811be8 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/PostRepliesController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/PostRepliesController.java @@ -5,6 +5,7 @@ import android.app.Activity; import android.content.Context; import android.graphics.Color; import android.os.Build; +import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.Window; @@ -20,6 +21,7 @@ import org.floens.chan.core.model.Post; import org.floens.chan.core.model.PostImage; import org.floens.chan.core.presenter.ThreadPresenter; import org.floens.chan.core.settings.ChanSettings; +import org.floens.chan.ui.cell.PostCell; import org.floens.chan.ui.helper.PostPopupHelper; import org.floens.chan.ui.view.LoadView; import org.floens.chan.ui.view.PostView; @@ -111,6 +113,8 @@ public class PostRepliesController extends Controller { } listView = (ListView) dataView.findViewById(R.id.post_list); + listView.setDivider(null); + listView.setDividerHeight(0); View repliesBack = dataView.findViewById(R.id.replies_back); repliesBack.setOnClickListener(new View.OnClickListener() { @@ -137,25 +141,26 @@ public class PostRepliesController extends Controller { ArrayAdapter adapter = new ArrayAdapter(context, 0) { @Override public View getView(int position, View convertView, ViewGroup parent) { - PostView postView; + PostCell postCell; if (convertView instanceof PostView) { - postView = (PostView) convertView; + postCell = (PostCell) convertView; } else { - postView = new PostView(context); + postCell = (PostCell) LayoutInflater.from(parent.getContext()).inflate(R.layout.cell_post, parent, false); } final Post p = getItem(position); + postCell.setPost(p, presenter, false, data.forPost.no); - postView.setPost(p, presenter, false); - postView.setHighlightQuotesWithNo(data.forPost.no); - postView.setOnClickListeners(new View.OnClickListener() { +// postView.setPost(p, presenter, false); +// postView.setHighlightQuotesWithNo(data.forPost.no); + /*postCell.setOnClickListeners(new View.OnClickListener() { @Override public void onClick(View v) { postPopupHelper.postClicked(p); } - }); + });*/ - return postView; + return postCell; } }; diff --git a/Clover/app/src/main/java/org/floens/chan/ui/fragment/PostRepliesFragment.java b/Clover/app/src/main/java/org/floens/chan/ui/fragment/PostRepliesFragment.java index 22aced75..3ca1aafe 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/fragment/PostRepliesFragment.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/fragment/PostRepliesFragment.java @@ -136,8 +136,8 @@ public class PostRepliesFragment extends DialogFragment { final Post p = getItem(position); - postView.setPost(p, presenter, false); - postView.setHighlightQuotesWithNo(repliesData.forPost.no); +// postView.setPost(p, presenter, false); +// postView.setHighlightQuotesWithNo(repliesData.forPost.no); postView.setOnClickListeners(new View.OnClickListener() { @Override public void onClick(View v) { diff --git a/Clover/app/src/main/java/org/floens/chan/ui/helper/PostHelper.java b/Clover/app/src/main/java/org/floens/chan/ui/helper/PostHelper.java new file mode 100644 index 00000000..26961bcd --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/ui/helper/PostHelper.java @@ -0,0 +1,45 @@ +package org.floens.chan.ui.helper; + +import android.content.res.Resources; +import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; +import android.text.SpannableString; +import android.text.TextUtils; +import android.text.style.ImageSpan; + +import org.floens.chan.R; +import org.floens.chan.utils.AndroidUtils; + +public class PostHelper { + public static BitmapDrawable stickyIcon; + public static BitmapDrawable closedIcon; + public static BitmapDrawable trashIcon; + public static BitmapDrawable archivedIcon; + + static { + Resources res = AndroidUtils.getRes(); + stickyIcon = new BitmapDrawable(res, BitmapFactory.decodeResource(res, R.drawable.sticky_icon)); + closedIcon = new BitmapDrawable(res, BitmapFactory.decodeResource(res, R.drawable.closed_icon)); + trashIcon = new BitmapDrawable(res, BitmapFactory.decodeResource(res, R.drawable.trash_icon)); + archivedIcon = new BitmapDrawable(res, BitmapFactory.decodeResource(res, R.drawable.archived_icon)); + } + + public static CharSequence addIcon(BitmapDrawable bitmapDrawable, int height) { + return addIcon(null, bitmapDrawable, height); + } + + public static CharSequence addIcon(CharSequence total, BitmapDrawable bitmapDrawable, int height) { + SpannableString string = new SpannableString(" "); + ImageSpan imageSpan = new ImageSpan(bitmapDrawable); + + int width = (int) (height / (bitmapDrawable.getIntrinsicHeight() / (float) bitmapDrawable.getIntrinsicWidth())); + + imageSpan.getDrawable().setBounds(0, 0, width, height); + string.setSpan(imageSpan, 0, 1, 0); + if (total == null) { + return string; + } else { + return TextUtils.concat(total, string); + } + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java b/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java index 0d909a77..1531e22e 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java @@ -130,6 +130,13 @@ public class ThreadLayout extends LoadView implements ThreadPresenter.ThreadPres callback.onShowPosts(); } + @Override + public void postClicked(Post post) { + if (postPopupHelper.isOpen()) { + postPopupHelper.postClicked(post); + } + } + @Override public void showError(VolleyError error) { String errorMessage; @@ -158,7 +165,7 @@ public class ThreadLayout extends LoadView implements ThreadPresenter.ThreadPres public void showPostInfo(String info) { new AlertDialog.Builder(getContext()) - .setTitle(R.string.post_info) + .setTitle(R.string.post_info_title) .setMessage(info) .setPositiveButton(R.string.ok, null) .show(); @@ -267,6 +274,8 @@ public class ThreadLayout extends LoadView implements ThreadPresenter.ThreadPres switch (this.visible) { case THREAD: threadListLayout.cleanup(); + postPopupHelper.popAll(); + showSearch(false); break; } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java b/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java index 095d9874..118d7e6f 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java @@ -38,6 +38,7 @@ import org.floens.chan.core.model.Post; import org.floens.chan.core.model.PostImage; import org.floens.chan.core.model.PostLinkable; import org.floens.chan.ui.adapter.PostAdapter; +import org.floens.chan.ui.cell.PostCell; import org.floens.chan.ui.cell.ThreadStatusCell; import org.floens.chan.ui.view.PostView; import org.floens.chan.ui.view.ThumbnailView; @@ -77,10 +78,10 @@ public class ThreadListLayout extends LinearLayout { recyclerView.setLayoutManager(linearLayoutManager); } - public void setCallbacks(PostAdapter.PostAdapterCallback postAdapterCallback, PostView.PostViewCallback postViewCallback, ThreadStatusCell.Callback statusCellCallback) { + public void setCallbacks(PostAdapter.PostAdapterCallback postAdapterCallback, PostCell.PostCellCallback postCellCallback, ThreadStatusCell.Callback statusCellCallback) { this.postAdapterCallback = postAdapterCallback; this.postViewCallback = postViewCallback; - postAdapter = new PostAdapter(recyclerView, postAdapterCallback, postViewCallback, statusCellCallback); + postAdapter = new PostAdapter(recyclerView, postAdapterCallback, postCellCallback, statusCellCallback); recyclerView.setAdapter(postAdapter); recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override @@ -142,7 +143,7 @@ public class ThreadListLayout extends LinearLayout { SpannedString commentSpannable = (SpannedString) post.comment; PostLinkable[] linkables = commentSpannable.getSpans(0, commentSpannable.length(), PostLinkable.class); for (PostLinkable linkable : linkables) { - ChanApplication.getRefWatcher().watch(linkable, linkable.key + " " + linkable.value); +// ChanApplication.getRefWatcher().watch(linkable, linkable.key + " " + linkable.value); } } } @@ -159,11 +160,11 @@ public class ThreadListLayout extends LinearLayout { ThumbnailView thumbnail = null; for (int i = 0; i < layoutManager.getChildCount(); i++) { View view = layoutManager.getChildAt(i); - if (view instanceof PostView) { - PostView postView = (PostView) view; + if (view instanceof PostCell) { + PostCell postView = (PostCell) view; Post post = postView.getPost(); if (post.hasImage && post.imageUrl.equals(postImage.imageUrl)) { - thumbnail = postView.getThumbnail(); + thumbnail = postView.getThumbnailView(); break; } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/settings/ListSettingView.java b/Clover/app/src/main/java/org/floens/chan/ui/settings/ListSettingView.java index 2d0ab391..022b5cac 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/settings/ListSettingView.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/settings/ListSettingView.java @@ -89,6 +89,10 @@ public class ListSettingView extends SettingView implements FloatingMenu.Floatin settingsController.onPreferenceChange(this); } + @Override + public void onFloatingMenuDismissed(FloatingMenu menu) { + } + private void selectItem() { String selectedKey = setting.get(); for (int i = 0; i < items.length; i++) { diff --git a/Clover/app/src/main/java/org/floens/chan/ui/toolbar/Toolbar.java b/Clover/app/src/main/java/org/floens/chan/ui/toolbar/Toolbar.java index e992ddb0..cb656255 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/toolbar/Toolbar.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/toolbar/Toolbar.java @@ -152,7 +152,7 @@ public class Toolbar extends LinearLayout implements View.OnClickListener, LoadV if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { arrowMenuView.setBackground(getAttrDrawable(getContext(), android.R.attr.selectableItemBackgroundBorderless)); } else { - arrowMenuView.setBackgroundResource(R.drawable.gray_background_selector); + arrowMenuView.setBackgroundResource(R.drawable.item_background); } leftButtonContainer.addView(arrowMenuView, new FrameLayout.LayoutParams(dp(56), FrameLayout.LayoutParams.MATCH_PARENT, Gravity.CENTER_VERTICAL)); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarMenuItem.java b/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarMenuItem.java index a2f68748..361f6bd5 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarMenuItem.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarMenuItem.java @@ -66,7 +66,7 @@ public class ToolbarMenuItem implements View.OnClickListener, FloatingMenu.Float if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { imageView.setBackground(getAttrDrawable(context, android.R.attr.selectableItemBackgroundBorderless)); } else { - imageView.setBackgroundResource(R.drawable.gray_background_selector); + imageView.setBackgroundResource(R.drawable.item_background); } } } @@ -109,6 +109,10 @@ public class ToolbarMenuItem implements View.OnClickListener, FloatingMenu.Float callback.onSubMenuItemClicked(this, item); } + @Override + public void onFloatingMenuDismissed(FloatingMenu menu) { + } + public interface ToolbarMenuItemCallback { void onMenuItemClicked(ToolbarMenuItem item); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/view/FloatingMenu.java b/Clover/app/src/main/java/org/floens/chan/ui/view/FloatingMenu.java index 94a20c97..13393927 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/view/FloatingMenu.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/view/FloatingMenu.java @@ -34,6 +34,7 @@ import org.floens.chan.R; import org.floens.chan.utils.AndroidUtils; import org.floens.chan.utils.Logger; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; @@ -168,6 +169,7 @@ public class FloatingMenu { } globalLayoutListener = null; popupWindow = null; + callback.onFloatingMenuDismissed(FloatingMenu.this); } }); @@ -188,6 +190,8 @@ public class FloatingMenu { public interface FloatingMenuCallback { void onFloatingMenuItemClicked(FloatingMenu menu, FloatingMenuItem item); + + void onFloatingMenuDismissed(FloatingMenu menu); } private static class FloatingMenuArrayAdapter extends ArrayAdapter { diff --git a/Clover/app/src/main/java/org/floens/chan/ui/view/FloatingMenuItem.java b/Clover/app/src/main/java/org/floens/chan/ui/view/FloatingMenuItem.java index d50f430a..68a49421 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/view/FloatingMenuItem.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/view/FloatingMenuItem.java @@ -17,6 +17,8 @@ */ package org.floens.chan.ui.view; +import static org.floens.chan.utils.AndroidUtils.getString; + public class FloatingMenuItem { private Object id; private String text; @@ -26,6 +28,11 @@ public class FloatingMenuItem { this.text = text; } + public FloatingMenuItem(Object id, int text) { + this.id = id; + this.text = getString(text); + } + public Object getId() { return id; } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/view/PostView.java b/Clover/app/src/main/java/org/floens/chan/ui/view/PostView.java index 0e7880e0..204bf105 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/view/PostView.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/view/PostView.java @@ -314,13 +314,13 @@ public class PostView extends LinearLayout implements View.OnClickListener, Post imageSize = 0; int repliesCountSize = 0; if (isList()) { - postCommentSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, ThemeHelper.getInstance().getFontSize(), getResources().getDisplayMetrics()); + postCommentSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12, getResources().getDisplayMetrics()); commentPadding = ta.getDimensionPixelSize(R.styleable.PostView_list_comment_padding, 0); postPadding = ta.getDimensionPixelSize(R.styleable.PostView_list_padding, 0); imageSize = ta.getDimensionPixelSize(R.styleable.PostView_list_image_size, 0); repliesCountSize = ta.getDimensionPixelSize(R.styleable.PostView_list_replies_count_size, 0); } else if (isGrid()) { - postCommentSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, ThemeHelper.getInstance().getFontSize() - 1, getResources().getDisplayMetrics()); + postCommentSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12 - 1, getResources().getDisplayMetrics()); commentPadding = ta.getDimensionPixelSize(R.styleable.PostView_grid_comment_padding, 0); postPadding = ta.getDimensionPixelSize(R.styleable.PostView_grid_padding, 0); imageSize = ta.getDimensionPixelSize(R.styleable.PostView_grid_image_size, 0); @@ -503,7 +503,7 @@ public class PostView extends LinearLayout implements View.OnClickListener, Post } @Override - public int getHighlightQuotesWithNo(PostLinkable postLinkable) { + public int getMarkedNo(PostLinkable postLinkable) { return highlightQuotesNo; } diff --git a/Clover/app/src/main/java/org/floens/chan/utils/AndroidUtils.java b/Clover/app/src/main/java/org/floens/chan/utils/AndroidUtils.java index 53e44458..a28dafdc 100644 --- a/Clover/app/src/main/java/org/floens/chan/utils/AndroidUtils.java +++ b/Clover/app/src/main/java/org/floens/chan/utils/AndroidUtils.java @@ -73,6 +73,10 @@ public class AndroidUtils { return ChanApplication.con; } + public static String getString(int res) { + return getRes().getString(res); + } + public static SharedPreferences getPreferences() { return PreferenceManager.getDefaultSharedPreferences(ChanApplication.con); } @@ -140,6 +144,10 @@ public class AndroidUtils { return (int) (dp * getRes().getDisplayMetrics().density); } + public static int sp(float sp) { + return (int) (sp * getRes().getDisplayMetrics().scaledDensity); + } + public static Typeface getTypeface(String name) { if (!typefaceCache.containsKey(name)) { Typeface typeface = Typeface.createFromAsset(getRes().getAssets(), "font/" + name); diff --git a/Clover/app/src/main/java/org/floens/chan/utils/ThemeHelper.java b/Clover/app/src/main/java/org/floens/chan/utils/ThemeHelper.java index d91a7f99..99ccbb15 100644 --- a/Clover/app/src/main/java/org/floens/chan/utils/ThemeHelper.java +++ b/Clover/app/src/main/java/org/floens/chan/utils/ThemeHelper.java @@ -48,8 +48,6 @@ public class ThemeHelper { private int linkColor; private int spoilerColor; private int inlineQuoteColor; - private int codeTagSize; - private int fontSize; public static ThemeHelper getInstance() { if (instance == null) { @@ -91,14 +89,21 @@ public class ThemeHelper { public void reloadPostViewColors(Context context) { this.context = context; - TypedArray ta = context.obtainStyledAttributes(null, R.styleable.PostView, R.attr.post_style, 0); - quoteColor = ta.getColor(R.styleable.PostView_quote_color, 0); - highlightQuoteColor = ta.getColor(R.styleable.PostView_highlight_quote_color, 0); - linkColor = ta.getColor(R.styleable.PostView_link_color, 0); - spoilerColor = ta.getColor(R.styleable.PostView_spoiler_color, 0); - inlineQuoteColor = ta.getColor(R.styleable.PostView_inline_quote_color, 0); - codeTagSize = ta.getDimensionPixelSize(R.styleable.PostView_code_tag_size, 0); - fontSize = ChanSettings.getFontSize(); + + TypedArray ta = context.obtainStyledAttributes(new int[]{ + R.attr.post_quote_color, + R.attr.post_highlight_quote_color, + R.attr.post_link_color, + R.attr.post_spoiler_color, + R.attr.post_inline_quote_color + }); + + quoteColor = ta.getColor(0, 0); + highlightQuoteColor = ta.getColor(1, 0); + linkColor = ta.getColor(2, 0); + spoilerColor = ta.getColor(3, 0); + inlineQuoteColor = ta.getColor(4, 0); + ta.recycle(); } @@ -121,12 +126,4 @@ public class ThemeHelper { public int getInlineQuoteColor() { return inlineQuoteColor; } - - public int getCodeTagSize() { - return codeTagSize; - } - - public int getFontSize() { - return fontSize; - } } diff --git a/Clover/app/src/main/res/drawable-v21/item_background.xml b/Clover/app/src/main/res/drawable-v21/item_background.xml new file mode 100644 index 00000000..cb4fda20 --- /dev/null +++ b/Clover/app/src/main/res/drawable-v21/item_background.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/Clover/app/src/main/res/drawable/gray_background_selector.xml b/Clover/app/src/main/res/drawable/item_background.xml similarity index 100% rename from Clover/app/src/main/res/drawable/gray_background_selector.xml rename to Clover/app/src/main/res/drawable/item_background.xml diff --git a/Clover/app/src/main/res/layout/cell_post.xml b/Clover/app/src/main/res/layout/cell_post.xml new file mode 100644 index 00000000..6f5e5dad --- /dev/null +++ b/Clover/app/src/main/res/layout/cell_post.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/Clover/app/src/main/res/values/attrs.xml b/Clover/app/src/main/res/values/attrs.xml index 0af82eaf..3d52f03b 100644 --- a/Clover/app/src/main/res/values/attrs.xml +++ b/Clover/app/src/main/res/values/attrs.xml @@ -63,7 +63,7 @@ along with this program. If not, see . - + @@ -75,4 +75,23 @@ along with this program. If not, see . + + + + + + + + + + + + + + + + + + + diff --git a/Clover/app/src/main/res/values/strings.xml b/Clover/app/src/main/res/values/strings.xml index ada811d7..2fdbaeb2 100644 --- a/Clover/app/src/main/res/values/strings.xml +++ b/Clover/app/src/main/res/values/strings.xml @@ -37,6 +37,16 @@ along with this program. If not, see . %d posts + + %d reply + %d replies + + + + %d image + %d images + + Settings Reload Reload board @@ -112,13 +122,24 @@ along with this program. If not, see . Open drawer Close drawer + posts + Quick reply + Highlight ID + Text copied to clipboard + Quote + Quote text + Info + Post info + Show links + Copy text + Report + Reply + reply replies image images post - posts - Post info Quote Quote text @@ -127,9 +148,6 @@ along with this program. If not, see . Copy text Report - Quick reply - Highlight ID - Text copied to clipboard Reply to Make thread in diff --git a/Clover/app/src/main/res/values/styles.xml b/Clover/app/src/main/res/values/styles.xml index 531aaf6d..d519ab79 100644 --- a/Clover/app/src/main/res/values/styles.xml +++ b/Clover/app/src/main/res/values/styles.xml @@ -16,16 +16,35 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . --> - +