diff --git a/Clover/app/src/main/java/org/floens/chan/controller/Controller.java b/Clover/app/src/main/java/org/floens/chan/controller/Controller.java index e20e0d45..3b9d3f12 100644 --- a/Clover/app/src/main/java/org/floens/chan/controller/Controller.java +++ b/Clover/app/src/main/java/org/floens/chan/controller/Controller.java @@ -40,11 +40,14 @@ public abstract class Controller { public Controller presentingController; public Controller presentedController; + public boolean alive = false; + public Controller(Context context) { this.context = context; } public void onCreate() { + alive = true; // Logger.test(getClass().getSimpleName() + " onCreate"); } @@ -57,6 +60,7 @@ public abstract class Controller { } public void onDestroy() { + alive = false; // Logger.test(getClass().getSimpleName() + " onDestroy"); } @@ -111,6 +115,7 @@ public abstract class Controller { ControllerLogic.transition(this, null, true, false, contentView); } ((BoardActivity) context).removeController(this); + presentingController.presentedController = null; } public View inflateRes(int resId) { diff --git a/Clover/app/src/main/java/org/floens/chan/controller/FadeInTransition.java b/Clover/app/src/main/java/org/floens/chan/controller/FadeInTransition.java index af74af72..2475e711 100644 --- a/Clover/app/src/main/java/org/floens/chan/controller/FadeInTransition.java +++ b/Clover/app/src/main/java/org/floens/chan/controller/FadeInTransition.java @@ -5,14 +5,14 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.view.View; -import android.view.animation.DecelerateInterpolator; +import android.view.animation.AccelerateDecelerateInterpolator; public class FadeInTransition extends ControllerTransition { @Override public void perform() { Animator toAlpha = ObjectAnimator.ofFloat(to.view, View.ALPHA, 0f, 1f); toAlpha.setDuration(200); - toAlpha.setInterpolator(new DecelerateInterpolator(2f)); + toAlpha.setInterpolator(new AccelerateDecelerateInterpolator()); toAlpha.addListener(new AnimatorListenerAdapter() { @Override diff --git a/Clover/app/src/main/java/org/floens/chan/controller/FadeOutTransition.java b/Clover/app/src/main/java/org/floens/chan/controller/FadeOutTransition.java index e823ae8c..c0ab0bed 100644 --- a/Clover/app/src/main/java/org/floens/chan/controller/FadeOutTransition.java +++ b/Clover/app/src/main/java/org/floens/chan/controller/FadeOutTransition.java @@ -5,14 +5,14 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.view.View; -import android.view.animation.DecelerateInterpolator; +import android.view.animation.AccelerateDecelerateInterpolator; public class FadeOutTransition extends ControllerTransition { @Override public void perform() { Animator toAlpha = ObjectAnimator.ofFloat(from.view, View.ALPHA, 1f, 0f); toAlpha.setDuration(200); - toAlpha.setInterpolator(new DecelerateInterpolator(2f)); + toAlpha.setInterpolator(new AccelerateDecelerateInterpolator()); toAlpha.addListener(new AnimatorListenerAdapter() { @Override diff --git a/Clover/app/src/main/java/org/floens/chan/core/model/Post.java b/Clover/app/src/main/java/org/floens/chan/core/model/Post.java index 75c5599f..386cda60 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/model/Post.java +++ b/Clover/app/src/main/java/org/floens/chan/core/model/Post.java @@ -23,7 +23,6 @@ import android.text.TextUtils; import org.floens.chan.ChanApplication; import org.floens.chan.chan.ChanUrls; import org.floens.chan.core.loader.ChanParser; -import org.floens.chan.ui.view.PostView; import org.jsoup.parser.Parser; import java.util.ArrayList; @@ -105,22 +104,9 @@ public class Post { public SpannableString capcodeSpan; public CharSequence nameTripcodeIdCapcodeSpan; - /** - * The PostView the Post is currently bound to. - */ - private PostView linkableListener; - public Post() { } - public void setLinkableListener(PostView listener) { - linkableListener = listener; - } - - public PostView getLinkableListener() { - return linkableListener; - } - /** * Finish up the data * diff --git a/Clover/app/src/main/java/org/floens/chan/core/model/PostLinkable.java b/Clover/app/src/main/java/org/floens/chan/core/model/PostLinkable.java index c2de1ca3..70b54366 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/model/PostLinkable.java +++ b/Clover/app/src/main/java/org/floens/chan/core/model/PostLinkable.java @@ -17,17 +17,21 @@ */ package org.floens.chan.core.model; +import android.support.annotation.NonNull; import android.text.TextPaint; import android.text.style.ClickableSpan; import android.view.View; 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. */ public class PostLinkable extends ClickableSpan { - public static enum Type { + public enum Type { QUOTE, LINK, SPOILER, THREAD } @@ -36,7 +40,8 @@ public class PostLinkable extends ClickableSpan { public final Object value; public final Type type; - private boolean clicked = false; + private List callbacks = new ArrayList<>(); + private boolean spoilerVisible = false; public PostLinkable(Post post, String key, Object value, Type type) { this.post = post; @@ -45,19 +50,33 @@ public class PostLinkable extends ClickableSpan { 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 public void onClick(View widget) { - if (post.getLinkableListener() != null) { - post.getLinkableListener().onLinkableClick(this); + Callback top = topCallback(); + if (top != null) { + top.onLinkableClick(this); } - clicked = true; + spoilerVisible = !spoilerVisible; } @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) { - 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()); } else { ds.setColor(ThemeHelper.getInstance().getQuoteColor()); @@ -70,7 +89,7 @@ public class PostLinkable extends ClickableSpan { ds.setUnderlineText(true); } else if (type == Type.SPOILER) { - if (!clicked) { + if (!spoilerVisible) { ds.setColor(ThemeHelper.getInstance().getSpoilerColor()); ds.bgColor = ThemeHelper.getInstance().getSpoilerColor(); 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 String board; public int threadId; @@ -89,4 +112,10 @@ public class PostLinkable extends ClickableSpan { this.postId = postId; } } + + public interface Callback { + void onLinkableClick(PostLinkable postLinkable); + + int getHighlightQuotesWithNo(PostLinkable postLinkable); + } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java b/Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java index 8245de29..29a486a3 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java @@ -77,6 +77,11 @@ public class StartActivity extends Activity { super.onDestroy(); stackTop().onDestroy(); + stack.clear(); + System.gc(); + System.gc(); + System.gc(); + System.runFinalization(); } @Override diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java index 404db81c..8e786d32 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java @@ -137,7 +137,7 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte } @Override - public void openThread(Loadable threadLoadable) { + public void showThread(Loadable threadLoadable) { ViewThreadController viewThreadController = new ViewThreadController(context); viewThreadController.setLoadable(threadLoadable); navigationController.pushController(viewThreadController); @@ -149,7 +149,7 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte @Override public void onPinClicked(Pin pin) { - openThread(pin.loadable); + showThread(pin.loadable); } @Override diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/PostRepliesController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/PostRepliesController.java new file mode 100644 index 00000000..102e1151 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/PostRepliesController.java @@ -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 adapter = new ArrayAdapter(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(); + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java index b1d1218f..35be39ca 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java @@ -21,6 +21,16 @@ public abstract class ThreadController extends Controller implements ThreadLayou view = threadLayout; } + @Override + public void onDestroy() { + super.onDestroy(); + threadLayout.getPresenter().unbindLoadable(); + } + + public void presentRepliesController(Controller controller) { + presentController(controller); + } + @Override public void showImages(List images, int index, Loadable loadable, final ThumbnailView thumbnail) { // Just ignore the showImages request when the image is not loaded diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/ViewThreadController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/ViewThreadController.java index 94addd4e..cd0cc8dc 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/ViewThreadController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/ViewThreadController.java @@ -105,7 +105,7 @@ public class ViewThreadController extends ThreadController implements ThreadLayo } @Override - public void openThread(final Loadable threadLoadable) { + public void showThread(final Loadable threadLoadable) { // TODO implement, scroll to post and fix title new AlertDialog.Builder(context) .setNegativeButton(R.string.cancel, null) diff --git a/Clover/app/src/main/java/org/floens/chan/ui/fragment/PostRepliesFragment.java b/Clover/app/src/main/java/org/floens/chan/ui/fragment/PostRepliesFragment.java index 71579f11..c54595d9 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/fragment/PostRepliesFragment.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/fragment/PostRepliesFragment.java @@ -51,10 +51,10 @@ public class PostRepliesFragment extends DialogFragment { public static PostRepliesFragment newInstance(PostPopupHelper.RepliesData repliesData, PostPopupHelper postPopupHelper, ThreadPresenter presenter) { PostRepliesFragment fragment = new PostRepliesFragment(); + fragment.repliesData = repliesData; fragment.postPopupHelper = postPopupHelper; fragment.presenter = presenter; - return fragment; } @@ -75,7 +75,7 @@ public class PostRepliesFragment extends DialogFragment { super.onDismiss(dialog); if (postPopupHelper != null) { - postPopupHelper.onPostRepliesPop(); +// postPopupHelper.onPostRepliesPop(); } } @@ -101,7 +101,7 @@ public class PostRepliesFragment extends DialogFragment { @Override public void onClick(View v) { if (postPopupHelper != null) { - postPopupHelper.closeAllPostFragments(); +// postPopupHelper.closeAllPostFragments(); } } }); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/helper/PostPopupHelper.java b/Clover/app/src/main/java/org/floens/chan/ui/helper/PostPopupHelper.java index e3fa4cf3..09af0a6e 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/helper/PostPopupHelper.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/helper/PostPopupHelper.java @@ -17,13 +17,12 @@ */ package org.floens.chan.ui.helper; -import android.app.Activity; -import android.app.FragmentTransaction; import android.content.Context; +import org.floens.chan.controller.Controller; import org.floens.chan.core.model.Post; 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.List; @@ -31,13 +30,15 @@ import java.util.List; public class PostPopupHelper { private Context context; private ThreadPresenter presenter; + private final PostPopupHelperCallback callback; private final List 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.presenter = presenter; + this.callback = callback; } public void showPosts(Post forPost, List posts) { @@ -45,47 +46,47 @@ public class PostPopupHelper { dataQueue.add(data); - if (currentPopupFragment != null) { - currentPopupFragment.dismissNoCallback(); + if (dataQueue.size() == 1) { + present(); } - - presentFragment(data); + presentingController.setPostRepliesData(data); } - public void onPostRepliesPop() { - if (dataQueue.size() == 0) - return; - - dataQueue.remove(dataQueue.size() - 1); + public void pop() { + if (dataQueue.size() > 0) { + dataQueue.remove(dataQueue.size() - 1); + } if (dataQueue.size() > 0) { - presentFragment(dataQueue.get(dataQueue.size() - 1)); + presentingController.setPostRepliesData(dataQueue.get(dataQueue.size() - 1)); } else { - currentPopupFragment = null; + dismiss(); } } - public void closeAllPostFragments() { + public void popAll() { dataQueue.clear(); - if (currentPopupFragment != null) { - currentPopupFragment.dismissNoCallback(); - currentPopupFragment = null; - } + dismiss(); } public void postClicked(Post p) { - closeAllPostFragments(); + popAll(); presenter.highlightPost(p.no); presenter.scrollToPost(p.no); } - private void presentFragment(RepliesData data) { - PostRepliesFragment fragment = PostRepliesFragment.newInstance(data, this, presenter); - // TODO fade animations on all platforms - FragmentTransaction ft = ((Activity) context).getFragmentManager().beginTransaction(); - ft.add(fragment, "postPopup"); - ft.commitAllowingStateLoss(); - currentPopupFragment = fragment; + private void dismiss() { + if (presentingController != null) { + presentingController.stopPresenting(); + presentingController = null; + } + } + + private void present() { + if (presentingController == null) { + presentingController = new PostRepliesController(context, this, presenter); + callback.presentRepliesController(presentingController); + } } public static class RepliesData { @@ -99,4 +100,8 @@ public class PostPopupHelper { this.posts = posts; } } + + public interface PostPopupHelperCallback { + void presentRepliesController(Controller controller); + } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java b/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java index bf0b0d68..4cbe6d8d 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java @@ -28,6 +28,7 @@ import android.widget.Toast; import com.android.volley.VolleyError; import org.floens.chan.R; +import org.floens.chan.controller.Controller; import org.floens.chan.core.model.ChanThread; import org.floens.chan.core.model.Loadable; 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. */ -public class ThreadLayout extends LoadView implements ThreadPresenter.ThreadPresenterCallback { +public class ThreadLayout extends LoadView implements ThreadPresenter.ThreadPresenterCallback, PostPopupHelper.PostPopupHelperCallback { private ThreadLayoutCallback callback; private ThreadPresenter presenter; @@ -74,7 +75,7 @@ public class ThreadLayout extends LoadView implements ThreadPresenter.ThreadPres threadListLayout = new ThreadListLayout(getContext()); threadListLayout.setCallbacks(presenter, presenter); - postPopupHelper = new PostPopupHelper(getContext(), presenter); + postPopupHelper = new PostPopupHelper(getContext(), presenter, this); switchVisible(false); } @@ -157,7 +158,7 @@ public class ThreadLayout extends LoadView implements ThreadPresenter.ThreadPres @Override public void showThread(Loadable threadLoadable) { - callback.openThread(threadLoadable); + callback.showThread(threadLoadable); } public void showPostsPopup(Post forPost, List 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 { - void openThread(Loadable threadLoadable); + void showThread(Loadable threadLoadable); void showImages(List images, int index, Loadable loadable, ThumbnailView thumbnail); void onShowPosts(); + + void presentRepliesController(Controller controller); } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/view/LoadView.java b/Clover/app/src/main/java/org/floens/chan/ui/view/LoadView.java index bddaa9c5..5f623d86 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/view/LoadView.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/view/LoadView.java @@ -25,6 +25,7 @@ import android.content.Context; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; +import android.view.animation.LinearInterpolator; import android.widget.FrameLayout; import android.widget.ProgressBar; @@ -141,6 +142,10 @@ public class LoadView extends FrameLayout { final AnimatorSet set = new AnimatorSet(); set.setDuration(fadeDuration); + if (fadeDuration > 0) { + set.setStartDelay(50); + } + set.setInterpolator(new LinearInterpolator()); set.play(ObjectAnimator.ofFloat(view, View.ALPHA, 0f)); animatorsOut.put(view, set); set.addListener(new AnimatorListenerAdapter() { @@ -156,6 +161,7 @@ public class LoadView extends FrameLayout { private void animateViewIn(View view) { final AnimatorSet set = new AnimatorSet(); set.setDuration(fadeDuration); + set.setInterpolator(new LinearInterpolator()); set.play(ObjectAnimator.ofFloat(view, View.ALPHA, 1f)); animatorsIn.put(view, set); set.addListener(new AnimatorListenerAdapter() { diff --git a/Clover/app/src/main/java/org/floens/chan/ui/view/PostView.java b/Clover/app/src/main/java/org/floens/chan/ui/view/PostView.java index 9cee09a3..340a6b48 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/view/PostView.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/view/PostView.java @@ -23,10 +23,12 @@ import android.content.res.TypedArray; import android.text.Layout; import android.text.Spannable; import android.text.SpannableString; +import android.text.SpannedString; import android.text.TextUtils; import android.text.format.DateUtils; import android.text.method.LinkMovementMethod; import android.text.style.AbsoluteSizeSpan; +import android.text.style.BackgroundColorSpan; import android.text.style.ClickableSpan; import android.text.style.ForegroundColorSpan; import android.util.AttributeSet; @@ -54,7 +56,7 @@ import org.floens.chan.utils.Time; 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( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 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 int highlightQuotesNo = -1; + private boolean ignoreNextOnClick = false; + private boolean isBuild = false; private LinearLayout full; private LinearLayout contentContainer; @@ -112,18 +116,23 @@ public class PostView extends LinearLayout implements View.OnClickListener { @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - - if (post != null) { - post.setLinkableListener(null); + if (this.post != null) { + setPostLinkableListener(null); } } 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.callback = callback; this.loadable = callback.getLoadable(); highlightQuotesNo = -1; + setPostLinkableListener(this); boolean boardCatalogMode = loadable.isBoardMode() || loadable.isCatalogMode(); @@ -174,11 +183,9 @@ public class PostView extends LinearLayout implements View.OnClickListener { commentView.setText(post.comment); if (loadable.isThreadMode()) { - post.setLinkableListener(this); commentView.setMovementMethod(new PostViewMovementMethod()); commentView.setOnClickListener(this); } else { - post.setLinkableListener(null); commentView.setOnClickListener(null); commentView.setClickable(false); commentView.setMovementMethod(null); @@ -267,8 +274,20 @@ public class PostView extends LinearLayout implements View.OnClickListener { highlightQuotesNo = no; } - public int getHighlightQuotesWithNo() { - return highlightQuotesNo; + private void setPostLinkableListener(PostLinkable.Callback callback) { + 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) { @@ -466,18 +485,35 @@ public class PostView extends LinearLayout implements View.OnClickListener { wrapper.setOnClickListener(this); } - public void setOnClickListeners(View.OnClickListener listener) { - commentView.setOnClickListener(listener); - full.setOnClickListener(listener); + public void setOnClickListeners(final View.OnClickListener listener) { + commentView.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (ignoreNextOnClick) { + ignoreNextOnClick = false; + } else { + listener.onClick(v); + } + } + }); } public void onLinkableClick(PostLinkable linkable) { callback.onPostLinkableClicked(linkable); } + @Override + public int getHighlightQuotesWithNo(PostLinkable postLinkable) { + return highlightQuotesNo; + } + @Override public void onClick(View v) { - callback.onPostClicked(post); + if (ignoreNextOnClick) { + ignoreNextOnClick = false; + } else { + callback.onPostClicked(post); + } } private boolean isList() { @@ -512,7 +548,51 @@ public class PostView extends LinearLayout implements View.OnClickListener { boolean isPostLastSeen(Post post); } + private static BackgroundColorSpan BACKGROUND_SPAN = new BackgroundColorSpan(0x6633B5E5); + 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 public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { int action = event.getAction(); @@ -551,5 +631,5 @@ public class PostView extends LinearLayout implements View.OnClickListener { return true; } } - } + }*/ } diff --git a/Clover/app/src/main/java/org/floens/chan/utils/AndroidUtils.java b/Clover/app/src/main/java/org/floens/chan/utils/AndroidUtils.java index c0e17bb7..aec48d97 100644 --- a/Clover/app/src/main/java/org/floens/chan/utils/AndroidUtils.java +++ b/Clover/app/src/main/java/org/floens/chan/utils/AndroidUtils.java @@ -136,6 +136,10 @@ public class AndroidUtils { 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) { view.requestFocus(); dialog.setOnShowListener(new DialogInterface.OnShowListener() { diff --git a/Clover/app/src/main/res/drawable-hdpi/dialog_full_dark.9.png b/Clover/app/src/main/res/drawable-hdpi/dialog_full_dark.9.png new file mode 100644 index 00000000..911f3fee Binary files /dev/null and b/Clover/app/src/main/res/drawable-hdpi/dialog_full_dark.9.png differ diff --git a/Clover/app/src/main/res/drawable-hdpi/dialog_full_light.9.png b/Clover/app/src/main/res/drawable-hdpi/dialog_full_light.9.png new file mode 100644 index 00000000..2129567f Binary files /dev/null and b/Clover/app/src/main/res/drawable-hdpi/dialog_full_light.9.png differ diff --git a/Clover/app/src/main/res/drawable-mdpi/dialog_full_dark.9.png b/Clover/app/src/main/res/drawable-mdpi/dialog_full_dark.9.png new file mode 100644 index 00000000..dc373160 Binary files /dev/null and b/Clover/app/src/main/res/drawable-mdpi/dialog_full_dark.9.png differ diff --git a/Clover/app/src/main/res/drawable-mdpi/dialog_full_light.9.png b/Clover/app/src/main/res/drawable-mdpi/dialog_full_light.9.png new file mode 100644 index 00000000..0c5770a3 Binary files /dev/null and b/Clover/app/src/main/res/drawable-mdpi/dialog_full_light.9.png differ diff --git a/Clover/app/src/main/res/drawable-xhdpi/dialog_full_dark.9.png b/Clover/app/src/main/res/drawable-xhdpi/dialog_full_dark.9.png new file mode 100644 index 00000000..75d36be3 Binary files /dev/null and b/Clover/app/src/main/res/drawable-xhdpi/dialog_full_dark.9.png differ diff --git a/Clover/app/src/main/res/drawable-xhdpi/dialog_full_light.9.png b/Clover/app/src/main/res/drawable-xhdpi/dialog_full_light.9.png new file mode 100644 index 00000000..d9bd3375 Binary files /dev/null and b/Clover/app/src/main/res/drawable-xhdpi/dialog_full_light.9.png differ diff --git a/Clover/app/src/main/res/drawable-xxhdpi/dialog_full_dark.9.png b/Clover/app/src/main/res/drawable-xxhdpi/dialog_full_dark.9.png new file mode 100644 index 00000000..b029809d Binary files /dev/null and b/Clover/app/src/main/res/drawable-xxhdpi/dialog_full_dark.9.png differ diff --git a/Clover/app/src/main/res/drawable-xxhdpi/dialog_full_light.9.png b/Clover/app/src/main/res/drawable-xxhdpi/dialog_full_light.9.png new file mode 100644 index 00000000..63dd1927 Binary files /dev/null and b/Clover/app/src/main/res/drawable-xxhdpi/dialog_full_light.9.png differ diff --git a/Clover/app/src/main/res/layout/post_replies.xml b/Clover/app/src/main/res/layout/post_replies.xml index e59f3fcd..51cea0cb 100644 --- a/Clover/app/src/main/res/layout/post_replies.xml +++ b/Clover/app/src/main/res/layout/post_replies.xml @@ -15,65 +15,75 @@ 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 . --> - + android:layout_height="match_parent"> + android:layout_gravity="center" + android:minWidth="320dp" + android:background="@drawable/dialog_full_light" + android:orientation="vertical"> - + - - + - + + - - - + + + + + - + + + - + diff --git a/Clover/app/src/main/res/layout/post_replies_bottombuttons.xml b/Clover/app/src/main/res/layout/post_replies_bottombuttons.xml index b5efee0f..9ce0573d 100644 --- a/Clover/app/src/main/res/layout/post_replies_bottombuttons.xml +++ b/Clover/app/src/main/res/layout/post_replies_bottombuttons.xml @@ -15,66 +15,75 @@ 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 . --> - - - + android:background="#88000000"> + android:layout_gravity="center" + android:minWidth="320dp" + android:background="@drawable/dialog_full_light" + android:orientation="vertical"> + + + + + + - + + - - + - + + + - - - + diff --git a/Clover/app/src/main/res/layout/post_replies_container.xml b/Clover/app/src/main/res/layout/post_replies_container.xml new file mode 100644 index 00000000..5788e019 --- /dev/null +++ b/Clover/app/src/main/res/layout/post_replies_container.xml @@ -0,0 +1,22 @@ + +