Initial commit.

This commit is contained in:
Krzysztof Magiera
2018-08-03 13:34:09 +02:00
commit 31281d6b68
92 changed files with 16706 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
package com.swmansion.rnscreens;
import android.arch.lifecycle.Lifecycle;
import android.arch.lifecycle.LifecycleObserver;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.View;
import android.view.ViewParent;
import com.facebook.react.modules.core.ChoreographerCompat;
import com.facebook.react.modules.core.ReactChoreographer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
public class LifecycleHelper {
public static @Nullable Fragment findNearestScreenFragmentAncestor(View view) {
ViewParent parent = view.getParent();
while (parent != null && !(parent instanceof Screen)) {
parent = parent.getParent();
}
if (parent != null) {
return ((Screen) parent).getFragment();
}
return null;
}
private Map<View, Lifecycle> mViewToLifecycleMap = new HashMap<>();
private View.OnLayoutChangeListener mRegisterOnLayoutChange = new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View view, int i, int i1, int i2, int i3, int i4, int i5, int i6, int i7) {
registerViewWithLifecycleOwner(view);
view.removeOnLayoutChangeListener(this);
}
};
private void registerViewWithLifecycleOwner(View view) {
Fragment parent = findNearestScreenFragmentAncestor(view);
if (parent != null && view instanceof LifecycleObserver) {
Lifecycle lifecycle = parent.getLifecycle();
lifecycle.addObserver((LifecycleObserver) view);
mViewToLifecycleMap.put(view, lifecycle);
}
}
public <T extends View & LifecycleObserver> void register(T view) {
// we need to wait until view is mounted in the hierarchy as this method is called only at the
// moment of the view creation. In order to register lifecycle observer we need to find ancestor
// of type Screen and this can only happen when the view is properly attached. We rely on Android's
// onLayout callback being triggered when the view gets added to the hierarchy and only then we
// attempt to locate lifecycle owner ancestor.
view.addOnLayoutChangeListener(mRegisterOnLayoutChange);
}
public <T extends View & LifecycleObserver> void unregister(T view) {
Lifecycle lifecycle = mViewToLifecycleMap.get(view);
if (lifecycle != null) {
lifecycle.removeObserver(view);
}
}
}

View File

@@ -0,0 +1,26 @@
package com.swmansion.rnscreens;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class RNScreenPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(
new ScreenContainerViewManager(),
new ScreenStackViewManager(),
new ScreenViewManager()
);
}
}

View File

@@ -0,0 +1,72 @@
package com.swmansion.rnscreens;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class Screen extends ViewGroup {
public static class ScreenFragment extends Fragment {
private Screen mScreenView;
public ScreenFragment() {
throw new IllegalStateException("Screen fragments should never be restored");
}
@SuppressLint("ValidFragment")
public ScreenFragment(Screen screenView) {
super();
mScreenView = screenView;
}
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return mScreenView;
}
}
private final Fragment mFragment;
private @Nullable ScreenContainer mContainer;
private boolean mActive;
public Screen(Context context) {
super(context);
mFragment = new ScreenFragment(this);
}
@Override
protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
// no-op
}
protected void setContainer(@Nullable ScreenContainer mContainer) {
this.mContainer = mContainer;
}
protected @Nullable ScreenContainer getContainer() {
return mContainer;
}
protected Fragment getFragment() {
return mFragment;
}
public void setActive(boolean active) {
mActive = active;
if (mContainer != null) {
mContainer.notifyChildUpdate();
}
}
public boolean isActive() {
return mActive;
}
}

View File

