Skip to content

Commit 9e99874

Browse files
authored
Color animations for top-bar (Android) (#7969)
1 parent 375636d commit 9e99874

37 files changed

+821
-194
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.reactnativenavigation
2+
3+
import androidx.annotation.VisibleForTesting
4+
5+
enum class RNNToggles {
6+
TOP_BAR_COLOR_ANIMATION,
7+
}
8+
9+
private val ToggleDefaults = mapOf(
10+
RNNToggles.TOP_BAR_COLOR_ANIMATION to false
11+
)
12+
13+
object RNNFeatureToggles {
14+
private var init = false
15+
private var toggles = mutableMapOf<RNNToggles, Boolean>()
16+
17+
@JvmStatic
18+
fun init() {
19+
assertNotInitialized()
20+
21+
init = true
22+
toggles = ToggleDefaults.toMutableMap()
23+
}
24+
25+
@JvmStatic
26+
fun init(overrides: Map<RNNToggles, Boolean>) {
27+
init()
28+
this.toggles.putAll(overrides)
29+
}
30+
31+
@JvmStatic
32+
fun init(vararg overrides: Pair<RNNToggles, Boolean>) {
33+
init(mapOf(*overrides))
34+
}
35+
36+
@JvmStatic
37+
fun isEnabled(toggleName: RNNToggles): Boolean {
38+
assertInitialized()
39+
return toggles.getOrElse(toggleName) { false }
40+
}
41+
42+
@VisibleForTesting
43+
@JvmStatic
44+
fun clear() {
45+
init = false
46+
toggles.clear()
47+
}
48+
49+
private fun assertNotInitialized() {
50+
if (init) {
51+
throw IllegalStateException("FeatureToggles already initialized")
52+
}
53+
}
54+
55+
private fun assertInitialized() {
56+
if (!init) {
57+
throw IllegalStateException("FeatureToggles not initialized")
58+
}
59+
}
60+
}

lib/android/app/src/main/java/com/reactnativenavigation/NavigationApplication.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.reactnativenavigation.react.ReactGateway;
1010
import com.reactnativenavigation.viewcontrollers.externalcomponent.ExternalComponentCreator;
1111

12+
import java.util.Collections;
1213
import java.io.IOException;
1314
import java.util.HashMap;
1415
import java.util.Map;
@@ -17,14 +18,23 @@
1718

1819
public abstract class NavigationApplication extends Application implements ReactApplication {
1920

20-
private ReactGateway reactGateway;
2121
public static NavigationApplication instance;
22-
final Map<String, ExternalComponentCreator> externalComponents = new HashMap<>();
22+
23+
private final Map<String, ExternalComponentCreator> externalComponents = new HashMap<>();
24+
private ReactGateway reactGateway;
25+
26+
public NavigationApplication() {
27+
this(Collections.emptyMap());
28+
}
29+
30+
public NavigationApplication(Map<RNNToggles, Boolean> featureToggleOverrides) {
31+
instance = this;
32+
RNNFeatureToggles.init(featureToggleOverrides);
33+
}
2334

2435
@Override
2536
public void onCreate() {
2637
super.onCreate();
27-
instance = this;
2838
try {
2939
SoLoader.init(this, OpenSourceMergedSoMapping.INSTANCE);
3040
} catch (IOException e) {

lib/android/app/src/main/java/com/reactnativenavigation/options/ValueAnimationOptions.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ class ValueAnimationOptions {
5050
to += this.to[animationValueAccessor!!(view)]
5151
}
5252
val animator = ObjectAnimator.ofFloat(view,
53-
animProp,
54-
from,
55-
to
53+
animProp,
54+
from,
55+
to
5656
)
5757
animator.interpolator = interpolator
5858
if (duration.hasValue()) animator.duration = duration.get().toLong()
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.reactnativenavigation.utils
2+
3+
import android.animation.Animator
4+
5+
open class StubAnimationListener: Animator.AnimatorListener {
6+
override fun onAnimationStart(animation: Animator) {}
7+
override fun onAnimationEnd(animation: Animator) {}
8+
override fun onAnimationCancel(animation: Animator) {}
9+
override fun onAnimationRepeat(animation: Animator) {}
10+
11+
companion object {
12+
@JvmStatic
13+
fun onAnimatorEnd(onEnd: (animation: Animator) -> Unit) = object: StubAnimationListener() {
14+
override fun onAnimationEnd(animation: Animator) {
15+
onEnd(animation)
16+
}
17+
}
18+
}
19+
}

lib/android/app/src/main/java/com/reactnativenavigation/utils/SystemUiUtils.kt

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -121,19 +121,25 @@ object SystemUiUtils {
121121
@ColorInt color: Int,
122122
translucent: Boolean
123123
) {
124-
val opaqueColor = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
125-
Color.BLACK
126-
}else{
127-
val colorAlpha = Color.alpha(color)
128-
val alpha = if (translucent && colorAlpha == 255) STATUS_BAR_HEIGHT_TRANSLUCENCY else colorAlpha/255.0f
129-
val red: Int = Color.red(color)
130-
val green: Int = Color.green(color)
131-
val blue: Int = Color.blue(color)
132-
Color.argb(ceil(alpha * 255).toInt(), red, green, blue)
133-
}
124+
val opaqueColor =
125+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
126+
Color.BLACK
127+
} else {
128+
val colorAlpha = Color.alpha(color)
129+
val alpha = if (translucent && colorAlpha == 255) STATUS_BAR_HEIGHT_TRANSLUCENCY else colorAlpha/255.0f
130+
val red: Int = Color.red(color)
131+
val green: Int = Color.green(color)
132+
val blue: Int = Color.blue(color)
133+
Color.argb(ceil(alpha * 255).toInt(), red, green, blue)
134+
}
134135
window?.statusBarColor = opaqueColor
135136
}
136137

138+
@JvmStatic
139+
fun getStatusBarColor(window: Window?): Int? {
140+
return window?.statusBarColor
141+
}
142+
137143
@JvmStatic
138144
fun hideStatusBar(window: Window?, view: View) {
139145
window?.let {

lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsAnimator.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package com.reactnativenavigation.viewcontrollers.bottomtabs
22

3-
import com.reactnativenavigation.views.animations.BaseViewAnimator
3+
import com.reactnativenavigation.views.animations.BaseViewAppearanceAnimator
44
import com.reactnativenavigation.views.bottomtabs.BottomTabs
55

6-
class BottomTabsAnimator(view: BottomTabs? = null) : BaseViewAnimator<BottomTabs>(HideDirection.Down, view) {
6+
class BottomTabsAnimator(view: BottomTabs? = null) : BaseViewAppearanceAnimator<BottomTabs>(HideDirection.Down, view) {
77
override fun onShowAnimationEnd() {
88
view.restoreBottomNavigation(false)
99
}

lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/child/ChildController.java

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,23 @@
22

33
import android.app.Activity;
44
import android.content.res.Configuration;
5-
import android.os.Build;
6-
import android.util.Log;
75
import android.view.View;
86
import android.view.ViewGroup;
9-
import android.view.WindowInsets;
7+
8+
import androidx.annotation.CallSuper;
9+
import androidx.core.view.ViewCompat;
10+
import androidx.core.view.WindowInsetsCompat;
1011

1112
import com.reactnativenavigation.options.Options;
12-
import com.reactnativenavigation.utils.LogKt;
13-
import com.reactnativenavigation.viewcontrollers.parent.ParentController;
14-
import com.reactnativenavigation.viewcontrollers.viewcontroller.Presenter;
15-
import com.reactnativenavigation.viewcontrollers.viewcontroller.NoOpYellowBoxDelegate;
1613
import com.reactnativenavigation.viewcontrollers.navigator.Navigator;
14+
import com.reactnativenavigation.viewcontrollers.viewcontroller.NoOpYellowBoxDelegate;
15+
import com.reactnativenavigation.viewcontrollers.viewcontroller.Presenter;
1716
import com.reactnativenavigation.viewcontrollers.viewcontroller.ViewController;
1817
import com.reactnativenavigation.viewcontrollers.viewcontroller.overlay.ViewControllerOverlay;
1918
import com.reactnativenavigation.views.component.Component;
2019

21-
import androidx.annotation.CallSuper;
22-
import androidx.core.graphics.Insets;
23-
import androidx.core.view.ViewCompat;
24-
import androidx.core.view.WindowCompat;
25-
import androidx.core.view.WindowInsetsCompat;
26-
2720
public abstract class ChildController<T extends ViewGroup> extends ViewController<T> {
28-
private final Presenter presenter;
21+
protected final Presenter presenter;
2922
private final ChildControllersRegistry childRegistry;
3023

3124
public ChildControllersRegistry getChildRegistry() {

lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/component/ComponentViewController.java

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,29 @@
11
package com.reactnativenavigation.viewcontrollers.component;
22

3+
import static com.reactnativenavigation.utils.ObjectUtils.perform;
4+
5+
import android.animation.Animator;
36
import android.app.Activity;
47
import android.content.res.Configuration;
58
import android.view.View;
69

7-
import com.reactnativenavigation.utils.LogKt;
8-
import com.reactnativenavigation.viewcontrollers.viewcontroller.ScrollEventListener;
9-
import com.reactnativenavigation.options.Options;
10-
import com.reactnativenavigation.viewcontrollers.viewcontroller.Presenter;
11-
import com.reactnativenavigation.utils.SystemUiUtils;
12-
import com.reactnativenavigation.viewcontrollers.viewcontroller.ReactViewCreator;
13-
import com.reactnativenavigation.viewcontrollers.child.ChildController;
14-
import com.reactnativenavigation.viewcontrollers.child.ChildControllersRegistry;
15-
import com.reactnativenavigation.viewcontrollers.viewcontroller.ViewController;
16-
import com.reactnativenavigation.views.component.ComponentLayout;
17-
1810
import androidx.annotation.NonNull;
11+
import androidx.annotation.Nullable;
1912
import androidx.core.graphics.Insets;
2013
import androidx.core.view.ViewCompat;
2114
import androidx.core.view.WindowInsetsCompat;
2215

23-
import static com.reactnativenavigation.utils.ObjectUtils.perform;
16+
import com.reactnativenavigation.options.Options;
17+
import com.reactnativenavigation.utils.SystemUiUtils;
18+
import com.reactnativenavigation.viewcontrollers.child.ChildController;
19+
import com.reactnativenavigation.viewcontrollers.child.ChildControllersRegistry;
20+
import com.reactnativenavigation.viewcontrollers.stack.statusbar.StatusBarController;
21+
import com.reactnativenavigation.viewcontrollers.viewcontroller.Presenter;
22+
import com.reactnativenavigation.viewcontrollers.viewcontroller.ReactViewCreator;
23+
import com.reactnativenavigation.viewcontrollers.viewcontroller.ScrollEventListener;
24+
import com.reactnativenavigation.views.component.ComponentLayout;
2425

25-
public class ComponentViewController extends ChildController<ComponentLayout> {
26+
public class ComponentViewController extends ChildController<ComponentLayout> implements StatusBarController {
2627
private final String componentName;
2728
private final ComponentPresenter presenter;
2829
private final ReactViewCreator viewCreator;
@@ -64,6 +65,29 @@ public void setDefaultOptions(Options defaultOptions) {
6465
presenter.setDefaultOptions(defaultOptions);
6566
}
6667

68+
@Override
69+
public StatusBarController getStatusBarController() {
70+
return this;
71+
}
72+
73+
@Nullable
74+
@Override
75+
public Animator getStatusBarPushAnimation(@NonNull Options appearingOptions) {
76+
if (super.presenter != null) {
77+
return super.presenter.getStatusBarPushAnimation(appearingOptions);
78+
}
79+
return null;
80+
}
81+
82+
@Nullable
83+
@Override
84+
public Animator getStatusBarPopAnimation(@NonNull Options appearingOptions, @NonNull Options disappearingOptions) {
85+
if (super.presenter != null) {
86+
return super.presenter.getStatusBarPopAnimation(appearingOptions, disappearingOptions);
87+
}
88+
return null;
89+
}
90+
6791
@Override
6892
public ScrollEventListener getScrollEventListener() {
6993
return perform(view, null, ComponentLayout::getScrollEventListener);

lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackController.java

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -159,16 +159,19 @@ public void push(ViewController<?> child, CommandListener listener) {
159159
listener.onError("A stack can't contain two children with the same id: " + child.getId());
160160
return;
161161
}
162-
final ViewController<?> toRemove = stack.peek();
163-
if (size() > 0) backButtonHelper.addToPushedChild(child);
164-
child.setParentController(this);
165-
stack.push(child.getId(), child);
166-
if (!isViewCreated()) return;
162+
163+
final ViewController<?> toRemove = pushChildToStack(child);
164+
165+
if (!isViewCreated()) {
166+
return;
167+
}
168+
167169
Options resolvedOptions = resolveCurrentOptions(presenter.getDefaultOptions());
168-
addChildToStack(child, resolvedOptions);
170+
updateChildLayout(child, resolvedOptions);
171+
169172
if (toRemove != null) {
170-
StackAnimationOptions animation = resolvedOptions.animations.push;
171-
if (animation.enabled.isTrueOrUndefined()) {
173+
StackAnimationOptions animOptions = resolvedOptions.animations.push;
174+
if (animOptions.enabled.isTrueOrUndefined()) {
172175
animator.push(
173176
child,
174177
toRemove,
@@ -195,7 +198,17 @@ private void onPushAnimationComplete(ViewController<?> toAdd, ViewController<?>
195198
listener.onSuccess(toAdd.getId());
196199
}
197200

198-
private void addChildToStack(ViewController<?> child, Options resolvedOptions) {
201+
private ViewController<?> pushChildToStack(ViewController<?> child) {
202+
final ViewController<?> toRemove = stack.peek();
203+
204+
if (size() > 0) backButtonHelper.addToPushedChild(child);
205+
206+
child.setParentController(this);
207+
stack.push(child.getId(), child);
208+
return toRemove;
209+
}
210+
211+
private void updateChildLayout(ViewController<?> child, Options resolvedOptions) {
199212
child.setWaitForRender(resolvedOptions.animations.push.waitForRender);
200213
if (size() == 1) presenter.applyInitialChildLayoutOptions(resolvedOptions);
201214
getView().addView(child.getView(), getView().getChildCount() - 1, matchParentWithBehaviour(new StackBehaviour(this)));
@@ -222,7 +235,7 @@ public void setRoot(@Size(min = 1) List<ViewController<?>> children, CommandList
222235
child.setParentController(this);
223236
stack.push(child.getId(), child);
224237
Options resolvedOptions = resolveCurrentOptions(presenter.getDefaultOptions());
225-
addChildToStack(child, resolvedOptions);
238+
updateChildLayout(child, resolvedOptions);
226239

227240
CommandListener listenerAdapter = new CommandListenerAdapter() {
228241
@Override
@@ -316,7 +329,7 @@ public void pop(Options mergeOptions, CommandListener listener) {
316329
appearing,
317330
disappearing,
318331
disappearingOptions,
319-
presenter.getAdditionalPopAnimations(appearingOptions, disappearingOptions),
332+
presenter.getAdditionalPopAnimations(appearingOptions, disappearingOptions, appearing),
320333
() -> finishPopping(appearing, disappearing, listener)
321334
);
322335
} else {

0 commit comments

Comments
 (0)