Redo the post view

Uses a layout file now
Done with RelativeLayout to reduce nested views
setPost is a bit faster
And general better code quality
filtering
Floens 10 years ago
parent e4f9d31247
commit e14a2253dc
  1. 50
      Clover/app/src/main/java/org/floens/chan/core/loader/ChanParser.java
  2. 4
      Clover/app/src/main/java/org/floens/chan/core/model/PostLinkable.java
  3. 79
      Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java
  4. 2
      Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java
  5. 4
      Clover/app/src/main/java/org/floens/chan/ui/adapter/PinAdapter.java
  6. 17
      Clover/app/src/main/java/org/floens/chan/ui/adapter/PostAdapter.java
  7. 473
      Clover/app/src/main/java/org/floens/chan/ui/cell/PostCell.java
  8. 6
      Clover/app/src/main/java/org/floens/chan/ui/cell/ThreadStatusCell.java
  9. 4
      Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java
  10. 4
      Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerController.java
  11. 21
      Clover/app/src/main/java/org/floens/chan/ui/controller/PostRepliesController.java
  12. 4
      Clover/app/src/main/java/org/floens/chan/ui/fragment/PostRepliesFragment.java
  13. 45
      Clover/app/src/main/java/org/floens/chan/ui/helper/PostHelper.java
  14. 11
      Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java
  15. 13
      Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java
  16. 4
      Clover/app/src/main/java/org/floens/chan/ui/settings/ListSettingView.java
  17. 2
      Clover/app/src/main/java/org/floens/chan/ui/toolbar/Toolbar.java
  18. 6
      Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarMenuItem.java
  19. 4
      Clover/app/src/main/java/org/floens/chan/ui/view/FloatingMenu.java
  20. 7
      Clover/app/src/main/java/org/floens/chan/ui/view/FloatingMenuItem.java
  21. 6
      Clover/app/src/main/java/org/floens/chan/ui/view/PostView.java
  22. 8
      Clover/app/src/main/java/org/floens/chan/utils/AndroidUtils.java
  23. 33
      Clover/app/src/main/java/org/floens/chan/utils/ThemeHelper.java
  24. 23
      Clover/app/src/main/res/drawable-v21/item_background.xml
  25. 0
      Clover/app/src/main/res/drawable/item_background.xml
  26. 82
      Clover/app/src/main/res/layout/cell_post.xml
  27. 21
      Clover/app/src/main/res/values/attrs.xml
  28. 28
      Clover/app/src/main/res/values/strings.xml
  29. 23
      Clover/app/src/main/res/values/styles.xml

@ -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();

@ -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);
}
}

@ -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<FloatingMenuItem> 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();

@ -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);

@ -243,7 +243,7 @@ public class PinAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> 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<RecyclerView.ViewHolder> 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

@ -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<RecyclerView.ViewHolder> {
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<RecyclerView.ViewHolder> {
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<RecyclerView.ViewHolder> {
@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<RecyclerView.ViewHolder> {
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<RecyclerView.ViewHolder> {
}
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;
}

@ -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<FloatingMenuItem> 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<FloatingMenuItem> 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;
}
}
}

@ -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

@ -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);

@ -158,6 +158,10 @@ public class ImageViewerController extends Controller implements View.OnClickLis
}
}
}
@Override
public void onFloatingMenuDismissed(FloatingMenu menu) {
}
});
menu.show();
break;

@ -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<Post> adapter = new ArrayAdapter<Post>(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;
}
};

@ -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) {

@ -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);
}
}
}

@ -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;
}
}

@ -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;
}
}

@ -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++) {

@ -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));

@ -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);

@ -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<String> {

@ -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;
}

@ -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;
}

@ -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);

@ -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;
}
}

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?><!--
Clover - 4chan browser https://github.com/Floens/Clover/
Copyright (C) 2014 Floens
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?attr/colorControlHighlight">
<item android:id="@android:id/mask">
<color android:color="@android:color/white" />
</item>
</ripple>

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<org.floens.chan.ui.cell.PostCell xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="73dp">
<ImageView
android:id="@+id/options"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:paddingBottom="15dp"
android:paddingLeft="15dp"
android:paddingRight="5dp"
android:paddingTop="5dp"
android:src="?post_options_drawable" />
<FrameLayout
android:id="@+id/thumbnail_container"
android:layout_width="72dp"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:background="?post_thumbnail_background"
android:minHeight="72dp">
<org.floens.chan.ui.view.ThumbnailView
android:id="@+id/thumbnail_view"
android:layout_width="72dp"
android:layout_height="72dp"
android:gravity="top" />
</FrameLayout>
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_alignWithParentIfMissing="true"
android:layout_toRightOf="@id/thumbnail_container"
android:paddingRight="25dp" />
<TextView
android:id="@+id/icons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignWithParentIfMissing="true"
android:layout_below="@id/title"
android:layout_toRightOf="@id/thumbnail_container" />
<TextView
android:id="@+id/comment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignWithParentIfMissing="true"
android:layout_below="@id/icons"
android:layout_toRightOf="@id/thumbnail_container" />
<TextView
android:id="@+id/replies"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignWithParentIfMissing="true"
android:layout_below="@id/comment"
android:layout_toRightOf="@id/thumbnail_container"
android:singleLine="true"
android:textColor="?attr/post_reply_count_color" />
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:background="?attr/post_divider_color" />
</org.floens.chan.ui.cell.PostCell>

