From abcf5d0cf93cf63e31095cfb08ac157528610e4f Mon Sep 17 00:00:00 2001 From: Floens Date: Sun, 14 Jun 2015 21:14:25 +0200 Subject: [PATCH] Add catalog view with post cards --- Clover/app/build.gradle | 1 + .../floens/chan/core/model/PostLinkable.java | 31 +++ .../chan/core/presenter/ThreadPresenter.java | 8 +- .../chan/core/settings/ChanSettings.java | 2 + .../floens/chan/ui/adapter/PostAdapter.java | 32 ++- .../org/floens/chan/ui/cell/CardPostCell.java | 210 ++++++++++++++++++ .../org/floens/chan/ui/cell/PostCell.java | 75 ++++--- .../chan/ui/cell/PostCellInterface.java | 56 +++++ .../chan/ui/controller/BrowseController.java | 26 +++ .../ui/controller/MainSettingsController.java | 15 +- .../ui/controller/PostRepliesController.java | 18 +- .../chan/ui/controller/ThreadController.java | 5 + .../ui/controller/ViewThreadController.java | 3 + .../floens/chan/ui/layout/ThreadLayout.java | 7 + .../chan/ui/layout/ThreadListLayout.java | 144 ++++++++++-- .../java/org/floens/chan/ui/theme/Theme.java | 11 +- .../org/floens/chan/ui/view/FastTextView.java | 106 +++++++++ .../chan/ui/view/FixedRatioThumbnailView.java | 47 ++++ .../floens/chan/ui/view/FloatingMenuItem.java | 8 + .../floens/chan/ui/view/ThumbnailView.java | 6 + Clover/app/src/main/res/layout/cell_post.xml | 3 +- .../src/main/res/layout/cell_post_card.xml | 90 ++++++++ .../app/src/main/res/layout/layout_thread.xml | 4 +- .../main/res/layout/layout_thread_list.xml | 1 + .../src/main/res/values-sw600dp/dimens.xml | 23 ++ Clover/app/src/main/res/values/dimens.xml | 23 +- Clover/app/src/main/res/values/strings.xml | 4 + 27 files changed, 878 insertions(+), 81 deletions(-) create mode 100644 Clover/app/src/main/java/org/floens/chan/ui/cell/CardPostCell.java create mode 100644 Clover/app/src/main/java/org/floens/chan/ui/cell/PostCellInterface.java create mode 100644 Clover/app/src/main/java/org/floens/chan/ui/view/FastTextView.java create mode 100644 Clover/app/src/main/java/org/floens/chan/ui/view/FixedRatioThumbnailView.java create mode 100644 Clover/app/src/main/res/layout/cell_post_card.xml create mode 100644 Clover/app/src/main/res/values-sw600dp/dimens.xml diff --git a/Clover/app/build.gradle b/Clover/app/build.gradle index 20914c7c..6be3b44a 100644 --- a/Clover/app/build.gradle +++ b/Clover/app/build.gradle @@ -74,6 +74,7 @@ dependencies { compile 'com.android.support:support-v13:22.2.0' compile 'com.android.support:appcompat-v7:22.2.0' compile 'com.android.support:recyclerview-v7:22.2.0' + compile 'com.android.support:cardview-v7:22.2.0' compile 'com.android.support:support-annotations:22.2.0' compile 'com.android.support:design:22.2.0' 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 4e756676..cfffa090 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 @@ -48,20 +48,51 @@ public class PostLinkable extends ClickableSpan { private List callbacks = new ArrayList<>(); private boolean spoilerVisible = false; +// private static boolean testingCallbacks = false; +// private static HashMap> callbacksTest = new HashMap<>(); + public PostLinkable(Theme theme, Post post, String key, Object value, Type type) { this.theme = theme; this.post = post; this.key = key; this.value = value; this.type = type; + + /*if (!testingCallbacks) { + testingCallbacks = true; + + AndroidUtils.runOnUiThread(testCallbacksRunnable, 1000); + }*/ } + /*private static Runnable testCallbacksRunnable = new Runnable() { + @Override + public void run() { + AndroidUtils.runOnUiThread(testCallbacksRunnable, 1000); + + Logger.test("Callbacks:"); + for (Map.Entry> entry : callbacksTest.entrySet()) { + if (entry.getValue().size() > 0) { + Logger.test(entry.getKey().key + " still has " + entry.getValue().size() + " bounded callbacks"); + } + } + } + };*/ + public void addCallback(Callback callback) { callbacks.add(callback); + + /*if (!callbacksTest.containsKey(this)) { + callbacksTest.put(this, new ArrayList()); + } + + callbacksTest.get(this).add(callback);*/ } public void removeCallback(Callback callback) { callbacks.remove(callback); + + /*callbacksTest.get(this).remove(callback);*/ } public boolean hasCallback(Callback callback) { 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 9face6f6..ab14adf2 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 @@ -23,11 +23,10 @@ import com.android.volley.VolleyError; import org.floens.chan.Chan; import org.floens.chan.R; +import org.floens.chan.chan.ChanLoader; import org.floens.chan.chan.ChanUrls; import org.floens.chan.core.http.DeleteHttpCall; import org.floens.chan.core.http.ReplyManager; -import org.floens.chan.chan.ChanLoader; -import org.floens.chan.core.net.LoaderPool; import org.floens.chan.core.manager.WatchManager; import org.floens.chan.core.model.ChanThread; import org.floens.chan.core.model.Loadable; @@ -36,10 +35,11 @@ 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.core.model.SavedReply; +import org.floens.chan.core.net.LoaderPool; import org.floens.chan.core.settings.ChanSettings; import org.floens.chan.database.DatabaseManager; import org.floens.chan.ui.adapter.PostAdapter; -import org.floens.chan.ui.cell.PostCell; +import org.floens.chan.ui.cell.PostCellInterface; import org.floens.chan.ui.cell.ThreadStatusCell; import org.floens.chan.ui.helper.PostHelper; import org.floens.chan.ui.layout.ThreadListLayout; @@ -53,7 +53,7 @@ import java.util.Locale; import static org.floens.chan.utils.AndroidUtils.getString; -public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapter.PostAdapterCallback, PostCell.PostCellCallback, ThreadStatusCell.Callback, ThreadListLayout.ThreadListLayoutCallback { +public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapter.PostAdapterCallback, PostCellInterface.PostCellCallback, ThreadStatusCell.Callback, ThreadListLayout.ThreadListLayoutCallback { 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; diff --git a/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java b/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java index eb793efa..c25dd3ea 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java +++ b/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java @@ -35,6 +35,7 @@ public class ChanSettings { public static final BooleanSetting videoAutoLoad; public static final BooleanSetting videoOpenExternal; public static final BooleanSetting videoErrorIgnore; + public static final StringSetting boardViewMode; public static final StringSetting postDefaultName; public static final BooleanSetting postPinThread; @@ -74,6 +75,7 @@ public class ChanSettings { videoAutoLoad = new BooleanSetting(p, "preference_autoplay", false); videoOpenExternal = new BooleanSetting(p, "preference_video_external", false); videoErrorIgnore = new BooleanSetting(p, "preference_video_error_ignore", false); + boardViewMode = new StringSetting(p, "preference_board_view_mode", "list"); // "list" or "grid" postDefaultName = new StringSetting(p, "preference_default_name", ""); postPinThread = new BooleanSetting(p, "preference_pin_on_post", false); 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 0268c470..7064270b 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 @@ -19,13 +19,14 @@ package org.floens.chan.ui.adapter; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; +import android.view.View; import android.view.ViewGroup; 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.PostCellInterface; import org.floens.chan.ui.cell.ThreadStatusCell; import java.util.ArrayList; @@ -36,7 +37,7 @@ public class PostAdapter extends RecyclerView.Adapter { private static final int TYPE_STATUS = 1; private final PostAdapterCallback postAdapterCallback; - private final PostCell.PostCellCallback postCellCallback; + private final PostCellInterface.PostCellCallback postCellCallback; private final ThreadStatusCell.Callback statusCellCallback; private RecyclerView recyclerView; @@ -48,8 +49,9 @@ public class PostAdapter extends RecyclerView.Adapter { private String highlightedPostId; private int highlightedPostNo = -1; private boolean filtering = false; + private PostCellInterface.PostViewMode postViewMode; - public PostAdapter(RecyclerView recyclerView, PostAdapterCallback postAdapterCallback, PostCell.PostCellCallback postCellCallback, ThreadStatusCell.Callback statusCellCallback) { + public PostAdapter(RecyclerView recyclerView, PostAdapterCallback postAdapterCallback, PostCellInterface.PostCellCallback postCellCallback, ThreadStatusCell.Callback statusCellCallback) { this.recyclerView = recyclerView; this.postAdapterCallback = postAdapterCallback; this.postCellCallback = postCellCallback; @@ -61,7 +63,17 @@ public class PostAdapter extends RecyclerView.Adapter { @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == TYPE_POST) { - PostCell postCell = (PostCell) LayoutInflater.from(parent.getContext()).inflate(R.layout.cell_post, parent, false); + int layout = 0; + switch (postViewMode) { + case LIST: + layout = R.layout.cell_post; + break; + case CARD: + layout = R.layout.cell_post_card; + break; + } + + PostCellInterface postCell = (PostCellInterface) LayoutInflater.from(parent.getContext()).inflate(layout, parent, false); return new PostViewHolder(postCell); } else { StatusViewHolder statusViewHolder = new StatusViewHolder((ThreadStatusCell) LayoutInflater.from(parent.getContext()).inflate(R.layout.cell_thread_status, parent, false)); @@ -77,7 +89,7 @@ public class PostAdapter extends RecyclerView.Adapter { PostViewHolder postViewHolder = (PostViewHolder) holder; Post post = displayList.get(position); boolean highlight = post == highlightedPost || post.id.equals(highlightedPostId) || post.no == highlightedPostNo; - postViewHolder.postView.setPost(post, postCellCallback, highlight, -1); + postViewHolder.postView.setPost(null, post, postCellCallback, highlight, -1); } else if (getItemViewType(position) == TYPE_STATUS) { ((StatusViewHolder) holder).threadStatusCell.update(); onScrolledToBottom(); @@ -209,11 +221,15 @@ public class PostAdapter extends RecyclerView.Adapter { return postAdapterCallback.getLoadable().isThreadMode(); } + public void setPostViewMode(PostCellInterface.PostViewMode postViewMode) { + this.postViewMode = postViewMode; + } + public static class PostViewHolder extends RecyclerView.ViewHolder { - private PostCell postView; + private PostCellInterface postView; - public PostViewHolder(PostCell postView) { - super(postView); + public PostViewHolder(PostCellInterface postView) { + super((View) postView); this.postView = postView; } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/cell/CardPostCell.java b/Clover/app/src/main/java/org/floens/chan/ui/cell/CardPostCell.java new file mode 100644 index 00000000..6b066c1f --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/ui/cell/CardPostCell.java @@ -0,0 +1,210 @@ +/* + * 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 . + */ +package org.floens.chan.ui.cell; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.support.v7.widget.CardView; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import org.floens.chan.R; +import org.floens.chan.core.model.Post; +import org.floens.chan.core.settings.ChanSettings; +import org.floens.chan.ui.theme.Theme; +import org.floens.chan.ui.theme.ThemeHelper; +import org.floens.chan.ui.view.FastTextView; +import org.floens.chan.ui.view.FloatingMenu; +import org.floens.chan.ui.view.FloatingMenuItem; +import org.floens.chan.ui.view.ThumbnailView; + +import java.util.ArrayList; +import java.util.List; + +import static org.floens.chan.utils.AndroidUtils.setRoundItemBackground; + +public class CardPostCell extends CardView implements PostCellInterface, View.OnClickListener { + private static final int COMMENT_MAX_LENGTH = 200; + + private boolean bound; + private Theme theme; + private Post post; + private PostCellInterface.PostCellCallback callback; + + private ThumbnailView thumbnailView; + private TextView title; + private FastTextView comment; + private TextView replies; + private ImageView options; + + public CardPostCell(Context context) { + super(context); + } + + public CardPostCell(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public CardPostCell(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + thumbnailView = (ThumbnailView) findViewById(R.id.thumbnail); + thumbnailView.setOnClickListener(this); + title = (TextView) findViewById(R.id.title); + comment = (FastTextView) findViewById(R.id.comment); + replies = (TextView) findViewById(R.id.replies); + options = (ImageView) findViewById(R.id.options); + setRoundItemBackground(options); + + int textSizeSp = Integer.parseInt(ChanSettings.fontSize.get()); + title.setTextSize(textSizeSp); + comment.setTextSize(textSizeSp); + replies.setTextSize(textSizeSp); + + setOnClickListener(this); + + options.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + 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) { + } + }); + menu.show(); + } + }); + } + + @Override + public void onClick(View v) { + if (v == thumbnailView) { + callback.onThumbnailClicked(post, thumbnailView); + } else if (v == this) { + callback.onPostClicked(post); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + if (post != null && bound) { + unbindPost(post); + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + if (post != null && !bound) { + bindPost(theme, post); + } + } + + public void setPost(Theme theme, final Post post, PostCellInterface.PostCellCallback callback, boolean highlighted, int markedNo) { + if (this.post == post) { + return; + } + + if (theme == null) { + theme = ThemeHelper.theme(); + } + + if (this.post != null && bound) { + unbindPost(this.post); + this.post = null; + } + + this.theme = theme; + this.post = post; + this.callback = callback; + + bindPost(theme, post); + } + + public Post getPost() { + return post; + } + + public ThumbnailView getThumbnailView() { + return thumbnailView; + } + + @Override + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + public boolean hasOverlappingRendering() { + return false; + } + + private void bindPost(Theme theme, Post post) { + bound = true; + + if (post.hasImage) { + thumbnailView.setVisibility(View.VISIBLE); + thumbnailView.setUrl(post.thumbnailUrl, thumbnailView.getWidth(), thumbnailView.getHeight()); + } else { + thumbnailView.setVisibility(View.GONE); + thumbnailView.setUrl(null, 0, 0); + } + + if (!TextUtils.isEmpty(post.subjectSpan)) { + title.setVisibility(View.VISIBLE); + title.setText(post.subjectSpan); + } else { + title.setVisibility(View.GONE); + title.setText(null); + } + + CharSequence commentText; + if (post.comment.length() > COMMENT_MAX_LENGTH) { + commentText = post.comment.subSequence(0, COMMENT_MAX_LENGTH); + } else { + commentText = post.comment; + } + + comment.setText(commentText); + comment.setTextColor(theme.textPrimary); + + replies.setText(getResources().getString(R.string.card_stats, post.replies, post.images)); + } + + private void unbindPost(Post post) { + bound = false; + } +} 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 index 6263cf14..cb54a07e 100644 --- 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 @@ -47,7 +47,6 @@ import com.android.volley.toolbox.ImageLoader; import org.floens.chan.Chan; 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; @@ -62,18 +61,14 @@ import org.floens.chan.utils.Time; import java.util.ArrayList; import java.util.List; -import static org.floens.chan.ui.theme.ThemeHelper.theme; import static org.floens.chan.utils.AndroidUtils.dp; import static org.floens.chan.utils.AndroidUtils.getRes; import static org.floens.chan.utils.AndroidUtils.setRoundItemBackground; import static org.floens.chan.utils.AndroidUtils.sp; -public class PostCell extends RelativeLayout implements PostLinkable.Callback { +public class PostCell extends RelativeLayout implements PostCellInterface, PostLinkable.Callback { private static final int COMMENT_MAX_LENGTH_BOARD = 500; - private Post post; - private boolean threadMode; - private ThumbnailView thumbnailView; private TextView title; private TextView icons; @@ -87,9 +82,13 @@ public class PostCell extends RelativeLayout implements PostLinkable.Callback { private int detailsSizePx; private int iconsTextSize; private int countrySizePx; + private int paddingPx; + private boolean threadMode; private boolean ignoreNextOnClick; - private int paddingPx; + private boolean bound = false; + private Theme theme; + private Post post; private PostCellCallback callback; private boolean highlighted; private int markedNo; @@ -104,6 +103,7 @@ public class PostCell extends RelativeLayout implements PostLinkable.Callback { } } }; + private ImageLoader.ImageContainer countryIconRequest; public PostCell(Context context) { super(context); @@ -206,20 +206,35 @@ public class PostCell extends RelativeLayout implements PostLinkable.Callback { protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - if (post != null) { + if (post != null && bound) { unbindPost(post); } } - public void setPost(final Post post, PostCellCallback callback, boolean highlighted, int markedNo) { - setPost(theme(), post, callback, highlighted, markedNo); + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + if (post != null && !bound) { + bindPost(theme, post); + } } - public void setPost(Theme theme, final Post post, PostCellCallback callback, boolean highlighted, int markedNo) { - if (this.post != null) { + public void setPost(Theme theme, final Post post, PostCellInterface.PostCellCallback callback, boolean highlighted, int markedNo) { + if (this.post == post && this.highlighted == highlighted && this.markedNo == markedNo) { + return; + } + + if (theme == null) { + theme = ThemeHelper.theme(); + } + + if (this.post != null && bound) { unbindPost(this.post); + this.post = null; } + this.theme = theme; this.post = post; this.callback = callback; this.highlighted = highlighted; @@ -243,6 +258,8 @@ public class PostCell extends RelativeLayout implements PostLinkable.Callback { } private void bindPost(Theme theme, Post post) { + bound = true; + threadMode = callback.getLoadable().isThreadMode(); setPostLinkableListener(post, this); @@ -364,6 +381,13 @@ public class PostCell extends RelativeLayout implements PostLinkable.Callback { } private void unbindPost(Post post) { + bound = false; + + if (countryIconRequest != null) { + countryIconRequest.cancelRequest(); + countryIconRequest = null; + } + setPostLinkableListener(post, null); } @@ -373,22 +397,23 @@ public class PostCell extends RelativeLayout implements PostLinkable.Callback { PostLinkable[] linkables = commentSpannable.getSpans(0, commentSpannable.length(), PostLinkable.class); for (PostLinkable linkable : linkables) { if (callback == null) { - if (linkable.hasCallback(this)) { + while (linkable.hasCallback(this)) { linkable.removeCallback(this); } } else { - linkable.addCallback(callback); + if (!linkable.hasCallback(this)) { + linkable.addCallback(callback); + } } } } } private void loadCountryIcon(final Theme theme) { - final Post requestedPost = post; - Chan.getVolleyImageLoader().get(post.countryUrl, new ImageLoader.ImageListener() { + countryIconRequest = Chan.getVolleyImageLoader().get(post.countryUrl, new ImageLoader.ImageListener() { @Override public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) { - if (response.getBitmap() != null && PostCell.this.post == requestedPost) { + if (response.getBitmap() != null) { CharSequence countryIcon = PostHelper.addIcon(new BitmapDrawable(getRes(), response.getBitmap()), iconsTextSize); SpannableString countryText = new SpannableString(post.countryName); @@ -421,22 +446,6 @@ public class PostCell extends RelativeLayout implements PostLinkable.Callback { 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); /** diff --git a/Clover/app/src/main/java/org/floens/chan/ui/cell/PostCellInterface.java b/Clover/app/src/main/java/org/floens/chan/ui/cell/PostCellInterface.java new file mode 100644 index 00000000..0817c716 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/ui/cell/PostCellInterface.java @@ -0,0 +1,56 @@ +/* + * 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 . + */ +package org.floens.chan.ui.cell; + +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.ui.theme.Theme; +import org.floens.chan.ui.view.FloatingMenuItem; +import org.floens.chan.ui.view.ThumbnailView; + +import java.util.List; + +public interface PostCellInterface { + void setPost(Theme theme, Post post, PostCellCallback callback, boolean highlighted, int markedN); + + Post getPost(); + + ThumbnailView getThumbnailView(); + + enum PostViewMode { + LIST, + CARD + } + + 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); + } +} 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 02580378..11876450 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 @@ -32,6 +32,8 @@ import org.floens.chan.core.manager.BoardManager; import org.floens.chan.core.model.Board; import org.floens.chan.core.model.Loadable; import org.floens.chan.core.model.Pin; +import org.floens.chan.core.settings.ChanSettings; +import org.floens.chan.ui.cell.PostCellInterface; import org.floens.chan.ui.layout.ThreadLayout; import org.floens.chan.ui.toolbar.ToolbarMenu; import org.floens.chan.ui.toolbar.ToolbarMenuItem; @@ -46,8 +48,11 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte private static final int REFRESH_ID = 1; private static final int SEARCH_ID = 101; private static final int SHARE_ID = 102; + private static final int VIEW_MODE_ID = 103; + private PostCellInterface.PostViewMode postViewMode; private List boardItems; + private FloatingMenuItem viewModeMenuItem; public BrowseController(Context context) { super(context); @@ -57,6 +62,9 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte public void onCreate() { super.onCreate(); + postViewMode = ChanSettings.boardViewMode.get().equals("list") ? PostCellInterface.PostViewMode.LIST : PostCellInterface.PostViewMode.CARD; + threadLayout.setPostViewMode(postViewMode); + navigationItem.hasDrawer = true; navigationItem.middleMenu = new FloatingMenu(context); navigationItem.middleMenu.setCallback(this); @@ -73,6 +81,9 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte List items = new ArrayList<>(); items.add(new FloatingMenuItem(SEARCH_ID, context.getString(R.string.action_search))); items.add(new FloatingMenuItem(SHARE_ID, context.getString(R.string.action_share))); + viewModeMenuItem = new FloatingMenuItem(VIEW_MODE_ID, context.getString( + postViewMode == PostCellInterface.PostViewMode.LIST ? R.string.action_switch_catalog : R.string.action_switch_board)); + items.add(viewModeMenuItem); overflow.setSubMenu(new FloatingMenu(context, overflow.getView(), items)); } @@ -95,6 +106,21 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte case SHARE_ID: String link = ChanUrls.getCatalogUrlDesktop(threadLayout.getPresenter().getLoadable().board); AndroidUtils.shareLink(link); + break; + case VIEW_MODE_ID: + if (postViewMode == PostCellInterface.PostViewMode.LIST) { + postViewMode = PostCellInterface.PostViewMode.CARD; + } else { + postViewMode = PostCellInterface.PostViewMode.LIST; + } + + ChanSettings.boardViewMode.set(postViewMode == PostCellInterface.PostViewMode.LIST ? "list" : "grid"); + + viewModeMenuItem.setText(context.getString( + postViewMode == PostCellInterface.PostViewMode.LIST ? R.string.action_switch_catalog : R.string.action_switch_board)); + + threadLayout.setPostViewMode(postViewMode); + break; } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/MainSettingsController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/MainSettingsController.java index b840d92c..1db42060 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/MainSettingsController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/MainSettingsController.java @@ -41,6 +41,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import de.greenrobot.event.EventBus; + public class MainSettingsController extends SettingsController implements ToolbarMenuItem.ToolbarMenuItemCallback, WatchSettingsController.WatchSettingControllerListener, PassSettingsController.PassSettingControllerListener { private static final int ADVANCED_SETTINGS = 1; private SettingView imageAutoLoadView; @@ -50,6 +52,7 @@ public class MainSettingsController extends SettingsController implements Toolba private LinkSettingView passLink; private int clickCount; private SettingView developerView; + private SettingView fontView; public MainSettingsController(Context context) { super(context); @@ -99,6 +102,8 @@ public class MainSettingsController extends SettingsController implements Toolba if (item == imageAutoLoadView) { videoAutoLoadView.setEnabled(ChanSettings.imageAutoLoad.get()); + } else if (item == fontView) { + EventBus.getDefault().post(new RefreshUIMessage("fontsize")); } } @@ -146,7 +151,7 @@ public class MainSettingsController extends SettingsController implements Toolba fontSizes.add(new ListSettingView.Item(name, String.valueOf(size))); } - appearance.add(new ListSettingView(this, ChanSettings.fontSize, s(R.string.setting_font_size), fontSizes.toArray(new ListSettingView.Item[fontSizes.size()]))); + fontView = appearance.add(new ListSettingView(this, ChanSettings.fontSize, s(R.string.setting_font_size), fontSizes.toArray(new ListSettingView.Item[fontSizes.size()]))); groups.add(appearance); @@ -229,4 +234,12 @@ public class MainSettingsController extends SettingsController implements Toolba private String s(int id) { return string(id); } + + public static class RefreshUIMessage { + public String reason; + + public RefreshUIMessage(String reason) { + this.reason = reason; + } + } } 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 74e485e4..65e4b31c 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 @@ -39,7 +39,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.cell.PostCellInterface; import org.floens.chan.ui.helper.PostPopupHelper; import org.floens.chan.ui.view.LoadView; import org.floens.chan.ui.view.ThumbnailView; @@ -105,8 +105,8 @@ public class PostRepliesController extends Controller { ThumbnailView thumbnail = null; for (int i = 0; i < listView.getChildCount(); i++) { View view = listView.getChildAt(i); - if (view instanceof PostCell) { - PostCell postView = (PostCell) view; + if (view instanceof PostCellInterface) { + PostCellInterface postView = (PostCellInterface) view; Post post = postView.getPost(); if (post.hasImage && post.imageUrl.equals(postImage.imageUrl)) { thumbnail = postView.getThumbnailView(); @@ -169,17 +169,17 @@ public class PostRepliesController extends Controller { ArrayAdapter adapter = new ArrayAdapter(context, 0) { @Override public View getView(int position, View convertView, ViewGroup parent) { - PostCell postCell; - if (convertView instanceof PostCell) { - postCell = (PostCell) convertView; + PostCellInterface postCell; + if (convertView instanceof PostCellInterface) { + postCell = (PostCellInterface) convertView; } else { - postCell = (PostCell) LayoutInflater.from(parent.getContext()).inflate(R.layout.cell_post, parent, false); + postCell = (PostCellInterface) LayoutInflater.from(parent.getContext()).inflate(R.layout.cell_post, parent, false); } final Post p = getItem(position); - postCell.setPost(p, presenter, false, data.forPost.no); + postCell.setPost(null, p, presenter, false, data.forPost.no); - return postCell; + return (View) postCell; } }; diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java index 3027bf25..0e46faee 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java @@ -49,6 +49,7 @@ public abstract class ThreadController extends Controller implements ThreadLayou threadLayout = (ThreadLayout) LayoutInflater.from(context).inflate(R.layout.layout_thread, null); threadLayout.setCallback(this); + swipeRefreshLayout = new SwipeRefreshLayout(context) { @Override public boolean canChildScrollUp() { @@ -87,6 +88,10 @@ public abstract class ThreadController extends Controller implements ThreadLayou threadLayout.getPresenter().onForegroundChanged(message.inForeground); } + public void onEvent(MainSettingsController.RefreshUIMessage message) { + threadLayout.getPresenter().requestData(); + } + @Override public void onRefresh() { threadLayout.refreshFromSwipe(); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/ViewThreadController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/ViewThreadController.java index 065b3916..05cbf7b9 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/ViewThreadController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/ViewThreadController.java @@ -27,6 +27,7 @@ import org.floens.chan.chan.ChanUrls; import org.floens.chan.core.manager.WatchManager; import org.floens.chan.core.model.Loadable; import org.floens.chan.core.model.Pin; +import org.floens.chan.ui.cell.PostCellInterface; import org.floens.chan.ui.layout.ThreadLayout; import org.floens.chan.ui.toolbar.ToolbarMenu; import org.floens.chan.ui.toolbar.ToolbarMenuItem; @@ -58,6 +59,8 @@ public class ViewThreadController extends ThreadController implements ThreadLayo public void onCreate() { super.onCreate(); + threadLayout.setPostViewMode(PostCellInterface.PostViewMode.LIST); + view.setBackgroundColor(getAttrColor(context, R.attr.backcolor)); navigationItem.hasDrawer = true; 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 8d743dc3..8a820fa8 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 @@ -51,6 +51,7 @@ import org.floens.chan.core.model.PostImage; import org.floens.chan.core.model.PostLinkable; import org.floens.chan.core.presenter.ThreadPresenter; import org.floens.chan.core.settings.ChanSettings; +import org.floens.chan.ui.cell.PostCellInterface; import org.floens.chan.ui.helper.PostPopupHelper; import org.floens.chan.ui.view.LoadView; import org.floens.chan.ui.view.ThumbnailView; @@ -88,6 +89,7 @@ public class ThreadLayout extends CoordinatorLayout implements ThreadPresenter.T private ProgressDialog deletingDialog; private boolean refreshedFromSwipe; private boolean showingReplyButton = false; + private PostCellInterface.PostViewMode postViewMode; public ThreadLayout(Context context) { super(context); @@ -159,6 +161,11 @@ public class ThreadLayout extends CoordinatorLayout implements ThreadPresenter.T presenter.requestData(); } + public void setPostViewMode(PostCellInterface.PostViewMode postViewMode) { + this.postViewMode = postViewMode; + threadListLayout.setPostViewMode(postViewMode); + } + @Override public void replyLayoutOpen(boolean open) { showReplyButton(!open); 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 d427e39d..b8869c63 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 @@ -18,6 +18,7 @@ package org.floens.chan.ui.layout; import android.content.Context; +import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; @@ -33,6 +34,7 @@ import org.floens.chan.core.model.PostImage; import org.floens.chan.core.presenter.ReplyPresenter; import org.floens.chan.ui.adapter.PostAdapter; import org.floens.chan.ui.cell.PostCell; +import org.floens.chan.ui.cell.PostCellInterface; import org.floens.chan.ui.cell.ThreadStatusCell; import org.floens.chan.ui.view.ThumbnailView; import org.floens.chan.utils.AndroidUtils; @@ -41,6 +43,8 @@ import org.floens.chan.utils.AnimationUtils; import java.util.List; import static org.floens.chan.utils.AndroidUtils.ROBOTO_MEDIUM; +import static org.floens.chan.utils.AndroidUtils.dp; +import static org.floens.chan.utils.AndroidUtils.getAttrColor; /** * A layout that wraps around a {@link RecyclerView} to manage showing posts. @@ -49,12 +53,15 @@ public class ThreadListLayout extends LinearLayout implements ReplyLayout.ReplyL private ReplyLayout reply; private TextView searchStatus; private RecyclerView recyclerView; - private LinearLayoutManager linearLayoutManager; + private RecyclerView.LayoutManager layoutManager; private PostAdapter postAdapter; private ChanThread showingThread; private ThreadListLayoutCallback callback; private ReplyLayoutStateCallback replyLayoutStateCallback; private boolean replyOpen; + private PostCellInterface.PostViewMode postViewMode; + private int spanCount = 2; + private int background; public ThreadListLayout(Context context, AttributeSet attrs) { super(context, attrs); @@ -71,8 +78,6 @@ public class ThreadListLayout extends LinearLayout implements ReplyLayout.ReplyL searchStatus.setTypeface(ROBOTO_MEDIUM); recyclerView = (RecyclerView) findViewById(R.id.recycler_view); - linearLayoutManager = new LinearLayoutManager(getContext()); - recyclerView.setLayoutManager(linearLayoutManager); } public void setCallbacks(PostAdapter.PostAdapterCallback postAdapterCallback, PostCell.PostCellCallback postCellCallback, @@ -80,6 +85,7 @@ public class ThreadListLayout extends LinearLayout implements ReplyLayout.ReplyL ReplyLayoutStateCallback replyLayoutStateCallback) { this.callback = callback; this.replyLayoutStateCallback = replyLayoutStateCallback; + postAdapter = new PostAdapter(recyclerView, postAdapterCallback, postCellCallback, statusCellCallback); recyclerView.setAdapter(postAdapter); recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @@ -87,12 +93,110 @@ public class ThreadListLayout extends LinearLayout implements ReplyLayout.ReplyL public void onScrolled(RecyclerView recyclerView, int dx, int dy) { // onScrolled can be called after cleanup() if (showingThread != null) { - showingThread.loadable.listViewIndex = Math.max(0, linearLayoutManager.findFirstVisibleItemPosition()); + switch (postViewMode) { + case LIST: + showingThread.loadable.listViewIndex = Math.max(0, ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition()); + break; + case CARD: + showingThread.loadable.listViewIndex = Math.max(0, ((GridLayoutManager) layoutManager).findFirstVisibleItemPosition()); + break; + } } } }); } + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + int cardWidth = getResources().getDimensionPixelSize(R.dimen.grid_card_width); + int maxSpans = getResources().getInteger(R.integer.grid_card_max_spans); + + spanCount = Math.max(1, Math.round(getMeasuredWidth() / cardWidth)); + if (maxSpans > 1 && spanCount > maxSpans) { + spanCount = maxSpans; + } + + if (postViewMode == PostCellInterface.PostViewMode.CARD) { + ((GridLayoutManager) layoutManager).setSpanCount(spanCount); + } + } + + public void setPostViewMode(PostCellInterface.PostViewMode postViewMode) { + if (this.postViewMode != postViewMode) { + this.postViewMode = postViewMode; + + layoutManager = null; + + switch (postViewMode) { + case LIST: + LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext()); + recyclerView.setPadding(0, 0, 0, 0); + recyclerView.setLayoutManager(linearLayoutManager); + layoutManager = linearLayoutManager; + + if (background != R.attr.backcolor) { + background = R.attr.backcolor; + setBackgroundColor(getAttrColor(getContext(), R.attr.backcolor)); + } + + break; + case CARD: + GridLayoutManager gridLayoutManager = new GridLayoutManager(null, spanCount, GridLayoutManager.VERTICAL, false); + // The cards have a 4dp padding, this way there is always 8dp between the edges + recyclerView.setPadding(dp(4), dp(4), dp(4), dp(4)); + recyclerView.setLayoutManager(gridLayoutManager); + layoutManager = gridLayoutManager; + + if (background != R.attr.backcolor_secondary) { + background = R.attr.backcolor_secondary; + setBackgroundColor(getAttrColor(getContext(), R.attr.backcolor_secondary)); + } + + break; + } + + recyclerView.getRecycledViewPool().clear(); + + postAdapter.setPostViewMode(postViewMode); + } + } + + public void showPosts(ChanThread thread, boolean initial) { + showingThread = thread; + if (initial) { + reply.bindLoadable(showingThread.loadable); + + recyclerView.setLayoutManager(null); + recyclerView.setLayoutManager(layoutManager); + recyclerView.getRecycledViewPool().clear(); + + switch (postViewMode) { + case LIST: + ((LinearLayoutManager) layoutManager).scrollToPositionWithOffset(thread.loadable.listViewIndex, 0); + break; + case CARD: + ((GridLayoutManager) layoutManager).scrollToPositionWithOffset(thread.loadable.listViewIndex, 0); + break; + } + } else { + switch (postViewMode) { + case LIST: + LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager; + if (linearLayoutManager.findLastCompletelyVisibleItemPosition() == postAdapter.getItemCount() - 1) { + linearLayoutManager.scrollToPositionWithOffset(postAdapter.getItemCount() - 1, 0); + } + break; + case CARD: + // No op + break; + } + } + + postAdapter.setThread(thread); + } + public boolean onBack() { if (reply.onBack()) { return true; @@ -121,20 +225,6 @@ public class ThreadListLayout extends LinearLayout implements ReplyLayout.ReplyL return reply.getPresenter(); } - public void showPosts(ChanThread thread, boolean initial) { - showingThread = thread; - if (initial) { - reply.bindLoadable(showingThread.loadable); - linearLayoutManager.scrollToPositionWithOffset(thread.loadable.listViewIndex, 0); - } else { - if (linearLayoutManager.findLastVisibleItemPosition() == postAdapter.getItemCount() - 1) { - linearLayoutManager.scrollToPositionWithOffset(postAdapter.getItemCount() - 1, 0); - } - } - - postAdapter.setThread(thread); - } - public void showError(String error) { postAdapter.showError(error); } @@ -172,8 +262,18 @@ public class ThreadListLayout extends LinearLayout implements ReplyLayout.ReplyL public boolean canChildScrollUp() { View top = recyclerView.getChildAt(0); if (top != null) { - if (top.getTop() == 0 && linearLayoutManager.findFirstVisibleItemPosition() == 0) { - return false; + + switch (postViewMode) { + case LIST: + if (top.getTop() == 0 && ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition() == 0) { + return false; + } + break; + case CARD: + if (top.getTop() == 0 && ((GridLayoutManager) layoutManager).findFirstVisibleItemPosition() == 0) { + return false; + } + break; } } return true; @@ -208,8 +308,8 @@ public class ThreadListLayout extends LinearLayout implements ReplyLayout.ReplyL ThumbnailView thumbnail = null; for (int i = 0; i < layoutManager.getChildCount(); i++) { View view = layoutManager.getChildAt(i); - if (view instanceof PostCell) { - PostCell postView = (PostCell) view; + if (view instanceof PostCellInterface) { + PostCellInterface postView = (PostCellInterface) view; Post post = postView.getPost(); if (post.hasImage && post.imageUrl.equals(postImage.imageUrl)) { thumbnail = postView.getThumbnailView(); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/theme/Theme.java b/Clover/app/src/main/java/org/floens/chan/ui/theme/Theme.java index cd9c5392..18b6fd48 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/theme/Theme.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/theme/Theme.java @@ -38,6 +38,9 @@ public class Theme { public boolean isLightTheme = true; public ThemeHelper.PrimaryColor primaryColor; + public int textPrimary; + public int textSecondary; + public int textHint; public int quoteColor; public int highlightQuoteColor; public int linkColor; @@ -96,7 +99,10 @@ public class Theme { R.attr.post_capcode_color, R.attr.post_details_color, R.attr.post_highlighted_color, - R.attr.post_saved_reply_color + R.attr.post_saved_reply_color, + R.attr.text_color_primary, + R.attr.text_color_secondary, + R.attr.text_color_hint }); quoteColor = ta.getColor(0, 0); @@ -112,6 +118,9 @@ public class Theme { detailsColor = ta.getColor(10, 0); highlightedColor = ta.getColor(11, 0); savedReplyColor = ta.getColor(12, 0); + textPrimary = ta.getColor(13, 0); + textSecondary = ta.getColor(14, 0); + textHint = ta.getColor(15, 0); ta.recycle(); } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/view/FastTextView.java b/Clover/app/src/main/java/org/floens/chan/ui/view/FastTextView.java new file mode 100644 index 00000000..d99b7a0f --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/ui/view/FastTextView.java @@ -0,0 +1,106 @@ +/* + * 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 . + */ +package org.floens.chan.ui.view; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.text.Layout; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.View; + +import static org.floens.chan.utils.AndroidUtils.sp; + +public class FastTextView extends View { + private TextPaint paint; + + private CharSequence text; + + private boolean update = false; + private StaticLayout layout; + + public FastTextView(Context context) { + super(context); + init(); + } + + public FastTextView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public FastTextView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + paint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + } + + public void setText(CharSequence text) { + if (!TextUtils.equals(this.text, text)) { + this.text = text; + + if (text == null) { + layout = null; + } else { + update = true; + } + } + } + + public void setTextSize(float size) { + int sizeSp = sp(size); + if (paint.getTextSize() != sizeSp) { + paint.setTextSize(sizeSp); + update = true; + } + } + + public void setTextColor(int color) { + if (paint.getColor() != color) { + paint.setColor(color); + update = true; + } + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + update = true; + } + + @Override + protected void onDraw(Canvas canvas) { + if (update) { + int width = getWidth() - getPaddingLeft() - getPaddingRight(); + layout = new StaticLayout(text, paint, width, Layout.Alignment.ALIGN_NORMAL, 1, 0, false); + update = false; + } + + if (layout != null) { + canvas.save(); + canvas.translate(getPaddingLeft(), getPaddingTop()); + layout.draw(canvas); + canvas.restore(); + } + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/ui/view/FixedRatioThumbnailView.java b/Clover/app/src/main/java/org/floens/chan/ui/view/FixedRatioThumbnailView.java new file mode 100644 index 00000000..a3fd2d0e --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/ui/view/FixedRatioThumbnailView.java @@ -0,0 +1,47 @@ +/* + * 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 . + */ +package org.floens.chan.ui.view; + +import android.content.Context; +import android.util.AttributeSet; + +public class FixedRatioThumbnailView extends ThumbnailView { + public FixedRatioThumbnailView(Context context) { + super(context); + } + + public FixedRatioThumbnailView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public FixedRatioThumbnailView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY && (heightMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.AT_MOST)) { + int width = MeasureSpec.getSize(widthMeasureSpec); + + super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec((int) (width / 16f * 9f), MeasureSpec.EXACTLY)); + } else { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + } +} 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 68a49421..d8f28122 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 @@ -37,7 +37,15 @@ public class FloatingMenuItem { return id; } + public void setId(Object id) { + this.id = id; + } + public String getText() { return text; } + + public void setText(String text) { + this.text = text; + } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/view/ThumbnailView.java b/Clover/app/src/main/java/org/floens/chan/ui/view/ThumbnailView.java index 3495c17d..b0a341b5 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/view/ThumbnailView.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/view/ThumbnailView.java @@ -146,6 +146,12 @@ public class ThumbnailView extends View implements ImageLoader.ImageListener { return true; } + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + calculate = true; + } + @Override protected void onDraw(Canvas canvas) { if (getAlpha() == 0f) { diff --git a/Clover/app/src/main/res/layout/cell_post.xml b/Clover/app/src/main/res/layout/cell_post.xml index d95017fb..ae66b14a 100644 --- a/Clover/app/src/main/res/layout/cell_post.xml +++ b/Clover/app/src/main/res/layout/cell_post.xml @@ -18,7 +18,8 @@ along with this program. If not, see . + android:layout_height="wrap_content" + tools:ignore="RtlHardcoded,RtlSymmetry"> + + + + + + + + + + + + + + + + + + + + + diff --git a/Clover/app/src/main/res/layout/layout_thread.xml b/Clover/app/src/main/res/layout/layout_thread.xml index b07f431a..2f7422dc 100644 --- a/Clover/app/src/main/res/layout/layout_thread.xml +++ b/Clover/app/src/main/res/layout/layout_thread.xml @@ -16,6 +16,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . --> @@ -33,6 +34,7 @@ along with this program. If not, see . android:alpha="0" android:scaleX="0" android:scaleY="0" - android:src="@drawable/ic_create_white_24dp" /> + android:src="@drawable/ic_create_white_24dp" + tools:ignore="RtlHardcoded" /> diff --git a/Clover/app/src/main/res/layout/layout_thread_list.xml b/Clover/app/src/main/res/layout/layout_thread_list.xml index b013c465..e57e3f30 100644 --- a/Clover/app/src/main/res/layout/layout_thread_list.xml +++ b/Clover/app/src/main/res/layout/layout_thread_list.xml @@ -45,6 +45,7 @@ along with this program. If not, see . android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" + android:clipToPadding="false" android:scrollbars="vertical" /> diff --git a/Clover/app/src/main/res/values-sw600dp/dimens.xml b/Clover/app/src/main/res/values-sw600dp/dimens.xml new file mode 100644 index 00000000..cbbfb63b --- /dev/null +++ b/Clover/app/src/main/res/values-sw600dp/dimens.xml @@ -0,0 +1,23 @@ + + + true + + 180dp + 5 + diff --git a/Clover/app/src/main/res/values/dimens.xml b/Clover/app/src/main/res/values/dimens.xml index c9fb5a44..dcc4d304 100644 --- a/Clover/app/src/main/res/values/dimens.xml +++ b/Clover/app/src/main/res/values/dimens.xml @@ -1,4 +1,25 @@ - + + false + 56dp + + 140dp + -1 diff --git a/Clover/app/src/main/res/values/strings.xml b/Clover/app/src/main/res/values/strings.xml index 8e0010a9..dc46e3ea 100644 --- a/Clover/app/src/main/res/values/strings.xml +++ b/Clover/app/src/main/res/values/strings.xml @@ -46,6 +46,8 @@ along with this program. If not, see . %d images + %1$dR %2$dI + Reload Pin Open in browser @@ -53,6 +55,8 @@ along with this program. If not, see . Download album Search Image search + Catalog mode + Board mode Search Found %1$s for "%2$s"