Made all controllers swipeable

Controllers now have addChildController and removeChildController methods that manage adding and removing child controllers to the controller. This way the methods like onBack are propagated correctly.
Reworked the toolbar setNavigation methods to allow the animation fraction to be set manually and to make it possible to cancel a transition.
multisite
Floens 10 years ago
parent 81adc3cb0d
commit 444277ddbd
  1. 132
      Clover/app/src/main/java/org/floens/chan/controller/Controller.java
  2. 99
      Clover/app/src/main/java/org/floens/chan/controller/ControllerLogic.java
  3. 4
      Clover/app/src/main/java/org/floens/chan/controller/ControllerTransition.java
  4. 138
      Clover/app/src/main/java/org/floens/chan/controller/NavigationController.java
  5. 4
      Clover/app/src/main/java/org/floens/chan/controller/transition/FadeInTransition.java
  6. 4
      Clover/app/src/main/java/org/floens/chan/controller/transition/FadeOutTransition.java
  7. 4
      Clover/app/src/main/java/org/floens/chan/controller/transition/PopControllerTransition.java
  8. 3
      Clover/app/src/main/java/org/floens/chan/controller/transition/PushControllerTransition.java
  9. 310
      Clover/app/src/main/java/org/floens/chan/controller/ui/NavigationControllerContainerLayout.java
  10. 8
      Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java
  11. 7
      Clover/app/src/main/java/org/floens/chan/ui/cell/PostCell.java
  12. 9
      Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java
  13. 86
      Clover/app/src/main/java/org/floens/chan/ui/controller/DrawerController.java
  14. 14
      Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerNavigationController.java
  15. 27
      Clover/app/src/main/java/org/floens/chan/ui/controller/PopupController.java
  16. 73
      Clover/app/src/main/java/org/floens/chan/ui/controller/SplitNavigationController.java
  17. 13
      Clover/app/src/main/java/org/floens/chan/ui/controller/StyledToolbarNavigationController.java
  18. 1
      Clover/app/src/main/java/org/floens/chan/ui/controller/ThemeSettingsController.java
  19. 36
      Clover/app/src/main/java/org/floens/chan/ui/controller/ToolbarNavigationController.java
  20. 6
      Clover/app/src/main/java/org/floens/chan/ui/controller/ViewThreadController.java
  21. 18
      Clover/app/src/main/java/org/floens/chan/ui/layout/PopupControllerContainer.java
  22. 1
      Clover/app/src/main/java/org/floens/chan/ui/toolbar/NavigationItem.java
  23. 187
      Clover/app/src/main/java/org/floens/chan/ui/toolbar/Toolbar.java
  24. 6
      Clover/app/src/main/res/layout/controller_navigation_drawer.xml
  25. 2
      Clover/app/src/main/res/layout/controller_navigation_image_viewer.xml
  26. 2
      Clover/app/src/main/res/layout/controller_navigation_toolbar.xml
  27. 8
      Clover/app/src/main/res/layout/layout_controller_popup.xml

