Post replies done

filtering
Floens 10 years ago
parent 24f59f50eb
commit afea1d063a
  1. 5
      Clover/app/src/main/java/org/floens/chan/controller/Controller.java
  2. 4
      Clover/app/src/main/java/org/floens/chan/controller/FadeInTransition.java
  3. 4
      Clover/app/src/main/java/org/floens/chan/controller/FadeOutTransition.java
  4. 14
      Clover/app/src/main/java/org/floens/chan/core/model/Post.java
  5. 45
      Clover/app/src/main/java/org/floens/chan/core/model/PostLinkable.java
  6. 5
      Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java
  7. 4
      Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java
  8. 192
      Clover/app/src/main/java/org/floens/chan/ui/controller/PostRepliesController.java
  9. 10
      Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java
  10. 2
      Clover/app/src/main/java/org/floens/chan/ui/controller/ViewThreadController.java
  11. 6
      Clover/app/src/main/java/org/floens/chan/ui/fragment/PostRepliesFragment.java
  12. 63
      Clover/app/src/main/java/org/floens/chan/ui/helper/PostPopupHelper.java
  13. 16
      Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java
  14. 6
      Clover/app/src/main/java/org/floens/chan/ui/view/LoadView.java
  15. 106
      Clover/app/src/main/java/org/floens/chan/ui/view/PostView.java
  16. 4
      Clover/app/src/main/java/org/floens/chan/utils/AndroidUtils.java
  17. BIN
      Clover/app/src/main/res/drawable-hdpi/dialog_full_dark.9.png
  18. BIN
      Clover/app/src/main/res/drawable-hdpi/dialog_full_light.9.png
  19. BIN
      Clover/app/src/main/res/drawable-mdpi/dialog_full_dark.9.png
  20. BIN
      Clover/app/src/main/res/drawable-mdpi/dialog_full_light.9.png
  21. BIN
      Clover/app/src/main/res/drawable-xhdpi/dialog_full_dark.9.png
  22. BIN
      Clover/app/src/main/res/drawable-xhdpi/dialog_full_light.9.png
  23. BIN
      Clover/app/src/main/res/drawable-xxhdpi/dialog_full_dark.9.png
  24. BIN
      Clover/app/src/main/res/drawable-xxhdpi/dialog_full_light.9.png
  25. 114
      Clover/app/src/main/res/layout/post_replies.xml
  26. 113
      Clover/app/src/main/res/layout/post_replies_bottombuttons.xml
  27. 22
      Clover/app/src/main/res/layout/post_replies_container.xml

