Use RecyclerView for the post list

filtering
Floens 10 years ago
parent 0cb9268e0f
commit 94d17a4c80
  1. 6
      Clover/app/build.gradle
  2. 16
      Clover/app/src/main/java/org/floens/chan/core/loader/ChanLoader.java
  3. 1
      Clover/app/src/main/java/org/floens/chan/core/model/Post.java
  4. 28
      Clover/app/src/main/java/org/floens/chan/core/net/ChanReaderRequest.java
  5. 78
      Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java
  6. 2
      Clover/app/src/main/java/org/floens/chan/core/watch/PinWatcher.java
  7. 4
      Clover/app/src/main/java/org/floens/chan/ui/ThemeActivity.java
  8. 10
      Clover/app/src/main/java/org/floens/chan/ui/activity/ImageViewActivity.java
  9. 4
      Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java
  10. 2
      Clover/app/src/main/java/org/floens/chan/ui/adapter/PinAdapter.java
  11. 350
      Clover/app/src/main/java/org/floens/chan/ui/adapter/PostAdapter.java
  12. 154
      Clover/app/src/main/java/org/floens/chan/ui/cell/ThreadStatusCell.java
  13. 11
      Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java
  14. 2
      Clover/app/src/main/java/org/floens/chan/ui/controller/PostRepliesController.java
  15. 17
      Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java
  16. 8
      Clover/app/src/main/java/org/floens/chan/ui/controller/ViewThreadController.java
  17. 2
      Clover/app/src/main/java/org/floens/chan/ui/fragment/PostRepliesFragment.java
  18. 27
      Clover/app/src/main/java/org/floens/chan/ui/fragment/ThreadFragment.java
  19. 4
      Clover/app/src/main/java/org/floens/chan/ui/helper/PostPopupHelper.java
  20. 32
      Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java
  21. 83
      Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java
  22. 6
      Clover/app/src/main/java/org/floens/chan/ui/view/PostView.java
  23. 14
      Clover/app/src/main/res/layout/cell_thread_status.xml
  24. 12
      Clover/app/src/main/res/layout/layout_thread_list.xml
  25. 3
      Clover/app/src/main/res/values/strings.xml

