Fixed scrolling to a post in PostAdapter.

captchafix
Florens Douwes 11 years ago
parent ebbfb3a33e
commit 3d7298ff05
  1. 9
      Chan/src/org/floens/chan/adapter/PostAdapter.java
  2. 43
      Chan/src/org/floens/chan/fragment/PostRepliesFragment.java
  3. 171
      Chan/src/org/floens/chan/manager/ThreadManager.java
  4. 13
      Chan/src/org/floens/chan/service/PinnedService.java
  5. 116
      Chan/src/org/floens/chan/utils/ScrollerRunnable.java
  6. 7
      Chan/src/org/floens/chan/view/PostView.java

@ -6,6 +6,7 @@ import java.util.List;
import org.floens.chan.R;
import org.floens.chan.manager.ThreadManager;
import org.floens.chan.model.Post;
import org.floens.chan.utils.ScrollerRunnable;
import org.floens.chan.utils.Utils;
import org.floens.chan.view.PostView;
import org.floens.chan.view.ThreadWatchCounterView;
@ -129,10 +130,14 @@ public class PostAdapter extends BaseAdapter {
}
public void scrollToPost(Post post) {
notifyDataSetChanged();
for (int i = 0; i < postList.size(); i++) {
if (postList.get(i).no == post.no) {
// listView.smoothScrollToPosition(i); does not work when a view is taller than the container
listView.setSelection(i);
// listView.smoothScrollToPosition(i);
ScrollerRunnable r = new ScrollerRunnable(listView);
r.start(i);
break;
}

@ -21,53 +21,53 @@ import android.widget.ListView;
*/
public class PostRepliesFragment extends DialogFragment {
private ListView listView;
private List<Post> posts;
private ThreadManager manager;
private boolean callback = true;
public static PostRepliesFragment newInstance(List<Post> posts, ThreadManager manager) {
PostRepliesFragment fragment = new PostRepliesFragment();
fragment.posts = posts;
fragment.manager = manager;
return fragment;
}
public void dismissNoCallback() {
callback = false;
dismiss();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(STYLE_NO_TITLE, 0);
}
@Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
if (callback && manager != null) {
manager.onPostRepliesPop();
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup unused, Bundle savedInstanceState) {
View container = inflater.inflate(R.layout.post_replies, null);
listView = (ListView) container.findViewById(R.id.post_list);
container.findViewById(R.id.replies_back).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
container.findViewById(R.id.replies_close).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@ -75,16 +75,16 @@ public class PostRepliesFragment extends DialogFragment {
dismiss();
}
});
return container;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (posts == null) {
// Restoring from background.
// Restoring from background.
dismiss();
} else {
ArrayAdapter<Post> adapter = new ArrayAdapter<Post>(getActivity(), 0) {
@ -96,23 +96,24 @@ public class PostRepliesFragment extends DialogFragment {
} else {
postView = new PostView(getActivity());
}
final Post p = getItem(position);
postView.setPost(p, manager);
postView.setOnClickListener(new View.OnClickListener() {
postView.setOnClickListeners(new View.OnClickListener() {
@Override
public void onClick(View v) {
manager.closeAllPostFragments();
dismiss();
manager.highlightPost(p);
manager.scrollToPost(p);
}
});
return postView;
}
};
adapter.addAll(posts);
listView.setAdapter(adapter);
}

@ -37,50 +37,51 @@ import android.widget.Toast;
import com.android.volley.VolleyError;
/**
* All PostView's need to have this referenced.
* This manages some things like pages, starting and stopping of loading,
* All PostView's need to have this referenced.
* This manages some things like pages, starting and stopping of loading,
* handling linkables, replies popups etc.
* onDestroy, onStart and onStop must be called from the activity/fragment
*/
public class ThreadManager implements Loader.LoaderListener {
private static final String TAG = "ThreadManager";
private final Activity activity;
private final ThreadManager.ThreadManagerListener threadManagerListener;
private final List<List<Post>> popupQueue = new ArrayList<List<Post>>();
private PostRepliesFragment currentPopupFragment;
private Post highlightedPost;
private Loader loader;
public ThreadManager(Activity context, final ThreadManagerListener listener) {
this.activity = context;
activity = context;
threadManagerListener = listener;
}
public void onDestroy() {
unbindLoader();
}
public void onStart() {
if (loader != null) {
loader.onStart();
}
}
public void onStop() {
if (loader != null) {
loader.onStop();
}
}
public void bindLoader(Loadable loadable) {
if (loader != null) {
unbindLoader();
}
loader = LoaderPool.getInstance().obtain(loadable, this);
}
public void unbindLoader() {
if (loader != null) {
LoaderPool.getInstance().release(loader, this);
@ -88,8 +89,10 @@ public class ThreadManager implements Loader.LoaderListener {
} else {
Logger.e(TAG, "Loader already unbinded");
}
highlightedPost = null;
}
public void requestData() {
if (loader != null) {
loader.requestData();
@ -97,7 +100,7 @@ public class ThreadManager implements Loader.LoaderListener {
Logger.e(TAG, "Loader null in requestData");
}
}
/**
* Called by postadapter and threadwatchcounterview.onclick
*/
@ -108,50 +111,50 @@ public class ThreadManager implements Loader.LoaderListener {
Logger.e(TAG, "Loader null in requestData");
}
}
@Override
public void onError(VolleyError error) {
threadManagerListener.onThreadLoadError(error);
}
@Override
public void onData(List<Post> result, boolean append) {
threadManagerListener.onThreadLoaded(result, append);
}
public boolean hasLoader() {
return loader != null;
}
public Post findPostById(int id) {
if (loader == null) return null;
return loader.findPostById(id);
}
public Loadable getLoadable() {
if (loader == null) return null;
return loader.getLoadable();
}
public Loader getLoader() {
return loader;
}
public void onThumbnailClicked(Post post) {
threadManagerListener.onThumbnailClicked(post);
}
public void onPostClicked(Post post) {
if (loader != null && loader.getLoadable().isBoardMode()) {
threadManagerListener.onOPClicked(post);
}
}
public void onPostLongClicked(final Post post) {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
String[] items = null;
String[] temp = activity.getResources().getStringArray(R.array.post_options);
// Only add the delete option when the post is a saved reply
if (DatabaseManager.getInstance().isSavedReply(post.board, post.no)) {
@ -161,7 +164,7 @@ public class ThreadManager implements Loader.LoaderListener {
} else {
items = temp;
}
builder.setItems(items, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
@ -187,66 +190,74 @@ public class ThreadManager implements Loader.LoaderListener {
}
}
});
builder.create().show();
}
public void openReply(boolean startInActivity) {
if (loader == null) return;
if (startInActivity) {
ReplyActivity.setLoadable(loader.getLoadable());
Intent i = new Intent(activity, ReplyActivity.class);
activity.startActivity(i);
} else {
ReplyFragment reply = ReplyFragment.newInstance(loader.getLoadable());
reply.show(activity.getFragmentManager(), "replyDialog");
reply.show(activity.getFragmentManager(), "replyDialog");
}
}
public void onPostLinkableClicked(PostLinkable linkable) {
handleLinkableSelected(linkable);
}
public void scrollToPost(Post post) {
threadManagerListener.onScrollTo(post);
}
public void highlightPost(Post post) {
highlightedPost = post;
}
public boolean isPostHightlighted(Post post) {
return highlightedPost != null && post.board.equals(highlightedPost.board) && post.no == highlightedPost.no;
}
private void copyToClipboard(String comment) {
ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("Post text", comment);
clipboard.setPrimaryClip(clip);
}
private void showPostInfo(Post post) {
String text = "";
if (post.hasImage) {
text += "File: " + post.filename + " \nSize: " + post.imageWidth + "x" + post.imageHeight + "\n\n";
}
text += "Time: " + post.date ;
if (!TextUtils.isEmpty(post.id)) {
text += "\nId: " + post.id;
}
if (!TextUtils.isEmpty(post.email)) {
text += "\nEmail: " + post.email;
}
if (!TextUtils.isEmpty(post.tripcode)) {
text += "\nTripcode: " + post.tripcode;
}
if (!TextUtils.isEmpty(post.countryName)) {
text += "\nCountry: " + post.countryName;
}
if (!TextUtils.isEmpty(post.capcode)) {
text += "\nCapcode: " + post.capcode;
}
AlertDialog dialog = new AlertDialog.Builder(activity)
.setTitle(R.string.post_info)
.setMessage(text)
@ -256,10 +267,10 @@ public class ThreadManager implements Loader.LoaderListener {
}
})
.create();
dialog.show();
}
/**
* When the user clicks a post:
* a. when there's one linkable, open the linkable.
@ -269,7 +280,7 @@ public class ThreadManager implements Loader.LoaderListener {
public void showPostLinkables(Post post) {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
final ArrayList<PostLinkable> linkables = post.linkables;
if (linkables.size() > 0) {
if (linkables.size() == 1) {
handleLinkableSelected(linkables.get(0));
@ -278,20 +289,20 @@ public class ThreadManager implements Loader.LoaderListener {
for (int i = 0; i < linkables.size(); i++) {
keys[i] = linkables.get(i).key;
}
builder.setItems(keys, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
handleLinkableSelected(linkables.get(which));
}
});
AlertDialog dialog = builder.create();
dialog.show();
}
}
}
public void showPostReplies(Post post) {
List<Post> p = new ArrayList<Post>();
for (int no : post.repliesFrom) {
@ -300,12 +311,12 @@ public class ThreadManager implements Loader.LoaderListener {
p.add(r);
}
}
if (p.size() > 0) {
showPostsRepliesFragment(p);
}
}
/**
* Handle when a linkable has been clicked.
* @param linkable the selected linkable.
@ -330,32 +341,32 @@ public class ThreadManager implements Loader.LoaderListener {
.setTitle(R.string.open_link_confirmation)
.setMessage(linkable.value)
.create();
dialog.show();
} else {
openLink(linkable);
}
}
}
/**
* When a linkable to a post has been clicked,
* When a linkable to a post has been clicked,
* show a dialog with the referenced post in it.
* @param linkable the clicked linkable.
*/
private void showPostReply(PostLinkable linkable) {
String value = linkable.value;
Post post = null;
try {
// Get post id
String[] splitted = value.split("#p");
if (splitted.length == 2) {
int id = Integer.parseInt(splitted[1]);
post = findPostById(id);
if (post != null) {
List<Post> l = new ArrayList<Post>();
l.add(post);
@ -366,7 +377,7 @@ public class ThreadManager implements Loader.LoaderListener {
e.printStackTrace();
}
}
/**
* Open an url.
* @param linkable Linkable with an url.
@ -374,53 +385,53 @@ public class ThreadManager implements Loader.LoaderListener {
private void openLink(PostLinkable linkable) {
activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(linkable.value)));
}
private void showPostsRepliesFragment(List<Post> list) {
// Post popups are now queued up, more than 32 popups on top of each other makes the system crash!
// Post popups are now queued up, more than 32 popups on top of each other makes the system crash!
popupQueue.add(list);
if (currentPopupFragment != null) {
currentPopupFragment.dismissNoCallback();
}
PostRepliesFragment popup = PostRepliesFragment.newInstance(list, this);
FragmentTransaction ft = activity.getFragmentManager().beginTransaction();
ft.add(popup, "postPopup");
ft.commit();
currentPopupFragment = popup;
}
public void onPostRepliesPop() {
if (popupQueue.size() == 0) return;
popupQueue.remove(popupQueue.size() - 1);
if (popupQueue.size() > 0) {
PostRepliesFragment popup = PostRepliesFragment.newInstance(popupQueue.get(popupQueue.size() - 1), this);
FragmentTransaction ft = activity.getFragmentManager().beginTransaction();
ft.add(popup, "postPopup");
ft.commit();
currentPopupFragment = popup;
} else {
currentPopupFragment = null;
}
}
public void closeAllPostFragments() {
popupQueue.clear();
currentPopupFragment = null;
}
private void deletePost(final Post post) {
final CheckBox view = new CheckBox(activity);
view.setText(R.string.delete_image_only);
int padding = activity.getResources().getDimensionPixelSize(R.dimen.general_padding);
view.setPadding(padding, padding, padding, padding);
new AlertDialog.Builder(activity)
.setTitle(R.string.delete_confirm)
.setView(view)
@ -437,7 +448,7 @@ public class ThreadManager implements Loader.LoaderListener {
})
.show();
}
private void doDeletePost(Post post, boolean onlyImageDelete) {
SavedReply reply = DatabaseManager.getInstance().getSavedReply(post.board, post.no);
if (reply == null) {
@ -447,19 +458,19 @@ public class ThreadManager implements Loader.LoaderListener {
reply.password = "boom";*/
return;
}
final ProgressDialog dialog = ProgressDialog.show(activity, null, activity.getString(R.string.delete_wait));
ReplyManager.getInstance().sendDelete(reply, onlyImageDelete, new DeleteListener() {
@Override
public void onResponse(DeleteResponse response) {
dialog.dismiss();
if (response.isNetworkError || response.isUserError) {
int resId = 0;
if (response.isTooSoonError) {
resId = R.string.delete_too_soon;
resId = R.string.delete_too_soon;
} else if (response.isInvalidPassword) {
resId = R.string.delete_password_incorrect;
} else if (response.isTooOldError) {
@ -467,7 +478,7 @@ public class ThreadManager implements Loader.LoaderListener {
} else {
resId = R.string.delete_fail;
}
Toast.makeText(activity, resId, Toast.LENGTH_LONG).show();
} else if (response.isSuccessful) {
Toast.makeText(activity, R.string.delete_success, Toast.LENGTH_SHORT).show();
@ -477,7 +488,7 @@ public class ThreadManager implements Loader.LoaderListener {
}
});
}
public interface ThreadManagerListener {
public void onThreadLoaded(List<Post> result, boolean append);
public void onThreadLoadError(VolleyError error);

@ -19,24 +19,11 @@ public class PinnedService extends Service {
private static final long FOREGROUND_INTERVAL = 10000L;
private static final long BACKGROUND_INTERVAL = 60000L;
private static PinnedService instance;
private static boolean activityInForeground = false;
private Thread loadThread;
private boolean running = true;
public PinnedService() {
instance = this;
}
/**
* Get the PinnedService instance
* @return the instance or null
*/
public static PinnedService getInstance() {
return instance;
}
public static void onActivityStart() {
Logger.test("onActivityStart");
activityInForeground = true;

@ -0,0 +1,116 @@
package org.floens.chan.utils;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.ListView;
public class ScrollerRunnable implements Runnable {
private static final int SCROLL_DURATION = 1;
private static final int MOVE_DOWN_POS = 1;
private static final int MOVE_UP_POS = 2;
private final ListView mList;
private int mMode;
private int mTargetPos;
private int mLastSeenPos;
private final int mExtraScroll;
public ScrollerRunnable(ListView listView) {
mList = listView;
mExtraScroll = ViewConfiguration.get(mList.getContext()).getScaledFadingEdgeLength();
}
public void start(int position) {
stop();
final int firstPos = mList.getFirstVisiblePosition();
final int lastPos = firstPos + mList.getChildCount() - 1;
int viewTravelCount = 0;
if (position <= firstPos) {
viewTravelCount = firstPos - position + 1;
mMode = MOVE_UP_POS;
} else if (position >= lastPos) {
viewTravelCount = position - lastPos + 1;
mMode = MOVE_DOWN_POS;
} else {
// Already on screen, nothing to do
return;
}
mTargetPos = position;
mLastSeenPos = ListView.INVALID_POSITION;
mList.post(this);
}
void stop() {
mList.removeCallbacks(this);
}
@Override
public void run() {
final int listHeight = mList.getHeight();
final int firstPos = mList.getFirstVisiblePosition();
switch (mMode) {
case MOVE_DOWN_POS: {
final int lastViewIndex = mList.getChildCount() - 1;
final int lastPos = firstPos + lastViewIndex;
if (lastViewIndex < 0) {
return;
}
if (lastPos == mLastSeenPos) {
// No new views, let things keep going.
// mList.post(this);
// return;
}
final View lastView = mList.getChildAt(lastViewIndex);
final int lastViewHeight = lastView.getHeight();
final int lastViewTop = lastView.getTop();
final int lastViewPixelsShowing = listHeight - lastViewTop;
final int extraScroll = lastPos < mList.getCount() - 1 ? mExtraScroll : mList.getPaddingBottom();
mList.smoothScrollBy(lastViewHeight - lastViewPixelsShowing + extraScroll, 0);
mLastSeenPos = lastPos;
if (lastPos < mTargetPos) {
mList.post(this);
}
break;
}
case MOVE_UP_POS: {
if (firstPos == mLastSeenPos) {
// No new views, let things keep going.
// mList.post(this);
// return;
}
final View firstView = mList.getChildAt(0);
if (firstView == null) {
return;
}
final int firstViewTop = firstView.getTop();
final int extraScroll = firstPos > 0 ? mExtraScroll : mList.getPaddingTop();
mList.smoothScrollBy(firstViewTop - extraScroll, 0);
mLastSeenPos = firstPos;
if (firstPos > mTargetPos) {
mList.post(this);
}
break;
}
default:
break;
}
}
}

@ -198,6 +198,8 @@ public class PostView extends LinearLayout implements View.OnClickListener, View
}
if (post.isSavedReply) {
full.setBackgroundColor(0xFFBCBCBC);
} else if (manager.isPostHightlighted(post)) {
full.setBackgroundColor(0xFFD6BAD0);
} else {
full.setBackgroundColor(0x00000000);
@ -297,6 +299,11 @@ public class PostView extends LinearLayout implements View.OnClickListener, View
full.setOnLongClickListener(this);
}
public void setOnClickListeners(View.OnClickListener listener) {
commentView.setOnClickListener(listener);
full.setOnClickListener(listener);
}
public void onLinkableClick(PostLinkable linkable) {
manager.onPostLinkableClicked(linkable);
}

Loading…
Cancel
Save