From 94d17a4c801ded93e8d50897bf08cdb798bb4456 Mon Sep 17 00:00:00 2001 From: Floens Date: Wed, 22 Apr 2015 18:07:09 +0200 Subject: [PATCH] Use RecyclerView for the post list --- Clover/app/build.gradle | 6 +- .../floens/chan/core/loader/ChanLoader.java | 16 +- .../java/org/floens/chan/core/model/Post.java | 1 + .../chan/core/net/ChanReaderRequest.java | 28 +- .../chan/core/presenter/ThreadPresenter.java | 78 +++- .../floens/chan/core/watch/PinWatcher.java | 2 +- .../org/floens/chan/ui/ThemeActivity.java | 4 +- .../chan/ui/activity/ImageViewActivity.java | 10 +- .../chan/ui/activity/StartActivity.java | 4 +- .../floens/chan/ui/adapter/PinAdapter.java | 2 +- .../floens/chan/ui/adapter/PostAdapter.java | 350 +++++++----------- .../floens/chan/ui/cell/ThreadStatusCell.java | 154 ++++++++ .../chan/ui/controller/BrowseController.java | 11 - .../ui/controller/PostRepliesController.java | 2 +- .../chan/ui/controller/ThreadController.java | 17 + .../ui/controller/ViewThreadController.java | 8 +- .../chan/ui/fragment/PostRepliesFragment.java | 2 +- .../chan/ui/fragment/ThreadFragment.java | 27 +- .../chan/ui/helper/PostPopupHelper.java | 4 +- .../floens/chan/ui/layout/ThreadLayout.java | 32 +- .../chan/ui/layout/ThreadListLayout.java | 83 ++--- .../org/floens/chan/ui/view/PostView.java | 6 +- .../main/res/layout/cell_thread_status.xml | 14 + .../main/res/layout/layout_thread_list.xml | 12 + Clover/app/src/main/res/values/strings.xml | 3 +- 25 files changed, 522 insertions(+), 354 deletions(-) create mode 100644 Clover/app/src/main/java/org/floens/chan/ui/cell/ThreadStatusCell.java create mode 100644 Clover/app/src/main/res/layout/cell_thread_status.xml create mode 100644 Clover/app/src/main/res/layout/layout_thread_list.xml diff --git a/Clover/app/build.gradle b/Clover/app/build.gradle index 2514ef85..b8a8a94d 100644 --- a/Clover/app/build.gradle +++ b/Clover/app/build.gradle @@ -71,9 +71,9 @@ android { } dependencies { - compile 'com.android.support:support-v13:22.0.0' - compile 'com.android.support:appcompat-v7:22.0.0' - compile 'com.android.support:recyclerview-v7:22.0.0' + compile 'com.android.support:support-v13:22.1.0' + compile 'com.android.support:appcompat-v7:22.1.0' + compile 'com.android.support:recyclerview-v7:22.1.0' compile 'org.jsoup:jsoup:1.8.1' compile 'com.j256.ormlite:ormlite-core:4.48' diff --git a/Clover/app/src/main/java/org/floens/chan/core/loader/ChanLoader.java b/Clover/app/src/main/java/org/floens/chan/core/loader/ChanLoader.java index 6a467fed..fad4c8f0 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/loader/ChanLoader.java +++ b/Clover/app/src/main/java/org/floens/chan/core/loader/ChanLoader.java @@ -253,7 +253,7 @@ public class ChanLoader { } private ChanReaderRequest getData() { -// Logger.i(TAG, "Requested " + loadable.board + ", " + loadable.no); + Logger.i(TAG, "Requested " + loadable.board + ", " + loadable.no); List cached = thread == null ? new ArrayList() : thread.posts; ChanReaderRequest request = ChanReaderRequest.newInstance(loadable, cached, @@ -329,23 +329,21 @@ public class ChanLoader { post.title = loadable.title; } - for (ChanLoaderCallback l : listeners) { - l.onChanLoaderData(thread); - } - lastLoadTime = Time.get(); if (loadable.isThreadMode()) { setTimer(result.size()); } + + for (ChanLoaderCallback l : listeners) { + l.onChanLoaderData(thread); + } } private void onError(VolleyError error) { if (destroyed) return; - thread = null; - Logger.e(TAG, "Error loading " + error.getMessage(), error); // 404 with more pages already loaded means endofline @@ -353,11 +351,11 @@ public class ChanLoader { error = new EndOfLineException(); } + clearTimer(); + for (ChanLoaderCallback l : listeners) { l.onChanLoaderError(error); } - - clearTimer(); } public interface ChanLoaderCallback { diff --git a/Clover/app/src/main/java/org/floens/chan/core/model/Post.java b/Clover/app/src/main/java/org/floens/chan/core/model/Post.java index 386cda60..fb355c02 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/model/Post.java +++ b/Clover/app/src/main/java/org/floens/chan/core/model/Post.java @@ -82,6 +82,7 @@ public class Post { public String rawComment; public String countryUrl; public boolean spoiler = false; + public int uniqueIps = 1; public boolean deleted = false; diff --git a/Clover/app/src/main/java/org/floens/chan/core/net/ChanReaderRequest.java b/Clover/app/src/main/java/org/floens/chan/core/net/ChanReaderRequest.java index 5b4172f6..e8e4e27a 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/net/ChanReaderRequest.java +++ b/Clover/app/src/main/java/org/floens/chan/core/net/ChanReaderRequest.java @@ -41,8 +41,7 @@ public class ChanReaderRequest extends JsonReaderRequest> { super(url, listener, errorListener); } - public static ChanReaderRequest newInstance(Loadable loadable, List cached, Listener> listener, - ErrorListener errorListener) { + public static ChanReaderRequest newInstance(Loadable loadable, List cached, Listener> listener, ErrorListener errorListener) { String url; if (loadable.isBoardMode()) { @@ -126,6 +125,11 @@ public class ChanReaderRequest extends JsonReaderRequest> { } } + // Replace OPs + if (totalList.get(0).isOP && serverList.size() > 0 && serverList.get(0).isOP) { + totalList.set(0, serverList.get(0)); + } + // Sort if it got out of order due to posts disappearing/reappearing /*if (loadable.isThreadMode()) { Collections.sort(totalList, new Comparator() { @@ -140,7 +144,7 @@ public class ChanReaderRequest extends JsonReaderRequest> { totalList.addAll(serverList); } - Set invalidatedPosts = new HashSet<>(); + Set postsReplyingToDeleted = new HashSet<>(); for (Post post : totalList) { if (!post.deleted) { post.repliesFrom.clear(); @@ -154,14 +158,14 @@ public class ChanReaderRequest extends JsonReaderRequest> { post.repliesTo.clear(); for (int no : post.repliesFrom) { - invalidatedPosts.add(no); + postsReplyingToDeleted.add(no); } post.repliesFrom.clear(); } } - for (int no : invalidatedPosts) { + for (int no : postsReplyingToDeleted) { for (Post post : totalList) { if (post.no == no) { if (!post.finish()) { @@ -357,6 +361,9 @@ public class ChanReaderRequest extends JsonReaderRequest> { case "spoiler": post.spoiler = reader.nextInt() == 1; break; + case "unique_ips": + post.uniqueIps = reader.nextInt(); + break; default: // Unknown/ignored key // log("Unknown/ignored key: " + key + "."); @@ -367,10 +374,13 @@ public class ChanReaderRequest extends JsonReaderRequest> { reader.endObject(); Post cachedResult = null; - for (Post possibleCached : cached) { - if (possibleCached.no == post.no) { - cachedResult = possibleCached; - break; + // Do not cache OPs to make sure the archived, replies etc. are updated + if (post.resto != 0) { + for (Post possibleCached : cached) { + if (possibleCached.no == post.no) { + cachedResult = possibleCached; + break; + } } } 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 e23645ef..a4a5a3c0 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java +++ b/Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java @@ -37,6 +37,7 @@ 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.ThreadStatusCell; import org.floens.chan.ui.view.PostView; import org.floens.chan.ui.view.ThumbnailView; import org.floens.chan.utils.AndroidUtils; @@ -44,7 +45,7 @@ import org.floens.chan.utils.AndroidUtils; import java.util.ArrayList; import java.util.List; -public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapter.PostAdapterCallback, PostView.PostViewCallback { +public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapter.PostAdapterCallback, PostView.PostViewCallback, ThreadStatusCell.Callback { private ThreadPresenterCallback threadPresenterCallback; private Loadable loadable; @@ -80,6 +81,19 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt chanLoader.requestData(); } + public void onForegroundChanged(boolean foreground) { + if (chanLoader != null) { + if (foreground) { + if (isWatching()) { + chanLoader.setAutoLoadMore(true); + chanLoader.requestMoreDataAndResetTimer(); + } + } else { + chanLoader.setAutoLoadMore(false); + } + } + } + public boolean pin() { if (chanLoader.getThread() != null) { WatchManager wm = ChanApplication.getWatchManager(); @@ -108,12 +122,12 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt */ @Override public void onChanLoaderData(ChanThread result) { + chanLoader.setAutoLoadMore(isWatching()); threadPresenterCallback.showPosts(result); } @Override public void onChanLoaderError(VolleyError error) { - // TODO show error in status view is content is shown threadPresenterCallback.showError(error); } @@ -127,12 +141,16 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt @Override public void onListScrolledToBottom() { + if (loadable.isThreadMode()) { + List posts = chanLoader.getThread().posts; + loadable.lastViewed = posts.get(posts.size() - 1).no; + } - } - - @Override - public void onListStatusClicked() { - + Pin pin = ChanApplication.getWatchManager().findPinByLoadable(loadable); + if (pin != null) { + pin.onBottomPostViewed(); + ChanApplication.getWatchManager().updatePin(pin); + } } @Override @@ -152,6 +170,22 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt scrollTo(position); } + public void scrollToPost(Post needle) { + int position = -1; + for (int i = 0; i < chanLoader.getThread().posts.size(); i++) { + Post post = chanLoader.getThread().posts.get(i); + if (post.no == needle.no) { + position = i; + break; + } + } + scrollTo(position); + } + + public void highlightPost(Post post) { + threadPresenterCallback.highlightPost(post); + } + /* * PostView callbacks */ @@ -236,9 +270,7 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt AndroidUtils.openLink(ChanUrls.getReportUrl(post.board, post.no)); break; case 6: // Id - //TODO -// highlightedId = post.id; -// threadManagerListener.onRefreshView(); + threadPresenterCallback.highlightPostId(post.id); break; case 7: // Delete // deletePost(post); TODO @@ -285,19 +317,31 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt } @Override - public boolean isPostHightlighted(Post post) { + public boolean isPostLastSeen(Post post) { return false; } - public void highlightPost(int no) { + /* + * ThreadStatusCell callbacks + */ + @Override + public long getTimeUntilLoadMore() { + return chanLoader.getTimeUntilLoadMore(); } - public void scrollToPost(int no) { + @Override + public boolean isWatching() { + return loadable.isThreadMode() && ChanSettings.autoRefreshThread.get() && !chanLoader.getThread().closed && !chanLoader.getThread().archived; } @Override - public boolean isPostLastSeen(Post post) { - return false; + public ChanThread getChanThread() { + return chanLoader.getThread(); + } + + @Override + public void onListStatusClicked() { + chanLoader.requestMoreDataAndResetTimer(); } private void showPostInfo(Post post) { @@ -360,5 +404,9 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt void showImages(List images, int index, Loadable loadable, ThumbnailView thumbnail); void scrollTo(int position); + + void highlightPost(Post post); + + void highlightPostId(String id); } } diff --git a/Clover/app/src/main/java/org/floens/chan/core/watch/PinWatcher.java b/Clover/app/src/main/java/org/floens/chan/core/watch/PinWatcher.java index 4de63aef..cef5a8d8 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/watch/PinWatcher.java +++ b/Clover/app/src/main/java/org/floens/chan/core/watch/PinWatcher.java @@ -116,7 +116,7 @@ public class PinWatcher implements ChanLoader.ChanLoaderCallback { @Override public void onChanLoaderError(VolleyError error) { - Logger.e(TAG, "PinWatcher onError: ", error); + Logger.e(TAG, "PinWatcher onError"); pin.isError = true; AndroidUtils.runOnUiThread(new Runnable() { diff --git a/Clover/app/src/main/java/org/floens/chan/ui/ThemeActivity.java b/Clover/app/src/main/java/org/floens/chan/ui/ThemeActivity.java index 8f484b5e..8e987953 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/ThemeActivity.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/ThemeActivity.java @@ -17,14 +17,14 @@ */ package org.floens.chan.ui; -import android.support.v7.app.ActionBarActivity; +import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.MenuItem; import org.floens.chan.R; import org.floens.chan.utils.ThemeHelper; -public class ThemeActivity extends ActionBarActivity { +public class ThemeActivity extends AppCompatActivity { private Toolbar toolbar; public void setTheme() { diff --git a/Clover/app/src/main/java/org/floens/chan/ui/activity/ImageViewActivity.java b/Clover/app/src/main/java/org/floens/chan/ui/activity/ImageViewActivity.java index 5bc8c0c4..c8d9202c 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/activity/ImageViewActivity.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/activity/ImageViewActivity.java @@ -112,11 +112,11 @@ public class ImageViewActivity extends ThemeActivity implements ViewPager.OnPage private void initPager() { // Get the posts with images ArrayList imagePosts = new ArrayList<>(); - for (Post post : postAdapter.getList()) { - if (post.hasImage) { - imagePosts.add(post); - } - } +// for (Post post : postAdapter.getList()) { +// if (post.hasImage) { +// imagePosts.add(post); +// } +// } // Setup our pages and adapter viewPager = (ViewPager) findViewById(R.id.image_pager); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java b/Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java index 29a486a3..de20181a 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java @@ -1,8 +1,8 @@ package org.floens.chan.ui.activity; -import android.app.Activity; import android.content.res.Configuration; import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; import android.view.ViewGroup; import org.floens.chan.ChanApplication; @@ -14,7 +14,7 @@ import org.floens.chan.utils.ThemeHelper; import java.util.ArrayList; import java.util.List; -public class StartActivity extends Activity { +public class StartActivity extends AppCompatActivity { private static final String TAG = "StartActivity"; private ViewGroup contentView; diff --git a/Clover/app/src/main/java/org/floens/chan/ui/adapter/PinAdapter.java b/Clover/app/src/main/java/org/floens/chan/ui/adapter/PinAdapter.java index c3bb53d9..a84f6fd5 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/adapter/PinAdapter.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/adapter/PinAdapter.java @@ -182,7 +182,7 @@ public class PinAdapter extends RecyclerView.Adapter im if (ChanSettings.watchEnabled.get()) { String count; if (pin.isError) { - count = "Err"; + count = "E"; } else { int c = pin.getNewPostCount(); if (c > 999) { 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 d1c5fc19..40711e34 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 @@ -17,94 +17,183 @@ */ package org.floens.chan.ui.adapter; -import android.content.Context; +import android.support.v7.widget.RecyclerView; import android.text.TextUtils; -import android.util.AttributeSet; -import android.view.Gravity; -import android.view.View; +import android.view.LayoutInflater; import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.Filter; -import android.widget.Filterable; -import android.widget.LinearLayout; -import android.widget.ProgressBar; -import android.widget.TextView; 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.ThreadStatusCell; import org.floens.chan.ui.view.PostView; import java.util.ArrayList; import java.util.List; -import java.util.Locale; -import static org.floens.chan.utils.AndroidUtils.dp; - -public class PostAdapter extends BaseAdapter implements Filterable { - private static final int VIEW_TYPE_ITEM = 0; - private static final int VIEW_TYPE_STATUS = 1; - - private final Object lock = new Object(); - - private final Context context; +public class PostAdapter extends RecyclerView.Adapter { + private static final int TYPE_POST = 0; + private static final int TYPE_STATUS = 1; private final PostAdapterCallback postAdapterCallback; private final PostView.PostViewCallback postViewCallback; + private final ThreadStatusCell.Callback statusCellCallback; + private RecyclerView recyclerView; - /** - * The list with the original data - */ private final List sourceList = new ArrayList<>(); - - /** - * The list that is displayed (filtered) - */ private final List displayList = new ArrayList<>(); - - private boolean endOfLine; private int lastPostCount = 0; - private String statusMessage = null; + private String error = null; private String filter = ""; private int pendingScrollToPost = -1; - private String statusPrefix = ""; + private Post highlightedPost; + private String highlightedPostId; - public PostAdapter(Context context, PostAdapterCallback postAdapterCallback, PostView.PostViewCallback postViewCallback) { + public PostAdapter(RecyclerView recyclerView, PostAdapterCallback postAdapterCallback, PostView.PostViewCallback postViewCallback, ThreadStatusCell.Callback statusCellCallback) { + this.recyclerView = recyclerView; this.postAdapterCallback = postAdapterCallback; - this.context = context; this.postViewCallback = postViewCallback; + this.statusCellCallback = statusCellCallback; + + setHasStableIds(true); + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + if (viewType == TYPE_POST) { + PostView postView = new PostView(parent.getContext()); + return new PostViewHolder(postView); + } else { + StatusViewHolder statusViewHolder = new StatusViewHolder((ThreadStatusCell) LayoutInflater.from(parent.getContext()).inflate(R.layout.cell_thread_status, parent, false)); + statusViewHolder.threadStatusCell.setCallback(statusCellCallback); + statusViewHolder.threadStatusCell.setError(error); + return statusViewHolder; + } } @Override - public int getCount() { - return displayList.size() + (showStatusView() ? 1 : 0); + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + if (getItemViewType(position) == TYPE_POST) { + PostViewHolder postViewHolder = (PostViewHolder) holder; + Post post = displayList.get(position); + boolean highlight = post == highlightedPost || post.id.equals(highlightedPostId); + postViewHolder.postView.setPost(post, postViewCallback, highlight); + } else if (getItemViewType(position) == TYPE_STATUS) { + ((StatusViewHolder)holder).threadStatusCell.update(); + onScrolledToBottom(); + } } @Override - public int getViewTypeCount() { - return 2; + public int getItemCount() { + if (showStatusView()) { + return displayList.size() + 1; + } else { + return displayList.size(); + } } @Override public int getItemViewType(int position) { - if (position == getCount() - 1) { - return showStatusView() ? VIEW_TYPE_STATUS : VIEW_TYPE_ITEM; + if (showStatusView()) { + if (position == getItemCount() - 1) { + return TYPE_STATUS; + } else { + return TYPE_POST; + } } else { - return VIEW_TYPE_ITEM; + return TYPE_POST; } } @Override - public Post getItem(int position) { - int realPosition = position; - if (realPosition >= 0 && realPosition < displayList.size()) { - return displayList.get(realPosition); + public long getItemId(int position) { + if (getItemViewType(position) != TYPE_POST) { + return -1; } else { - return null; + return displayList.get(position).no; + } + } + + public void setThread(ChanThread thread) { + showError(null); + sourceList.clear(); + sourceList.addAll(thread.posts); + + displayList.clear(); + displayList.addAll(sourceList); + + // Update all, recyclerview will figure out all the animations + notifyDataSetChanged(); + } + + public void cleanup() { + highlightedPost = null; + sourceList.clear(); + displayList.clear(); + lastPostCount = 0; + } + + public void showError(String error) { + this.error = error; + if (showStatusView()) { + RecyclerView.ViewHolder viewHolder = recyclerView.findViewHolderForAdapterPosition(getItemCount() - 1); + // Recyclerview did not sync yet + if (viewHolder instanceof StatusViewHolder) { + ThreadStatusCell threadStatusCell = ((StatusViewHolder) viewHolder).threadStatusCell; + threadStatusCell.setError(error); + threadStatusCell.update(); + } + } + } + + public void highlightPost(Post post) { + highlightedPostId = null; + highlightedPost = post; + notifyDataSetChanged(); + } + + public void highlightPostId(String id) { + highlightedPost = null; + highlightedPostId = id; + notifyDataSetChanged(); + } + + private void onScrolledToBottom() { + if (lastPostCount != sourceList.size()) { + lastPostCount = sourceList.size(); + postAdapterCallback.onListScrolledToBottom(); } } + private boolean showStatusView() { + return postAdapterCallback.getLoadable().isThreadMode(); + } + + private boolean isFiltering() { + return !TextUtils.isEmpty(filter); + } + + public static class PostViewHolder extends RecyclerView.ViewHolder { + private PostView postView; + + public PostViewHolder(PostView postView) { + super(postView); + this.postView = postView; + } + } + + public static class StatusViewHolder extends RecyclerView.ViewHolder { + private ThreadStatusCell threadStatusCell; + + public StatusViewHolder(ThreadStatusCell threadStatusCell) { + super(threadStatusCell); + this.threadStatusCell = threadStatusCell; + } + } + +/* @Override public long getItemId(int position) { return position; @@ -113,7 +202,7 @@ public class PostAdapter extends BaseAdapter implements Filterable { @Override public View getView(int position, View convertView, ViewGroup parent) { if (position >= getCount() - 1) { - onGetBottomView(); + onScrolledToBottom(); } switch (getItemViewType(position)) { @@ -216,76 +305,8 @@ public class PostAdapter extends BaseAdapter implements Filterable { } notifyDataSetChanged(); - } - - public List getList() { - return sourceList; - } - - public void setEndOfLine(boolean endOfLine) { - this.endOfLine = endOfLine; - - notifyDataSetChanged(); - } - - /* TODO - public void scrollToPost(int no) { - if (isFiltering()) { - pendingScrollToPost = no; - } else { - notifyDataSetChanged(); - - synchronized (lock) { - for (int i = 0; i < displayList.size(); i++) { - if (displayList.get(i).no == no) { - if (Math.abs(i - listView.getFirstVisiblePosition()) > 20 || listView.getChildCount() == 0) { - listView.setSelection(i); - } else { - ScrollerRunnable r = new ScrollerRunnable(listView); - r.start(i); - } - - break; - } - } - } - } }*/ - public void setStatusMessage(String loadMessage) { - this.statusMessage = loadMessage; - } - - public String getStatusMessage() { - return statusMessage; - } - - private void onGetBottomView() { - /*if (postAdapterCallback.getLoadable().isBoardMode() && !endOfLine) { - // Try to load more posts - threadManager.requestNextData(); - }*/ - - if (lastPostCount != sourceList.size()) { - lastPostCount = sourceList.size(); - postAdapterCallback.onListScrolledToBottom(); - notifyDataSetChanged(); - } - } - - private boolean showStatusView() { - Loadable l = postAdapterCallback.getLoadable(); - if (l != null) { - return l.isBoardMode() || l.isThreadMode(); - } else { - return false; - } - } - - private boolean isFiltering() { - return !TextUtils.isEmpty(filter); - } - public interface PostAdapterCallback { void onFilteredResults(String filter, int count, boolean all); @@ -293,107 +314,6 @@ public class PostAdapter extends BaseAdapter implements Filterable { void onListScrolledToBottom(); - void onListStatusClicked(); - void scrollTo(int position); } - - public class StatusView extends LinearLayout { - boolean detached = false; - - public StatusView(Context activity) { - super(activity); - init(); - } - - public StatusView(Context activity, AttributeSet attr) { - super(activity, attr); - init(); - } - - public StatusView(Context activity, AttributeSet attr, int style) { - super(activity, attr, style); - init(); - } - - public void init() { - // TODO - /* - ChanLoader chanLoader = threadManager.getChanLoader(); - if (chanLoader == null) - return; - - setGravity(Gravity.CENTER); - - Loadable loadable = chanLoader.getLoadable(); - if (loadable.isThreadMode()) { - String error = getStatusMessage(); - if (error != null) { - setText(error); - } else { - if (threadManager.isWatching()) { - long time = chanLoader.getTimeUntilLoadMore() / 1000L; - if (time == 0) { - setText(statusPrefix + context.getString(R.string.thread_refresh_now)); - } else { - setText(statusPrefix + context.getString(R.string.thread_refresh_countdown, time)); - } - - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - if (!detached) { - notifyDataSetChanged(); - } - } - }, 1000); - } else { - if (chanLoader.getTimeUntilLoadMore() == 0) { - setText(statusPrefix + context.getString(R.string.thread_refresh_now)); - } else { - setText(statusPrefix + context.getString(R.string.thread_refresh_bar_inactive)); - } - } - - setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - ChanLoader chanLoader = threadManager.getChanLoader(); - if (chanLoader != null) { - chanLoader.requestMoreDataAndResetTimer(); - setText(context.getString(R.string.thread_refresh_now)); - } - - notifyDataSetChanged(); - } - }); - } - - Utils.setPressedDrawable(this); - } else if (loadable.isBoardMode()) { - if (endOfLine) { - setText(context.getString(R.string.thread_load_end_of_line)); - } else { - setProgressBar(); - } - }*/ - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - detached = true; - } - - private void setText(String string) { - TextView text = new TextView(context); - text.setText(string); - text.setGravity(Gravity.CENTER); - addView(text, new LayoutParams(LayoutParams.MATCH_PARENT, dp(48))); - } - - private void setProgressBar() { - addView(new ProgressBar(context)); - } - } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/cell/ThreadStatusCell.java b/Clover/app/src/main/java/org/floens/chan/ui/cell/ThreadStatusCell.java new file mode 100644 index 00000000..5ee6702d --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/ui/cell/ThreadStatusCell.java @@ -0,0 +1,154 @@ +package org.floens.chan.ui.cell; + +import android.content.Context; +import android.os.Build; +import android.os.Handler; +import android.os.Message; +import android.util.AttributeSet; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.floens.chan.R; +import org.floens.chan.core.model.ChanThread; +import org.floens.chan.core.model.Post; + +import static org.floens.chan.utils.AndroidUtils.getAttrDrawable; + +public class ThreadStatusCell extends LinearLayout implements View.OnClickListener { + private static final int UPDATE_INTERVAL = 1000; + private static final int MESSAGE_INVALIDATE = 1; + + private Callback callback; + + private boolean running = false; + + private TextView text; + private String error; + private Handler handler = new Handler(new Handler.Callback() { + @Override + public boolean handleMessage(Message msg) { + if (msg.what == MESSAGE_INVALIDATE) { + if (running) { + schedule(); + } + + update(); + return true; + } else { + return false; + } + } + }); + + 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); + } + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + text = (TextView) findViewById(R.id.text); + + setOnClickListener(this); + } + + public void setCallback(Callback callback) { + this.callback = callback; + } + + public void setError(String error) { + this.error = error; + } + + public void update() { + if (error != null) { + text.setText(error); + } else { + ChanThread chanThread = callback.getChanThread(); + if (chanThread == null) { + return; // Recyclerview not clearing immediately or view didn't receive onDetachedFromWindow + } + String statusText = ""; + + if (chanThread.archived) { + statusText += getContext().getString(R.string.thread_archived) + "\n"; + } else if (chanThread.closed) { + statusText += getContext().getString(R.string.thread_closed) + "\n"; + } + + if (!chanThread.archived && !chanThread.closed) { + long time = callback.getTimeUntilLoadMore() / 1000L; + if (!callback.isWatching()) { + statusText += getContext().getString(R.string.thread_refresh_bar_inactive) + "\n"; + } else if (time <= 0) { + statusText += getContext().getString(R.string.thread_refresh_now) + "\n"; + } else { + statusText += getContext().getString(R.string.thread_refresh_countdown, time) + "\n"; + } + } + + Post op = chanThread.op; + statusText += getContext().getString(R.string.thread_stats, op.replies, op.images, op.uniqueIps); + + text.setText(statusText); + } + } + + private void schedule() { + running = true; + Message message = handler.obtainMessage(1); + if (!handler.hasMessages(MESSAGE_INVALIDATE)) { + handler.sendMessageDelayed(message, UPDATE_INTERVAL); + } + } + + private void unschedule() { + running = false; + handler.removeMessages(MESSAGE_INVALIDATE); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + schedule(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + unschedule(); + } + + @Override + public void onWindowFocusChanged(boolean hasWindowFocus) { + super.onWindowFocusChanged(hasWindowFocus); + if (hasWindowFocus) { + schedule(); + } else { + unschedule(); + } + } + + @Override + public void onClick(View v) { + callback.onListStatusClicked(); + update(); + } + + public interface Callback { + long getTimeUntilLoadMore(); + + boolean isWatching(); + + ChanThread getChanThread(); + + void onListStatusClicked(); + } +} 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 8e786d32..535c5c3f 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 @@ -42,8 +42,6 @@ import org.floens.chan.utils.AndroidUtils; import java.util.ArrayList; import java.util.List; -import de.greenrobot.event.EventBus; - public class BrowseController extends ThreadController implements ToolbarMenuItem.ToolbarMenuItemCallback, ThreadLayout.ThreadLayoutCallback, FloatingMenu.FloatingMenuCallback, RootNavigationController.DrawerCallbacks { private static final int REFRESH_ID = 1; private static final int POST_ID = 2; @@ -61,8 +59,6 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte public void onCreate() { super.onCreate(); - EventBus.getDefault().register(this); - navigationItem.hasDrawer = true; navigationItem.middleMenu = new FloatingMenu(context); navigationItem.middleMenu.setCallback(this); @@ -87,13 +83,6 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte loadBoard(ChanApplication.getBoardManager().getSavedBoards().get(0)); } - @Override - public void onDestroy() { - super.onDestroy(); - - EventBus.getDefault().unregister(this); - } - @Override public void onMenuItemClicked(ToolbarMenuItem item) { switch ((Integer) item.getId()) { 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 356ed98d..2461df25 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 @@ -146,7 +146,7 @@ public class PostRepliesController extends Controller { final Post p = getItem(position); - postView.setPost(p, presenter); + postView.setPost(p, presenter, false); postView.setHighlightQuotesWithNo(data.forPost.no); postView.setOnClickListeners(new View.OnClickListener() { @Override 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 fe455e26..3a85f350 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 @@ -2,6 +2,7 @@ package org.floens.chan.ui.controller; import android.content.Context; +import org.floens.chan.ChanApplication; import org.floens.chan.controller.Controller; import org.floens.chan.core.model.Loadable; import org.floens.chan.core.model.PostImage; @@ -10,11 +11,20 @@ import org.floens.chan.ui.view.ThumbnailView; import java.util.List; +import de.greenrobot.event.EventBus; + public abstract class ThreadController extends Controller implements ThreadLayout.ThreadLayoutCallback, ImageViewerController.PreviewCallback { protected ThreadLayout threadLayout; public ThreadController(Context context) { super(context); + } + + @Override + public void onCreate() { + super.onCreate(); + + EventBus.getDefault().register(this); threadLayout = new ThreadLayout(context); threadLayout.setCallback(this); @@ -24,7 +34,14 @@ public abstract class ThreadController extends Controller implements ThreadLayou @Override public void onDestroy() { super.onDestroy(); + threadLayout.getPresenter().unbindLoadable(); + + EventBus.getDefault().unregister(this); + } + + public void onEvent(ChanApplication.ForegroundChangedMessage message) { + threadLayout.getPresenter().onForegroundChanged(message.inForeground); } public void presentRepliesController(Controller controller) { 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 cd0cc8dc..8336d0db 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 @@ -35,8 +35,6 @@ import org.floens.chan.utils.AndroidUtils; import java.util.Arrays; -import de.greenrobot.event.EventBus; - public class ViewThreadController extends ThreadController implements ThreadLayout.ThreadLayoutCallback, ToolbarMenuItem.ToolbarMenuItemCallback, RootNavigationController.DrawerCallbacks { private static final int POST_ID = 1; private static final int PIN_ID = 2; @@ -59,8 +57,6 @@ public class ViewThreadController extends ThreadController implements ThreadLayo public void onCreate() { super.onCreate(); - EventBus.getDefault().register(this); - view.setBackgroundColor(0xffffffff); navigationItem.hasDrawer = true; @@ -88,8 +84,6 @@ public class ViewThreadController extends ThreadController implements ThreadLayo @Override public void onDestroy() { super.onDestroy(); - - EventBus.getDefault().unregister(this); } public void onEvent(WatchManager.PinAddedMessage message) { @@ -106,7 +100,7 @@ public class ViewThreadController extends ThreadController implements ThreadLayo @Override public void showThread(final Loadable threadLoadable) { - // TODO implement, scroll to post and fix title + // TODO implement, scroll to post new AlertDialog.Builder(context) .setNegativeButton(R.string.cancel, null) .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { diff --git a/Clover/app/src/main/java/org/floens/chan/ui/fragment/PostRepliesFragment.java b/Clover/app/src/main/java/org/floens/chan/ui/fragment/PostRepliesFragment.java index c54595d9..22aced75 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/fragment/PostRepliesFragment.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/fragment/PostRepliesFragment.java @@ -136,7 +136,7 @@ public class PostRepliesFragment extends DialogFragment { final Post p = getItem(position); - postView.setPost(p, presenter); + postView.setPost(p, presenter, false); postView.setHighlightQuotesWithNo(repliesData.forPost.no); postView.setOnClickListeners(new View.OnClickListener() { @Override diff --git a/Clover/app/src/main/java/org/floens/chan/ui/fragment/ThreadFragment.java b/Clover/app/src/main/java/org/floens/chan/ui/fragment/ThreadFragment.java index b7dfc283..3718f7b2 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/fragment/ThreadFragment.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/fragment/ThreadFragment.java @@ -207,11 +207,11 @@ public class ThreadFragment extends Fragment implements ThreadManager.ThreadMana case R.id.action_download_album: // Get the posts with images ArrayList imagePosts = new ArrayList<>(); - for (Post post : postAdapter.getList()) { - if (post.hasImage) { - imagePosts.add(post); - } - } +// for (Post post : postAdapter.getList()) { +// if (post.hasImage) { +// imagePosts.add(post); +// } +// } if (imagePosts.size() > 0) { List list = new ArrayList<>(); @@ -273,7 +273,7 @@ public class ThreadFragment extends Fragment implements ThreadManager.ThreadMana } } - postAdapter.setStatusMessage(null); +// postAdapter.setError(null); postAdapter.setThread(thread); if (highlightedPost >= 0) { @@ -288,14 +288,14 @@ public class ThreadFragment extends Fragment implements ThreadManager.ThreadMana @Override public void onThreadLoadError(VolleyError error) { if (error instanceof EndOfLineException) { - postAdapter.setEndOfLine(true); +// postAdapter.setEndOfLine(true); } else { if (postAdapter == null) { if (container != null) { container.setView(getLoadErrorView(error)); } } else { - postAdapter.setStatusMessage(getLoadErrorText(error)); +// postAdapter.setError(getLoadErrorText(error)); } } @@ -320,11 +320,6 @@ public class ThreadFragment extends Fragment implements ThreadManager.ThreadMana } - @Override - public void onListStatusClicked() { - - } - @Override public void scrollTo(int position) { @@ -344,7 +339,7 @@ public class ThreadFragment extends Fragment implements ThreadManager.ThreadMana ListView list = new ListView(getActivity()); listView = list; // postAdapter = new PostAdapter(getActivity(), threadManager, listView, this); - listView.setAdapter(postAdapter); +// listView.setAdapter(postAdapter); list.setSelectionFromTop(loadable.listViewIndex, loadable.listViewTop); } else if (viewMode == ThreadManager.ViewMode.GRID) { GridView grid = new GridView(getActivity()); @@ -353,7 +348,7 @@ public class ThreadFragment extends Fragment implements ThreadManager.ThreadMana grid.setColumnWidth(postGridWidth); listView = grid; // postAdapter = new PostAdapter(getActivity(), threadManager, listView, this); - listView.setAdapter(postAdapter); +// listView.setAdapter(postAdapter); listView.setSelection(loadable.listViewIndex); } @@ -427,7 +422,7 @@ public class ThreadFragment extends Fragment implements ThreadManager.ThreadMana private void doFilter(String filter) { if (postAdapter != null) { - postAdapter.setFilter(filter); +// postAdapter.setFilter(filter); } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/helper/PostPopupHelper.java b/Clover/app/src/main/java/org/floens/chan/ui/helper/PostPopupHelper.java index 7b704fe6..bba63ee6 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/helper/PostPopupHelper.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/helper/PostPopupHelper.java @@ -81,8 +81,8 @@ public class PostPopupHelper { public void postClicked(Post p) { popAll(); - presenter.highlightPost(p.no); - presenter.scrollToPost(p.no); + presenter.highlightPost(p); + presenter.scrollToPost(p); } private void dismiss() { 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 1d656c0a..1edd6ca3 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 @@ -72,7 +72,7 @@ public class ThreadLayout extends LoadView implements ThreadPresenter.ThreadPres private TextView errorText; private Button errorRetryButton; private PostPopupHelper postPopupHelper; - private Visible visible; + private Visible visible = Visible.LOADING; public ThreadLayout(Context context) { super(context); @@ -92,8 +92,8 @@ public class ThreadLayout extends LoadView implements ThreadPresenter.ThreadPres private void init() { presenter = new ThreadPresenter(this); - threadListLayout = new ThreadListLayout(getContext()); - threadListLayout.setCallbacks(presenter, presenter); + threadListLayout = (ThreadListLayout) LayoutInflater.from(getContext()).inflate(R.layout.layout_thread_list, this, false); + threadListLayout.setCallbacks(presenter, presenter, presenter); postPopupHelper = new PostPopupHelper(getContext(), presenter, this); @@ -104,7 +104,6 @@ public class ThreadLayout extends LoadView implements ThreadPresenter.ThreadPres errorRetryButton = (Button) errorLayout.findViewById(R.id.button); errorRetryButton.setOnClickListener(this); - switchVisible(Visible.LOADING); } @@ -132,8 +131,6 @@ public class ThreadLayout extends LoadView implements ThreadPresenter.ThreadPres @Override public void showError(VolleyError error) { - switchVisible(Visible.ERROR); - String errorMessage; if (error.getCause() instanceof SSLException) { errorMessage = getContext().getString(R.string.thread_load_failed_ssl); @@ -145,7 +142,12 @@ public class ThreadLayout extends LoadView implements ThreadPresenter.ThreadPres errorMessage = getContext().getString(R.string.thread_load_failed_parsing); } - errorText.setText(errorMessage); + if (visible == Visible.THREAD) { + threadListLayout.showError(errorMessage); + } else { + switchVisible(Visible.ERROR); + errorText.setText(errorMessage); + } } @Override @@ -222,6 +224,16 @@ public class ThreadLayout extends LoadView implements ThreadPresenter.ThreadPres threadListLayout.scrollTo(position); } + @Override + public void highlightPost(Post post) { + threadListLayout.highlightPost(post); + } + + @Override + public void highlightPostId(String id) { + threadListLayout.highlightPostId(id); + } + public ThumbnailView getThumbnail(PostImage postImage) { if (postPopupHelper.isOpen()) { return postPopupHelper.getThumbnail(postImage); @@ -236,6 +248,12 @@ public class ThreadLayout extends LoadView implements ThreadPresenter.ThreadPres private void switchVisible(Visible visible) { if (this.visible != visible) { + switch (this.visible) { + case THREAD: + threadListLayout.cleanup(); + break; + } + this.visible = visible; switch (visible) { case LOADING: 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 d02fce70..a1346cc4 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,84 +18,75 @@ package org.floens.chan.ui.layout; import android.content.Context; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.view.View; -import android.widget.ListView; import android.widget.RelativeLayout; +import org.floens.chan.R; import org.floens.chan.core.model.ChanThread; import org.floens.chan.core.model.Post; import org.floens.chan.core.model.PostImage; import org.floens.chan.ui.adapter.PostAdapter; +import org.floens.chan.ui.cell.ThreadStatusCell; import org.floens.chan.ui.view.PostView; import org.floens.chan.ui.view.ThumbnailView; /** - * A layout that wraps around a listview to manage showing posts. + * A layout that wraps around a {@link RecyclerView} to manage showing posts. */ public class ThreadListLayout extends RelativeLayout { - private ListView listView; + private RecyclerView recyclerView; private PostAdapter postAdapter; private PostAdapter.PostAdapterCallback postAdapterCallback; private PostView.PostViewCallback postViewCallback; - private int restoreListViewIndex; - private int restoreListViewTop; - - public ThreadListLayout(Context context) { - super(context); - init(); - } - public ThreadListLayout(Context context, AttributeSet attrs) { super(context, attrs); - init(); - } - - public ThreadListLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(); } @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - restoreListViewIndex = listView.getFirstVisiblePosition(); - restoreListViewTop = listView.getChildAt(0) == null ? 0 : listView.getChildAt(0).getTop(); + protected void onFinishInflate() { + super.onFinishInflate(); + recyclerView = (RecyclerView) findViewById(R.id.recycler_view); + LinearLayoutManager lm = new LinearLayoutManager(getContext()); + recyclerView.setLayoutManager(lm); } - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - listView.setSelectionFromTop(restoreListViewIndex, restoreListViewTop); - } - - private void init() { - listView = new ListView(getContext()); - addView(listView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); - } - - public void setCallbacks(PostAdapter.PostAdapterCallback postAdapterCallback, PostView.PostViewCallback postViewCallback) { + public void setCallbacks(PostAdapter.PostAdapterCallback postAdapterCallback, PostView.PostViewCallback postViewCallback, ThreadStatusCell.Callback statusCellCallback) { this.postAdapterCallback = postAdapterCallback; this.postViewCallback = postViewCallback; - - postAdapter = new PostAdapter(getContext(), postAdapterCallback, postViewCallback); - listView.setAdapter(postAdapter); + postAdapter = new PostAdapter(recyclerView, postAdapterCallback, postViewCallback, statusCellCallback); + recyclerView.setAdapter(postAdapter); + recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + } + }); } public void showPosts(ChanThread thread, boolean initial) { if (initial) { - listView.setSelectionFromTop(0, 0); - restoreListViewIndex = 0; - restoreListViewTop = 0; + recyclerView.scrollToPosition(0); } postAdapter.setThread(thread); } + public void showError(String error) { + postAdapter.showError(error); + } + + public void cleanup() { + postAdapter.cleanup(); + } + public ThumbnailView getThumbnail(PostImage postImage) { + RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); + ThumbnailView thumbnail = null; - for (int i = 0; i < listView.getChildCount(); i++) { - View view = listView.getChildAt(i); + for (int i = 0; i < layoutManager.getChildCount(); i++) { + View view = layoutManager.getChildAt(i); if (view instanceof PostView) { PostView postView = (PostView) view; Post post = postView.getPost(); @@ -109,6 +100,14 @@ public class ThreadListLayout extends RelativeLayout { } public void scrollTo(int position) { - listView.smoothScrollToPosition(position); + recyclerView.smoothScrollToPosition(position); + } + + public void highlightPost(Post post) { + postAdapter.highlightPost(post); + } + + public void highlightPostId(String id) { + postAdapter.highlightPostId(id); } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/view/PostView.java b/Clover/app/src/main/java/org/floens/chan/ui/view/PostView.java index 340a6b48..0e7880e0 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/view/PostView.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/view/PostView.java @@ -121,7 +121,7 @@ public class PostView extends LinearLayout implements View.OnClickListener, Post } } - public void setPost(final Post post, final PostViewCallback callback) { + public void setPost(final Post post, final PostViewCallback callback, boolean highlighted) { if (this.post != null) { // Remove callbacks from the old post while it is still set setPostLinkableListener(null); @@ -249,7 +249,7 @@ public class PostView extends LinearLayout implements View.OnClickListener, Post if (post.isSavedReply) { full.setBackgroundColor(savedReplyColor); - } else if (callback.isPostHightlighted(post)) { + } else if (highlighted) { full.setBackgroundColor(highlightedColor); } else { full.setBackgroundColor(0x00000000); @@ -543,8 +543,6 @@ public class PostView extends LinearLayout implements View.OnClickListener, Post void onPostLinkableClicked(PostLinkable linkable); - boolean isPostHightlighted(Post post); - boolean isPostLastSeen(Post post); } diff --git a/Clover/app/src/main/res/layout/cell_thread_status.xml b/Clover/app/src/main/res/layout/cell_thread_status.xml new file mode 100644 index 00000000..99cd5a3f --- /dev/null +++ b/Clover/app/src/main/res/layout/cell_thread_status.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/Clover/app/src/main/res/layout/layout_thread_list.xml b/Clover/app/src/main/res/layout/layout_thread_list.xml new file mode 100644 index 00000000..54cf99e6 --- /dev/null +++ b/Clover/app/src/main/res/layout/layout_thread_list.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/Clover/app/src/main/res/values/strings.xml b/Clover/app/src/main/res/values/strings.xml index f2c1694e..0e6fcf3e 100644 --- a/Clover/app/src/main/res/values/strings.xml +++ b/Clover/app/src/main/res/values/strings.xml @@ -85,6 +85,7 @@ along with this program. If not, see . Retry Archived Closed + %1$sR / %2$sI / %3$sP Board editor Add, remove and reorder your boards here.\nThe topmost board will be loaded automatically. @@ -116,7 +117,7 @@ along with this program. If not, see . Quote Quote text Info - Show clickables + Show links Copy text Report