@ -40,11 +40,14 @@ public abstract class Controller {
public Controller presentingController; public Controller presentingController;
public Controller presentedController; public Controller presentedController;
public boolean alive = false;
public Controller(Context context) { public Controller(Context context) {
this.context = context; this.context = context;
} }
public void onCreate() { public void onCreate() {
alive = true;
// Logger.test(getClass().getSimpleName() + " onCreate"); // Logger.test(getClass().getSimpleName() + " onCreate");
} }
@ -57,6 +60,7 @@ public abstract class Controller {
} }
public void onDestroy() { public void onDestroy() {
alive = false;
// Logger.test(getClass().getSimpleName() + " onDestroy"); // Logger.test(getClass().getSimpleName() + " onDestroy");
} }
@ -111,6 +115,7 @@ public abstract class Controller {
ControllerLogic.transition(this, null, true, false, contentView); ControllerLogic.transition(this, null, true, false, contentView);
} }
((BoardActivity) context).removeController(this); ((BoardActivity) context).removeController(this);
presentingController.presentedController = null;
} }
public View inflateRes(int resId) { public View inflateRes(int resId) {

@ -5,14 +5,14 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet; import android.animation.AnimatorSet;
import android.animation.ObjectAnimator; import android.animation.ObjectAnimator;
import android.view.View; import android.view.View;
import android.view.animation.DecelerateInterpolator; import android.view.animation.AccelerateDecelerateInterpolator;
public class FadeInTransition extends ControllerTransition { public class FadeInTransition extends ControllerTransition {
@Override @Override
public void perform() { public void perform() {
Animator toAlpha = ObjectAnimator.ofFloat(to.view, View.ALPHA, 0f, 1f); Animator toAlpha = ObjectAnimator.ofFloat(to.view, View.ALPHA, 0f, 1f);
toAlpha.setDuration(200); toAlpha.setDuration(200);
toAlpha.setInterpolator(new DecelerateInterpolator(2f)); toAlpha.setInterpolator(new AccelerateDecelerateInterpolator());
toAlpha.addListener(new AnimatorListenerAdapter() { toAlpha.addListener(new AnimatorListenerAdapter() {
@Override @Override

@ -5,14 +5,14 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet; import android.animation.AnimatorSet;
import android.animation.ObjectAnimator; import android.animation.ObjectAnimator;
import android.view.View; import android.view.View;
import android.view.animation.DecelerateInterpolator; import android.view.animation.AccelerateDecelerateInterpolator;
public class FadeOutTransition extends ControllerTransition { public class FadeOutTransition extends ControllerTransition {
@Override @Override
public void perform() { public void perform() {
Animator toAlpha = ObjectAnimator.ofFloat(from.view, View.ALPHA, 1f, 0f); Animator toAlpha = ObjectAnimator.ofFloat(from.view, View.ALPHA, 1f, 0f);
toAlpha.setDuration(200); toAlpha.setDuration(200);
toAlpha.setInterpolator(new DecelerateInterpolator(2f)); toAlpha.setInterpolator(new AccelerateDecelerateInterpolator());
toAlpha.addListener(new AnimatorListenerAdapter() { toAlpha.addListener(new AnimatorListenerAdapter() {
@Override @Override

@ -23,7 +23,6 @@ import android.text.TextUtils;
import org.floens.chan.ChanApplication; import org.floens.chan.ChanApplication;
import org.floens.chan.chan.ChanUrls; import org.floens.chan.chan.ChanUrls;
import org.floens.chan.core.loader.ChanParser; import org.floens.chan.core.loader.ChanParser;
import org.floens.chan.ui.view.PostView;
import org.jsoup.parser.Parser; import org.jsoup.parser.Parser;
import java.util.ArrayList; import java.util.ArrayList;
@ -105,22 +104,9 @@ public class Post {
public SpannableString capcodeSpan; public SpannableString capcodeSpan;
public CharSequence nameTripcodeIdCapcodeSpan; public CharSequence nameTripcodeIdCapcodeSpan;
/**
* The PostView the Post is currently bound to.
*/
private PostView linkableListener;
public Post() { public Post() {
} }
public void setLinkableListener(PostView listener) {
linkableListener = listener;
}
public PostView getLinkableListener() {
return linkableListener;
}
/** /**
* Finish up the data * Finish up the data
* *

@ -17,17 +17,21 @@
*/ */
package org.floens.chan.core.model; package org.floens.chan.core.model;
import android.support.annotation.NonNull;
import android.text.TextPaint; import android.text.TextPaint;
import android.text.style.ClickableSpan; import android.text.style.ClickableSpan;
import android.view.View; import android.view.View;
import org.floens.chan.utils.ThemeHelper; import org.floens.chan.utils.ThemeHelper;
import java.util.ArrayList;
import java.util.List;
/** /**
* Anything that links to something in a post uses this entity. * Anything that links to something in a post uses this entity.
*/ */
public class PostLinkable extends ClickableSpan { public class PostLinkable extends ClickableSpan {
public static enum Type { public enum Type {
QUOTE, LINK, SPOILER, THREAD QUOTE, LINK, SPOILER, THREAD
} }
@ -36,7 +40,8 @@ public class PostLinkable extends ClickableSpan {
public final Object value; public final Object value;
public final Type type; public final Type type;
private boolean clicked = false; private List<Callback> callbacks = new ArrayList<>();
private boolean spoilerVisible = false;
public PostLinkable(Post post, String key, Object value, Type type) { public PostLinkable(Post post, String key, Object value, Type type) {
this.post = post; this.post = post;
@ -45,19 +50,33 @@ public class PostLinkable extends ClickableSpan {
this.type = type; this.type = type;
} }
public void addCallback(Callback callback) {
callbacks.add(callback);
}
public void removeCallback(Callback callback) {
callbacks.remove(callback);
}
public boolean hasCallback(Callback callback) {
return callbacks.contains(callback);
}
@Override @Override
public void onClick(View widget) { public void onClick(View widget) {
if (post.getLinkableListener() != null) { Callback top = topCallback();
post.getLinkableListener().onLinkableClick(this); if (top != null) {
top.onLinkableClick(this);
} }
clicked = true; spoilerVisible = !spoilerVisible;
} }
@Override @Override
public void updateDrawState(TextPaint ds) { public void updateDrawState(@NonNull TextPaint ds) {
if (type == Type.QUOTE || type == Type.LINK || type == Type.THREAD) { if (type == Type.QUOTE || type == Type.LINK || type == Type.THREAD) {
if (type == Type.QUOTE) { if (type == Type.QUOTE) {
if (value instanceof Integer && post.getLinkableListener() != null && (Integer) value == post.getLinkableListener().getHighlightQuotesWithNo()) { Callback top = topCallback();
if (value instanceof Integer && top != null && (Integer) value == top.getHighlightQuotesWithNo(this)) {
ds.setColor(ThemeHelper.getInstance().getHighlightQuoteColor()); ds.setColor(ThemeHelper.getInstance().getHighlightQuoteColor());
} else { } else {
ds.setColor(ThemeHelper.getInstance().getQuoteColor()); ds.setColor(ThemeHelper.getInstance().getQuoteColor());
@ -70,7 +89,7 @@ public class PostLinkable extends ClickableSpan {
ds.setUnderlineText(true); ds.setUnderlineText(true);
} else if (type == Type.SPOILER) { } else if (type == Type.SPOILER) {
if (!clicked) { if (!spoilerVisible) {
ds.setColor(ThemeHelper.getInstance().getSpoilerColor()); ds.setColor(ThemeHelper.getInstance().getSpoilerColor());
ds.bgColor = ThemeHelper.getInstance().getSpoilerColor(); ds.bgColor = ThemeHelper.getInstance().getSpoilerColor();
ds.setUnderlineText(false); ds.setUnderlineText(false);
@ -78,6 +97,10 @@ public class PostLinkable extends ClickableSpan {
} }
} }
private Callback topCallback() {
return callbacks.size() > 0 ? callbacks.get(callbacks.size() - 1) : null;
}
public static class ThreadLink { public static class ThreadLink {
public String board; public String board;
public int threadId; public int threadId;
@ -89,4 +112,10 @@ public class PostLinkable extends ClickableSpan {
this.postId = postId; this.postId = postId;
} }
} }
public interface Callback {
void onLinkableClick(PostLinkable postLinkable);
int getHighlightQuotesWithNo(PostLinkable postLinkable);
}
} }

@ -77,6 +77,11 @@ public class StartActivity extends Activity {
super.onDestroy(); super.onDestroy();
stackTop().onDestroy(); stackTop().onDestroy();
stack.clear();
System.gc();
System.gc();
System.gc();
System.runFinalization();
} }
@Override @Override

@ -137,7 +137,7 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte
} }
@Override @Override
public void openThread(Loadable threadLoadable) { public void showThread(Loadable threadLoadable) {
ViewThreadController viewThreadController = new ViewThreadController(context); ViewThreadController viewThreadController = new ViewThreadController(context);
viewThreadController.setLoadable(threadLoadable); viewThreadController.setLoadable(threadLoadable);
navigationController.pushController(viewThreadController); navigationController.pushController(viewThreadController);
@ -149,7 +149,7 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte
@Override @Override
public void onPinClicked(Pin pin) { public void onPinClicked(Pin pin) {
openThread(pin.loadable); showThread(pin.loadable);
} }
@Override @Override

@ -0,0 +1,192 @@
package org.floens.chan.ui.controller;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.os.Build;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.animation.LinearInterpolator;
import android.widget.AbsListView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import org.floens.chan.R;
import org.floens.chan.controller.Controller;
import org.floens.chan.core.model.Post;
import org.floens.chan.core.presenter.ThreadPresenter;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.ui.helper.PostPopupHelper;
import org.floens.chan.ui.view.LoadView;
import org.floens.chan.ui.view.PostView;
import org.floens.chan.utils.ThemeHelper;
public class PostRepliesController extends Controller {
private static final int TRANSITION_DURATION = 200;
private PostPopupHelper postPopupHelper;
private ThreadPresenter presenter;
private int statusBarColorPrevious;
private boolean first = true;
private LoadView loadView;
public PostRepliesController(Context context, PostPopupHelper postPopupHelper, ThreadPresenter presenter) {
super(context);
this.postPopupHelper = postPopupHelper;
this.presenter = presenter;
}
@Override
public void onCreate() {
super.onCreate();
view = inflateRes(R.layout.post_replies_container);
// Clicking outside the popup view
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
postPopupHelper.popAll();
}
});
loadView = (LoadView) view.findViewById(R.id.loadview);
if (Build.VERSION.SDK_INT >= 21) {
statusBarColorPrevious = getWindow().getStatusBarColor();
if (statusBarColorPrevious != 0) {
animateStatusBar(true, statusBarColorPrevious);
}
}
}
@Override
public void stopPresenting() {
super.stopPresenting();
if (Build.VERSION.SDK_INT >= 21) {
if (statusBarColorPrevious != 0) {
animateStatusBar(false, statusBarColorPrevious);
}
}
}
public void setPostRepliesData(PostPopupHelper.RepliesData data) {
displayData(data);
}
private void displayData(final PostPopupHelper.RepliesData data) {
View dataView;
if (ChanSettings.repliesButtonsBottom.get()) {
dataView = inflateRes(R.layout.post_replies_bottombuttons);
} else {
dataView = inflateRes(R.layout.post_replies);
}
ListView listView = (ListView) dataView.findViewById(R.id.post_list);
View repliesBack = dataView.findViewById(R.id.replies_back);
repliesBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
postPopupHelper.pop();
}
});
View repliesClose = dataView.findViewById(R.id.replies_close);
repliesClose.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
postPopupHelper.popAll();
}
});
if (!ThemeHelper.getInstance().getTheme().isLightTheme) {
((TextView) dataView.findViewById(R.id.replies_back_icon)).setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_action_back_dark, 0, 0, 0);
((TextView) dataView.findViewById(R.id.replies_close_icon)).setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_action_done_dark, 0, 0, 0);
dataView.findViewById(R.id.container).setBackgroundResource(R.drawable.dialog_full_dark);
}
ArrayAdapter<Post> adapter = new ArrayAdapter<Post>(context, 0) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
PostView postView;
if (convertView instanceof PostView) {
postView = (PostView) convertView;
} else {
postView = new PostView(context);
}
final Post p = getItem(position);
postView.setPost(p, presenter);
postView.setHighlightQuotesWithNo(data.forPost.no);
postView.setOnClickListeners(new View.OnClickListener() {
@Override
public void onClick(View v) {
postPopupHelper.postClicked(p);
}
});
return postView;
}
};
adapter.addAll(data.posts);
listView.setAdapter(adapter);
listView.setSelectionFromTop(data.listViewIndex, data.listViewTop);
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
data.listViewIndex = view.getFirstVisiblePosition();
View v = view.getChildAt(0);
data.listViewTop = (v == null) ? 0 : v.getTop();
}
});
loadView.setFadeDuration(first ? 0 : 200);
first = false;
loadView.setView(dataView);
}
@Override
public boolean onBack() {
postPopupHelper.pop();
return true;
}
private void animateStatusBar(boolean in, final int originalColor) {
ValueAnimator statusBar = ValueAnimator.ofFloat(in ? 0f : 0.5f, in ? 0.5f : 0f);
statusBar.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if (Build.VERSION.SDK_INT >= 21) { // Make lint happy
float progress = (float) animation.getAnimatedValue();
if (progress == 0f) {
getWindow().setStatusBarColor(originalColor);
} else {
int r = (int) ((1f - progress) * Color.red(originalColor));
int g = (int) ((1f - progress) * Color.green(originalColor));
int b = (int) ((1f - progress) * Color.blue(originalColor));
getWindow().setStatusBarColor(Color.argb(255, r, g, b));
}
}
}
});
statusBar.setDuration(TRANSITION_DURATION).setInterpolator(new LinearInterpolator());
statusBar.start();
}
private Window getWindow() {
return ((Activity) context).getWindow();
}
}

@ -21,6 +21,16 @@ public abstract class ThreadController extends Controller implements ThreadLayou
view = threadLayout; view = threadLayout;
} }
@Override
public void onDestroy() {
super.onDestroy();
threadLayout.getPresenter().unbindLoadable();
}
public void presentRepliesController(Controller controller) {
presentController(controller);
}
@Override @Override
public void showImages(List<PostImage> images, int index, Loadable loadable, final ThumbnailView thumbnail) { public void showImages(List<PostImage> images, int index, Loadable loadable, final ThumbnailView thumbnail) {
// Just ignore the showImages request when the image is not loaded // Just ignore the showImages request when the image is not loaded

@ -105,7 +105,7 @@ public class ViewThreadController extends ThreadController implements ThreadLayo
} }
@Override @Override
public void openThread(final Loadable threadLoadable) { public void showThread(final Loadable threadLoadable) {
// TODO implement, scroll to post and fix title // TODO implement, scroll to post and fix title
new AlertDialog.Builder(context) new AlertDialog.Builder(context)
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)

@ -51,10 +51,10 @@ public class PostRepliesFragment extends DialogFragment {
public static PostRepliesFragment newInstance(PostPopupHelper.RepliesData repliesData, PostPopupHelper postPopupHelper, ThreadPresenter presenter) { public static PostRepliesFragment newInstance(PostPopupHelper.RepliesData repliesData, PostPopupHelper postPopupHelper, ThreadPresenter presenter) {
PostRepliesFragment fragment = new PostRepliesFragment(); PostRepliesFragment fragment = new PostRepliesFragment();
fragment.repliesData = repliesData; fragment.repliesData = repliesData;
fragment.postPopupHelper = postPopupHelper; fragment.postPopupHelper = postPopupHelper;
fragment.presenter = presenter; fragment.presenter = presenter;
return fragment; return fragment;
} }
@ -75,7 +75,7 @@ public class PostRepliesFragment extends DialogFragment {
super.onDismiss(dialog); super.onDismiss(dialog);
if (postPopupHelper != null) { if (postPopupHelper != null) {
postPopupHelper.onPostRepliesPop(); // postPopupHelper.onPostRepliesPop();
} }
} }
@ -101,7 +101,7 @@ public class PostRepliesFragment extends DialogFragment {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
if (postPopupHelper != null) { if (postPopupHelper != null) {
postPopupHelper.closeAllPostFragments(); // postPopupHelper.closeAllPostFragments();
} }
} }
}); });

@ -17,13 +17,12 @@
*/ */
package org.floens.chan.ui.helper; package org.floens.chan.ui.helper;
import android.app.Activity;
import android.app.FragmentTransaction;
import android.content.Context; import android.content.Context;
import org.floens.chan.controller.Controller;
import org.floens.chan.core.model.Post; import org.floens.chan.core.model.Post;
import org.floens.chan.core.presenter.ThreadPresenter; import org.floens.chan.core.presenter.ThreadPresenter;
import org.floens.chan.ui.fragment.PostRepliesFragment; import org.floens.chan.ui.controller.PostRepliesController;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -31,13 +30,15 @@ import java.util.List;
public class PostPopupHelper { public class PostPopupHelper {
private Context context; private Context context;
private ThreadPresenter presenter; private ThreadPresenter presenter;
private final PostPopupHelperCallback callback;
private final List<RepliesData> dataQueue = new ArrayList<>(); private final List<RepliesData> dataQueue = new ArrayList<>();
private PostRepliesFragment currentPopupFragment; private PostRepliesController presentingController;
public PostPopupHelper(Context context, ThreadPresenter presenter) { public PostPopupHelper(Context context, ThreadPresenter presenter, PostPopupHelperCallback callback) {
this.context = context; this.context = context;
this.presenter = presenter; this.presenter = presenter;
this.callback = callback;
} }
public void showPosts(Post forPost, List<Post> posts) { public void showPosts(Post forPost, List<Post> posts) {
@ -45,47 +46,47 @@ public class PostPopupHelper {
dataQueue.add(data); dataQueue.add(data);
if (currentPopupFragment != null) { if (dataQueue.size() == 1) {
currentPopupFragment.dismissNoCallback(); present();
} }
presentingController.setPostRepliesData(data);
presentFragment(data);
} }
public void onPostRepliesPop() { public void pop() {
if (dataQueue.size() == 0) if (dataQueue.size() > 0) {
return; dataQueue.remove(dataQueue.size() - 1);
}
dataQueue.remove(dataQueue.size() - 1);
if (dataQueue.size() > 0) { if (dataQueue.size() > 0) {
presentFragment(dataQueue.get(dataQueue.size() - 1)); presentingController.setPostRepliesData(dataQueue.get(dataQueue.size() - 1));
} else { } else {
currentPopupFragment = null; dismiss();
} }
} }
public void closeAllPostFragments() { public void popAll() {
dataQueue.clear(); dataQueue.clear();
if (currentPopupFragment != null) { dismiss();
currentPopupFragment.dismissNoCallback();
currentPopupFragment = null;
}
} }
public void postClicked(Post p) { public void postClicked(Post p) {
closeAllPostFragments(); popAll();
presenter.highlightPost(p.no); presenter.highlightPost(p.no);
presenter.scrollToPost(p.no); presenter.scrollToPost(p.no);
} }
private void presentFragment(RepliesData data) { private void dismiss() {
PostRepliesFragment fragment = PostRepliesFragment.newInstance(data, this, presenter); if (presentingController != null) {
// TODO fade animations on all platforms presentingController.stopPresenting();
FragmentTransaction ft = ((Activity) context).getFragmentManager().beginTransaction(); presentingController = null;
ft.add(fragment, "postPopup"); }
ft.commitAllowingStateLoss(); }
currentPopupFragment = fragment;
private void present() {
if (presentingController == null) {
presentingController = new PostRepliesController(context, this, presenter);
callback.presentRepliesController(presentingController);
}
} }
public static class RepliesData { public static class RepliesData {
@ -99,4 +100,8 @@ public class PostPopupHelper {
this.posts = posts; this.posts = posts;
} }
} }
public interface PostPopupHelperCallback {
void presentRepliesController(Controller controller);
}
} }

@ -28,6 +28,7 @@ import android.widget.Toast;
import com.android.volley.VolleyError; import com.android.volley.VolleyError;
import org.floens.chan.R; import org.floens.chan.R;
import org.floens.chan.controller.Controller;
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;
@ -45,7 +46,7 @@ import java.util.List;
/** /**
* Wrapper around ThreadListLayout, so that it cleanly manages between loadbar and listview. * Wrapper around ThreadListLayout, so that it cleanly manages between loadbar and listview.
*/ */
public class ThreadLayout extends LoadView implements ThreadPresenter.ThreadPresenterCallback { public class ThreadLayout extends LoadView implements ThreadPresenter.ThreadPresenterCallback, PostPopupHelper.PostPopupHelperCallback {
private ThreadLayoutCallback callback; private ThreadLayoutCallback callback;
private ThreadPresenter presenter; private ThreadPresenter presenter;
@ -74,7 +75,7 @@ public class ThreadLayout extends LoadView implements ThreadPresenter.ThreadPres
threadListLayout = new ThreadListLayout(getContext()); threadListLayout = new ThreadListLayout(getContext());
threadListLayout.setCallbacks(presenter, presenter); threadListLayout.setCallbacks(presenter, presenter);
postPopupHelper = new PostPopupHelper(getContext(), presenter); postPopupHelper = new PostPopupHelper(getContext(), presenter, this);
switchVisible(false); switchVisible(false);
} }
@ -157,7 +158,7 @@ public class ThreadLayout extends LoadView implements ThreadPresenter.ThreadPres
@Override @Override
public void showThread(Loadable threadLoadable) { public void showThread(Loadable threadLoadable) {
callback.openThread(threadLoadable); callback.showThread(threadLoadable);
} }
public void showPostsPopup(Post forPost, List<Post> posts) { public void showPostsPopup(Post forPost, List<Post> posts) {
@ -185,11 +186,18 @@ public class ThreadLayout extends LoadView implements ThreadPresenter.ThreadPres
} }
} }
@Override
public void presentRepliesController(Controller controller) {
callback.presentRepliesController(controller);
}
public interface ThreadLayoutCallback { public interface ThreadLayoutCallback {
void openThread(Loadable threadLoadable); void showThread(Loadable threadLoadable);
void showImages(List<PostImage> images, int index, Loadable loadable, ThumbnailView thumbnail); void showImages(List<PostImage> images, int index, Loadable loadable, ThumbnailView thumbnail);
void onShowPosts(); void onShowPosts();
void presentRepliesController(Controller controller);
} }
} }

