items = new ArrayList<>();
+ NavigationItem.MenuOverflowBuilder menuOverflowBuilder = navigation.buildMenu()
+ .withItem(R.drawable.ic_image_white_24dp, this::albumClicked)
+ .withItem(PIN_ID, R.drawable.ic_bookmark_outline_white_24dp, this::pinClicked)
+ .withOverflow();
+
if (!ChanSettings.enableReplyFab.get()) {
- items.add(new FloatingMenuItem(REPLY_ID, context.getString(R.string.action_reply)));
+ menuOverflowBuilder.withSubItem(R.string.action_reply, this::replyClicked);
}
- items.add(new FloatingMenuItem(SEARCH_ID, R.string.action_search));
- items.add(new FloatingMenuItem(REFRESH_ID, R.string.action_reload));
- items.add(new FloatingMenuItem(OPEN_BROWSER_ID, R.string.action_open_browser));
- items.add(new FloatingMenuItem(SHARE_ID, R.string.action_share));
- items.add(new FloatingMenuItem(UP_ID, R.string.action_up));
- items.add(new FloatingMenuItem(DOWN_ID, R.string.action_down));
- overflowItem = navigation.createOverflow(context, this, items);
+
+ menuOverflowBuilder
+ .withSubItem(R.string.action_search, this::searchClicked)
+ .withSubItem(R.string.action_reload, this::reloadClicked)
+ .withSubItem(R.string.action_open_browser, this::openBrowserClicked)
+ .withSubItem(R.string.action_share, this::shareClicked)
+ .withSubItem(R.string.action_up, this::upClicked)
+ .withSubItem(R.string.action_down, this::downClicked)
+ .build()
+ .build();
loadThread(loadable);
}
+ private void albumClicked(ToolbarMenuItem item) {
+ threadLayout.getPresenter().showAlbum();
+ }
+
+ private void pinClicked(ToolbarMenuItem item) {
+ threadLayout.getPresenter().pin();
+ setPinIconState(true);
+ updateDrawerHighlighting(loadable);
+ }
+
+ private void searchClicked(ToolbarMenuSubItem item) {
+ ((ToolbarNavigationController) navigationController).showSearch();
+ }
+
+ private void replyClicked(ToolbarMenuSubItem item) {
+ threadLayout.openReply(true);
+ }
+
+ private void reloadClicked(ToolbarMenuSubItem item) {
+ threadLayout.getPresenter().requestData();
+ }
+
+ private void openBrowserClicked(ToolbarMenuSubItem item) {
+ Loadable loadable = threadLayout.getPresenter().getLoadable();
+ String link = loadable.site.resolvable().desktopUrl(loadable, null);
+ AndroidUtils.openLinkInBrowser((Activity) context, link);
+ }
+
+ private void shareClicked(ToolbarMenuSubItem item) {
+ Loadable loadable = threadLayout.getPresenter().getLoadable();
+ String link = loadable.site.resolvable().desktopUrl(loadable, null);
+ AndroidUtils.shareLink(link);
+ }
+
+ private void upClicked(ToolbarMenuSubItem item) {
+ threadLayout.getPresenter().scrollTo(0, false);
+ }
+
+ private void downClicked(ToolbarMenuSubItem item) {
+ threadLayout.getPresenter().scrollTo(-1, false);
+ }
+
+ @Override
+ public void onShow() {
+ super.onShow();
+
+ ThreadPresenter presenter = threadLayout.getPresenter();
+ if (presenter != null) {
+ setPinIconState(false);
+ }
+ }
+
@Override
public void onDestroy() {
super.onDestroy();
@@ -114,15 +161,15 @@ public class ViewThreadController extends ThreadController implements ThreadLayo
}
public void onEvent(WatchManager.PinAddedMessage message) {
- setPinIconState();
+ setPinIconState(true);
}
public void onEvent(WatchManager.PinRemovedMessage message) {
- setPinIconState();
+ setPinIconState(true);
}
public void onEvent(WatchManager.PinChangedMessage message) {
- setPinIconState();
+ setPinIconState(false);
// Update title
if (message.pin.loadable == loadable) {
onShowPosts();
@@ -151,16 +198,18 @@ public class ViewThreadController extends ThreadController implements ThreadLayo
this.loadable = presenter.getLoadable();
navigation.title = loadable.title;
((ToolbarNavigationController) navigationController).toolbar.updateTitle(navigation);
- setPinIconState(presenter.isPinned());
+ setPinIconState(false);
updateDrawerHighlighting(loadable);
updateLeftPaneHighlighting(loadable);
presenter.requestInitialData();
int counter = ChanSettings.threadOpenCounter.increase();
if (counter == 2) {
- HintPopup.show(context, overflowItem.getView(), context.getString(R.string.thread_up_down_hint), -dp(1), 0);
+ View view = navigation.findItem(ToolbarMenu.OVERFLOW_ID).getView();
+ HintPopup.show(context, view, context.getString(R.string.thread_up_down_hint), -dp(1), 0);
} else if (counter == 3) {
- HintPopup.show(context, pinItem.getView(), context.getString(R.string.thread_pin_hint), -dp(1), 0);
+ View view = navigation.findItem(PIN_ID).getView();
+ HintPopup.show(context, view, context.getString(R.string.thread_pin_hint), -dp(1), 0);
}
}
}
@@ -173,53 +222,6 @@ public class ViewThreadController extends ThreadController implements ThreadLayo
((ToolbarNavigationController) navigationController).toolbar.updateTitle(navigation);
}
- @Override
- public void onMenuItemClicked(ToolbarMenuItem item) {
- switch ((Integer) item.getId()) {
- case ALBUM_ID:
- threadLayout.getPresenter().showAlbum();
- break;
- case PIN_ID:
- setPinIconState(threadLayout.getPresenter().pin());
- updateDrawerHighlighting(loadable);
- break;
- }
- }
-
- @Override
- public void onSubMenuItemClicked(ToolbarMenuItem parent, FloatingMenuItem item) {
- Integer id = (Integer) item.getId();
-
- switch (id) {
- case REPLY_ID:
- threadLayout.openReply(true);
- break;
- case REFRESH_ID:
- threadLayout.getPresenter().requestData();
- break;
- case SEARCH_ID:
- ((ToolbarNavigationController) navigationController).showSearch();
- break;
- case SHARE_ID:
- case OPEN_BROWSER_ID:
- Loadable loadable = threadLayout.getPresenter().getLoadable();
- String link = loadable.site.resolvable().desktopUrl(loadable, null);
-
- if (id == SHARE_ID) {
- AndroidUtils.shareLink(link);
- } else {
- AndroidUtils.openLinkInBrowser((Activity) context, link);
- }
-
- break;
- case UP_ID:
- case DOWN_ID:
- boolean up = id == UP_ID;
- threadLayout.getPresenter().scrollTo(up ? 0 : -1, false);
- break;
- }
- }
-
private void updateDrawerHighlighting(Loadable loadable) {
Pin pin = loadable == null ? null : watchManager.findPinByLoadable(loadable);
@@ -254,11 +256,26 @@ public class ViewThreadController extends ThreadController implements ThreadLayo
}
}
- private void setPinIconState() {
- setPinIconState(watchManager.findPinByLoadable(loadable) != null);
+ private void setPinIconState(boolean animated) {
+ ThreadPresenter presenter = threadLayout.getPresenter();
+ if (presenter != null) {
+ setPinIconStateDrawable(presenter.isPinned(), animated);
+ }
}
- private void setPinIconState(boolean pinned) {
- pinItem.setImage(pinned ? R.drawable.ic_bookmark_white_24dp : R.drawable.ic_bookmark_outline_white_24dp);
+ private void setPinIconStateDrawable(boolean pinned, boolean animated) {
+ if (pinned == pinItemPinned) {
+ return;
+ }
+ pinItemPinned = pinned;
+
+ Drawable outline = context.getResources().getDrawable(
+ R.drawable.ic_bookmark_outline_white_24dp);
+ Drawable white = context.getResources().getDrawable(
+ R.drawable.ic_bookmark_white_24dp);
+
+ Drawable drawable = pinned ? white : outline;
+
+ navigation.findItem(PIN_ID).setImage(drawable, animated);
}
}
diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/WatchSettingsController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/WatchSettingsController.java
index bca5927f..3177fe51 100644
--- a/Clover/app/src/main/java/org/floens/chan/ui/controller/WatchSettingsController.java
+++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/WatchSettingsController.java
@@ -62,7 +62,7 @@ public class WatchSettingsController extends SettingsController implements Compo
SwitchCompat globalSwitch = new SwitchCompat(context);
globalSwitch.setChecked(enabled);
globalSwitch.setOnCheckedChangeListener(this);
- navigation.rightView = globalSwitch;
+ navigation.setRightView(globalSwitch);
populatePreferences();
diff --git a/Clover/app/src/main/java/org/floens/chan/ui/layout/SearchLayout.java b/Clover/app/src/main/java/org/floens/chan/ui/layout/SearchLayout.java
index b8b9c9e4..c5e63a5a 100644
--- a/Clover/app/src/main/java/org/floens/chan/ui/layout/SearchLayout.java
+++ b/Clover/app/src/main/java/org/floens/chan/ui/layout/SearchLayout.java
@@ -130,12 +130,7 @@ public class SearchLayout extends LinearLayout {
}
public void openKeyboard() {
- searchView.post(new Runnable() {
- @Override
- public void run() {
- AndroidUtils.requestViewAndKeyboardFocus(searchView);
- }
- });
+ searchView.post(() -> AndroidUtils.requestViewAndKeyboardFocus(searchView));
}
public interface SearchLayoutCallback {
diff --git a/Clover/app/src/main/java/org/floens/chan/ui/toolbar/NavigationItem.java b/Clover/app/src/main/java/org/floens/chan/ui/toolbar/NavigationItem.java
index 3e95122c..9db87438 100644
--- a/Clover/app/src/main/java/org/floens/chan/ui/toolbar/NavigationItem.java
+++ b/Clover/app/src/main/java/org/floens/chan/ui/toolbar/NavigationItem.java
@@ -17,16 +17,18 @@
*/
package org.floens.chan.ui.toolbar;
-import android.content.Context;
import android.view.View;
-import org.floens.chan.ui.view.FloatingMenu;
-import org.floens.chan.ui.view.FloatingMenuItem;
-
-import java.util.List;
+import org.floens.chan.R;
import static org.floens.chan.utils.AndroidUtils.getString;
+/**
+ * The navigation properties for a Controller. Controls common properties that parent controlers
+ * need to know, such as the title of the controller.
+ *
+ * This is also used to set up the toolbar menu, see {@link #buildMenu()}.
+ */
public class NavigationItem {
public String title = "";
public String subtitle = "";
@@ -39,18 +41,110 @@ public class NavigationItem {
boolean search = false;
String searchText;
- public ToolbarMenu menu;
- public ToolbarMiddleMenu middleMenu;
- public View rightView;
+ protected ToolbarMenu menu;
+ protected ToolbarMiddleMenu middleMenu;
+ protected View rightView;
- public ToolbarMenuItem createOverflow(Context context, ToolbarMenuItem.ToolbarMenuItemCallback callback, List items) {
- ToolbarMenuItem overflow = menu.createOverflow(callback);
- FloatingMenu overflowMenu = new FloatingMenu(context, overflow.getView(), items);
- overflow.setSubMenu(overflowMenu);
- return overflow;
+ public boolean hasArrow() {
+ return hasBack || search;
}
public void setTitle(int resId) {
title = getString(resId);
}
+
+ public MenuBuilder buildMenu() {
+ return new MenuBuilder(this);
+ }
+
+ public void setMiddleMenu(ToolbarMiddleMenu middleMenu) {
+ this.middleMenu = middleMenu;
+ }
+
+ public void setRightView(View view) {
+ rightView = view;
+ }
+
+ public ToolbarMenuItem findItem(int id) {
+ if (menu != null) {
+ return menu.findItem(id);
+ }
+ return null;
+ }
+
+ public ToolbarMenuSubItem findSubItem(int id) {
+ if (menu != null) {
+ return menu.findSubItem(id);
+ }
+ return null;
+ }
+
+ public static class MenuBuilder {
+ private final NavigationItem navigationItem;
+ private final ToolbarMenu menu;
+
+ public MenuBuilder(NavigationItem navigationItem) {
+ this.navigationItem = navigationItem;
+ menu = new ToolbarMenu(null);
+ }
+
+ public MenuBuilder withItem(int drawable, ToolbarMenuItem.ClickCallback clicked) {
+ return withItem(-1, drawable, clicked);
+ }
+
+ public MenuBuilder withItem(int id, int drawable, ToolbarMenuItem.ClickCallback clicked) {
+ return withItem(new ToolbarMenuItem(id, drawable, clicked));
+ }
+
+ public MenuBuilder withItem(ToolbarMenuItem menuItem) {
+ menu.addItem(menuItem);
+ return this;
+ }
+
+ public MenuOverflowBuilder withOverflow() {
+ return new MenuOverflowBuilder(
+ this,
+ new ToolbarMenuItem(
+ ToolbarMenu.OVERFLOW_ID,
+ R.drawable.ic_more_vert_white_24dp,
+ ToolbarMenuItem::showSubmenu));
+ }
+
+ public ToolbarMenu build() {
+ navigationItem.menu = menu;
+ return menu;
+ }
+ }
+
+ public static class MenuOverflowBuilder {
+ private final MenuBuilder menuBuilder;
+ private final ToolbarMenuItem menuItem;
+
+ public MenuOverflowBuilder(MenuBuilder menuBuilder, ToolbarMenuItem menuItem) {
+ this.menuBuilder = menuBuilder;
+ this.menuItem = menuItem;
+ }
+
+ public MenuOverflowBuilder withSubItem(int text, ToolbarMenuSubItem.ClickCallback clicked) {
+ return withSubItem(-1, getString(text), clicked);
+ }
+
+ public MenuOverflowBuilder withSubItem(String text, ToolbarMenuSubItem.ClickCallback clicked) {
+ return withSubItem(-1, text, clicked);
+ }
+
+ public MenuOverflowBuilder withSubItem(int id, int text, ToolbarMenuSubItem.ClickCallback clicked) {
+ return withSubItem(id, getString(text), clicked);
+ }
+
+ public MenuOverflowBuilder withSubItem(int id, String text, ToolbarMenuSubItem.ClickCallback clicked) {
+ menuItem.addSubItem(new ToolbarMenuSubItem(id, text, clicked));
+
+ return this;
+ }
+
+ public MenuBuilder build() {
+ return menuBuilder.withItem(menuItem);
+ }
+ }
}
diff --git a/Clover/app/src/main/java/org/floens/chan/ui/toolbar/Toolbar.java b/Clover/app/src/main/java/org/floens/chan/ui/toolbar/Toolbar.java
index abd5581b..e096d3d3 100644
--- a/Clover/app/src/main/java/org/floens/chan/ui/toolbar/Toolbar.java
+++ b/Clover/app/src/main/java/org/floens/chan/ui/toolbar/Toolbar.java
@@ -17,45 +17,31 @@
*/
package org.floens.chan.ui.toolbar;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
-import android.graphics.drawable.Drawable;
import android.os.Build;
-import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
-import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.Gravity;
-import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
-import android.widget.TextView;
import org.floens.chan.R;
import org.floens.chan.ui.drawable.ArrowMenuDrawable;
-import org.floens.chan.ui.drawable.DropdownArrowDrawable;
-import org.floens.chan.ui.layout.SearchLayout;
-import org.floens.chan.ui.view.LoadView;
-import org.floens.chan.utils.AndroidUtils;
import java.util.ArrayList;
import java.util.List;
import static org.floens.chan.utils.AndroidUtils.dp;
-import static org.floens.chan.utils.AndroidUtils.getAttrColor;
-import static org.floens.chan.utils.AndroidUtils.removeFromParentView;
+import static org.floens.chan.utils.AndroidUtils.hideKeyboard;
import static org.floens.chan.utils.AndroidUtils.setRoundItemBackground;
-public class Toolbar extends LinearLayout implements View.OnClickListener {
+public class Toolbar extends LinearLayout implements
+ View.OnClickListener, ToolbarPresenter.Callback, ToolbarContainer.Callback {
public static final int TOOLBAR_COLLAPSE_HIDE = 1000000;
public static final int TOOLBAR_COLLAPSE_SHOW = -1000000;
@@ -74,23 +60,18 @@ public class Toolbar extends LinearLayout implements View.OnClickListener {
}
};
+ private ToolbarPresenter presenter;
+
private ImageView arrowMenuView;
private ArrowMenuDrawable arrowMenuDrawable;
- private LoadView navigationItemContainer;
+ private ToolbarContainer navigationItemContainer;
private ToolbarCallback callback;
- private boolean openKeyboardAfterSearchViewCreated = false;
private int lastScrollDeltaOffset;
private int scrollOffset;
private List collapseCallbacks = new ArrayList<>();
- private boolean transitioning = false;
- private NavigationItem fromItem;
- private LinearLayout fromView;
- private NavigationItem toItem;
- private LinearLayout toView;
-
public Toolbar(Context context) {
this(context, null);
}
@@ -106,7 +87,13 @@ public class Toolbar extends LinearLayout implements View.OnClickListener {
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
- return transitioning || super.dispatchTouchEvent(ev);
+ return isTransitioning() || super.dispatchTouchEvent(ev);
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ return true;
}
public int getToolbarHeight() {
@@ -143,7 +130,10 @@ public class Toolbar extends LinearLayout implements View.OnClickListener {
scrollOffset = Math.max(0, Math.min(getHeight(), scrollOffset));
if (animated) {
- animate().translationY(-scrollOffset).setDuration(300).setInterpolator(new DecelerateInterpolator(2f)).start();
+ animate().translationY(-scrollOffset)
+ .setDuration(300)
+ .setInterpolator(new DecelerateInterpolator(2f))
+ .start();
boolean collapse = scrollOffset > 0;
for (ToolbarCollapseCallback c : collapseCallbacks) {
@@ -181,94 +171,45 @@ public class Toolbar extends LinearLayout implements View.OnClickListener {
}
}
-// public void updateNavigation() {
-// closeSearchInternal();
-// setNavigationItem(false, false, toItem);
-// }
-
- public NavigationItem getNavigationItem() {
- return toItem;
+ public void openSearch() {
+ presenter.openSearch();
}
- public boolean openSearch() {
- return openSearchInternal();
+ public boolean closeSearch() {
+ return presenter.closeSearch();
}
- public boolean closeSearch() {
- return closeSearchInternal();
+ public boolean isTransitioning() {
+ return navigationItemContainer.isTransitioning();
}
public void setNavigationItem(final boolean animate, final boolean pushing, final NavigationItem item) {
- setNavigationItemInternal(animate, pushing, item);
+ ToolbarPresenter.AnimationStyle animationStyle;
+ if (!animate) {
+ animationStyle = ToolbarPresenter.AnimationStyle.NONE;
+ } else if (pushing) {
+ animationStyle = ToolbarPresenter.AnimationStyle.PUSH;
+ } else {
+ animationStyle = ToolbarPresenter.AnimationStyle.POP;
+ }
+
+ presenter.set(item, animationStyle);
}
public void setArrowMenuIconShown(boolean show) {
arrowMenuView.setVisibility(show ? View.VISIBLE : View.GONE);
}
- public boolean isTransitioning() {
- return transitioning;
- }
-
public void beginTransition(NavigationItem newItem) {
- if (transitioning) {
- throw new IllegalStateException("beginTransition called when already transitioning");
- }
-
- attachNavigationItem(newItem);
-
- navigationItemContainer.addView(toView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
-
- transitioning = true;
+ presenter.startTransition(newItem, ToolbarPresenter.TransitionAnimationStyle.POP);
}
- public void transitionProgress(float progress, boolean pushing) {
- if (!transitioning) {
- throw new IllegalStateException("transitionProgress called while not transitioning");
- }
-
- progress = Math.max(0f, Math.min(1f, progress));
-
- final int offset = dp(16);
-
- toView.setTranslationY((pushing ? offset : -offset) * (1f - progress));
- toView.setAlpha(progress);
-
- if (fromItem != null) {
- fromView.setTranslationY((pushing ? -offset : offset) * progress);
- fromView.setAlpha(1f - progress);
- }
-
- float arrowEnd = toItem.hasBack || toItem.search ? 1f : 0f;
- if (arrowMenuDrawable.getProgress() != arrowEnd) {
- arrowMenuDrawable.setProgress(toItem.hasBack || toItem.search ? progress : 1f - progress);
- }
+ public void transitionProgress(float progress) {
+ presenter.setTransitionProgress(progress);
}
- public void finishTransition(boolean finished) {
- if (!transitioning) {
- throw new IllegalStateException("finishTransition called when not transitioning");
- }
-
- if (finished) {
- if (fromItem != null) {
- // From a search otherwise
- if (fromItem != toItem) {
- removeNavigationItem(fromItem, fromView);
- fromView = null;
- }
- }
- setArrowMenuProgress(toItem.hasBack || toItem.search ? 1f : 0f);
- } else {
- removeNavigationItem(toItem, toView);
- setArrowMenuProgress(fromItem.hasBack || fromItem.search ? 1f : 0f);
- toItem = fromItem;
- toView = fromView;
- }
-
- fromItem = null;
- fromView = null;
- transitioning = false;
+ public void finishTransition(boolean completed) {
+ presenter.stopTransition(completed);
}
public void setCallback(ToolbarCallback callback) {
@@ -282,38 +223,12 @@ public class Toolbar extends LinearLayout implements View.OnClickListener {
}
}
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- return true;
- }
-
- public void setArrowMenuProgress(float progress) {
- arrowMenuDrawable.setProgress(progress);
- }
-
- public void setShowArrowMenu(boolean show) {
- arrowMenuView.setVisibility(show ? VISIBLE : GONE);
- }
-
public ArrowMenuDrawable getArrowMenuDrawable() {
return arrowMenuDrawable;
}
public void updateTitle(NavigationItem navigationItem) {
- LinearLayout view = navigationItem == fromItem ? fromView : (navigationItem == toItem ? toView : null);
- if (view != null) {
- TextView titleView = view.findViewById(R.id.title);
- if (titleView != null) {
- titleView.setText(navigationItem.title);
- }
-
- if (!TextUtils.isEmpty(navigationItem.subtitle)) {
- TextView subtitleView = view.findViewById(R.id.subtitle);
- if (subtitleView != null) {
- subtitleView.setText(navigationItem.subtitle);
- }
- }
- }
+ presenter.update(navigationItem);
}
private void init() {
@@ -321,6 +236,13 @@ public class Toolbar extends LinearLayout implements View.OnClickListener {
if (isInEditMode()) return;
+ presenter = new ToolbarPresenter();
+ presenter.create(this);
+
+ initView();
+ }
+
+ private void initView() {
FrameLayout leftButtonContainer = new FrameLayout(getContext());
addView(leftButtonContainer, LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
@@ -333,11 +255,17 @@ public class Toolbar extends LinearLayout implements View.OnClickListener {
setRoundItemBackground(arrowMenuView);
- leftButtonContainer.addView(arrowMenuView, new FrameLayout.LayoutParams(getResources().getDimensionPixelSize(R.dimen.toolbar_height), FrameLayout.LayoutParams.MATCH_PARENT, Gravity.CENTER_VERTICAL));
+ int toolbarSize = getResources().getDimensionPixelSize(R.dimen.toolbar_height);
+ FrameLayout.LayoutParams leftButtonContainerLp = new FrameLayout.LayoutParams(
+ toolbarSize, FrameLayout.LayoutParams.MATCH_PARENT, Gravity.CENTER_VERTICAL);
+ leftButtonContainer.addView(arrowMenuView, leftButtonContainerLp);
- navigationItemContainer = new LoadView(getContext());
+ navigationItemContainer = new ToolbarContainer(getContext());
addView(navigationItemContainer, new LayoutParams(0, LayoutParams.MATCH_PARENT, 1f));
+ navigationItemContainer.setCallback(this);
+ navigationItemContainer.setArrowMenu(arrowMenuDrawable);
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (getElevation() == 0f) {
setElevation(dp(4f));
@@ -345,216 +273,56 @@ public class Toolbar extends LinearLayout implements View.OnClickListener {
}
}
- private boolean openSearchInternal() {
- if (toItem != null && !toItem.search) {
- toItem.search = true;
- openKeyboardAfterSearchViewCreated = true;
- setNavigationItemInternal(true, false, toItem);
- callback.onSearchVisibilityChanged(toItem, true);
- return true;
- } else {
- return false;
- }
+ @Override
+ public void showForNavigationItem(
+ NavigationItem item, ToolbarPresenter.AnimationStyle animation) {
+ navigationItemContainer.set(item, animation);
}
- private boolean closeSearchInternal() {
- if (toItem != null && toItem.search) {
- toItem.search = false;
- toItem.searchText = null;
- setNavigationItemInternal(true, false, toItem);
- callback.onSearchVisibilityChanged(toItem, false);
- return true;
- } else {
- return false;
- }
+ @Override
+ public void containerStartTransition(
+ NavigationItem item, ToolbarPresenter.TransitionAnimationStyle animation) {
+ navigationItemContainer.startTransition(item, animation);
}
- private void setNavigationItemInternal(boolean animate, final boolean pushing, NavigationItem newItem) {
- if (transitioning) {
- throw new IllegalStateException("setNavigationItemInternal called when already transitioning");
- }
-
- attachNavigationItem(newItem);
-
- transitioning = true;
-
- if (fromItem == toItem) {
- // Search toggled
- navigationItemContainer.setListener(new LoadView.Listener() {
- @Override
- public void onLoadViewRemoved(View view) {
- // Remove the menu from the navigation item
- ((ViewGroup) view).removeAllViews();
- finishTransition(true);
- navigationItemContainer.setListener(null);
- }
- });
- navigationItemContainer.setView(toView, animate);
-
- animateArrow(toItem.hasBack || toItem.search);
- } else {
- navigationItemContainer.addView(toView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
-
- if (animate) {
- toView.setAlpha(0f);
-
- final ValueAnimator animator = ObjectAnimator.ofFloat(0f, 1f);
- animator.setDuration(300);
- animator.setInterpolator(new DecelerateInterpolator(2f));
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- finishTransition(true);
- }
- });
- animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- transitionProgress((float) animation.getAnimatedValue(), pushing);
- }
- });
- if (!pushing) {
- animator.setStartDelay(100);
- }
-
- // hack to avoid the animation jumping when the current frame has a lot to do,
- // which is often because setting a new navigationitem is almost always done
- // when setting a new controller.
- post(new Runnable() {
- @Override
- public void run() {
- animator.start();
- }
- });
- } else {
- arrowMenuDrawable.setProgress(toItem.hasBack || toItem.search ? 1f : 0f);
- finishTransition(true);
- }
- }
+ @Override
+ public void containerStopTransition(boolean didComplete) {
+ navigationItemContainer.stopTransition(didComplete);
}
- private void attachNavigationItem(NavigationItem newItem) {
- if (transitioning) {
- throw new IllegalStateException("attachNavigationItem called while transitioning");
- }
-
- fromItem = toItem;
- fromView = toView;
- toItem = newItem;
- toView = createNavigationItemView(toItem);
-
- if (!toItem.search) {
- AndroidUtils.hideKeyboard(navigationItemContainer);
- }
+ @Override
+ public void containerSetTransitionProgress(float progress) {
+ navigationItemContainer.setTransitionProgress(progress);
}
- private void removeNavigationItem(NavigationItem item, LinearLayout view) {
- if (!transitioning) {
- throw new IllegalStateException("removeNavigationItem called while not transitioning");
- }
-
- view.removeAllViews();
- navigationItemContainer.removeView(view);
+ @Override
+ public void searchInput(String input) {
+ presenter.searchInput(input);
}
- private LinearLayout createNavigationItemView(final NavigationItem item) {
- if (item.search) {
- return createSearchLayout(item);
- } else {
- return createNavigationLayout(item);
- }
+ @Override
+ public String searchHint(NavigationItem item) {
+ return callback.getSearchHint(item);
}
- @NonNull
- private LinearLayout createNavigationLayout(NavigationItem item) {
- @SuppressLint("InflateParams")
- LinearLayout menu = (LinearLayout) LayoutInflater.from(getContext()).inflate(R.layout.toolbar_menu, null);
- menu.setGravity(Gravity.CENTER_VERTICAL);
-
- FrameLayout titleContainer = menu.findViewById(R.id.title_container);
-
- final TextView titleView = menu.findViewById(R.id.title);
- titleView.setTypeface(AndroidUtils.ROBOTO_MEDIUM);
- titleView.setText(item.title);
- titleView.setTextColor(0xffffffff);
-
- if (item.middleMenu != null) {
- int arrowColor = getAttrColor(getContext(), R.attr.dropdown_light_color);
- int arrowPressedColor = getAttrColor(getContext(), R.attr.dropdown_light_pressed_color);
- Drawable drawable = new DropdownArrowDrawable(dp(12), dp(12), true, arrowColor, arrowPressedColor);
- titleView.setCompoundDrawablesWithIntrinsicBounds(null, null, drawable, null);
- titleView.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- item.middleMenu.show(titleView);
- }
- });
- }
-
- TextView subtitleView = menu.findViewById(R.id.subtitle);
- if (!TextUtils.isEmpty(item.subtitle)) {
- ViewGroup.LayoutParams titleParams = titleView.getLayoutParams();
- titleParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
- titleView.setLayoutParams(titleParams);
- subtitleView.setText(item.subtitle);
- subtitleView.setTextColor(0xffffffff);
- titleView.setPadding(titleView.getPaddingLeft(), dp(5f), titleView.getPaddingRight(), titleView.getPaddingBottom());
- } else {
- titleContainer.removeView(subtitleView);
- }
-
- if (item.rightView != null) {
- removeFromParentView(item.rightView);
- item.rightView.setPadding(0, 0, dp(16), 0);
- menu.addView(item.rightView, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
- }
+ @Override
+ public void onSearchVisibilityChanged(NavigationItem item, boolean visible) {
+ callback.onSearchVisibilityChanged(item, visible);
- if (item.menu != null) {
- removeFromParentView(item.menu);
- menu.addView(item.menu, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
+ if (!visible) {
+ hideKeyboard(navigationItemContainer);
}
-
- return menu;
}
- @NonNull
- private LinearLayout createSearchLayout(NavigationItem item) {
- SearchLayout searchLayout = new SearchLayout(getContext());
-
- searchLayout.setCallback(new SearchLayout.SearchLayoutCallback() {
- @Override
- public void onSearchEntered(String entered) {
- item.searchText = entered;
- callback.onSearchEntered(item, entered);
- }
- });
-
- if (item.searchText != null) {
- searchLayout.setText(item.searchText);
- }
-
- searchLayout.setHint(callback.getSearchHint(item));
- if (openKeyboardAfterSearchViewCreated) {
- openKeyboardAfterSearchViewCreated = false;
- searchLayout.openKeyboard();
- }
-
- searchLayout.setPadding(dp(16), searchLayout.getPaddingTop(), searchLayout.getPaddingRight(), searchLayout.getPaddingBottom());
-
- return searchLayout;
+ @Override
+ public void onSearchInput(NavigationItem item, String input) {
+ callback.onSearchEntered(item, input);
}
- private void animateArrow(boolean toArrow) {
- float to = toArrow ? 1f : 0f;
- if (to != arrowMenuDrawable.getProgress()) {
- ValueAnimator arrowAnimation = ValueAnimator.ofFloat(arrowMenuDrawable.getProgress(), to);
- arrowAnimation.setDuration(300);
- arrowAnimation.setInterpolator(new DecelerateInterpolator(2f));
- arrowAnimation.addUpdateListener(animation ->
- setArrowMenuProgress((float) animation.getAnimatedValue()));
- arrowAnimation.start();
- }
+ @Override
+ public void updateViewForItem(NavigationItem item, boolean current) {
+ navigationItemContainer.update(item, current);
}
public interface ToolbarCallback {
diff --git a/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarContainer.java b/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarContainer.java
new file mode 100644
index 00000000..00171c45
--- /dev/null
+++ b/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarContainer.java
@@ -0,0 +1,530 @@
+/*
+ * 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 .
+ */
+package org.floens.chan.ui.toolbar;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.LinearInterpolator;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import org.floens.chan.R;
+import org.floens.chan.ui.drawable.ArrowMenuDrawable;
+import org.floens.chan.ui.drawable.DropdownArrowDrawable;
+import org.floens.chan.ui.layout.SearchLayout;
+import org.floens.chan.utils.AndroidUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.floens.chan.utils.AndroidUtils.dp;
+import static org.floens.chan.utils.AndroidUtils.getAttrColor;
+import static org.floens.chan.utils.AndroidUtils.removeFromParentView;
+
+/**
+ * The container for the views created by the toolbar for the navigation items.
+ *
+ * It will strictly only transition between two views. If a new view is set
+ * and a transition is in progress, it is stopped before adding the new view.
+ *
+ * For normal animations the previousView is the view that is animated away from, and the
+ * currentView is the view where is animated to. The previousView is removed and cleared if the
+ * animation finished.
+ *
+ * Transitions are user-controlled animations that can be cancelled of finished. For that the
+ * currentView describes the view that was originally there, and the transitionView is the view
+ * what is possibly transitioned to.
+ *
+ * This is also the class that is responsible for the orientation and animation of the arrow-menu
+ * drawable.
+ */
+public class ToolbarContainer extends FrameLayout {
+ private Callback callback;
+
+ private ArrowMenuDrawable arrowMenu;
+
+ @Nullable
+ private ItemView previousView;
+
+ @Nullable
+ private ItemView currentView;
+
+ @Nullable
+ private ItemView transitionView;
+
+ @Nullable
+ private ToolbarPresenter.TransitionAnimationStyle transitionAnimationStyle;
+
+ private Map animatorSet = new HashMap<>();
+
+ public ToolbarContainer(Context context) {
+ this(context, null);
+ }
+
+ public ToolbarContainer(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ToolbarContainer(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public void setCallback(Callback callback) {
+ this.callback = callback;
+ }
+
+ public void setArrowMenu(ArrowMenuDrawable arrowMenu) {
+ this.arrowMenu = arrowMenu;
+ }
+
+ public void set(NavigationItem item, ToolbarPresenter.AnimationStyle animation) {
+ if (transitionView != null) {
+ throw new IllegalStateException("Currently in transition mode");
+ }
+
+ endAnimations();
+
+ ItemView itemView = new ItemView(item);
+
+ previousView = currentView;
+ currentView = itemView;
+
+ addView(itemView.view, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+
+ if (getChildCount() > 2) {
+ throw new IllegalArgumentException("More than 2 child views attached");
+ }
+
+ // Can't run the animation if there is no previous view
+ // Otherwise just show it without an animation.
+ if (animation != ToolbarPresenter.AnimationStyle.NONE && previousView != null) {
+ setAnimation(itemView, previousView, animation);
+ } else {
+ if (previousView != null) {
+ removeItem(previousView);
+ previousView = null;
+ }
+ }
+
+ if (animation == ToolbarPresenter.AnimationStyle.NONE) {
+ setArrowProgress(1f, !currentView.item.hasArrow());
+ }
+
+ itemView.attach();
+ }
+
+ public void update(NavigationItem item, boolean current) {
+ // TODO
+ View view = viewForItem(item);
+ if (view != null) {
+ TextView titleView = view.findViewById(R.id.title);
+ if (titleView != null) {
+ titleView.setText(item.title);
+ }
+
+ if (!TextUtils.isEmpty(item.subtitle)) {
+ TextView subtitleView = view.findViewById(R.id.subtitle);
+ if (subtitleView != null) {
+ subtitleView.setText(item.subtitle);
+ }
+ }
+ }
+ }
+
+ public View viewForItem(NavigationItem item) {
+ ItemView itemView = itemViewForItem(item);
+ if (itemView == null) {
+ return null;
+ }
+ return itemView.view;
+ }
+
+ private ItemView itemViewForItem(NavigationItem item) {
+ if (currentView != null && item == currentView.item) {
+ return currentView;
+ } else if (previousView != null && item == previousView.item) {
+ return previousView;
+ } else if (transitionView != null && item == transitionView.item) {
+ return transitionView;
+ } else {
+ return null;
+ }
+ }
+
+ public boolean isTransitioning() {
+ return transitionView != null || previousView != null;
+ }
+
+ public void startTransition(
+ NavigationItem item, ToolbarPresenter.TransitionAnimationStyle style) {
+ if (transitionView != null) {
+ throw new IllegalStateException("Already in transition mode");
+ }
+
+ endAnimations();
+
+ ItemView itemView = new ItemView(item);
+
+ transitionView = itemView;
+ transitionAnimationStyle = style;
+ addView(itemView.view, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+
+ if (getChildCount() > 2) {
+ throw new IllegalArgumentException("More than 2 child views attached");
+ }
+
+ itemView.attach();
+ }
+
+ public void stopTransition(boolean didComplete) {
+ if (transitionView == null) {
+ throw new IllegalStateException("Not in transition mode");
+ }
+
+ if (didComplete) {
+ removeItem(currentView);
+ currentView = transitionView;
+ transitionView = null;
+ } else {
+ removeItem(transitionView);
+ transitionView = null;
+ }
+
+ if (getChildCount() != 1) {
+ throw new IllegalStateException("Not 1 view attached");
+ }
+ }
+
+ public void setTransitionProgress(float progress) {
+ if (transitionView == null) {
+ throw new IllegalStateException("Not in transition mode");
+ }
+
+ transitionProgressAnimation(progress, transitionAnimationStyle);
+ }
+
+ private void endAnimations() {
+ if (previousView != null) {
+ endAnimation(previousView.view);
+ if (previousView != null) {
+ throw new IllegalStateException("Animation end did not remove view");
+ }
+ }
+
+ if (currentView != null) {
+ endAnimation(currentView.view);
+ }
+ }
+
+ private void endAnimation(View view) {
+ Animator a = animatorSet.remove(view);
+ if (a != null) {
+ a.end();
+ }
+ }
+
+ private void setAnimation(ItemView view, ItemView previousView,
+ ToolbarPresenter.AnimationStyle animationStyle) {
+ if (animationStyle == ToolbarPresenter.AnimationStyle.PUSH ||
+ animationStyle == ToolbarPresenter.AnimationStyle.POP) {
+ final boolean pushing = animationStyle == ToolbarPresenter.AnimationStyle.PUSH;
+
+ // Previous animation
+ ValueAnimator previousAnimation = getShortAnimator();
+ previousAnimation.addUpdateListener(a -> {
+ float value = (float) a.getAnimatedValue();
+ setPreviousAnimationProgress(previousView.view, pushing, value);
+ });
+ previousAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ animatorSet.remove(previousView.view);
+ removeItem(previousView);
+ ToolbarContainer.this.previousView = null;
+ }
+ });
+ if (!pushing) previousAnimation.setStartDelay(100);
+ animatorSet.put(previousView.view, previousAnimation);
+
+ post(previousAnimation::start);
+
+ // Current animation + arrow
+ view.view.setAlpha(0f);
+ ValueAnimator animation = getShortAnimator();
+ animation.addUpdateListener(a -> {
+ float value = (float) a.getAnimatedValue();
+ setAnimationProgress(view.view, pushing, value);
+
+ if (previousView.item.hasArrow() != currentView.item.hasArrow()) {
+ setArrowProgress(value, !currentView.item.hasArrow());
+ }
+ });
+ animation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ animatorSet.remove(view.view);
+ }
+ });
+ if (!pushing) animation.setStartDelay(100);
+ animatorSet.put(view.view, animation);
+
+ post(animation::start);
+ } else if (animationStyle == ToolbarPresenter.AnimationStyle.FADE) {
+ // Previous animation
+ ValueAnimator previousAnimation =
+ ObjectAnimator.ofFloat(previousView.view, View.ALPHA, 1f, 0f);
+ previousAnimation.setDuration(300);
+ previousAnimation.setInterpolator(new LinearInterpolator());
+ previousAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ animatorSet.remove(previousView.view);
+ removeItem(previousView);
+ ToolbarContainer.this.previousView = null;
+ }
+ });
+ animatorSet.put(previousView.view, previousAnimation);
+
+ post(previousAnimation::start);
+
+ // Current animation + arrow
+ view.view.setAlpha(0f);
+ ValueAnimator animation = ObjectAnimator.ofFloat(view.view, View.ALPHA, 0f, 1f);
+ animation.setDuration(300);
+ animation.setInterpolator(new LinearInterpolator());
+ animation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ animatorSet.remove(view.view);
+ }
+ });
+ // A different animator for the arrow because that one needs the deceleration
+ // interpolator.
+ ValueAnimator arrow = ValueAnimator.ofFloat(0f, 1f);
+ arrow.setDuration(300);
+ arrow.setInterpolator(new DecelerateInterpolator(2f));
+ arrow.addUpdateListener(a -> {
+ float value = (float) a.getAnimatedValue();
+ if (previousView.item.hasArrow() != currentView.item.hasArrow()) {
+ setArrowProgress(value, !currentView.item.hasArrow());
+ }
+ });
+
+ AnimatorSet animationAndArrow = new AnimatorSet();
+ animationAndArrow.playTogether(animation, arrow);
+ animationAndArrow.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ animatorSet.remove(view.view);
+ }
+ });
+
+ animatorSet.put(view.view, animationAndArrow);
+
+ post(animationAndArrow::start);
+ }
+ }
+
+ private void setPreviousAnimationProgress(View view, boolean pushing, float progress) {
+ final int offset = dp(16);
+ view.setTranslationY((pushing ? -offset : offset) * progress);
+ view.setAlpha(1f - progress);
+ }
+
+ private void setAnimationProgress(View view, boolean pushing, float progress) {
+ final int offset = dp(16);
+ view.setTranslationY((pushing ? offset : -offset) * (1f - progress));
+ view.setAlpha(progress);
+ }
+
+ private void setArrowProgress(float progress, boolean reverse) {
+ if (reverse) {
+ progress = 1f - progress;
+ }
+ progress = Math.max(0f, Math.min(1f, progress));
+
+ arrowMenu.setProgress(progress);
+ }
+
+ private void transitionProgressAnimation(
+ float progress, ToolbarPresenter.TransitionAnimationStyle style) {
+ progress = Math.max(0f, Math.min(1f, progress));
+
+ final int offset = dp(16);
+
+ boolean pushing = style == ToolbarPresenter.TransitionAnimationStyle.PUSH;
+
+ transitionView.view.setTranslationY((pushing ? offset : -offset) * (1f - progress));
+ transitionView.view.setAlpha(progress);
+
+ currentView.view.setTranslationY((pushing ? -offset : offset) * progress);
+ currentView.view.setAlpha(1f - progress);
+
+ if (transitionView.item.hasArrow() != currentView.item.hasArrow()) {
+ setArrowProgress(progress, !transitionView.item.hasArrow());
+ }
+ }
+
+ private ValueAnimator getShortAnimator() {
+ final ValueAnimator animator = ObjectAnimator.ofFloat(0f, 1f);
+ animator.setDuration(300);
+ animator.setInterpolator(new DecelerateInterpolator(2f));
+ return animator;
+ }
+
+ private void removeItem(ItemView item) {
+ item.remove();
+ removeView(item.view);
+ }
+
+ private class ItemView {
+ final View view;
+ final NavigationItem item;
+
+ @Nullable
+ private ToolbarMenuView menuView;
+
+ public ItemView(NavigationItem item) {
+ this.view = createNavigationItemView(item);
+ this.item = item;
+ }
+
+ public void attach() {
+ if (item.menu != null) {
+ menuView.attach(item.menu);
+ }
+ }
+
+ public void remove() {
+ if (menuView != null) {
+ menuView.detach();
+ }
+ }
+
+ private LinearLayout createNavigationItemView(final NavigationItem item) {
+ if (item.search) {
+ return createSearchLayout(item);
+ } else {
+ return createNavigationLayout(item);
+ }
+ }
+
+ @NonNull
+ private LinearLayout createNavigationLayout(NavigationItem item) {
+ @SuppressLint("InflateParams")
+ LinearLayout menu = (LinearLayout) LayoutInflater.from(getContext()).inflate(R.layout.toolbar_menu, null);
+ menu.setGravity(Gravity.CENTER_VERTICAL);
+
+ FrameLayout titleContainer = menu.findViewById(R.id.title_container);
+
+ // Title
+ final TextView titleView = menu.findViewById(R.id.title);
+ titleView.setTypeface(AndroidUtils.ROBOTO_MEDIUM);
+ titleView.setText(item.title);
+ titleView.setTextColor(0xffffffff);
+
+ // Middle title with arrow and callback
+ if (item.middleMenu != null) {
+ int arrowColor = getAttrColor(getContext(), R.attr.dropdown_light_color);
+ int arrowPressedColor = getAttrColor(
+ getContext(), R.attr.dropdown_light_pressed_color);
+ Drawable drawable = new DropdownArrowDrawable(
+ dp(12), dp(12), true, arrowColor, arrowPressedColor);
+ titleView.setCompoundDrawablesWithIntrinsicBounds(
+ null, null, drawable, null);
+ titleView.setOnClickListener(v -> item.middleMenu.show(titleView));
+ }
+
+ // Possible subtitle.
+ TextView subtitleView = menu.findViewById(R.id.subtitle);
+ if (!TextUtils.isEmpty(item.subtitle)) {
+ ViewGroup.LayoutParams titleParams = titleView.getLayoutParams();
+ titleParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ titleView.setLayoutParams(titleParams);
+ subtitleView.setText(item.subtitle);
+ subtitleView.setTextColor(0xffffffff);
+ titleView.setPadding(titleView.getPaddingLeft(), dp(5f),
+ titleView.getPaddingRight(), titleView.getPaddingBottom());
+ } else {
+ titleContainer.removeView(subtitleView);
+ }
+
+ // Possible view shown at the right side.
+ if (item.rightView != null) {
+ removeFromParentView(item.rightView);
+ item.rightView.setPadding(0, 0, dp(16), 0);
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.MATCH_PARENT);
+ menu.addView(item.rightView, lp);
+ }
+
+ // Possible menu with items.
+ if (item.menu != null) {
+ menuView = new ToolbarMenuView(getContext());
+
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.MATCH_PARENT);
+ menu.addView(this.menuView, lp);
+ }
+
+ return menu;
+ }
+
+ @NonNull
+ private LinearLayout createSearchLayout(NavigationItem item) {
+ SearchLayout searchLayout = new SearchLayout(getContext());
+
+ searchLayout.setCallback(input -> {
+ callback.searchInput(input);
+ });
+
+ if (item.searchText != null) {
+ searchLayout.setText(item.searchText);
+ }
+
+ searchLayout.setHint(callback.searchHint(item));
+ searchLayout.setPadding(dp(16), searchLayout.getPaddingTop(), searchLayout.getPaddingRight(), searchLayout.getPaddingBottom());
+
+ return searchLayout;
+ }
+ }
+
+ public interface Callback {
+ void searchInput(String input);
+
+ String searchHint(NavigationItem item);
+ }
+}
diff --git a/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarMenu.java b/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarMenu.java
index 48ef772c..ec7c943c 100644
--- a/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarMenu.java
+++ b/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarMenu.java
@@ -18,52 +18,42 @@
package org.floens.chan.ui.toolbar;
import android.content.Context;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-import org.floens.chan.R;
import java.util.ArrayList;
import java.util.List;
-import static org.floens.chan.utils.AndroidUtils.dp;
-
-public class ToolbarMenu extends LinearLayout {
- private List items = new ArrayList<>();
-
- public ToolbarMenu(Context context) {
- this(context, null);
- }
-
- public ToolbarMenu(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
+public class ToolbarMenu {
+ public static final int OVERFLOW_ID = 1000;
- public ToolbarMenu(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public final List items = new ArrayList<>();
- setOrientation(HORIZONTAL);
- setGravity(Gravity.CENTER_VERTICAL);
+ public ToolbarMenu(@Deprecated Context context) {
}
public ToolbarMenuItem addItem(ToolbarMenuItem item) {
items.add(item);
- ImageView icon = item.getView();
- if (icon != null) {
- int viewIndex = Math.min(getChildCount(), item.getOrder());
- addView(icon, viewIndex);
- }
return item;
}
- public ToolbarMenuItem createOverflow(ToolbarMenuItem.ToolbarMenuItemCallback callback) {
- ToolbarMenuItem overflow = addItem(new ToolbarMenuItem(getContext(), callback, 100, 100, R.drawable.ic_more_vert_white_24dp));
- ImageView overflowImage = overflow.getView();
- overflowImage.setLayoutParams(new LinearLayout.LayoutParams(dp(44), dp(54)));
- overflowImage.setPadding(dp(8), 0, dp(16), 0);
+ public ToolbarMenuItem findItem(int id) {
+ for (ToolbarMenuItem item : items) {
+ if (item.id.equals(id)) {
+ return item;
+ }
+ }
+ return null;
+ }
+
+ public ToolbarMenuSubItem findSubItem(int id) {
+ ToolbarMenuItem overflow = findItem(OVERFLOW_ID);
+ if (overflow != null) {
+ for (ToolbarMenuSubItem subItem : overflow.subItems) {
+ if (subItem.id == id) {
+ return subItem;
+ }
+ }
+ }
- return overflow;
+ return null;
}
}
diff --git a/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarMenuItem.java b/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarMenuItem.java
index 08d63bb8..1159b12a 100644
--- a/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarMenuItem.java
+++ b/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarMenuItem.java
@@ -17,74 +17,141 @@
*/
package org.floens.chan.ui.toolbar;
-import android.content.Context;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.TransitionDrawable;
import android.view.View;
import android.widget.ImageView;
-import android.widget.LinearLayout;
import org.floens.chan.ui.view.FloatingMenu;
import org.floens.chan.ui.view.FloatingMenuItem;
+import org.floens.chan.utils.Logger;
-import static org.floens.chan.utils.AndroidUtils.dp;
-import static org.floens.chan.utils.AndroidUtils.setRoundItemBackground;
+import java.util.ArrayList;
+import java.util.List;
-public class ToolbarMenuItem implements View.OnClickListener, FloatingMenu.FloatingMenuCallback {
- private ToolbarMenuItemCallback callback;
- private Object id;
- private int order;
- private FloatingMenu subMenu;
+import static org.floens.chan.utils.AndroidUtils.getRes;
+import static org.floens.chan.utils.AndroidUtils.removeFromParentView;
- private ImageView imageView;
+/**
+ * An item for the Toolbar menu. These are ImageViews with an icon, that wehen pressed call
+ * some callback. Add them with the NavigationItem MenuBuilder.
+ */
+public class ToolbarMenuItem {
+ private static final String TAG = "ToolbarMenuItem";
+
+ public Object id;
+ public int order;
+
+ public boolean overflowStyle = false;
+
+ public boolean visible = true;
+
+ public Drawable drawable;
+
+ public final List subItems = new ArrayList<>();
- public ToolbarMenuItem(Context context, ToolbarMenuItem.ToolbarMenuItemCallback callback, int order, int drawable) {
- this(context, callback, order, order, context.getResources().getDrawable(drawable));
+ private ClickCallback clicked;
+
+ // Views, only non-null if attached to ToolbarMenuView.
+ private ImageView view;
+
+ public ToolbarMenuItem(int id, int drawable, ClickCallback clicked) {
+ this.id = id;
+ this.drawable = getRes().getDrawable(drawable);
+ this.clicked = clicked;
}
- public ToolbarMenuItem(Context context, ToolbarMenuItem.ToolbarMenuItemCallback callback, Object id, int order, int drawable) {
- this(context, callback, id, order, context.getResources().getDrawable(drawable));
+ public void attach(ImageView view) {
+ if (this.view != null) {
+ throw new IllegalStateException("Already attached");
+ }
+
+ this.view = view;
}
- public ToolbarMenuItem(Context context, ToolbarMenuItem.ToolbarMenuItemCallback callback, Object id, int order, Drawable drawable) {
- this.id = id;
- this.order = order;
- this.callback = callback;
-
- if (drawable != null) {
- imageView = new ImageView(context);
- imageView.setOnClickListener(this);
- imageView.setFocusable(true);
- imageView.setScaleType(ImageView.ScaleType.CENTER);
- imageView.setLayoutParams(new LinearLayout.LayoutParams(dp(50), dp(56)));
-
- imageView.setImageDrawable(drawable);
- setRoundItemBackground(imageView);
+ public void detach() {
+ if (this.view == null) {
+ throw new IllegalStateException("Not attached");
}
+
+ removeFromParentView(this.view);
+ this.view = null;
}
- public void setImage(Drawable drawable) {
- imageView.setImageDrawable(drawable);
+ public ImageView getView() {
+ return view;
+ }
+
+ public void addSubItem(ToolbarMenuSubItem subItem) {
+ subItems.add(subItem);
+ }
+
+ public void setVisible(boolean visible) {
+ this.visible = visible;
+
+ if (view != null) {
+ view.setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
}
public void setImage(int drawable) {
- imageView.setImageResource(drawable);
+ setImage(getRes().getDrawable(drawable));
}
- public void setSubMenu(FloatingMenu subMenu) {
- this.subMenu = subMenu;
- subMenu.setCallback(this);
+ public void setImage(Drawable drawable) {
+ setImage(drawable, false);
}
- public FloatingMenu getSubMenu() {
- return subMenu;
+ public void setImage(Drawable drawable, boolean animated) {
+ if (view == null) {
+ this.drawable = drawable;
+ return;
+ }
+
+ if (!animated) {
+ view.setImageDrawable(drawable);
+ } else {
+ TransitionDrawable transitionDrawable = new TransitionDrawable(new Drawable[]{
+ this.drawable.mutate(), drawable.mutate()
+ });
+
+ view.setImageDrawable(transitionDrawable);
+
+ transitionDrawable.setCrossFadeEnabled(true);
+ transitionDrawable.startTransition(100);
+ }
+
+ this.drawable = drawable;
+ }
+
+ public void setSubMenu(FloatingMenu subMenu) {
}
- @Override
- public void onClick(View v) {
- if (subMenu != null && !subMenu.isShowing()) {
- subMenu.show();
+ public void showSubmenu() {
+ if (view == null) {
+ Logger.w(TAG, "Item not attached, can't show submenu");
+ return;
}
- callback.onMenuItemClicked(this);
+
+ List floatingMenuItems = new ArrayList<>();
+ List subItems = new ArrayList<>(this.subItems);
+ for (ToolbarMenuSubItem subItem : subItems) {
+ floatingMenuItems.add(new FloatingMenuItem(subItem.id, subItem.text, subItem.enabled));
+ }
+
+ FloatingMenu overflowMenu = new FloatingMenu(view.getContext(), view, floatingMenuItems);
+ overflowMenu.setCallback(new FloatingMenu.FloatingMenuCallback() {
+ @Override
+ public void onFloatingMenuItemClicked(FloatingMenu menu, FloatingMenuItem item) {
+ ToolbarMenuSubItem subItem = subItems.get(floatingMenuItems.indexOf(item));
+ subItem.performClick();
+ }
+
+ @Override
+ public void onFloatingMenuDismissed(FloatingMenu menu) {
+ }
+ });
+ overflowMenu.show();
}
public Object getId() {
@@ -95,22 +162,13 @@ public class ToolbarMenuItem implements View.OnClickListener, FloatingMenu.Float
return order;
}
- public ImageView getView() {
- return imageView;
- }
-
- @Override
- public void onFloatingMenuItemClicked(FloatingMenu menu, FloatingMenuItem item) {
- callback.onSubMenuItemClicked(this, item);
- }
-
- @Override
- public void onFloatingMenuDismissed(FloatingMenu menu) {
+ public void performClick() {
+ if (clicked != null) {
+ clicked.clicked(this);
+ }
}
- public interface ToolbarMenuItemCallback {
- void onMenuItemClicked(ToolbarMenuItem item);
-
- void onSubMenuItemClicked(ToolbarMenuItem parent, FloatingMenuItem item);
+ public interface ClickCallback {
+ void clicked(ToolbarMenuItem item);
}
}
diff --git a/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarMenuSubItem.java b/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarMenuSubItem.java
new file mode 100644
index 00000000..fe74c399
--- /dev/null
+++ b/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarMenuSubItem.java
@@ -0,0 +1,64 @@
+/*
+ * 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 .
+ */
+package org.floens.chan.ui.toolbar;
+
+import static org.floens.chan.utils.AndroidUtils.getString;
+
+/**
+ * An item for a submenu of a ToolbarMenuItem. Most common as subitem for the overflow button.
+ * Add with NavigationItem MenuBuilder.
+ */
+public class ToolbarMenuSubItem {
+ public int id;
+ public String text;
+ public boolean enabled = true;
+ public ClickCallback clicked;
+
+ public ToolbarMenuSubItem(int id, int text, ClickCallback clicked) {
+ this(id, getString(text), clicked);
+ }
+
+ public ToolbarMenuSubItem(int id, int text, boolean enabled) {
+ this(id, getString(text), enabled, null);
+ }
+
+ public ToolbarMenuSubItem(int id, String text, ClickCallback clicked) {
+ this(id, text, true, clicked);
+ }
+
+ public ToolbarMenuSubItem(String text, ClickCallback clicked) {
+ this(-1, text, true, clicked);
+ }
+
+ public ToolbarMenuSubItem(int id, String text, boolean enabled, ClickCallback clicked) {
+ this.id = id;
+ this.text = text;
+ this.enabled = enabled;
+ this.clicked = clicked;
+ }
+
+ public void performClick() {
+ if (clicked != null) {
+ clicked.clicked(this);
+ }
+ }
+
+ public interface ClickCallback {
+ void clicked(ToolbarMenuSubItem subItem);
+ }
+}
diff --git a/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarMenuView.java b/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarMenuView.java
new file mode 100644
index 00000000..f99cadb3
--- /dev/null
+++ b/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarMenuView.java
@@ -0,0 +1,100 @@
+/*
+ * 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 .
+ */
+package org.floens.chan.ui.toolbar;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import java.util.Collections;
+
+import static org.floens.chan.utils.AndroidUtils.dp;
+import static org.floens.chan.utils.AndroidUtils.setRoundItemBackground;
+
+/**
+ * The container view for the list of ToolbarMenuItems, a list of ImageViews.
+ */
+public class ToolbarMenuView extends LinearLayout {
+ private ToolbarMenu menu;
+
+ public ToolbarMenuView(Context context) {
+ this(context, null);
+ }
+
+ public ToolbarMenuView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ToolbarMenuView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ setOrientation(HORIZONTAL);
+ setGravity(Gravity.CENTER_VERTICAL);
+ }
+
+ public void attach(ToolbarMenu menu) {
+ this.menu = menu;
+
+ setupMenuViews();
+ }
+
+ public void detach() {
+ for (ToolbarMenuItem item : menu.items) {
+ item.detach();
+ }
+ }
+
+ private void setupMenuViews() {
+ removeAllViews();
+
+ Collections.sort(menu.items, (a, b) -> a.order - b.order);
+
+ for (ToolbarMenuItem item : menu.items) {
+ ImageView imageView = new ImageView(getContext());
+
+ imageView.setOnClickListener((v) -> {
+ handleClick(item);
+ });
+ imageView.setFocusable(true);
+ imageView.setScaleType(ImageView.ScaleType.CENTER);
+
+ imageView.setVisibility(item.visible ? View.VISIBLE : View.GONE);
+
+ if (item.overflowStyle) {
+ imageView.setLayoutParams(new LayoutParams(dp(44), dp(56)));
+ imageView.setPadding(dp(8), 0, dp(16), 0);
+ } else {
+ imageView.setLayoutParams(new LinearLayout.LayoutParams(dp(50), dp(56)));
+ }
+
+ imageView.setImageDrawable(item.drawable);
+ setRoundItemBackground(imageView);
+
+ addView(imageView);
+
+ item.attach(imageView);
+ }
+ }
+
+ private void handleClick(ToolbarMenuItem item) {
+ item.performClick();
+ }
+}
diff --git a/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarPresenter.java b/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarPresenter.java
new file mode 100644
index 00000000..1c20496c
--- /dev/null
+++ b/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarPresenter.java
@@ -0,0 +1,159 @@
+/*
+ * 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 .
+ */
+package org.floens.chan.ui.toolbar;
+
+public class ToolbarPresenter {
+ public enum AnimationStyle {
+ NONE,
+ PUSH,
+ POP,
+ FADE
+ }
+
+ public enum TransitionAnimationStyle {
+ PUSH,
+ POP
+ }
+
+ private Callback callback;
+
+ private NavigationItem item;
+ private NavigationItem search;
+ private NavigationItem transition;
+
+ void create(Callback callback) {
+ this.callback = callback;
+ }
+
+ void set(NavigationItem newItem, AnimationStyle animation) {
+ cancelTransitionIfNeeded();
+ if (closeSearchIfNeeded()) {
+ animation = AnimationStyle.FADE;
+ }
+
+ item = newItem;
+
+ callback.showForNavigationItem(item, animation);
+ }
+
+ void update(NavigationItem updatedItem) {
+ callback.updateViewForItem(updatedItem, updatedItem == item);
+ }
+
+ void startTransition(NavigationItem newItem, TransitionAnimationStyle animation) {
+ cancelTransitionIfNeeded();
+ if (closeSearchIfNeeded()) {
+ callback.showForNavigationItem(item, AnimationStyle.NONE);
+ }
+
+ transition = newItem;
+
+ callback.containerStartTransition(transition, animation);
+ }
+
+ void stopTransition(boolean didComplete) {
+ if (transition == null) {
+ return;
+ }
+
+ callback.containerStopTransition(didComplete);
+
+ if (didComplete) {
+ item = transition;
+ callback.showForNavigationItem(item, AnimationStyle.NONE);
+ }
+ transition = null;
+ }
+
+ void setTransitionProgress(float progress) {
+ if (transition == null) {
+ return;
+ }
+
+ callback.containerSetTransitionProgress(progress);
+ }
+
+ void openSearch() {
+ if (search != null) {
+ return;
+ }
+
+ cancelTransitionIfNeeded();
+
+ search = new NavigationItem();
+ search.search = true;
+ callback.showForNavigationItem(search, AnimationStyle.FADE);
+
+ callback.onSearchVisibilityChanged(item, true);
+ }
+
+ boolean closeSearch() {
+ if (search == null) {
+ return false;
+ }
+
+ search = null;
+ set(item, AnimationStyle.FADE);
+
+ callback.onSearchVisibilityChanged(item, false);
+
+ return true;
+ }
+
+ private void cancelTransitionIfNeeded() {
+ if (transition != null) {
+ callback.containerStopTransition(false);
+ transition = null;
+ }
+ }
+
+ private boolean closeSearchIfNeeded() {
+ // Cancel search
+ if (search != null) {
+ search = null;
+ callback.onSearchVisibilityChanged(item, false);
+ return true;
+ }
+ return false;
+ }
+
+ void searchInput(String input) {
+ if (search == null) {
+ return;
+ }
+
+ search.searchText = input;
+ callback.onSearchInput(item, search.searchText);
+ }
+
+ interface Callback {
+ void showForNavigationItem(NavigationItem item, AnimationStyle animation);
+
+ void containerStartTransition(NavigationItem item, TransitionAnimationStyle animation);
+
+ void containerStopTransition(boolean didComplete);
+
+ void containerSetTransitionProgress(float progress);
+
+ void onSearchVisibilityChanged(NavigationItem item, boolean visible);
+
+ void onSearchInput(NavigationItem item, String input);
+
+ void updateViewForItem(NavigationItem item, boolean current);
+ }
+}
diff --git a/Clover/app/src/main/java/org/floens/chan/ui/view/CustomScaleImageView.java b/Clover/app/src/main/java/org/floens/chan/ui/view/CustomScaleImageView.java
index 51ab9e81..e3f329e1 100644
--- a/Clover/app/src/main/java/org/floens/chan/ui/view/CustomScaleImageView.java
+++ b/Clover/app/src/main/java/org/floens/chan/ui/view/CustomScaleImageView.java
@@ -68,6 +68,10 @@ public class CustomScaleImageView extends SubsamplingScaleImageView {
public void onPreviewLoadError(Exception e) {
}
+ @Override
+ public void onPreviewReleased() {
+ }
+
@Override
public void onImageLoadError(Exception e) {
Logger.w(TAG, "onImageLoadError", e);
diff --git a/Clover/app/src/main/java/org/floens/chan/ui/view/FloatingMenu.java b/Clover/app/src/main/java/org/floens/chan/ui/view/FloatingMenu.java
index 1f5e3575..18e4181d 100644
--- a/Clover/app/src/main/java/org/floens/chan/ui/view/FloatingMenu.java
+++ b/Clover/app/src/main/java/org/floens/chan/ui/view/FloatingMenu.java
@@ -27,7 +27,6 @@ import android.view.ViewTreeObserver;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;
-import android.widget.PopupWindow;
import android.widget.TextView;
import org.floens.chan.R;
@@ -202,16 +201,13 @@ public class FloatingMenu {
};
anchor.getViewTreeObserver().addOnGlobalLayoutListener(globalLayoutListener);
- popupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
- @Override
- public void onDismiss() {
- if (anchor.getViewTreeObserver().isAlive()) {
- anchor.getViewTreeObserver().removeGlobalOnLayoutListener(globalLayoutListener);
- }
- globalLayoutListener = null;
- popupWindow = null;
- callback.onFloatingMenuDismissed(FloatingMenu.this);
+ popupWindow.setOnDismissListener(() -> {
+ if (anchor.getViewTreeObserver().isAlive()) {
+ anchor.getViewTreeObserver().removeGlobalOnLayoutListener(globalLayoutListener);
}
+ globalLayoutListener = null;
+ popupWindow = null;
+ callback.onFloatingMenuDismissed(FloatingMenu.this);
});
popupWindow.show();
diff --git a/Clover/app/src/main/java/org/floens/chan/ui/view/MultiImageView.java b/Clover/app/src/main/java/org/floens/chan/ui/view/MultiImageView.java
index a4a5f438..8b5a63f6 100644
--- a/Clover/app/src/main/java/org/floens/chan/ui/view/MultiImageView.java
+++ b/Clover/app/src/main/java/org/floens/chan/ui/view/MultiImageView.java
@@ -22,9 +22,7 @@ import android.content.ContextWrapper;
import android.content.Intent;
import android.media.AudioManager;
import android.media.MediaPlayer;
-import android.net.Uri;
import android.os.Build;
-import android.os.StrictMode;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
@@ -40,9 +38,10 @@ import com.android.volley.toolbox.ImageLoader.ImageContainer;
import com.davemorrissey.labs.subscaleview.ImageSource;
import org.floens.chan.R;
-import org.floens.chan.core.cache.FileCacheListener;
import org.floens.chan.core.cache.FileCache;
import org.floens.chan.core.cache.FileCacheDownloader;
+import org.floens.chan.core.cache.FileCacheListener;
+import org.floens.chan.core.cache.FileCacheProvider;
import org.floens.chan.core.model.PostImage;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.utils.AndroidUtils;
@@ -386,16 +385,10 @@ public class MultiImageView extends FrameLayout implements View.OnClickListener
private void setVideoFile(final File file) {
if (ChanSettings.videoOpenExternal.get()) {
Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setDataAndType(Uri.fromFile(file), "video/*");
-
- {
- StrictMode.VmPolicy vmPolicy = StrictMode.getVmPolicy();
- StrictMode.setVmPolicy(StrictMode.VmPolicy.LAX);
+ intent.setDataAndType(FileCacheProvider.getUriForFile(file), "video/*");
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- AndroidUtils.openIntent(intent);
-
- StrictMode.setVmPolicy(vmPolicy);
- }
+ AndroidUtils.openIntent(intent);
onModeLoaded(Mode.MOVIE, videoView);
} else {
diff --git a/Clover/app/src/main/res/xml/file_paths.xml b/Clover/app/src/main/res/xml/file_paths.xml
new file mode 100644
index 00000000..13fca269
--- /dev/null
+++ b/Clover/app/src/main/res/xml/file_paths.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/Clover/build.gradle b/Clover/build.gradle
index b6cb00b4..b32f870a 100644
--- a/Clover/build.gradle
+++ b/Clover/build.gradle
@@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.1.2'
+ classpath 'com.android.tools.build:gradle:3.1.3'
}
}