@ -63,7 +63,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<attr name="grid_replies_count_size" format="dimension" />
<attr name="list_comment_max_height" format="dimension" />
<attr name="list_padding" format="dimension" />
<attr name="list_image_size" format="dimension" />
<attr name="list_image_size" format="reference|dimension" />
<attr name="list_comment_padding" format="dimension" />
<attr name="list_replies_count_size" format="dimension" />
<attr name="grid_width" format="dimension" />
@ -75,4 +75,23 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</declare-styleable>
<attr name="board_edit_item_style" format="reference" />
<attr name="post_thumbnail_background" format="color" />
<attr name="post_saved_reply_color" format="color" />
<attr name="post_highlighted_color" format="color" />
<attr name="post_reply_count_color" format="color" />
<attr name="post_reply_color" format="color" />
<attr name="post_name_color" format="color" />
<attr name="post_subject_color" format="color" />
<attr name="post_details_color" format="color" />
<attr name="post_quote_color" format="color" />
<attr name="post_highlight_quote_color" format="color" />
<attr name="post_link_color" format="color" />
<attr name="post_spoiler_color" format="color" />
<attr name="post_capcode_color" format="color" />
<attr name="post_id_background_light" format="color" />
<attr name="post_id_background_dark" format="color" />
<attr name="post_inline_quote_color" format="color" />
<attr name="post_divider_color" format="color" />
<attr name="post_options_drawable" format="integer" />
</resources>

@ -37,6 +37,16 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<item quantity="other">%d posts</item>
</plurals>
<plurals name="reply">
<item quantity="one">%d reply</item>
<item quantity="other">%d replies</item>
</plurals>
<plurals name="image">
<item quantity="one">%d image</item>
<item quantity="other">%d images</item>
</plurals>
<string name="action_settings">Settings</string>
<string name="action_reload">Reload</string>
<string name="action_reload_board">Reload board</string>
@ -112,13 +122,24 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<string name="drawer_open">Open drawer</string>
<string name="drawer_close">Close drawer</string>
<string name="multiple_posts">posts</string>
<string name="post_quick_reply">Quick reply</string>
<string name="post_highlight_id">Highlight ID</string>
<string name="post_text_copied_to_clipboard">Text copied to clipboard</string>
<string name="post_quote">Quote</string>
<string name="post_quote_text">Quote text</string>
<string name="post_info">Info</string>
<string name="post_info_title">Post info</string>
<string name="post_show_links">Show links</string>
<string name="post_copy_text">Copy text</string>
<string name="post_report">Report</string>
<string name="post_reply">Reply</string>
<string name="one_reply">reply</string>
<string name="multiple_replies">replies</string>
<string name="one_image">image</string>
<string name="multiple_images">images</string>
<string name="one_post">post</string>
<string name="multiple_posts">posts</string>
<string name="post_info">Post info</string>
<string-array name="post_options">
<item>Quote</item>
<item>Quote text</item>
@ -127,9 +148,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<item>Copy text</item>
<item>Report</item>
</string-array>
<string name="post_quick_reply">Quick reply</string>
<string name="post_highlight_id">Highlight ID</string>
<string name="post_text_copied_to_clipboard">Text copied to clipboard</string>
<string name="reply">Reply to</string>
<string name="reply_to_board">Make thread in</string>

@ -16,16 +16,35 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<!-- Main light theme -->
<style name="Chan.Theme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryDark">@color/primary_dark</item>
<item name="colorAccent">@color/accent_material_light</item>
<item name="android:windowBackground">@android:color/white</item>
<item name="windowActionModeOverlay">true</item>
<item name="dropDownListViewStyle">@style/ToolbarDropDownListViewStyle</item>
<item name="windowActionModeOverlay">true</item>
<item name="post_thumbnail_background">#FFDDDDDD</item>
<item name="post_saved_reply_color">#FFBCBCBC</item>
<item name="post_highlighted_color">#FFD6BAD0</item>
<item name="post_reply_count_color">#FF646464</item>
<item name="post_reply_color">#FF646464</item>
<item name="post_name_color">#ff117743</item>
<item name="post_subject_color">#ff0F0C5D</item>
<item name="post_details_color">#ff646464</item>
<item name="post_quote_color">#ffDD0000</item>
<item name="post_highlight_quote_color">#ff950000</item>
<item name="post_link_color">#ff0000B4</item>
<item name="post_spoiler_color">#ff000000</item>
<item name="post_capcode_color">#ffff0000</item>
<item name="post_id_background_light">#ff636363</item>
<item name="post_id_background_dark">#00000000</item>
<item name="post_inline_quote_color">#ff789922</item>
<item name="post_divider_color">#1E000000</item>
<item name="post_options_drawable">@drawable/ic_overflow</item>
</style>
<!-- For the toolbar dropdown list -->

Loading…
Cancel
Save