@ -71,9 +71,9 @@ android {
} }
dependencies { dependencies {
compile 'com.android.support:support-v13:22.0.0' compile 'com.android.support:support-v13:22.1.0'
compile 'com.android.support:appcompat-v7:22.0.0' compile 'com.android.support:appcompat-v7:22.1.0'
compile 'com.android.support:recyclerview-v7:22.0.0' compile 'com.android.support:recyclerview-v7:22.1.0'
compile 'org.jsoup:jsoup:1.8.1' compile 'org.jsoup:jsoup:1.8.1'
compile 'com.j256.ormlite:ormlite-core:4.48' compile 'com.j256.ormlite:ormlite-core:4.48'

@ -253,7 +253,7 @@ public class ChanLoader {
} }
private ChanReaderRequest getData() { private ChanReaderRequest getData() {
// Logger.i(TAG, "Requested " + loadable.board + ", " + loadable.no); Logger.i(TAG, "Requested " + loadable.board + ", " + loadable.no);
List<Post> cached = thread == null ? new ArrayList<Post>() : thread.posts; List<Post> cached = thread == null ? new ArrayList<Post>() : thread.posts;
ChanReaderRequest request = ChanReaderRequest.newInstance(loadable, cached, ChanReaderRequest request = ChanReaderRequest.newInstance(loadable, cached,
@ -329,23 +329,21 @@ public class ChanLoader {
post.title = loadable.title; post.title = loadable.title;
} }
for (ChanLoaderCallback l : listeners) {
l.onChanLoaderData(thread);
}
lastLoadTime = Time.get(); lastLoadTime = Time.get();
if (loadable.isThreadMode()) { if (loadable.isThreadMode()) {
setTimer(result.size()); setTimer(result.size());
} }
for (ChanLoaderCallback l : listeners) {
l.onChanLoaderData(thread);
}
} }
private void onError(VolleyError error) { private void onError(VolleyError error) {
if (destroyed) if (destroyed)
return; return;
thread = null;
Logger.e(TAG, "Error loading " + error.getMessage(), error); Logger.e(TAG, "Error loading " + error.getMessage(), error);
// 404 with more pages already loaded means endofline // 404 with more pages already loaded means endofline
@ -353,11 +351,11 @@ public class ChanLoader {
error = new EndOfLineException(); error = new EndOfLineException();
} }
clearTimer();
for (ChanLoaderCallback l : listeners) { for (ChanLoaderCallback l : listeners) {
l.onChanLoaderError(error); l.onChanLoaderError(error);
} }
clearTimer();
} }
public interface ChanLoaderCallback { public interface ChanLoaderCallback {

@ -82,6 +82,7 @@ public class Post {
public String rawComment; public String rawComment;
public String countryUrl; public String countryUrl;
public boolean spoiler = false; public boolean spoiler = false;
public int uniqueIps = 1;
public boolean deleted = false; public boolean deleted = false;

@ -41,8 +41,7 @@ public class ChanReaderRequest extends JsonReaderRequest<List<Post>> {
super(url, listener, errorListener); super(url, listener, errorListener);
} }
public static ChanReaderRequest newInstance(Loadable loadable, List<Post> cached, Listener<List<Post>> listener, public static ChanReaderRequest newInstance(Loadable loadable, List<Post> cached, Listener<List<Post>> listener, ErrorListener errorListener) {
ErrorListener errorListener) {
String url; String url;
if (loadable.isBoardMode()) { if (loadable.isBoardMode()) {
@ -126,6 +125,11 @@ public class ChanReaderRequest extends JsonReaderRequest<List<Post>> {
} }
} }
// 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 // Sort if it got out of order due to posts disappearing/reappearing
/*if (loadable.isThreadMode()) { /*if (loadable.isThreadMode()) {
Collections.sort(totalList, new Comparator<Post>() { Collections.sort(totalList, new Comparator<Post>() {
@ -140,7 +144,7 @@ public class ChanReaderRequest extends JsonReaderRequest<List<Post>> {
totalList.addAll(serverList); totalList.addAll(serverList);
} }
Set<Integer> invalidatedPosts = new HashSet<>(); Set<Integer> postsReplyingToDeleted = new HashSet<>();
for (Post post : totalList) { for (Post post : totalList) {
if (!post.deleted) { if (!post.deleted) {
post.repliesFrom.clear(); post.repliesFrom.clear();
@ -154,14 +158,14 @@ public class ChanReaderRequest extends JsonReaderRequest<List<Post>> {
post.repliesTo.clear(); post.repliesTo.clear();
for (int no : post.repliesFrom) { for (int no : post.repliesFrom) {
invalidatedPosts.add(no); postsReplyingToDeleted.add(no);
} }
post.repliesFrom.clear(); post.repliesFrom.clear();
} }
} }
for (int no : invalidatedPosts) { for (int no : postsReplyingToDeleted) {
for (Post post : totalList) { for (Post post : totalList) {
if (post.no == no) { if (post.no == no) {
if (!post.finish()) { if (!post.finish()) {
@ -357,6 +361,9 @@ public class ChanReaderRequest extends JsonReaderRequest<List<Post>> {
case "spoiler": case "spoiler":
post.spoiler = reader.nextInt() == 1; post.spoiler = reader.nextInt() == 1;
break; break;
case "unique_ips":
post.uniqueIps = reader.nextInt();
break;
default: default:
// Unknown/ignored key // Unknown/ignored key
// log("Unknown/ignored key: " + key + "."); // log("Unknown/ignored key: " + key + ".");
@ -367,10 +374,13 @@ public class ChanReaderRequest extends JsonReaderRequest<List<Post>> {
reader.endObject(); reader.endObject();
Post cachedResult = null; Post cachedResult = null;
for (Post possibleCached : cached) { // Do not cache OPs to make sure the archived, replies etc. are updated
if (possibleCached.no == post.no) { if (post.resto != 0) {
cachedResult = possibleCached; for (Post possibleCached : cached) {
break; if (possibleCached.no == post.no) {
cachedResult = possibleCached;
break;
}
} }
} }

@ -37,6 +37,7 @@ import org.floens.chan.core.model.PostLinkable;
import org.floens.chan.core.model.SavedReply; import org.floens.chan.core.model.SavedReply;
import org.floens.chan.core.settings.ChanSettings; import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.ui.adapter.PostAdapter; 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.PostView;
import org.floens.chan.ui.view.ThumbnailView; import org.floens.chan.ui.view.ThumbnailView;
import org.floens.chan.utils.AndroidUtils; import org.floens.chan.utils.AndroidUtils;
@ -44,7 +45,7 @@ import org.floens.chan.utils.AndroidUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; 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 ThreadPresenterCallback threadPresenterCallback;
private Loadable loadable; private Loadable loadable;
@ -80,6 +81,19 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
chanLoader.requestData(); 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() { public boolean pin() {
if (chanLoader.getThread() != null) { if (chanLoader.getThread() != null) {
WatchManager wm = ChanApplication.getWatchManager(); WatchManager wm = ChanApplication.getWatchManager();
@ -108,12 +122,12 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
*/ */
@Override @Override
public void onChanLoaderData(ChanThread result) { public void onChanLoaderData(ChanThread result) {
chanLoader.setAutoLoadMore(isWatching());
threadPresenterCallback.showPosts(result); threadPresenterCallback.showPosts(result);
} }
@Override @Override
public void onChanLoaderError(VolleyError error) { public void onChanLoaderError(VolleyError error) {
// TODO show error in status view is content is shown
threadPresenterCallback.showError(error); threadPresenterCallback.showError(error);
} }
@ -127,12 +141,16 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
@Override @Override
public void onListScrolledToBottom() { public void onListScrolledToBottom() {
if (loadable.isThreadMode()) {
List<Post> posts = chanLoader.getThread().posts;
loadable.lastViewed = posts.get(posts.size() - 1).no;
}
} Pin pin = ChanApplication.getWatchManager().findPinByLoadable(loadable);
if (pin != null) {
@Override pin.onBottomPostViewed();
public void onListStatusClicked() { ChanApplication.getWatchManager().updatePin(pin);
}
} }
@Override @Override
@ -152,6 +170,22 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
scrollTo(position); 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 * PostView callbacks
*/ */
@ -236,9 +270,7 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
AndroidUtils.openLink(ChanUrls.getReportUrl(post.board, post.no)); AndroidUtils.openLink(ChanUrls.getReportUrl(post.board, post.no));
break; break;
case 6: // Id case 6: // Id
//TODO threadPresenterCallback.highlightPostId(post.id);
// highlightedId = post.id;
// threadManagerListener.onRefreshView();
break; break;
case 7: // Delete case 7: // Delete
// deletePost(post); TODO // deletePost(post); TODO
@ -285,19 +317,31 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
} }
@Override @Override
public boolean isPostHightlighted(Post post) { public boolean isPostLastSeen(Post post) {
return false; 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 @Override
public boolean isPostLastSeen(Post post) { public ChanThread getChanThread() {
return false; return chanLoader.getThread();
}
@Override
public void onListStatusClicked() {
chanLoader.requestMoreDataAndResetTimer();
} }
private void showPostInfo(Post post) { private void showPostInfo(Post post) {
@ -360,5 +404,9 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
void showImages(List<PostImage> images, int index, Loadable loadable, ThumbnailView thumbnail); void showImages(List<PostImage> images, int index, Loadable loadable, ThumbnailView thumbnail);
void scrollTo(int position); void scrollTo(int position);
void highlightPost(Post post);
void highlightPostId(String id);
} }
} }

@ -116,7 +116,7 @@ public class PinWatcher implements ChanLoader.ChanLoaderCallback {
@Override @Override
public void onChanLoaderError(VolleyError error) { public void onChanLoaderError(VolleyError error) {
Logger.e(TAG, "PinWatcher onError: ", error); Logger.e(TAG, "PinWatcher onError");
pin.isError = true; pin.isError = true;
AndroidUtils.runOnUiThread(new Runnable() { AndroidUtils.runOnUiThread(new Runnable() {

@ -17,14 +17,14 @@
*/ */
package org.floens.chan.ui; 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.support.v7.widget.Toolbar;
import android.view.MenuItem; import android.view.MenuItem;
import org.floens.chan.R; import org.floens.chan.R;
import org.floens.chan.utils.ThemeHelper; import org.floens.chan.utils.ThemeHelper;
public class ThemeActivity extends ActionBarActivity { public class ThemeActivity extends AppCompatActivity {
private Toolbar toolbar; private Toolbar toolbar;
public void setTheme() { public void setTheme() {

@ -112,11 +112,11 @@ public class ImageViewActivity extends ThemeActivity implements ViewPager.OnPage
private void initPager() { private void initPager() {
// Get the posts with images // Get the posts with images
ArrayList<Post> imagePosts = new ArrayList<>(); ArrayList<Post> imagePosts = new ArrayList<>();
for (Post post : postAdapter.getList()) { // for (Post post : postAdapter.getList()) {
if (post.hasImage) { // if (post.hasImage) {
imagePosts.add(post); // imagePosts.add(post);
} // }
} // }
// Setup our pages and adapter // Setup our pages and adapter
viewPager = (ViewPager) findViewById(R.id.image_pager); viewPager = (ViewPager) findViewById(R.id.image_pager);

@ -1,8 +1,8 @@
package org.floens.chan.ui.activity; package org.floens.chan.ui.activity;
import android.app.Activity;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.ViewGroup; import android.view.ViewGroup;
import org.floens.chan.ChanApplication; import org.floens.chan.ChanApplication;
@ -14,7 +14,7 @@ import org.floens.chan.utils.ThemeHelper;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class StartActivity extends Activity { public class StartActivity extends AppCompatActivity {
private static final String TAG = "StartActivity"; private static final String TAG = "StartActivity";
private ViewGroup contentView; private ViewGroup contentView;

@ -182,7 +182,7 @@ public class PinAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> im
if (ChanSettings.watchEnabled.get()) { if (ChanSettings.watchEnabled.get()) {
String count; String count;
if (pin.isError) { if (pin.isError) {
count = "Err"; count = "E";
} else { } else {
int c = pin.getNewPostCount(); int c = pin.getNewPostCount();
if (c > 999) { if (c > 999) {

@ -17,94 +17,183 @@
*/ */
package org.floens.chan.ui.adapter; package org.floens.chan.ui.adapter;
import android.content.Context; import android.support.v7.widget.RecyclerView;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.AttributeSet; import android.view.LayoutInflater;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup; 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.R;
import org.floens.chan.core.model.ChanThread; import org.floens.chan.core.model.ChanThread;
import org.floens.chan.core.model.Loadable; import org.floens.chan.core.model.Loadable;
import org.floens.chan.core.model.Post; import org.floens.chan.core.model.Post;
import org.floens.chan.ui.cell.ThreadStatusCell;
import org.floens.chan.ui.view.PostView; import org.floens.chan.ui.view.PostView;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale;
import static org.floens.chan.utils.AndroidUtils.dp; public class PostAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int TYPE_POST = 0;
public class PostAdapter extends BaseAdapter implements Filterable { private static final int TYPE_STATUS = 1;
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;
private final PostAdapterCallback postAdapterCallback; private final PostAdapterCallback postAdapterCallback;
private final PostView.PostViewCallback postViewCallback; private final PostView.PostViewCallback postViewCallback;
private final ThreadStatusCell.Callback statusCellCallback;
private RecyclerView recyclerView;
/**
* The list with the original data
*/
private final List<Post> sourceList = new ArrayList<>(); private final List<Post> sourceList = new ArrayList<>();
/**
* The list that is displayed (filtered)
*/
private final List<Post> displayList = new ArrayList<>(); private final List<Post> displayList = new ArrayList<>();
private boolean endOfLine;
private int lastPostCount = 0; private int lastPostCount = 0;
private String statusMessage = null; private String error = null;
private String filter = ""; private String filter = "";
private int pendingScrollToPost = -1; 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.postAdapterCallback = postAdapterCallback;
this.context = context;
this.postViewCallback = postViewCallback; 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 @Override
public int getCount() { public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
return displayList.size() + (showStatusView() ? 1 : 0); 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 @Override
public int getViewTypeCount() { public int getItemCount() {
return 2; if (showStatusView()) {
return displayList.size() + 1;
} else {
return displayList.size();
}
} }
@Override @Override
public int getItemViewType(int position) { public int getItemViewType(int position) {
if (position == getCount() - 1) { if (showStatusView()) {
return showStatusView() ? VIEW_TYPE_STATUS : VIEW_TYPE_ITEM; if (position == getItemCount() - 1) {
return TYPE_STATUS;
} else {
return TYPE_POST;
}
} else { } else {
return VIEW_TYPE_ITEM; return TYPE_POST;
} }
} }
@Override @Override
public Post getItem(int position) { public long getItemId(int position) {
int realPosition = position; if (getItemViewType(position) != TYPE_POST) {
if (realPosition >= 0 && realPosition < displayList.size()) { return -1;
return displayList.get(realPosition);
} else { } 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 @Override
public long getItemId(int position) { public long getItemId(int position) {
return position; return position;
@ -113,7 +202,7 @@ public class PostAdapter extends BaseAdapter implements Filterable {
@Override @Override
public View getView(int position, View convertView, ViewGroup parent) { public View getView(int position, View convertView, ViewGroup parent) {
if (position >= getCount() - 1) { if (position >= getCount() - 1) {
onGetBottomView(); onScrolledToBottom();
} }
switch (getItemViewType(position)) { switch (getItemViewType(position)) {
@ -216,76 +305,8 @@ public class PostAdapter extends BaseAdapter implements Filterable {
} }
notifyDataSetChanged(); notifyDataSetChanged();
}
public List<Post> 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 { public interface PostAdapterCallback {
void onFilteredResults(String filter, int count, boolean all); void onFilteredResults(String filter, int count, boolean all);
@ -293,107 +314,6 @@ public class PostAdapter extends BaseAdapter implements Filterable {
void onListScrolledToBottom(); void onListScrolledToBottom();
void onListStatusClicked();
void scrollTo(int position); 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));
}
}
} }

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

@ -42,8 +42,6 @@ import org.floens.chan.utils.AndroidUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import de.greenrobot.event.EventBus;
public class BrowseController extends ThreadController implements ToolbarMenuItem.ToolbarMenuItemCallback, ThreadLayout.ThreadLayoutCallback, FloatingMenu.FloatingMenuCallback, RootNavigationController.DrawerCallbacks { 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 REFRESH_ID = 1;
private static final int POST_ID = 2; private static final int POST_ID = 2;
@ -61,8 +59,6 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
EventBus.getDefault().register(this);
navigationItem.hasDrawer = true; navigationItem.hasDrawer = true;
navigationItem.middleMenu = new FloatingMenu(context); navigationItem.middleMenu = new FloatingMenu(context);
navigationItem.middleMenu.setCallback(this); navigationItem.middleMenu.setCallback(this);
@ -87,13 +83,6 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte
loadBoard(ChanApplication.getBoardManager().getSavedBoards().get(0)); loadBoard(ChanApplication.getBoardManager().getSavedBoards().get(0));
} }
@Override
public void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
@Override @Override
public void onMenuItemClicked(ToolbarMenuItem item) { public void onMenuItemClicked(ToolbarMenuItem item) {
switch ((Integer) item.getId()) { switch ((Integer) item.getId()) {

@ -146,7 +146,7 @@ public class PostRepliesController extends Controller {
final Post p = getItem(position); final Post p = getItem(position);
postView.setPost(p, presenter); postView.setPost(p, presenter, false);
postView.setHighlightQuotesWithNo(data.forPost.no); postView.setHighlightQuotesWithNo(data.forPost.no);
postView.setOnClickListeners(new View.OnClickListener() { postView.setOnClickListeners(new View.OnClickListener() {
@Override @Override

@ -2,6 +2,7 @@ package org.floens.chan.ui.controller;
import android.content.Context; import android.content.Context;
import org.floens.chan.ChanApplication;
import org.floens.chan.controller.Controller; import org.floens.chan.controller.Controller;
import org.floens.chan.core.model.Loadable; import org.floens.chan.core.model.Loadable;
import org.floens.chan.core.model.PostImage; import org.floens.chan.core.model.PostImage;
@ -10,11 +11,20 @@ import org.floens.chan.ui.view.ThumbnailView;
import java.util.List; import java.util.List;
import de.greenrobot.event.EventBus;
public abstract class ThreadController extends Controller implements ThreadLayout.ThreadLayoutCallback, ImageViewerController.PreviewCallback { public abstract class ThreadController extends Controller implements ThreadLayout.ThreadLayoutCallback, ImageViewerController.PreviewCallback {
protected ThreadLayout threadLayout; protected ThreadLayout threadLayout;
public ThreadController(Context context) { public ThreadController(Context context) {
super(context); super(context);
}
@Override
public void onCreate() {
super.onCreate();
EventBus.getDefault().register(this);
threadLayout = new ThreadLayout(context); threadLayout = new ThreadLayout(context);
threadLayout.setCallback(this); threadLayout.setCallback(this);
@ -24,7 +34,14 @@ public abstract class ThreadController extends Controller implements ThreadLayou
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
threadLayout.getPresenter().unbindLoadable(); threadLayout.getPresenter().unbindLoadable();
EventBus.getDefault().unregister(this);
}
public void onEvent(ChanApplication.ForegroundChangedMessage message) {
threadLayout.getPresenter().onForegroundChanged(message.inForeground);
} }
public void presentRepliesController(Controller controller) { public void presentRepliesController(Controller controller) {

@ -35,8 +35,6 @@ import org.floens.chan.utils.AndroidUtils;
import java.util.Arrays; import java.util.Arrays;
import de.greenrobot.event.EventBus;
public class ViewThreadController extends ThreadController implements ThreadLayout.ThreadLayoutCallback, ToolbarMenuItem.ToolbarMenuItemCallback, RootNavigationController.DrawerCallbacks { public class ViewThreadController extends ThreadController implements ThreadLayout.ThreadLayoutCallback, ToolbarMenuItem.ToolbarMenuItemCallback, RootNavigationController.DrawerCallbacks {
private static final int POST_ID = 1; private static final int POST_ID = 1;
private static final int PIN_ID = 2; private static final int PIN_ID = 2;
@ -59,8 +57,6 @@ public class ViewThreadController extends ThreadController implements ThreadLayo
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
EventBus.getDefault().register(this);
view.setBackgroundColor(0xffffffff); view.setBackgroundColor(0xffffffff);
navigationItem.hasDrawer = true; navigationItem.hasDrawer = true;
@ -88,8 +84,6 @@ public class ViewThreadController extends ThreadController implements ThreadLayo
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
EventBus.getDefault().unregister(this);
} }
public void onEvent(WatchManager.PinAddedMessage message) { public void onEvent(WatchManager.PinAddedMessage message) {
@ -106,7 +100,7 @@ public class ViewThreadController extends ThreadController implements ThreadLayo
@Override @Override
public void showThread(final Loadable threadLoadable) { public void showThread(final Loadable threadLoadable) {
// TODO implement, scroll to post and fix title // TODO implement, scroll to post
new AlertDialog.Builder(context) new AlertDialog.Builder(context)
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {

@ -136,7 +136,7 @@ public class PostRepliesFragment extends DialogFragment {
final Post p = getItem(position); final Post p = getItem(position);
postView.setPost(p, presenter); postView.setPost(p, presenter, false);
postView.setHighlightQuotesWithNo(repliesData.forPost.no); postView.setHighlightQuotesWithNo(repliesData.forPost.no);
postView.setOnClickListeners(new View.OnClickListener() { postView.setOnClickListeners(new View.OnClickListener() {
@Override @Override

@ -207,11 +207,11 @@ public class ThreadFragment extends Fragment implements ThreadManager.ThreadMana
case R.id.action_download_album: case R.id.action_download_album:
// Get the posts with images // Get the posts with images
ArrayList<Post> imagePosts = new ArrayList<>(); ArrayList<Post> imagePosts = new ArrayList<>();
for (Post post : postAdapter.getList()) { // for (Post post : postAdapter.getList()) {
if (post.hasImage) { // if (post.hasImage) {
imagePosts.add(post); // imagePosts.add(post);
} // }
} // }
if (imagePosts.size() > 0) { if (imagePosts.size() > 0) {
List<ImageSaver.DownloadPair> list = new ArrayList<>(); List<ImageSaver.DownloadPair> list = new ArrayList<>();
@ -273,7 +273,7 @@ public class ThreadFragment extends Fragment implements ThreadManager.ThreadMana
} }
} }
postAdapter.setStatusMessage(null); // postAdapter.setError(null);
postAdapter.setThread(thread); postAdapter.setThread(thread);
if (highlightedPost >= 0) { if (highlightedPost >= 0) {
@ -288,14 +288,14 @@ public class ThreadFragment extends Fragment implements ThreadManager.ThreadMana
@Override @Override
public void onThreadLoadError(VolleyError error) { public void onThreadLoadError(VolleyError error) {
if (error instanceof EndOfLineException) { if (error instanceof EndOfLineException) {
postAdapter.setEndOfLine(true); // postAdapter.setEndOfLine(true);
} else { } else {
if (postAdapter == null) { if (postAdapter == null) {
if (container != null) { if (container != null) {
container.setView(getLoadErrorView(error)); container.setView(getLoadErrorView(error));
} }
} else { } 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 @Override
public void scrollTo(int position) { public void scrollTo(int position) {
@ -344,7 +339,7 @@ public class ThreadFragment extends Fragment implements ThreadManager.ThreadMana
ListView list = new ListView(getActivity()); ListView list = new ListView(getActivity());
listView = list; listView = list;
// postAdapter = new PostAdapter(getActivity(), threadManager, listView, this); // postAdapter = new PostAdapter(getActivity(), threadManager, listView, this);
listView.setAdapter(postAdapter); // listView.setAdapter(postAdapter);
list.setSelectionFromTop(loadable.listViewIndex, loadable.listViewTop); list.setSelectionFromTop(loadable.listViewIndex, loadable.listViewTop);
} else if (viewMode == ThreadManager.ViewMode.GRID) { } else if (viewMode == ThreadManager.ViewMode.GRID) {
GridView grid = new GridView(getActivity()); GridView grid = new GridView(getActivity());
@ -353,7 +348,7 @@ public class ThreadFragment extends Fragment implements ThreadManager.ThreadMana
grid.setColumnWidth(postGridWidth); grid.setColumnWidth(postGridWidth);
listView = grid; listView = grid;
// postAdapter = new PostAdapter(getActivity(), threadManager, listView, this); // postAdapter = new PostAdapter(getActivity(), threadManager, listView, this);
listView.setAdapter(postAdapter); // listView.setAdapter(postAdapter);
listView.setSelection(loadable.listViewIndex); listView.setSelection(loadable.listViewIndex);
} }
@ -427,7 +422,7 @@ public class ThreadFragment extends Fragment implements ThreadManager.ThreadMana
private void doFilter(String filter) { private void doFilter(String filter) {
if (postAdapter != null) { if (postAdapter != null) {
postAdapter.setFilter(filter); // postAdapter.setFilter(filter);
} }
} }

@ -81,8 +81,8 @@ public class PostPopupHelper {
public void postClicked(Post p) { public void postClicked(Post p) {
popAll(); popAll();
presenter.highlightPost(p.no); presenter.highlightPost(p);
presenter.scrollToPost(p.no); presenter.scrollToPost(p);
} }
private void dismiss() { private void dismiss() {

@ -72,7 +72,7 @@ public class ThreadLayout extends LoadView implements ThreadPresenter.ThreadPres
private TextView errorText; private TextView errorText;
private Button errorRetryButton; private Button errorRetryButton;
private PostPopupHelper postPopupHelper; private PostPopupHelper postPopupHelper;
private Visible visible; private Visible visible = Visible.LOADING;
public ThreadLayout(Context context) { public ThreadLayout(Context context) {
super(context); super(context);
@ -92,8 +92,8 @@ public class ThreadLayout extends LoadView implements ThreadPresenter.ThreadPres
private void init() { private void init() {
presenter = new ThreadPresenter(this); presenter = new ThreadPresenter(this);
threadListLayout = new ThreadListLayout(getContext()); threadListLayout = (ThreadListLayout) LayoutInflater.from(getContext()).inflate(R.layout.layout_thread_list, this, false);
threadListLayout.setCallbacks(presenter, presenter); threadListLayout.setCallbacks(presenter, presenter, presenter);
postPopupHelper = new PostPopupHelper(getContext(), presenter, this); 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 = (Button) errorLayout.findViewById(R.id.button);
errorRetryButton.setOnClickListener(this); errorRetryButton.setOnClickListener(this);
switchVisible(Visible.LOADING); switchVisible(Visible.LOADING);
} }
@ -132,8 +131,6 @@ public class ThreadLayout extends LoadView implements ThreadPresenter.ThreadPres
@Override @Override
public void showError(VolleyError error) { public void showError(VolleyError error) {
switchVisible(Visible.ERROR);
String errorMessage; String errorMessage;
if (error.getCause() instanceof SSLException) { if (error.getCause() instanceof SSLException) {
errorMessage = getContext().getString(R.string.thread_load_failed_ssl); 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); 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 @Override
@ -222,6 +224,16 @@ public class ThreadLayout extends LoadView implements ThreadPresenter.ThreadPres
threadListLayout.scrollTo(position); 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) { public ThumbnailView getThumbnail(PostImage postImage) {
if (postPopupHelper.isOpen()) { if (postPopupHelper.isOpen()) {
return postPopupHelper.getThumbnail(postImage); return postPopupHelper.getThumbnail(postImage);
@ -236,6 +248,12 @@ public class ThreadLayout extends LoadView implements ThreadPresenter.ThreadPres
private void switchVisible(Visible visible) { private void switchVisible(Visible visible) {
if (this.visible != visible) { if (this.visible != visible) {
switch (this.visible) {
case THREAD:
threadListLayout.cleanup();
break;
}
this.visible = visible; this.visible = visible;
switch (visible) { switch (visible) {
case LOADING: case LOADING:

@ -18,84 +18,75 @@
package org.floens.chan.ui.layout; package org.floens.chan.ui.layout;
import android.content.Context; import android.content.Context;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View; import android.view.View;
import android.widget.ListView;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import org.floens.chan.R;
import org.floens.chan.core.model.ChanThread; import org.floens.chan.core.model.ChanThread;
import org.floens.chan.core.model.Post; import org.floens.chan.core.model.Post;
import org.floens.chan.core.model.PostImage; import org.floens.chan.core.model.PostImage;
import org.floens.chan.ui.adapter.PostAdapter; 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.PostView;
import org.floens.chan.ui.view.ThumbnailView; 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 { public class ThreadListLayout extends RelativeLayout {
private ListView listView; private RecyclerView recyclerView;
private PostAdapter postAdapter; private PostAdapter postAdapter;
private PostAdapter.PostAdapterCallback postAdapterCallback; private PostAdapter.PostAdapterCallback postAdapterCallback;
private PostView.PostViewCallback postViewCallback; private PostView.PostViewCallback postViewCallback;
private int restoreListViewIndex;
private int restoreListViewTop;
public ThreadListLayout(Context context) {
super(context);
init();
}
public ThreadListLayout(Context context, AttributeSet attrs) { public ThreadListLayout(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
init();
}
public ThreadListLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
} }
@Override @Override
protected void onDetachedFromWindow() { protected void onFinishInflate() {
super.onDetachedFromWindow(); super.onFinishInflate();
restoreListViewIndex = listView.getFirstVisiblePosition(); recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
restoreListViewTop = listView.getChildAt(0) == null ? 0 : listView.getChildAt(0).getTop(); LinearLayoutManager lm = new LinearLayoutManager(getContext());
recyclerView.setLayoutManager(lm);
} }
@Override public void setCallbacks(PostAdapter.PostAdapterCallback postAdapterCallback, PostView.PostViewCallback postViewCallback, ThreadStatusCell.Callback statusCellCallback) {
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) {
this.postAdapterCallback = postAdapterCallback; this.postAdapterCallback = postAdapterCallback;
this.postViewCallback = postViewCallback; this.postViewCallback = postViewCallback;
postAdapter = new PostAdapter(recyclerView, postAdapterCallback, postViewCallback, statusCellCallback);
postAdapter = new PostAdapter(getContext(), postAdapterCallback, postViewCallback); recyclerView.setAdapter(postAdapter);
listView.setAdapter(postAdapter); recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
}
});
} }
public void showPosts(ChanThread thread, boolean initial) { public void showPosts(ChanThread thread, boolean initial) {
if (initial) { if (initial) {
listView.setSelectionFromTop(0, 0); recyclerView.scrollToPosition(0);
restoreListViewIndex = 0;
restoreListViewTop = 0;
} }
postAdapter.setThread(thread); postAdapter.setThread(thread);
} }
public void showError(String error) {
postAdapter.showError(error);
}
public void cleanup() {
postAdapter.cleanup();
}
public ThumbnailView getThumbnail(PostImage postImage) { public ThumbnailView getThumbnail(PostImage postImage) {
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
ThumbnailView thumbnail = null; ThumbnailView thumbnail = null;
for (int i = 0; i < listView.getChildCount(); i++) { for (int i = 0; i < layoutManager.getChildCount(); i++) {
View view = listView.getChildAt(i); View view = layoutManager.getChildAt(i);
if (view instanceof PostView) { if (view instanceof PostView) {
PostView postView = (PostView) view; PostView postView = (PostView) view;
Post post = postView.getPost(); Post post = postView.getPost();
@ -109,6 +100,14 @@ public class ThreadListLayout extends RelativeLayout {
} }
public void scrollTo(int position) { 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);
} }
} }

@ -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) { if (this.post != null) {
// Remove callbacks from the old post while it is still set // Remove callbacks from the old post while it is still set
setPostLinkableListener(null); setPostLinkableListener(null);
@ -249,7 +249,7 @@ public class PostView extends LinearLayout implements View.OnClickListener, Post
if (post.isSavedReply) { if (post.isSavedReply) {
full.setBackgroundColor(savedReplyColor); full.setBackgroundColor(savedReplyColor);
} else if (callback.isPostHightlighted(post)) { } else if (highlighted) {
full.setBackgroundColor(highlightedColor); full.setBackgroundColor(highlightedColor);
} else { } else {
full.setBackgroundColor(0x00000000); full.setBackgroundColor(0x00000000);
@ -543,8 +543,6 @@ public class PostView extends LinearLayout implements View.OnClickListener, Post
void onPostLinkableClicked(PostLinkable linkable); void onPostLinkableClicked(PostLinkable linkable);
boolean isPostHightlighted(Post post);
boolean isPostLastSeen(Post post); boolean isPostLastSeen(Post post);
} }

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<org.floens.chan.ui.cell.ThreadStatusCell xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="56dp"
android:orientation="horizontal">
<TextView
android:id="@+id/text"
android:gravity="center"
android:textColor="#ff757575"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</org.floens.chan.ui.cell.ThreadStatusCell>

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<org.floens.chan.ui.layout.ThreadListLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</org.floens.chan.ui.layout.ThreadListLayout>

@ -85,6 +85,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<string name="thread_load_failed_retry">Retry</string> <string name="thread_load_failed_retry">Retry</string>
<string name="thread_archived">Archived</string> <string name="thread_archived">Archived</string>
<string name="thread_closed">Closed</string> <string name="thread_closed">Closed</string>
<string name="thread_stats">%1$sR / %2$sI / %3$sP</string>
<string name="board_edit">Board editor</string> <string name="board_edit">Board editor</string>
<string name="board_edit_header">Add, remove and reorder your boards here.\nThe topmost board will be loaded automatically.</string> <string name="board_edit_header">Add, remove and reorder your boards here.\nThe topmost board will be loaded automatically.</string>
@ -116,7 +117,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<item>Quote</item> <item>Quote</item>
<item>Quote text</item> <item>Quote text</item>
<item>Info</item> <item>Info</item>
<item>Show clickables</item> <item>Show links</item>
<item>Copy text</item> <item>Copy text</item>
<item>Report</item> <item>Report</item>
</string-array> </string-array>

Loading…
Cancel
Save