@@ -0,0 +1,161 @@
package com.swmansion.rnscreens;
import android.app.Activity;
import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.view.ViewGroup;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.core.ChoreographerCompat;
import com.facebook.react.modules.core.ReactChoreographer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class ScreenContainer extends ViewGroup {
private final ArrayList<Screen> mScreens = new ArrayList<>();
private final Set<Screen> mActiveScreens = new HashSet<>();
private final FragmentManager mFragmentManager;
private @Nullable FragmentTransaction mCurrentTransaction;
private boolean mNeedUpdate;
private ChoreographerCompat.FrameCallback mFrameCallback = new ChoreographerCompat.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
updateIfNeeded();
}
};
public ScreenContainer(Context context) {
super(context);
Activity activity = ((ReactContext) context).getCurrentActivity();
if (activity instanceof FragmentActivity) {
mFragmentManager = ((FragmentActivity) activity).getSupportFragmentManager();
} else {
throw new IllegalStateException(
"In order to use RNScreen components your app's activity need to extend ReactFragmentActivity or ReactCompatActivity");
}
}
@Override
protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
// no-op
}
protected void markUpdated() {
if (!mNeedUpdate) {
mNeedUpdate = true;
// enqueue callback of NATIVE_ANIMATED_MODULE type as all view operations are executed in
// DISPATCH_UI type and we want the callback to be called right after in the same frame.
ReactChoreographer.getInstance().postFrameCallback(
ReactChoreographer.CallbackType.NATIVE_ANIMATED_MODULE,
mFrameCallback);
}
}
protected void notifyChildUpdate() {
markUpdated();
}
protected void addScreen(Screen screen, int index) {
mScreens.add(index, screen);
screen.setContainer(this);
markUpdated();
}
protected void removeScreenAt(int index) {
mScreens.get(index).setContainer(null);
mScreens.remove(index);
markUpdated();
}
protected int getScreenCount() {
return mScreens.size();
}
protected Screen getScreenAt(int index) {
return mScreens.get(index);
}
private FragmentTransaction getOrCreateTransaction() {
if (mCurrentTransaction == null) {
mCurrentTransaction = mFragmentManager.beginTransaction();
mCurrentTransaction.setReorderingAllowed(true);
}
return mCurrentTransaction;
}
private void tryCommitTransaction() {
if (mCurrentTransaction != null) {
mCurrentTransaction.commitNow();
mCurrentTransaction = null;
}
}
private void attachScreen(Screen screen) {
getOrCreateTransaction().add(getId(), screen.getFragment());
mActiveScreens.add(screen);
}
private void moveToFront(Screen screen) {
FragmentTransaction transaction = getOrCreateTransaction();
Fragment fragment = screen.getFragment();
transaction.remove(fragment);
transaction.add(getId(), fragment);
}
private void detachScreen(Screen screen) {
getOrCreateTransaction().remove(screen.getFragment());
mActiveScreens.remove(screen);
}
protected boolean isScreenActive(Screen screen, List<Screen> allScreens) {
return screen.isActive();
}
private void updateIfNeeded() {
if (!mNeedUpdate || mFragmentManager.isDestroyed()) {
return;
}
mNeedUpdate = false;
// detach screens that are no longer active
Set<Screen> orphaned = new HashSet<>(mActiveScreens);
for (int i = 0, size = mScreens.size(); i < size; i++) {
Screen screen = mScreens.get(i);
boolean isActive = isScreenActive(screen, mScreens);
if (!isActive && mActiveScreens.contains(screen)) {
detachScreen(screen);
}
orphaned.remove(screen);
}
if (!orphaned.isEmpty()) {
Object[] orphanedAry = orphaned.toArray();
for (int i = 0; i < orphanedAry.length; i++) {
detachScreen((Screen) orphanedAry[i]);
}
}
// attach newly activated screens
boolean addedBefore = false;
for (int i = 0, size = mScreens.size(); i < size; i++) {
Screen screen = mScreens.get(i);
boolean isActive = isScreenActive(screen, mScreens);
if (isActive && !mActiveScreens.contains(screen)) {
addedBefore = true;
attachScreen(screen);
} else if (isActive && addedBefore) {
moveToFront(screen);
}
}
tryCommitTransaction();
}
}

View File

@@ -0,0 +1,46 @@
package com.swmansion.rnscreens;
import android.view.View;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
@ReactModule(name = ScreenContainerViewManager.REACT_CLASS)
public class ScreenContainerViewManager extends ViewGroupManager<ScreenContainer> {
protected static final String REACT_CLASS = "RNSScreenContainer";
@Override
public String getName() {
return REACT_CLASS;
}
@Override
protected ScreenContainer createViewInstance(ThemedReactContext reactContext) {
return new ScreenContainer(reactContext);
}
@Override
public void addView(ScreenContainer parent, View child, int index) {
if (!(child instanceof Screen)) {
throw new IllegalArgumentException("Attempt attach child that is not of type RNScreen");
}
parent.addScreen((Screen) child, index);
}
@Override
public void removeViewAt(ScreenContainer parent, int index) {
parent.removeScreenAt(index);
}
@Override
public int getChildCount(ScreenContainer parent) {
return parent.getScreenCount();
}
@Override
public View getChildAt(ScreenContainer parent, int index) {
return parent.getScreenAt(index);
}
}

View File

@@ -0,0 +1,35 @@
package com.swmansion.rnscreens;
import android.content.Context;
import java.util.List;
public class ScreenStack extends ScreenContainer {
private float mTransitioning;
public ScreenStack(Context context) {
super(context);
}
public void setTransitioning(float transitioning) {
if (transitioning != mTransitioning) {
mTransitioning = transitioning;
markUpdated();
}
}
@Override
protected boolean isScreenActive(Screen screen, List<Screen> allScreens) {
int size = allScreens.size();
if (size < 1) {
return false;
}
Screen lastScreen = allScreens.get(size - 1);
if (mTransitioning != 0 && size > 1) {
Screen secondToLast = allScreens.get(size - 2);
return screen == lastScreen || screen == secondToLast;
}
return screen == lastScreen;
}
}

View File

@@ -0,0 +1,24 @@
package com.swmansion.rnscreens;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.annotations.ReactProp;
public class ScreenStackViewManager extends ScreenContainerViewManager {
protected static final String REACT_CLASS = "RNSScreenStack";
@Override
public String getName() {
return REACT_CLASS;
}
@Override
protected ScreenContainer createViewInstance(ThemedReactContext reactContext) {
return new ScreenStack(reactContext);
}
@ReactProp(name = "transitioning", defaultFloat = 0)
public void setTransitioning(ScreenStack view, float transitioning) {
view.setTransitioning(transitioning);
}
}

View File

@@ -0,0 +1,27 @@
package com.swmansion.rnscreens;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp;
@ReactModule(name = ScreenViewManager.REACT_CLASS)
public class ScreenViewManager extends ViewGroupManager<Screen> {
protected static final String REACT_CLASS = "RNSScreen";
@Override
public String getName() {
return REACT_CLASS;
}
@Override
protected Screen createViewInstance(ThemedReactContext reactContext) {
return new Screen(reactContext);
}
@ReactProp(name = "active", defaultBoolean = false)
public void setActive(Screen view, boolean active) {
view.setActive(active);
}
}