@ -25,6 +25,7 @@ import android.content.Context;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.Gravity; import android.view.Gravity;
import android.view.View; import android.view.View;
import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.ProgressBar; import android.widget.ProgressBar;
@ -141,6 +142,10 @@ public class LoadView extends FrameLayout {
final AnimatorSet set = new AnimatorSet(); final AnimatorSet set = new AnimatorSet();
set.setDuration(fadeDuration); set.setDuration(fadeDuration);
if (fadeDuration > 0) {
set.setStartDelay(50);
}
set.setInterpolator(new LinearInterpolator());
set.play(ObjectAnimator.ofFloat(view, View.ALPHA, 0f)); set.play(ObjectAnimator.ofFloat(view, View.ALPHA, 0f));
animatorsOut.put(view, set); animatorsOut.put(view, set);
set.addListener(new AnimatorListenerAdapter() { set.addListener(new AnimatorListenerAdapter() {
@ -156,6 +161,7 @@ public class LoadView extends FrameLayout {
private void animateViewIn(View view) { private void animateViewIn(View view) {
final AnimatorSet set = new AnimatorSet(); final AnimatorSet set = new AnimatorSet();
set.setDuration(fadeDuration); set.setDuration(fadeDuration);
set.setInterpolator(new LinearInterpolator());
set.play(ObjectAnimator.ofFloat(view, View.ALPHA, 1f)); set.play(ObjectAnimator.ofFloat(view, View.ALPHA, 1f));
animatorsIn.put(view, set); animatorsIn.put(view, set);
set.addListener(new AnimatorListenerAdapter() { set.addListener(new AnimatorListenerAdapter() {

@ -23,10 +23,12 @@ import android.content.res.TypedArray;
import android.text.Layout; import android.text.Layout;
import android.text.Spannable; import android.text.Spannable;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.SpannedString;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.format.DateUtils; import android.text.format.DateUtils;
import android.text.method.LinkMovementMethod; import android.text.method.LinkMovementMethod;
import android.text.style.AbsoluteSizeSpan; import android.text.style.AbsoluteSizeSpan;
import android.text.style.BackgroundColorSpan;
import android.text.style.ClickableSpan; import android.text.style.ClickableSpan;
import android.text.style.ForegroundColorSpan; import android.text.style.ForegroundColorSpan;
import android.util.AttributeSet; import android.util.AttributeSet;
@ -54,7 +56,7 @@ import org.floens.chan.utils.Time;
import static org.floens.chan.utils.AndroidUtils.setPressedDrawable; import static org.floens.chan.utils.AndroidUtils.setPressedDrawable;
public class PostView extends LinearLayout implements View.OnClickListener { public class PostView extends LinearLayout implements View.OnClickListener, PostLinkable.Callback {
private final static LinearLayout.LayoutParams matchParams = new LinearLayout.LayoutParams( private final static LinearLayout.LayoutParams matchParams = new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
private final static LinearLayout.LayoutParams wrapParams = new LinearLayout.LayoutParams( private final static LinearLayout.LayoutParams wrapParams = new LinearLayout.LayoutParams(
@ -71,6 +73,8 @@ public class PostView extends LinearLayout implements View.OnClickListener {
private Loadable loadable; private Loadable loadable;
private int highlightQuotesNo = -1; private int highlightQuotesNo = -1;
private boolean ignoreNextOnClick = false;
private boolean isBuild = false; private boolean isBuild = false;
private LinearLayout full; private LinearLayout full;
private LinearLayout contentContainer; private LinearLayout contentContainer;
@ -112,18 +116,23 @@ public class PostView extends LinearLayout implements View.OnClickListener {
@Override @Override
protected void onDetachedFromWindow() { protected void onDetachedFromWindow() {
super.onDetachedFromWindow(); super.onDetachedFromWindow();
if (this.post != null) {
if (post != null) { setPostLinkableListener(null);
post.setLinkableListener(null);
} }
} }
public void setPost(final Post post, final PostViewCallback callback) { public void setPost(final Post post, final PostViewCallback callback) {
if (this.post != null) {
// Remove callbacks from the old post while it is still set
setPostLinkableListener(null);
}
this.post = post; this.post = post;
this.callback = callback; this.callback = callback;
this.loadable = callback.getLoadable(); this.loadable = callback.getLoadable();
highlightQuotesNo = -1; highlightQuotesNo = -1;
setPostLinkableListener(this);
boolean boardCatalogMode = loadable.isBoardMode() || loadable.isCatalogMode(); boolean boardCatalogMode = loadable.isBoardMode() || loadable.isCatalogMode();
@ -174,11 +183,9 @@ public class PostView extends LinearLayout implements View.OnClickListener {
commentView.setText(post.comment); commentView.setText(post.comment);
if (loadable.isThreadMode()) { if (loadable.isThreadMode()) {
post.setLinkableListener(this);
commentView.setMovementMethod(new PostViewMovementMethod()); commentView.setMovementMethod(new PostViewMovementMethod());
commentView.setOnClickListener(this); commentView.setOnClickListener(this);
} else { } else {
post.setLinkableListener(null);
commentView.setOnClickListener(null); commentView.setOnClickListener(null);
commentView.setClickable(false); commentView.setClickable(false);
commentView.setMovementMethod(null); commentView.setMovementMethod(null);
@ -267,8 +274,20 @@ public class PostView extends LinearLayout implements View.OnClickListener {
highlightQuotesNo = no; highlightQuotesNo = no;
} }
public int getHighlightQuotesWithNo() { private void setPostLinkableListener(PostLinkable.Callback callback) {
return highlightQuotesNo; if (post.comment instanceof SpannedString) {
SpannedString commentSpannable = (SpannedString) post.comment;
PostLinkable[] linkables = commentSpannable.getSpans(0, commentSpannable.length(), PostLinkable.class);
for (PostLinkable linkable : linkables) {
if (callback == null) {
if (linkable.hasCallback(this)) {
linkable.removeCallback(this);
}
} else {
linkable.addCallback(callback);
}
}
}
} }
private void buildView(final Context context, TypedArray ta) { private void buildView(final Context context, TypedArray ta) {
@ -466,18 +485,35 @@ public class PostView extends LinearLayout implements View.OnClickListener {
wrapper.setOnClickListener(this); wrapper.setOnClickListener(this);
} }
public void setOnClickListeners(View.OnClickListener listener) { public void setOnClickListeners(final View.OnClickListener listener) {
commentView.setOnClickListener(listener); commentView.setOnClickListener(new OnClickListener() {
full.setOnClickListener(listener); @Override
public void onClick(View v) {
if (ignoreNextOnClick) {
ignoreNextOnClick = false;
} else {
listener.onClick(v);
}
}
});
} }
public void onLinkableClick(PostLinkable linkable) { public void onLinkableClick(PostLinkable linkable) {
callback.onPostLinkableClicked(linkable); callback.onPostLinkableClicked(linkable);
} }
@Override
public int getHighlightQuotesWithNo(PostLinkable postLinkable) {
return highlightQuotesNo;
}
@Override @Override
public void onClick(View v) { public void onClick(View v) {
callback.onPostClicked(post); if (ignoreNextOnClick) {
ignoreNextOnClick = false;
} else {
callback.onPostClicked(post);
}
} }
private boolean isList() { private boolean isList() {
@ -512,7 +548,51 @@ public class PostView extends LinearLayout implements View.OnClickListener {
boolean isPostLastSeen(Post post); boolean isPostLastSeen(Post post);
} }
private static BackgroundColorSpan BACKGROUND_SPAN = new BackgroundColorSpan(0x6633B5E5);
private class PostViewMovementMethod extends LinkMovementMethod { private class PostViewMovementMethod extends LinkMovementMethod {
@Override
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
int action = event.getActionMasked();
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_DOWN) {
int x = (int) event.getX();
int y = (int) event.getY();
x -= widget.getTotalPaddingLeft();
y -= widget.getTotalPaddingTop();
x += widget.getScrollX();
y += widget.getScrollY();
Layout layout = widget.getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);
if (link.length != 0) {
if (action == MotionEvent.ACTION_UP) {
ignoreNextOnClick = true;
link[0].onClick(widget);
buffer.removeSpan(BACKGROUND_SPAN);
} else if (action == MotionEvent.ACTION_DOWN) {
buffer.setSpan(BACKGROUND_SPAN, buffer.getSpanStart(link[0]), buffer.getSpanEnd(link[0]), 0);
} else if (action == MotionEvent.ACTION_CANCEL) {
buffer.removeSpan(BACKGROUND_SPAN);
}
return true;
} else {
buffer.removeSpan(BACKGROUND_SPAN);
}
}
return true;
}
}
/*private class PostViewMovementMethod extends LinkMovementMethod {
@Override @Override
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
int action = event.getAction(); int action = event.getAction();
@ -551,5 +631,5 @@ public class PostView extends LinearLayout implements View.OnClickListener {
return true; return true;
} }
} }
} }*/
} }

@ -136,6 +136,10 @@ public class AndroidUtils {
new Handler(Looper.getMainLooper()).post(runnable); new Handler(Looper.getMainLooper()).post(runnable);
} }
public static void runOnUiThread(Runnable runnable, long delay) {
new Handler(Looper.getMainLooper()).postDelayed(runnable, delay);
}
public static void requestKeyboardFocus(Dialog dialog, final View view) { public static void requestKeyboardFocus(Dialog dialog, final View view) {
view.requestFocus(); view.requestFocus();
dialog.setOnShowListener(new DialogInterface.OnShowListener() { dialog.setOnShowListener(new DialogInterface.OnShowListener() {

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 935 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 944 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

@ -15,65 +15,75 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <org.floens.chan.ui.view.LoadView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/loadview"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:minWidth="320dp"
android:orientation="vertical">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:id="@+id/container"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:baselineAligned="false" android:layout_gravity="center"
android:divider="?android:attr/dividerVertical" android:minWidth="320dp"
android:dividerPadding="12dp" android:background="@drawable/dialog_full_light"
android:orientation="horizontal" android:orientation="vertical">
android:showDividers="middle">
<FrameLayout <LinearLayout
android:id="@+id/replies_back" android:layout_width="match_parent"
style="?android:actionButtonStyle" android:layout_height="wrap_content"
android:layout_width="0dp" android:baselineAligned="false"
android:layout_height="match_parent" android:divider="?android:attr/dividerVertical"
android:layout_weight="1"> android:dividerPadding="12dp"
android:orientation="horizontal"
android:showDividers="middle">
<TextView <FrameLayout
android:id="@+id/replies_back_icon" android:id="@+id/replies_back"
style="?android:actionBarTabTextStyle" style="?android:actionButtonStyle"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_gravity="center" android:layout_weight="1">
android:drawableLeft="@drawable/ic_action_back"
android:drawablePadding="8dp"
android:gravity="center_vertical"
android:paddingRight="20dp"
android:text="@string/back" />
</FrameLayout>
<FrameLayout <TextView
android:id="@+id/replies_close" android:id="@+id/replies_back_icon"
style="?android:actionButtonStyle" style="?android:actionBarTabTextStyle"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_weight="1"> android:layout_gravity="center"
android:drawableLeft="@drawable/ic_action_back"
android:drawablePadding="8dp"
android:gravity="center_vertical"
android:paddingRight="20dp"
android:text="@string/back" />
</FrameLayout>
<TextView <FrameLayout
android:id="@+id/replies_close_icon" android:id="@+id/replies_close"
style="?android:actionBarTabTextStyle" style="?android:actionButtonStyle"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_gravity="center" android:layout_weight="1">
android:drawableLeft="@drawable/ic_action_done"
android:drawablePadding="8dp" <TextView
android:gravity="center_vertical" android:id="@+id/replies_close_icon"
android:paddingRight="20dp" style="?android:actionBarTabTextStyle"
android:text="@string/close" /> android:layout_width="wrap_content"
</FrameLayout> android:layout_height="wrap_content"
</LinearLayout> android:layout_gravity="center"
android:drawableLeft="@drawable/ic_action_done"
android:drawablePadding="8dp"
android:gravity="center_vertical"
android:paddingRight="20dp"
android:text="@string/close" />
</FrameLayout>
</LinearLayout>
<ListView <ListView
android:id="@+id/post_list" android:id="@+id/post_list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />
</LinearLayout>
</LinearLayout> </org.floens.chan.ui.view.LoadView>

@ -15,66 +15,75 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:minWidth="320dp" android:background="#88000000">
android:orientation="vertical">
<ListView
android:id="@+id/post_list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:id="@+id/container"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:baselineAligned="false" android:layout_gravity="center"
android:divider="?android:attr/dividerVertical" android:minWidth="320dp"
android:dividerPadding="12dp" android:background="@drawable/dialog_full_light"
android:orientation="horizontal" android:orientation="vertical">
android:showDividers="middle">
<ListView
android:id="@+id/post_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:baselineAligned="false"
android:divider="?android:attr/dividerVertical"
android:dividerPadding="12dp"
android:orientation="horizontal"
android:showDividers="middle">
<FrameLayout
android:id="@+id/replies_back"
style="?android:actionButtonStyle"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
<FrameLayout <TextView
android:id="@+id/replies_back" android:id="@+id/replies_back_icon"
style="?android:actionButtonStyle" style="?android:actionBarTabTextStyle"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_weight="1"> android:layout_gravity="center"
android:drawableLeft="@drawable/ic_action_back"
android:drawablePadding="8dp"
android:gravity="center_vertical"
android:paddingRight="20dp"
android:text="@string/back" />
</FrameLayout>
<TextView <FrameLayout
android:id="@+id/replies_back_icon" android:id="@+id/replies_close"
style="?android:actionBarTabTextStyle" style="?android:actionButtonStyle"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_gravity="center" android:layout_weight="1">
android:drawableLeft="@drawable/ic_action_back"
android:drawablePadding="8dp"
android:gravity="center_vertical"
android:paddingRight="20dp"
android:text="@string/back" />
</FrameLayout>
<FrameLayout <TextView
android:id="@+id/replies_close" android:id="@+id/replies_close_icon"
style="?android:actionButtonStyle" style="?android:actionBarTabTextStyle"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_weight="1"> android:layout_gravity="center"
android:drawableLeft="@drawable/ic_action_done"
android:drawablePadding="8dp"
android:gravity="center_vertical"
android:paddingRight="20dp"
android:text="@string/close" />
</FrameLayout>
</LinearLayout>
<TextView
android:id="@+id/replies_close_icon"
style="?android:actionBarTabTextStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:drawableLeft="@drawable/ic_action_done"
android:drawablePadding="8dp"
android:gravity="center_vertical"
android:paddingRight="20dp"
android:text="@string/close" />
</FrameLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </FrameLayout>

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