@ -23,10 +23,17 @@ import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.ViewGroup; import android.view.ViewGroup;
import org.floens.chan.controller.transition.FadeInTransition;
import org.floens.chan.controller.transition.FadeOutTransition;
import org.floens.chan.ui.activity.StartActivity; import org.floens.chan.ui.activity.StartActivity;
import org.floens.chan.ui.controller.SplitNavigationController;
import org.floens.chan.ui.toolbar.NavigationItem; import org.floens.chan.ui.toolbar.NavigationItem;
import org.floens.chan.utils.AndroidUtils;
import org.floens.chan.utils.Logger; import org.floens.chan.utils.Logger;
import java.util.ArrayList;
import java.util.List;
public abstract class Controller { public abstract class Controller {
private static final boolean LOG_STATES = false; private static final boolean LOG_STATES = false;
@ -37,21 +44,26 @@ public abstract class Controller {
public Controller parentController; public Controller parentController;
public List<Controller> childControllers = new ArrayList<>();
// NavigationControllers members // NavigationControllers members
public Controller previousSiblingController; public Controller previousSiblingController;
public NavigationController navigationController; public NavigationController navigationController;
public SplitNavigationController splitNavigationController;
/** /**
* Controller that this controller is presented by. * Controller that this controller is presented by.
*/ */
public Controller presentingController; public Controller presentingByController;
/** /**
* Controller that this controller is presenting. * Controller that this controller is presenting.
*/ */
public Controller presentedController; public Controller presentingThisController;
public boolean alive = false; public boolean alive = false;
public boolean shown = false;
public Controller(Context context) { public Controller(Context context) {
this.context = context; this.context = context;
@ -65,15 +77,29 @@ public abstract class Controller {
} }
public void onShow() { public void onShow() {
shown = true;
if (LOG_STATES) { if (LOG_STATES) {
Logger.test(getClass().getSimpleName() + " onShow"); Logger.test(getClass().getSimpleName() + " onShow");
} }
for (Controller controller : childControllers) {
if (!controller.shown) {
controller.onShow();
}
}
} }
public void onHide() { public void onHide() {
shown = false;
if (LOG_STATES) { if (LOG_STATES) {
Logger.test(getClass().getSimpleName() + " onHide"); Logger.test(getClass().getSimpleName() + " onHide");
} }
for (Controller controller : childControllers) {
if (controller.shown) {
controller.onHide();
}
}
} }
public void onDestroy() { public void onDestroy() {
@ -81,16 +107,72 @@ public abstract class Controller {
if (LOG_STATES) { if (LOG_STATES) {
Logger.test(getClass().getSimpleName() + " onDestroy"); Logger.test(getClass().getSimpleName() + " onDestroy");
} }
while (childControllers.size() > 0) {
removeChildController(childControllers.get(0));
}
}
public void addChildController(Controller controller) {
childControllers.add(controller);
controller.parentController = this;
controller.splitNavigationController = splitNavigationController;
controller.onCreate();
}
public boolean removeChildController(Controller controller) {
controller.onDestroy();
return childControllers.remove(controller);
}
public void attach(ViewGroup parentView, boolean over) {
ViewGroup.LayoutParams params = view.getLayoutParams();
if (params == null) {
params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
} else {
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
params.height = ViewGroup.LayoutParams.MATCH_PARENT;
}
view.setLayoutParams(params);
if (over) {
parentView.addView(view, view.getLayoutParams());
} else {
parentView.addView(view, 0, view.getLayoutParams());
}
}
public void detach() {
AndroidUtils.removeFromParentView(view);
} }
public void onConfigurationChanged(Configuration newConfig) { public void onConfigurationChanged(Configuration newConfig) {
for (Controller controller : childControllers) {
controller.onConfigurationChanged(newConfig);
}
} }
public boolean dispatchKeyEvent(KeyEvent event) { public boolean dispatchKeyEvent(KeyEvent event) {
for (int i = childControllers.size() - 1; i >= 0; i--) {
Controller controller = childControllers.get(i);
if (controller.dispatchKeyEvent(event)) {
return true;
}
}
return false; return false;
} }
public boolean onBack() { public boolean onBack() {
for (int i = childControllers.size() - 1; i >= 0; i--) {
Controller controller = childControllers.get(i);
if (controller.onBack()) {
return true;
}
}
return false; return false;
} }
@ -100,21 +182,19 @@ public abstract class Controller {
public void presentController(Controller controller, boolean animated) { public void presentController(Controller controller, boolean animated) {
ViewGroup contentView = ((StartActivity) context).getContentView(); ViewGroup contentView = ((StartActivity) context).getContentView();
presentedController = controller; presentingThisController = controller;
controller.presentingController = this; controller.presentingByController = this;
controller.onCreate();
controller.attach(contentView, true);
controller.onShow();
if (animated) { if (animated) {
ControllerTransition transition = new FadeInTransition(); ControllerTransition transition = new FadeInTransition();
transition.setCallback(new ControllerTransition.Callback() { transition.to = controller;
@Override transition.perform();
public void onControllerTransitionCompleted(ControllerTransition transition) {
ControllerLogic.finishTransition(transition);
}
});
ControllerLogic.startTransition(null, controller, true, contentView, transition);
} else {
ControllerLogic.transition(null, controller, true, contentView);
} }
((StartActivity) context).addController(controller); ((StartActivity) context).addController(controller);
} }
@ -123,22 +203,36 @@ public abstract class Controller {
} }
public void stopPresenting(boolean animated) { public void stopPresenting(boolean animated) {
ViewGroup contentView = ((StartActivity) context).getContentView();
if (animated) { if (animated) {
ControllerTransition transition = new FadeOutTransition(); ControllerTransition transition = new FadeOutTransition();
transition.from = this;
transition.perform();
transition.setCallback(new ControllerTransition.Callback() { transition.setCallback(new ControllerTransition.Callback() {
@Override @Override
public void onControllerTransitionCompleted(ControllerTransition transition) { public void onControllerTransitionCompleted(ControllerTransition transition) {
ControllerLogic.finishTransition(transition); finishPresenting();
} }
}); });
ControllerLogic.startTransition(this, null, false, contentView, transition);
} else { } else {
ControllerLogic.transition(this, null, false, contentView); finishPresenting();
} }
((StartActivity) context).removeController(this); ((StartActivity) context).removeController(this);
presentingController.presentedController = null; presentingByController.presentingThisController = null;
}
private void finishPresenting() {
onHide();
detach();
onDestroy();
}
public Controller getTop() {
if (childControllers.size() > 0) {
return childControllers.get(childControllers.size() - 1);
} else {
return null;
}
} }
public ViewGroup inflateRes(int resId) { public ViewGroup inflateRes(int resId) {

@ -1,99 +0,0 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.controller;
import android.view.ViewGroup;
import org.floens.chan.utils.AndroidUtils;
public class ControllerLogic {
public static void attach(Controller controller, ViewGroup view, boolean over) {
ViewGroup.LayoutParams params = controller.view.getLayoutParams();
if (params == null) {
params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
} else {
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
params.height = ViewGroup.LayoutParams.MATCH_PARENT;
}
controller.view.setLayoutParams(params);
if (over) {
view.addView(controller.view, controller.view.getLayoutParams());
} else {
view.addView(controller.view, 0, controller.view.getLayoutParams());
}
}
public static void detach(Controller controller) {
AndroidUtils.removeFromParentView(controller.view);
}
public static void transition(Controller from, Controller to, boolean pushing, ViewGroup toView) {
transition(from, to, pushing, !pushing, toView);
}
public static void transition(Controller from, Controller to, boolean createTo, boolean destroyFrom, ViewGroup toView) {
if (to != null) {
if (createTo) {
to.onCreate();
}
attach(to, toView, true);
to.onShow();
}
if (from != null) {
from.onHide();
detach(from);
if (destroyFrom) {
from.onDestroy();
}
}
}
public static void startTransition(Controller from, Controller to, boolean pushing, ViewGroup toView, ControllerTransition transition) {
transition.destroyFrom = !pushing;
transition.from = from;
transition.to = to;
if (to != null) {
if (pushing) {
to.onCreate();
}
attach(to, toView, transition.viewOver);
to.onShow();
}
transition.perform();
}
public static void finishTransition(ControllerTransition transition) {
if (transition.from != null) {
transition.from.onHide();
detach(transition.from);
if (transition.destroyFrom) {
transition.from.onDestroy();
}
}
}
}

@ -28,7 +28,9 @@ public abstract class ControllerTransition {
public abstract void perform(); public abstract void perform();
public void onCompleted() { public void onCompleted() {
this.callback.onControllerTransitionCompleted(this); if (callback != null) {
callback.onControllerTransitionCompleted(this);
}
} }
public void setCallback(Callback callback) { public void setCallback(Callback callback) {

@ -18,17 +18,15 @@
package org.floens.chan.controller; package org.floens.chan.controller;
import android.content.Context; import android.content.Context;
import android.content.res.Configuration;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.ViewGroup;
import java.util.ArrayList; import org.floens.chan.controller.transition.PopControllerTransition;
import java.util.List; import org.floens.chan.controller.transition.PushControllerTransition;
import org.floens.chan.controller.ui.NavigationControllerContainerLayout;
public abstract class NavigationController extends Controller implements ControllerTransition.Callback { public abstract class NavigationController extends Controller {
protected ViewGroup container; protected NavigationControllerContainerLayout container;
protected List<Controller> controllerList = new ArrayList<>();
protected ControllerTransition controllerTransition; protected ControllerTransition controllerTransition;
protected boolean blockingInput = false; protected boolean blockingInput = false;
@ -36,15 +34,6 @@ public abstract class NavigationController extends Controller implements Control
super(context); super(context);
} }
@Override
public void onDestroy() {
super.onDestroy();
while (controllerList.size() > 0) {
popController(false);
}
}
public boolean pushController(final Controller to) { public boolean pushController(final Controller to) {
return pushController(to, true); return pushController(to, true);
} }
@ -56,7 +45,7 @@ public abstract class NavigationController extends Controller implements Control
public boolean pushController(final Controller to, ControllerTransition controllerTransition) { public boolean pushController(final Controller to, ControllerTransition controllerTransition) {
if (blockingInput) return false; if (blockingInput) return false;
final Controller from = controllerList.size() > 0 ? controllerList.get(controllerList.size() - 1) : null; final Controller from = getTop();
if (from == null && controllerTransition != null) { if (from == null && controllerTransition != null) {
throw new IllegalArgumentException("Cannot animate push when from is null"); throw new IllegalArgumentException("Cannot animate push when from is null");
@ -65,8 +54,6 @@ public abstract class NavigationController extends Controller implements Control
to.navigationController = this; to.navigationController = this;
to.previousSiblingController = from; to.previousSiblingController = from;
controllerList.add(to);
transition(from, to, true, controllerTransition); transition(from, to, true, controllerTransition);
return true; return true;
@ -83,33 +70,80 @@ public abstract class NavigationController extends Controller implements Control
public boolean popController(ControllerTransition controllerTransition) { public boolean popController(ControllerTransition controllerTransition) {
if (blockingInput) return false; if (blockingInput) return false;
final Controller from = controllerList.get(controllerList.size() - 1); final Controller from = getTop();
final Controller to = controllerList.size() > 1 ? controllerList.get(controllerList.size() - 2) : null; final Controller to = childControllers.size() > 1 ? childControllers.get(childControllers.size() - 2) : null;
transition(from, to, false, controllerTransition); transition(from, to, false, controllerTransition);
return true; return true;
} }
public void transition(Controller from, Controller to, boolean pushing, ControllerTransition controllerTransition) { public boolean isBlockingInput() {
return blockingInput;
}
public boolean beginSwipeTransition(final Controller from, final Controller to) {
if (blockingInput) return false;
if (this.controllerTransition != null) {
throw new IllegalArgumentException("Cannot transition while another transition is in progress.");
}
to.attach(container, false);
to.onShow();
return true;
}
public void swipeTransitionProgress(float progress) {
}
public void endSwipeTransition(final Controller from, final Controller to, boolean finish) {
if (finish) {
from.onHide();
from.detach();
removeChildController(from);
} else {
to.onHide();
to.detach();
}
controllerTransition = null;
blockingInput = false;
}
public void transition(final Controller from, final Controller to, final boolean pushing, ControllerTransition controllerTransition) {
if (this.controllerTransition != null) { if (this.controllerTransition != null) {
throw new IllegalArgumentException("Cannot transition while another transition is in progress."); throw new IllegalArgumentException("Cannot transition while another transition is in progress.");
} }
if (!pushing && controllerList.size() == 0) { if (!pushing && childControllers.size() == 0) {
throw new IllegalArgumentException("Cannot pop with no controllers left"); throw new IllegalArgumentException("Cannot pop with no controllers left");
} }
if (pushing && to != null) {
addChildController(to);
}
if (to != null) {
to.attach(container, pushing);
to.onShow();
}
if (controllerTransition != null) { if (controllerTransition != null) {
controllerTransition.from = from;
controllerTransition.to = to;
blockingInput = true; blockingInput = true;
this.controllerTransition = controllerTransition; this.controllerTransition = controllerTransition;
controllerTransition.setCallback(this); controllerTransition.setCallback(new ControllerTransition.Callback() {
ControllerLogic.startTransition(from, to, pushing, container, controllerTransition); @Override
public void onControllerTransitionCompleted(ControllerTransition transition) {
finishTransition(from, pushing);
}
});
controllerTransition.perform();
} else { } else {
ControllerLogic.transition(from, to, pushing, container); finishTransition(from, pushing);
if (!pushing) {
controllerList.remove(from);
}
} }
if (to != null) { if (to != null) {
@ -121,48 +155,35 @@ public abstract class NavigationController extends Controller implements Control
} }
} }
protected void controllerPushed(Controller controller) { private void finishTransition(Controller from, boolean pushing) {
} if (from != null) {
from.onHide();
protected void controllerPopped(Controller controller) { from.detach();
} }
@Override
public void onControllerTransitionCompleted(ControllerTransition transition) {
ControllerLogic.finishTransition(transition);
if (transition.destroyFrom) { if (!pushing && from != null) {
controllerList.remove(transition.from); removeChildController(from);
} }
controllerTransition = null; controllerTransition = null;
blockingInput = false; blockingInput = false;
} }
public Controller getTop() { protected void controllerPushed(Controller controller) {
if (controllerList.size() > 0) {
return controllerList.get(controllerList.size() - 1);
} else {
return null;
}
} }
/* protected void controllerPopped(Controller controller) {
* Used to save instance state
*/
public List<Controller> getControllerList() {
return controllerList;
} }
public boolean onBack() { public boolean onBack() {
if (blockingInput) return true; if (blockingInput) return true;
if (controllerList.size() > 0) { if (childControllers.size() > 0) {
Controller top = controllerList.get(controllerList.size() - 1); Controller top = getTop();
if (top.onBack()) { if (top.onBack()) {
return true; return true;
} else { } else {
if (controllerList.size() > 1) { if (childControllers.size() > 1) {
popController(); popController();
return true; return true;
} else { } else {
@ -177,13 +198,6 @@ public abstract class NavigationController extends Controller implements Control
@Override @Override
public boolean dispatchKeyEvent(KeyEvent event) { public boolean dispatchKeyEvent(KeyEvent event) {
Controller top = getTop(); Controller top = getTop();
return (top != null && top.dispatchKeyEvent(event)) || super.dispatchKeyEvent(event); return (top != null && top.dispatchKeyEvent(event));
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
for (Controller controller : controllerList) {
controller.onConfigurationChanged(newConfig);
}
} }
} }

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.floens.chan.controller; package org.floens.chan.controller.transition;
import android.animation.Animator; import android.animation.Animator;
import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorListenerAdapter;
@ -24,6 +24,8 @@ import android.animation.ObjectAnimator;
import android.view.View; import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AccelerateDecelerateInterpolator;
import org.floens.chan.controller.ControllerTransition;
public class FadeInTransition extends ControllerTransition { public class FadeInTransition extends ControllerTransition {
@Override @Override
public void perform() { public void perform() {

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.floens.chan.controller; package org.floens.chan.controller.transition;
import android.animation.Animator; import android.animation.Animator;
import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorListenerAdapter;
@ -24,6 +24,8 @@ import android.animation.ObjectAnimator;
import android.view.View; import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AccelerateDecelerateInterpolator;
import org.floens.chan.controller.ControllerTransition;
public class FadeOutTransition extends ControllerTransition { public class FadeOutTransition extends ControllerTransition {
@Override @Override
public void perform() { public void perform() {

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.floens.chan.controller; package org.floens.chan.controller.transition;
import android.animation.Animator; import android.animation.Animator;
import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorListenerAdapter;
@ -25,6 +25,8 @@ import android.view.View;
import android.view.animation.AccelerateInterpolator; import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator; import android.view.animation.DecelerateInterpolator;
import org.floens.chan.controller.ControllerTransition;
public class PopControllerTransition extends ControllerTransition { public class PopControllerTransition extends ControllerTransition {
public PopControllerTransition() { public PopControllerTransition() {
viewOver = false; viewOver = false;

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.floens.chan.controller; package org.floens.chan.controller.transition;
import android.animation.Animator; import android.animation.Animator;
import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorListenerAdapter;
@ -25,6 +25,7 @@ import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.DecelerateInterpolator; import android.view.animation.DecelerateInterpolator;
import org.floens.chan.controller.ControllerTransition;
import org.floens.chan.utils.AndroidUtils; import org.floens.chan.utils.AndroidUtils;
public class PushControllerTransition extends ControllerTransition { public class PushControllerTransition extends ControllerTransition {

@ -0,0 +1,310 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.controller.ui;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import android.widget.FrameLayout;
import android.widget.Scroller;
import org.floens.chan.controller.Controller;
import org.floens.chan.controller.NavigationController;
public class NavigationControllerContainerLayout extends FrameLayout {
private NavigationController navigationController;
private int slopPixels;
private int flingPixels;
private int maxFlingPixels;
private boolean swipeEnabled = true;
private MotionEvent downEvent;
private boolean dontStartSwiping = false;
private MotionEvent swipeStartEvent;
private VelocityTracker velocityTracker;
private Scroller scroller;
private boolean popAfterSwipe = false;
private Paint shadowPaint;
private Rect shadowRect = new Rect();
private boolean drawShadow;
private int swipePosition;
private boolean swiping = false;
private Controller swipingController;
public NavigationControllerContainerLayout(Context context) {
super(context);
init();
}
public NavigationControllerContainerLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public NavigationControllerContainerLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
ViewConfiguration viewConfiguration = ViewConfiguration.get(getContext());
slopPixels = (int) (viewConfiguration.getScaledTouchSlop() * 0.5f);
flingPixels = viewConfiguration.getScaledMinimumFlingVelocity();
maxFlingPixels = viewConfiguration.getScaledMaximumFlingVelocity();
scroller = new Scroller(getContext());
shadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
}
public void setSwipeEnabled(boolean swipeEnabled) {
this.swipeEnabled = swipeEnabled;
}
public void setNavigationController(NavigationController navigationController) {
this.navigationController = navigationController;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (!swipeEnabled || swiping) {
return false;
}
int actionMasked = event.getActionMasked();
if (actionMasked != MotionEvent.ACTION_DOWN && downEvent == null) {
// Action down wasn't called here, ignore
return false;
}
if (!navigationController.getTop().navigationItem.swipeable) {
return false;
}
if (getBelowTop() == null) {
// Cannot swipe now
return false;
}
switch (actionMasked) {
case MotionEvent.ACTION_DOWN:
// Logger.test("onInterceptTouchEvent down");
downEvent = MotionEvent.obtain(event);
break;
case MotionEvent.ACTION_MOVE: {
// Logger.test("onInterceptTouchEvent move");
float x = (event.getX() - downEvent.getX());
float y = (event.getY() - downEvent.getY());
if (Math.abs(y) >= slopPixels) {
// Logger.test("dontStartSwiping = true");
dontStartSwiping = true;
}
if (!dontStartSwiping && Math.abs(x) > Math.abs(y) && x >= slopPixels && !navigationController.isBlockingInput()) {
// Logger.test("Start tracking swipe");
downEvent.recycle();
downEvent = null;
swipeStartEvent = MotionEvent.obtain(event);
velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);
swiping = true;
return true;
}
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP: {
// Logger.test("onInterceptTouchEvent cancel/up");
downEvent.recycle();
downEvent = null;
dontStartSwiping = false;
break;
}
}
return false;
}
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept) {
if (downEvent != null) {
downEvent.recycle();
downEvent = null;
}
dontStartSwiping = false;
}
super.requestDisallowInterceptTouchEvent(disallowIntercept);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// This touch wasn't initiated with onInterceptTouchEvent
if (swipeStartEvent == null) {
return false;
}
if (swipingController == null) {
// Start of swipe
// Logger.test("Start of swipe");
swipingController = navigationController.getTop();
drawShadow = true;
// long start = Time.startTiming();
Controller below = getBelowTop();
navigationController.beginSwipeTransition(swipingController, below);
// Time.endTiming("attach", start);
}
float translationX = Math.max(0, event.getX() - swipeStartEvent.getX());
setTopControllerTranslation((int) translationX);
velocityTracker.addMovement(event);
switch (event.getActionMasked()) {
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP: {
// Logger.test("onTouchEvent cancel or up");
scroller.forceFinished(true);
velocityTracker.addMovement(event);
velocityTracker.computeCurrentVelocity(1000);
float velocity = velocityTracker.getXVelocity();
if (translationX > 0f) {
boolean isFling = false;
if (velocity > 0f && Math.abs(velocity) > flingPixels && Math.abs(velocity) < maxFlingPixels) {
scroller.fling((int) translationX, 0, (int) velocity, 0, 0, Integer.MAX_VALUE, 0, 0);
if (scroller.getFinalX() >= getWidth()) {
isFling = true;
}
}
if (isFling) {
// Logger.test("Flinging with velocity = " + velocity);
popAfterSwipe = true;
ViewCompat.postOnAnimation(this, flingRunnable);
} else {
// Logger.test("Snapping back!");
scroller.forceFinished(true);
scroller.startScroll((int) translationX, 0, -((int) translationX), 0, 300);
popAfterSwipe = false;
ViewCompat.postOnAnimation(this, flingRunnable);
}
} else {
finishSwipe();
}
swipeStartEvent.recycle();
swipeStartEvent = null;
velocityTracker.recycle();
velocityTracker = null;
break;
}
}
return true;
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (drawShadow) {
float alpha = Math.min(1f, Math.max(0f, 0.5f - (swipePosition / (float) getWidth()) * 0.5f));
shadowPaint.setColor(Color.argb((int) (alpha * 255f), 0, 0, 0));
shadowRect.set(0, 0, swipePosition, getHeight());
canvas.drawRect(shadowRect, shadowPaint);
}
}
private void finishSwipe() {
Controller below = getBelowTop();
navigationController.endSwipeTransition(swipingController, below, popAfterSwipe);
swipingController = null;
drawShadow = false;
swiping = false;
}
private Runnable flingRunnable = new Runnable() {
@Override
public void run() {
boolean finished = false;
if (scroller.computeScrollOffset()) {
float translationX = scroller.getCurrX();
setTopControllerTranslation((int) translationX);
// The view is not visible anymore. End it before the fling completely finishes.
if (translationX >= getWidth()) {
finished = true;
}
} else {
finished = true;
}
if (!finished) {
ViewCompat.postOnAnimation(NavigationControllerContainerLayout.this, flingRunnable);
} else {
finishSwipe();
}
}
};
private void setTopControllerTranslation(int translationX) {
swipePosition = translationX;
swipingController.view.setTranslationX(swipePosition);
navigationController.swipeTransitionProgress(swipePosition / (float) getWidth());
invalidate();
}
private Controller getBelowTop() {
if (navigationController.childControllers.size() >= 2) {
return navigationController.childControllers.get(navigationController.childControllers.size() - 2);
} else {
return null;
}
}
}

@ -220,11 +220,11 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat
} else { } else {
Loadable thread = null; Loadable thread = null;
if (drawerController.getChildController() instanceof SplitNavigationController) { if (drawerController.childControllers.get(0) instanceof SplitNavigationController) {
SplitNavigationController splitNavigationController = (SplitNavigationController) drawerController.getChildController(); SplitNavigationController splitNavigationController = (SplitNavigationController) drawerController.childControllers.get(0);
if (splitNavigationController.rightController instanceof NavigationController) { if (splitNavigationController.rightController instanceof NavigationController) {
NavigationController rightNavigationController = (NavigationController) splitNavigationController.rightController; NavigationController rightNavigationController = (NavigationController) splitNavigationController.rightController;
for (Controller controller : rightNavigationController.getControllerList()) { for (Controller controller : rightNavigationController.childControllers) {
if (controller instanceof ViewThreadController) { if (controller instanceof ViewThreadController) {
thread = ((ViewThreadController) controller).getLoadable(); thread = ((ViewThreadController) controller).getLoadable();
break; break;
@ -233,7 +233,7 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat
} }
} else { } else {
List<Controller> controllers = mainNavigationController.getControllerList(); List<Controller> controllers = mainNavigationController.childControllers;
for (Controller controller : controllers) { for (Controller controller : controllers) {
if (controller instanceof ViewThreadController) { if (controller instanceof ViewThreadController) {
thread = ((ViewThreadController) controller).getLoadable(); thread = ((ViewThreadController) controller).getLoadable();

@ -78,7 +78,7 @@ import static org.floens.chan.utils.AndroidUtils.sp;
public class PostCell extends LinearLayout implements PostCellInterface, PostLinkable.Callback { public class PostCell extends LinearLayout implements PostCellInterface, PostLinkable.Callback {
private static final String TAG = "PostCell"; private static final String TAG = "PostCell";
private static final int COMMENT_MAX_LENGTH_BOARD = 500; private static final int COMMENT_MAX_LENGTH_BOARD = 350;
private ThumbnailView thumbnailView; private ThumbnailView thumbnailView;
private FastTextView title; private FastTextView title;
@ -91,11 +91,9 @@ public class PostCell extends LinearLayout implements PostCellInterface, PostLin
private boolean commentClickable = false; private boolean commentClickable = false;
private int detailsSizePx; private int detailsSizePx;
private int iconsTextSize;
private int countrySizePx; private int countrySizePx;
private int paddingPx; private int paddingPx;
private boolean threadMode; private boolean threadMode;
// private boolean ignoreNextOnClick;
private boolean bound = false; private boolean bound = false;
private Theme theme; private Theme theme;
@ -143,9 +141,8 @@ public class PostCell extends LinearLayout implements PostCellInterface, PostLin
title.setTextSize(textSizeSp); title.setTextSize(textSizeSp);
title.setPadding(paddingPx, paddingPx, dp(52), 0); title.setPadding(paddingPx, paddingPx, dp(52), 0);
iconsTextSize = sp(textSizeSp);
countrySizePx = sp(textSizeSp - 3); countrySizePx = sp(textSizeSp - 3);
icons.setHeight(iconsTextSize); icons.setHeight(sp(textSizeSp));
icons.setSpacing(dp(4)); icons.setSpacing(dp(4));
icons.setPadding(paddingPx, dp(4), paddingPx, 0); icons.setPadding(paddingPx, dp(4), paddingPx, 0);

@ -209,8 +209,8 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte
loadBoard(((FloatingMenuItemBoard) item).board); loadBoard(((FloatingMenuItemBoard) item).board);
} else { } else {
BoardEditController boardEditController = new BoardEditController(context); BoardEditController boardEditController = new BoardEditController(context);
if (navigationController.navigationController instanceof SplitNavigationController) { if (splitNavigationController != null) {
navigationController.navigationController.pushController(boardEditController); splitNavigationController.pushController(boardEditController);
} else { } else {
navigationController.pushController(boardEditController); navigationController.pushController(boardEditController);
} }
@ -234,9 +234,7 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte
} }
public void showThread(Loadable threadLoadable, boolean animated) { public void showThread(Loadable threadLoadable, boolean animated) {
if (navigationController.navigationController instanceof SplitNavigationController) { if (splitNavigationController != null) {
SplitNavigationController splitNavigationController = (SplitNavigationController) navigationController.navigationController;
if (splitNavigationController.rightController instanceof StyledToolbarNavigationController) { if (splitNavigationController.rightController instanceof StyledToolbarNavigationController) {
StyledToolbarNavigationController navigationController = (StyledToolbarNavigationController) splitNavigationController.rightController; StyledToolbarNavigationController navigationController = (StyledToolbarNavigationController) splitNavigationController.rightController;
@ -274,6 +272,7 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte
for (FloatingMenuItem item : boardItems) { for (FloatingMenuItem item : boardItems) {
if (((FloatingMenuItemBoard) item).board == board) { if (((FloatingMenuItemBoard) item).board == board) {
navigationItem.middleMenu.setSelectedItem(item); navigationItem.middleMenu.setSelectedItem(item);
break;
} }
} }
navigationItem.updateTitle(); navigationItem.updateTitle();

@ -28,7 +28,6 @@ import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper; import android.support.v7.widget.helper.ItemTouchHelper;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.Gravity; import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View; import android.view.View;
import android.widget.EditText; import android.widget.EditText;
import android.widget.FrameLayout; import android.widget.FrameLayout;
@ -39,7 +38,6 @@ import android.widget.TextView;
import org.floens.chan.Chan; import org.floens.chan.Chan;
import org.floens.chan.R; import org.floens.chan.R;
import org.floens.chan.controller.Controller; import org.floens.chan.controller.Controller;
import org.floens.chan.controller.ControllerLogic;
import org.floens.chan.controller.NavigationController; import org.floens.chan.controller.NavigationController;
import org.floens.chan.core.manager.WatchManager; import org.floens.chan.core.manager.WatchManager;
import org.floens.chan.core.model.Pin; import org.floens.chan.core.model.Pin;
@ -65,8 +63,6 @@ public class DrawerController extends Controller implements PinAdapter.Callback,
protected LinearLayout settings; protected LinearLayout settings;
protected PinAdapter pinAdapter; protected PinAdapter pinAdapter;
private NavigationController childController;
public DrawerController(Context context) { public DrawerController(Context context) {
super(context); super(context);
} }
@ -114,21 +110,13 @@ public class DrawerController extends Controller implements PinAdapter.Callback,
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
if (childController != null) {
childController.onDestroy();
}
EventBus.getDefault().unregister(this); EventBus.getDefault().unregister(this);
} }
public void setChildController(NavigationController childController) { public void setChildController(Controller childController) {
childController.parentController = this; addChildController(childController);
ControllerLogic.transition(this.childController, childController, true, true, container); childController.attach(container, true);
this.childController = childController; childController.onShow();
}
public NavigationController getChildController() {
return childController;
} }
@Override @Override
@ -151,7 +139,9 @@ public class DrawerController extends Controller implements PinAdapter.Callback,
} }
public void onMenuClicked() { public void onMenuClicked() {
drawerLayout.openDrawer(drawer); if (getStyledToolbarNavigationController().getTop().navigationItem.hasDrawer) {
drawerLayout.openDrawer(drawer);
}
} }
@Override @Override
@ -159,35 +149,19 @@ public class DrawerController extends Controller implements PinAdapter.Callback,
if (drawerLayout.isDrawerOpen(drawer)) { if (drawerLayout.isDrawerOpen(drawer)) {
drawerLayout.closeDrawer(drawer); drawerLayout.closeDrawer(drawer);
return true; return true;
} else if (childController != null && childController.onBack()) {
return true;
} else { } else {
return super.onBack(); return super.onBack();
} }
} }
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
return (childController != null && childController.dispatchKeyEvent(event)) || super.dispatchKeyEvent(event);
}
@Override @Override
public void onPinClicked(Pin pin) { public void onPinClicked(Pin pin) {
drawerLayout.closeDrawer(Gravity.LEFT); drawerLayout.closeDrawer(Gravity.LEFT);
if (childController instanceof StyledToolbarNavigationController) { StyledToolbarNavigationController navigationController = getStyledToolbarNavigationController();
if (childController.getTop() instanceof ThreadController) { if (navigationController.getTop() instanceof ThreadController) {
((ThreadController) childController.getTop()).openPin(pin); ThreadController threadController = (ThreadController) navigationController.getTop();
} threadController.openPin(pin);
} else if (childController instanceof SplitNavigationController) {
SplitNavigationController splitNavigationController = (SplitNavigationController) childController;
if (splitNavigationController.leftController instanceof NavigationController) {
NavigationController navigationController = (NavigationController) splitNavigationController.leftController;
if (navigationController.getTop() instanceof ThreadController) {
ThreadController threadController = (ThreadController) navigationController.getTop();
threadController.openPin(pin);
}
}
} }
} }
@ -295,13 +269,8 @@ public class DrawerController extends Controller implements PinAdapter.Callback,
} }
} }
if (childController instanceof StyledToolbarNavigationController) { if (getTop() != null) {
((StyledToolbarNavigationController) childController).toolbar.getArrowMenuDrawable().setBadge(count, color); getStyledToolbarNavigationController().toolbar.getArrowMenuDrawable().setBadge(count, color);
} else if (childController instanceof SplitNavigationController) {
SplitNavigationController splitNavigationController = (SplitNavigationController) childController;
if (splitNavigationController.leftController instanceof StyledToolbarNavigationController) {
((StyledToolbarNavigationController) splitNavigationController.leftController).toolbar.getArrowMenuDrawable().setBadge(count, color);
}
} }
} }
@ -317,7 +286,34 @@ public class DrawerController extends Controller implements PinAdapter.Callback,
} }
private void openController(Controller controller) { private void openController(Controller controller) {
childController.pushController(controller); Controller top = getTop();
if (top instanceof NavigationController) {
((NavigationController) top).pushController(controller);
} else if (top instanceof SplitNavigationController) {
((SplitNavigationController) top).pushController(controller);
}
drawerLayout.closeDrawer(Gravity.LEFT); drawerLayout.closeDrawer(Gravity.LEFT);
} }
private StyledToolbarNavigationController getStyledToolbarNavigationController() {
StyledToolbarNavigationController navigationController = null;
Controller top = getTop();
if (top instanceof StyledToolbarNavigationController) {
navigationController = (StyledToolbarNavigationController) top;
} else if (top instanceof SplitNavigationController) {
SplitNavigationController splitNavigationController = (SplitNavigationController) top;
if (splitNavigationController.leftController instanceof StyledToolbarNavigationController) {
navigationController = (StyledToolbarNavigationController) splitNavigationController.leftController;
}
}
if (navigationController == null) {
throw new IllegalStateException("The child controller of a DrawerController must either be StyledToolbarNavigationController" +
"or an SplitNavigationController that has a StyledToolbarNavigationController.");
}
return navigationController;
}
} }

@ -18,14 +18,11 @@
package org.floens.chan.ui.controller; package org.floens.chan.ui.controller;
import android.content.Context; import android.content.Context;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import org.floens.chan.R; import org.floens.chan.R;
import org.floens.chan.controller.NavigationController; import org.floens.chan.controller.ui.NavigationControllerContainerLayout;
import org.floens.chan.core.model.Loadable; import org.floens.chan.core.model.Loadable;
import org.floens.chan.core.model.PostImage; import org.floens.chan.core.model.PostImage;
import org.floens.chan.ui.theme.ThemeHelper;
import org.floens.chan.ui.toolbar.Toolbar; import org.floens.chan.ui.toolbar.Toolbar;
import java.util.List; import java.util.List;
@ -42,15 +39,16 @@ public class ImageViewerNavigationController extends ToolbarNavigationController
super.onCreate(); super.onCreate();
view = inflateRes(R.layout.controller_navigation_image_viewer); view = inflateRes(R.layout.controller_navigation_image_viewer);
container = (ViewGroup) view.findViewById(R.id.container); container = (NavigationControllerContainerLayout) view.findViewById(R.id.container);
container.setNavigationController(this);
container.setSwipeEnabled(false);
toolbar = (Toolbar) view.findViewById(R.id.toolbar); toolbar = (Toolbar) view.findViewById(R.id.toolbar);
toolbar.setCallback(this); toolbar.setCallback(this);
imageViewerController = new ImageViewerController(context, toolbar);
pushController(imageViewerController, false);
} }
public void showImages(final List<PostImage> images, final int index, final Loadable loadable, final ImageViewerController.PreviewCallback previewCallback) { public void showImages(final List<PostImage> images, final int index, final Loadable loadable, final ImageViewerController.PreviewCallback previewCallback) {
imageViewerController = new ImageViewerController(context, toolbar);
pushController(imageViewerController, false);
imageViewerController.setPreviewCallback(previewCallback); imageViewerController.setPreviewCallback(previewCallback);
imageViewerController.getPresenter().showImages(images, index, loadable); imageViewerController.getPresenter().showImages(images, index, loadable);
} }

@ -23,15 +23,12 @@ import android.widget.FrameLayout;
import org.floens.chan.R; import org.floens.chan.R;
import org.floens.chan.controller.Controller; import org.floens.chan.controller.Controller;
import org.floens.chan.controller.ControllerLogic;
import org.floens.chan.controller.NavigationController; import org.floens.chan.controller.NavigationController;
public class PopupController extends Controller implements View.OnClickListener { public class PopupController extends Controller implements View.OnClickListener {
private FrameLayout topView; private FrameLayout topView;
private FrameLayout container; private FrameLayout container;
private NavigationController childController;
public PopupController(Context context) { public PopupController(Context context) {
super(context); super(context);
} }
@ -46,24 +43,10 @@ public class PopupController extends Controller implements View.OnClickListener
container = (FrameLayout) view.findViewById(R.id.container); container = (FrameLayout) view.findViewById(R.id.container);
} }
@Override
public void onDestroy() {
super.onDestroy();
if (childController != null) {
childController.onDestroy();
}
}
public void setChildController(NavigationController childController) { public void setChildController(NavigationController childController) {
childController.parentController = this; addChildController(childController);
ControllerLogic.transition(this.childController, childController, true, true, container); childController.attach(container, true);
this.childController = childController; childController.onShow();
}
@Override
public boolean onBack() {
return childController != null && childController.onBack();
} }
@Override @Override
@ -72,8 +55,8 @@ public class PopupController extends Controller implements View.OnClickListener
} }
public void dismiss() { public void dismiss() {
if (presentingController instanceof SplitNavigationController) { if (presentingByController instanceof SplitNavigationController) {
((SplitNavigationController) presentingController).popAll(); ((SplitNavigationController) presentingByController).popAll();
} }
} }
} }

@ -27,15 +27,15 @@ import android.widget.LinearLayout;
import org.floens.chan.R; import org.floens.chan.R;
import org.floens.chan.controller.Controller; import org.floens.chan.controller.Controller;
import org.floens.chan.controller.ControllerLogic;
import org.floens.chan.controller.ControllerTransition; import org.floens.chan.controller.ControllerTransition;
import org.floens.chan.controller.NavigationController; import org.floens.chan.controller.transition.PopControllerTransition;
import org.floens.chan.controller.transition.PushControllerTransition;
import org.floens.chan.utils.AndroidUtils; import org.floens.chan.utils.AndroidUtils;
import static org.floens.chan.utils.AndroidUtils.dp; import static org.floens.chan.utils.AndroidUtils.dp;
import static org.floens.chan.utils.AndroidUtils.getAttrColor; import static org.floens.chan.utils.AndroidUtils.getAttrColor;
public class SplitNavigationController extends NavigationController implements AndroidUtils.OnMeasuredCallback { public class SplitNavigationController extends Controller implements AndroidUtils.OnMeasuredCallback {
public Controller leftController; public Controller leftController;
public Controller rightController; public Controller rightController;
@ -55,6 +55,8 @@ public class SplitNavigationController extends NavigationController implements A
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
splitNavigationController = this;
LinearLayout wrap = new LinearLayout(context); LinearLayout wrap = new LinearLayout(context);
view = wrap; view = wrap;
@ -73,44 +75,56 @@ public class SplitNavigationController extends NavigationController implements A
AndroidUtils.waitForMeasure(view, this); AndroidUtils.waitForMeasure(view, this);
} }
@Override
public void onDestroy() {
super.onDestroy();
if (leftController != null) {
leftController.onDestroy();
}
if (rightController != null) {
rightController.onDestroy();
}
}
public void setEmptyView(ViewGroup emptyView) { public void setEmptyView(ViewGroup emptyView) {
this.emptyView = emptyView; this.emptyView = emptyView;
} }
public void setLeftController(Controller leftController) { public void setLeftController(Controller leftController) {
leftController.navigationController = this; if (this.leftController != null) {
ControllerLogic.transition(this.leftController, leftController, true, true, leftControllerView); this.leftController.onHide();
this.leftController.detach();
removeChildController(this.leftController);
}
this.leftController = leftController; this.leftController = leftController;
if (leftController != null) {
addChildController(leftController);
leftController.attach(leftControllerView, true);
leftController.onShow();
} else {
}
} }
public void setRightController(Controller rightController) { public void setRightController(Controller rightController) {
if (rightController != null) { if (this.rightController != null) {
rightController.navigationController = this; this.rightController.onHide();
this.rightController.detach();
removeChildController(this.rightController);
} else { } else {
rightControllerView.removeAllViews(); rightControllerView.removeAllViews();
} }
ControllerLogic.transition(this.rightController, rightController, true, true, rightControllerView);
this.rightController = rightController; this.rightController = rightController;
if (rightController == null) { if (rightController != null) {
addChildController(rightController);
rightController.attach(rightControllerView, true);
rightController.onShow();
} else {
rightControllerView.addView(emptyView); rightControllerView.addView(emptyView);
} }
} }
@Override public boolean pushController(final Controller to) {
return pushController(to, true);
}
public boolean pushController(final Controller to, boolean animated) {
return pushController(to, animated ? new PushControllerTransition() : null);
}
public boolean pushController(Controller to, ControllerTransition controllerTransition) { public boolean pushController(Controller to, ControllerTransition controllerTransition) {
if (popup == null) { if (popup == null) {
popup = new PopupController(context); popup = new PopupController(context);
@ -125,11 +139,18 @@ public class SplitNavigationController extends NavigationController implements A
return true; return true;
} }
@Override public boolean popController() {
return popController(true);
}
public boolean popController(boolean animated) {
return popController(animated ? new PopControllerTransition() : null);
}
public boolean popController(ControllerTransition controllerTransition) { public boolean popController(ControllerTransition controllerTransition) {
if (popup != null) { if (popup != null) {
if (popupChild.getControllerList().size() == 1) { if (popupChild.childControllers.size() == 1) {
presentedController.stopPresenting(); presentingThisController.stopPresenting();
popup = null; popup = null;
popupChild = null; popupChild = null;
} else { } else {
@ -143,7 +164,7 @@ public class SplitNavigationController extends NavigationController implements A
public void popAll() { public void popAll() {
if (popup != null) { if (popup != null) {
presentedController.stopPresenting(); presentingThisController.stopPresenting();
popup = null; popup = null;
popupChild = null; popupChild = null;
} }

@ -18,11 +18,11 @@
package org.floens.chan.ui.controller; package org.floens.chan.ui.controller;
import android.content.Context; import android.content.Context;
import android.view.ViewGroup;
import org.floens.chan.R; import org.floens.chan.R;
import org.floens.chan.controller.Controller; import org.floens.chan.controller.Controller;
import org.floens.chan.controller.ControllerTransition; import org.floens.chan.controller.ControllerTransition;
import org.floens.chan.controller.ui.NavigationControllerContainerLayout;
import org.floens.chan.ui.theme.ThemeHelper; import org.floens.chan.ui.theme.ThemeHelper;
import org.floens.chan.ui.toolbar.Toolbar; import org.floens.chan.ui.toolbar.Toolbar;
@ -36,7 +36,8 @@ public class StyledToolbarNavigationController extends ToolbarNavigationControll
super.onCreate(); super.onCreate();
view = inflateRes(R.layout.controller_navigation_toolbar); view = inflateRes(R.layout.controller_navigation_toolbar);
container = (ViewGroup) view.findViewById(R.id.container); container = (NavigationControllerContainerLayout) view.findViewById(R.id.container);
container.setNavigationController(this);
toolbar = (Toolbar) view.findViewById(R.id.toolbar); toolbar = (Toolbar) view.findViewById(R.id.toolbar);
toolbar.setBackgroundColor(ThemeHelper.getInstance().getTheme().primaryColor.color); toolbar.setBackgroundColor(ThemeHelper.getInstance().getTheme().primaryColor.color);
toolbar.setCallback(this); toolbar.setCallback(this);
@ -58,11 +59,10 @@ public class StyledToolbarNavigationController extends ToolbarNavigationControll
public boolean onBack() { public boolean onBack() {
if (super.onBack()) { if (super.onBack()) {
return true; return true;
} else if (parentController instanceof PopupController && controllerList.size() == 1) { } else if (parentController instanceof PopupController && childControllers.size() == 1) {
((PopupController) parentController).dismiss(); ((PopupController) parentController).dismiss();
return true; return true;
} else if (navigationController instanceof SplitNavigationController && controllerList.size() == 1) { } else if (splitNavigationController != null && childControllers.size() == 1) {
SplitNavigationController splitNavigationController = (SplitNavigationController) navigationController;
if (splitNavigationController.rightController == this) { if (splitNavigationController.rightController == this) {
splitNavigationController.setRightController(null); splitNavigationController.setRightController(null);
return true; return true;
@ -85,8 +85,7 @@ public class StyledToolbarNavigationController extends ToolbarNavigationControll
private DrawerController getDrawerController() { private DrawerController getDrawerController() {
if (parentController instanceof DrawerController) { if (parentController instanceof DrawerController) {
return (DrawerController) parentController; return (DrawerController) parentController;
} else if (navigationController instanceof SplitNavigationController) { } else if (splitNavigationController != null) {
SplitNavigationController splitNavigationController = (SplitNavigationController) navigationController;
if (splitNavigationController.parentController instanceof DrawerController) { if (splitNavigationController.parentController instanceof DrawerController) {
return (DrawerController) splitNavigationController.parentController; return (DrawerController) splitNavigationController.parentController;
} }

@ -116,6 +116,7 @@ public class ThemeSettingsController extends Controller implements View.OnClickL
super.onCreate(); super.onCreate();
navigationItem.setTitle(R.string.settings_screen_theme); navigationItem.setTitle(R.string.settings_screen_theme);
navigationItem.swipeable = false;
view = inflateRes(R.layout.controller_theme); view = inflateRes(R.layout.controller_theme);
themeHelper = ThemeHelper.getInstance(); themeHelper = ThemeHelper.getInstance();

@ -51,6 +51,38 @@ public abstract class ToolbarNavigationController extends NavigationController i
} }
} }
@Override
public boolean beginSwipeTransition(Controller from, Controller to) {
if (!super.beginSwipeTransition(from, to)) {
return false;
}
toolbar.processScrollCollapse(Toolbar.TOOLBAR_COLLAPSE_SHOW, true);
toolbar.beginTransition(to.navigationItem);
return true;
}
@Override
public void swipeTransitionProgress(float progress) {
super.swipeTransitionProgress(progress);
toolbar.transitionProgress(progress, false);
}
@Override
public void endSwipeTransition(Controller from, Controller to, boolean finish) {
super.endSwipeTransition(from, to, finish);
toolbar.finishTransition(finish);
if (finish) {
updateToolbarCollapse(to, controllerTransition != null);
} else {
updateToolbarCollapse(from, controllerTransition != null);
}
}
@Override @Override
public void onMenuOrBackClicked(boolean isArrow) { public void onMenuOrBackClicked(boolean isArrow) {
if (isArrow) { if (isArrow) {
@ -104,8 +136,4 @@ public abstract class ToolbarNavigationController extends NavigationController i
void onSearchEntered(String entered); void onSearchEntered(String entered);
} }
public interface ToolbarMenuCallback {
void onMenuOrBackClicked(boolean isArrow);
}
} }

@ -202,9 +202,9 @@ public class ViewThreadController extends ThreadController implements ThreadLayo
if (navigationController.parentController instanceof DrawerController) { if (navigationController.parentController instanceof DrawerController) {
((DrawerController) navigationController.parentController).setPinHighlighted(pin); ((DrawerController) navigationController.parentController).setPinHighlighted(pin);
} else if (navigationController.navigationController instanceof SplitNavigationController) { } else if (splitNavigationController != null) {
if (((SplitNavigationController) navigationController.navigationController).parentController instanceof DrawerController) { if (splitNavigationController.parentController instanceof DrawerController) {
((DrawerController) ((SplitNavigationController) navigationController.navigationController).parentController).setPinHighlighted(pin); ((DrawerController) splitNavigationController.parentController).setPinHighlighted(pin);
} }
} }
} }

@ -21,6 +21,8 @@ import android.content.Context;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import org.floens.chan.utils.Logger;
import static org.floens.chan.utils.AndroidUtils.dp; import static org.floens.chan.utils.AndroidUtils.dp;
public class PopupControllerContainer extends FrameLayout { public class PopupControllerContainer extends FrameLayout {
@ -38,10 +40,18 @@ public class PopupControllerContainer extends FrameLayout {
@Override @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int maxHeight = dp(600); int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (MeasureSpec.getSize(heightMeasureSpec) > maxHeight) { int widthSize = MeasureSpec.getSize(widthMeasureSpec);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.getMode(heightMeasureSpec)); int heightSize = MeasureSpec.getSize(heightMeasureSpec);
FrameLayout.LayoutParams child = (LayoutParams) getChildAt(0).getLayoutParams();
Logger.test("%s %s", MeasureSpec.toString(widthMeasureSpec), MeasureSpec.toString(heightMeasureSpec));
if (heightMode == MeasureSpec.EXACTLY && heightSize < dp(600)) {
child.height = heightSize;
} else {
child.height = dp(600);
} }
super.onMeasure(widthMeasureSpec, heightMeasureSpec); super.onMeasure(widthMeasureSpec, heightMeasureSpec);

@ -37,6 +37,7 @@ public class NavigationItem {
public View rightView; public View rightView;
public boolean hasDrawer = false; public boolean hasDrawer = false;
public boolean collapseToolbar = false; public boolean collapseToolbar = false;
public boolean swipeable = true;
boolean search = false; boolean search = false;
String searchText; String searchText;

@ -19,7 +19,6 @@ package org.floens.chan.ui.toolbar;
import android.animation.Animator; import android.animation.Animator;
import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator; import android.animation.ObjectAnimator;
import android.animation.ValueAnimator; import android.animation.ValueAnimator;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
@ -46,9 +45,6 @@ import org.floens.chan.ui.layout.SearchLayout;
import org.floens.chan.ui.view.LoadView; import org.floens.chan.ui.view.LoadView;
import org.floens.chan.utils.AndroidUtils; 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.dp;
import static org.floens.chan.utils.AndroidUtils.getAttrColor; import static org.floens.chan.utils.AndroidUtils.getAttrColor;
import static org.floens.chan.utils.AndroidUtils.setRoundItemBackground; import static org.floens.chan.utils.AndroidUtils.setRoundItemBackground;
@ -83,11 +79,13 @@ public class Toolbar extends LinearLayout implements View.OnClickListener, LoadV
private LoadView navigationItemContainer; private LoadView navigationItemContainer;
private ToolbarCallback callback; private ToolbarCallback callback;
private NavigationItem navigationItem;
private boolean openKeyboardAfterSearchViewCreated = false; private boolean openKeyboardAfterSearchViewCreated = false;
private int lastScrollDeltaOffset; private int lastScrollDeltaOffset;
private int scrollOffset; private int scrollOffset;
private NavigationItem fromItem;
private NavigationItem toItem;
public Toolbar(Context context) { public Toolbar(Context context) {
super(context); super(context);
init(); init();
@ -136,13 +134,13 @@ public class Toolbar extends LinearLayout implements View.OnClickListener, LoadV
recyclerView.removeOnScrollListener(recyclerViewOnScrollListener); recyclerView.removeOnScrollListener(recyclerViewOnScrollListener);
} }
public void updateNavigation() { // public void updateNavigation() {
closeSearchInternal(); // closeSearchInternal();
setNavigationItem(false, false, navigationItem); // setNavigationItem(false, false, toItem);
} // }
public NavigationItem getNavigationItem() { public NavigationItem getNavigationItem() {
return navigationItem; return toItem;
} }
public boolean openSearch() { public boolean openSearch() {
@ -157,6 +155,44 @@ public class Toolbar extends LinearLayout implements View.OnClickListener, LoadV
setNavigationItemInternal(animate, pushing, item); setNavigationItemInternal(animate, pushing, item);
} }
public void beginTransition(NavigationItem newItem) {
attachNavigationItem(newItem);
navigationItemContainer.addView(toItem.view, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
public void transitionProgress(float progress, boolean pushing) {
final int offset = dp(16);
toItem.view.setTranslationY((pushing ? offset : -offset) * (1f - progress));
toItem.view.setAlpha(progress);
if (fromItem != null) {
fromItem.view.setTranslationY((pushing ? -offset : offset) * progress);
fromItem.view.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 finishTransition(boolean finished) {
if (finished) {
if (fromItem != null) {
removeNavigationItem(fromItem);
}
setArrowMenuProgress(toItem.hasBack || toItem.search ? 1f : 0f);
} else {
removeNavigationItem(toItem);
setArrowMenuProgress(toItem.hasBack || toItem.search ? 1f : 0f);
toItem = fromItem;
}
fromItem = null;
}
public void setCallback(ToolbarCallback callback) { public void setCallback(ToolbarCallback callback) {
this.callback = callback; this.callback = callback;
} }
@ -178,7 +214,7 @@ public class Toolbar extends LinearLayout implements View.OnClickListener, LoadV
@Override @Override
public void onLoadViewRemoved(View view) { public void onLoadViewRemoved(View view) {
// TODO: this is kinda a hack // Remove the menu from the navigation item
if (view instanceof ViewGroup) { if (view instanceof ViewGroup) {
((ViewGroup) view).removeAllViews(); ((ViewGroup) view).removeAllViews();
} }
@ -227,10 +263,10 @@ public class Toolbar extends LinearLayout implements View.OnClickListener, LoadV
} }
private boolean openSearchInternal() { private boolean openSearchInternal() {
if (navigationItem != null && !navigationItem.search) { if (toItem != null && !toItem.search) {
navigationItem.search = true; toItem.search = true;
openKeyboardAfterSearchViewCreated = true; openKeyboardAfterSearchViewCreated = true;
setNavigationItemInternal(true, false, navigationItem); setNavigationItemInternal(true, false, toItem);
callback.onSearchVisibilityChanged(true); callback.onSearchVisibilityChanged(true);
return true; return true;
} else { } else {
@ -239,11 +275,10 @@ public class Toolbar extends LinearLayout implements View.OnClickListener, LoadV
} }
private boolean closeSearchInternal() { private boolean closeSearchInternal() {
if (navigationItem != null && navigationItem.search) { if (toItem != null && toItem.search) {
navigationItem.search = false; toItem.search = false;
navigationItem.searchText = null; toItem.searchText = null;
setNavigationItemInternal(true, false, navigationItem); setNavigationItemInternal(true, false, toItem);
AndroidUtils.hideKeyboard(navigationItemContainer);
callback.onSearchVisibilityChanged(false); callback.onSearchVisibilityChanged(false);
return true; return true;
} else { } else {
@ -251,88 +286,61 @@ public class Toolbar extends LinearLayout implements View.OnClickListener, LoadV
} }
} }
private void setNavigationItemInternal(boolean animate, boolean pushing, NavigationItem toItem) { private void setNavigationItemInternal(boolean animate, final boolean pushing, NavigationItem newItem) {
final NavigationItem fromItem = navigationItem; attachNavigationItem(newItem);
boolean same = toItem == navigationItem;
if (!toItem.search) {
AndroidUtils.hideKeyboard(navigationItemContainer);
}
if (fromItem != null) {
fromItem.toolbar = null;
}
toItem.toolbar = this;
if (!animate) {
if (fromItem != null) {
removeNavigationItem(fromItem);
}
setArrowMenuProgress(toItem.hasBack ? 1f : 0f);
}
toItem.view = createNavigationItemView(toItem); if (fromItem == toItem) {
// Search toggled
// use the LoadView animation when from a search
if (same) {
navigationItemContainer.setView(toItem.view, animate); navigationItemContainer.setView(toItem.view, animate);
animateArrow(toItem.hasBack || toItem.search);
} else { } else {
navigationItemContainer.addView(toItem.view, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); navigationItemContainer.addView(toItem.view, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
final int duration = 300; if (animate) {
final int offset = dp(16); toItem.view.setAlpha(0f);
final int delay = pushing ? 0 : 100;
if (animate) {
animateArrow(toItem.hasBack || toItem.search, toItem.search ? 0 : delay);
}
// Use the LoadView animation when from a search
if (animate && !same) {
toItem.view.setAlpha(0f);
List<Animator> animations = new ArrayList<>(5);
Animator toYAnimation = ObjectAnimator.ofFloat(toItem.view, View.TRANSLATION_Y, pushing ? offset : -offset, 0f); ValueAnimator animator = ObjectAnimator.ofFloat(0f, 1f);
toYAnimation.setDuration(duration); animator.setDuration(300);
toYAnimation.setInterpolator(new DecelerateInterpolator(2f)); animator.setInterpolator(new DecelerateInterpolator(2f));
toYAnimation.addListener(new AnimatorListenerAdapter() { animator.addListener(new AnimatorListenerAdapter() {
@Override @Override
public void onAnimationEnd(Animator animation) { public void onAnimationEnd(Animator animation) {
if (fromItem != null) { finishTransition(true);
removeNavigationItem(fromItem);
} }
});
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
transitionProgress((float) animation.getAnimatedValue(), pushing);
}
});
if (!pushing) {
animator.setStartDelay(100);
} }
}); animator.start();
animations.add(toYAnimation); } else {
arrowMenuDrawable.setProgress(toItem.hasBack || toItem.search ? 1f : 0f);
finishTransition(true);
}
}
}
Animator toAlphaAnimation = ObjectAnimator.ofFloat(toItem.view, View.ALPHA, 0f, 1f); private void attachNavigationItem(NavigationItem newItem) {
toAlphaAnimation.setDuration(duration); fromItem = toItem;
toAlphaAnimation.setInterpolator(new DecelerateInterpolator(2f)); toItem = newItem;
animations.add(toAlphaAnimation);
if (fromItem != null) { if (!toItem.search) {
Animator fromYAnimation = ObjectAnimator.ofFloat(fromItem.view, View.TRANSLATION_Y, 0f, pushing ? -offset : offset); AndroidUtils.hideKeyboard(navigationItemContainer);
fromYAnimation.setDuration(duration); }
fromYAnimation.setInterpolator(new DecelerateInterpolator(2f));
animations.add(fromYAnimation);
Animator fromAlphaAnimation = ObjectAnimator.ofFloat(fromItem.view, View.ALPHA, 1f, 0f);
fromAlphaAnimation.setDuration(duration);
fromAlphaAnimation.setInterpolator(new DecelerateInterpolator(2f));
animations.add(fromAlphaAnimation);
}
AnimatorSet set = new AnimatorSet(); if (fromItem != null) {
set.setStartDelay(delay); fromItem.toolbar = null;
set.playTogether(animations);
set.start();
} }
navigationItem = toItem; toItem.toolbar = this;
toItem.view = createNavigationItemView(toItem);
} }
private void removeNavigationItem(NavigationItem item) { private void removeNavigationItem(NavigationItem item) {
@ -428,7 +436,7 @@ public class Toolbar extends LinearLayout implements View.OnClickListener, LoadV
} }
} }
private void animateArrow(boolean toArrow, long delay) { private void animateArrow(boolean toArrow) {
float to = toArrow ? 1f : 0f; float to = toArrow ? 1f : 0f;
if (to != arrowMenuDrawable.getProgress()) { if (to != arrowMenuDrawable.getProgress()) {
ValueAnimator arrowAnimation = ValueAnimator.ofFloat(arrowMenuDrawable.getProgress(), to); ValueAnimator arrowAnimation = ValueAnimator.ofFloat(arrowMenuDrawable.getProgress(), to);
@ -440,7 +448,6 @@ public class Toolbar extends LinearLayout implements View.OnClickListener, LoadV
setArrowMenuProgress((float) animation.getAnimatedValue()); setArrowMenuProgress((float) animation.getAnimatedValue());
} }
}); });
arrowAnimation.setStartDelay(delay);
arrowAnimation.start(); arrowAnimation.start();
} }
} }

@ -21,12 +21,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<FrameLayout <org.floens.chan.ui.view.TouchBlockingFrameLayout
android:id="@+id/container" android:id="@+id/container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />
<LinearLayout <org.floens.chan.ui.view.TouchBlockingLinearLayout
android:id="@+id/drawer" android:id="@+id/drawer"
android:layout_width="200dp" android:layout_width="200dp"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -87,6 +87,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</LinearLayout> </LinearLayout>
</LinearLayout> </org.floens.chan.ui.view.TouchBlockingLinearLayout>
</android.support.v4.widget.DrawerLayout> </android.support.v4.widget.DrawerLayout>

@ -28,7 +28,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:background="#87000000" android:background="#87000000"
tools:ignore="UnusedAttribute" /> tools:ignore="UnusedAttribute" />
<FrameLayout <org.floens.chan.controller.ui.NavigationControllerContainerLayout
android:id="@+id/container" android:id="@+id/container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />

@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?backcolor"> android:background="?backcolor">
<FrameLayout <org.floens.chan.controller.ui.NavigationControllerContainerLayout
android:id="@+id/container" android:id="@+id/container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />

@ -15,17 +15,17 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <org.floens.chan.ui.layout.PopupControllerContainer xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/top_view" android:id="@+id/top_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="#88000000"> android:background="#88000000">
<org.floens.chan.ui.layout.PopupControllerContainer <FrameLayout
android:id="@+id/container" android:id="@+id/container"
android:layout_width="600dp" android:layout_width="600dp"
android:layout_height="wrap_content" android:layout_height="600dp"
android:layout_gravity="center" android:layout_gravity="center"
android:background="@drawable/dialog_full_light" /> android:background="@drawable/dialog_full_light" />
</FrameLayout> </org.floens.chan.ui.layout.PopupControllerContainer>

Loading…
Cancel
Save