Преглед на файлове

Add Modal implementation

Guy Carmeli преди 8 години
родител
ревизия
c68ac978e8

+ 54
- 0
android/app/src/main/java/com/reactnativenavigation/controllers/ModalController.java Целия файл

@@ -0,0 +1,54 @@
1
+package com.reactnativenavigation.controllers;
2
+
3
+import android.support.annotation.Nullable;
4
+
5
+import com.reactnativenavigation.modal.RnnModal;
6
+import com.reactnativenavigation.utils.RefUtils;
7
+
8
+import java.lang.ref.WeakReference;
9
+import java.util.HashMap;
10
+import java.util.Map;
11
+
12
+/**
13
+ * Created by guyc on 06/05/16.
14
+ */
15
+public class ModalController {
16
+    private static ModalController sInstance;
17
+
18
+    private final Map<String, WeakReference<RnnModal>> mModals;
19
+
20
+    private ModalController() {
21
+        mModals = new HashMap<>();
22
+    }
23
+
24
+    public static synchronized ModalController getInstance() {
25
+        if (sInstance == null) {
26
+            sInstance = new ModalController();
27
+        }
28
+
29
+        return sInstance;
30
+    }
31
+
32
+    public void add(RnnModal modal, String navigatorId) {
33
+        mModals.put(navigatorId, new WeakReference<>(modal));
34
+    }
35
+
36
+    public boolean isModalDisplayed(String navigatorId) {
37
+        return mModals.size() != 0 && mModals.containsKey(navigatorId);
38
+    }
39
+
40
+    @Nullable
41
+    public RnnModal get(String navigatorId) {
42
+        if (mModals.containsKey(navigatorId)) {
43
+            return RefUtils.get(mModals.get(navigatorId));
44
+        }
45
+
46
+        return null;
47
+    }
48
+
49
+    public void remove(String navigatorId) {
50
+        if (mModals.containsKey(navigatorId)) {
51
+            mModals.remove(navigatorId);
52
+        }
53
+    }
54
+}

+ 74
- 0
android/app/src/main/java/com/reactnativenavigation/modal/RnnModal.java Целия файл

@@ -0,0 +1,74 @@
1
+package com.reactnativenavigation.modal;
2
+
3
+import android.annotation.SuppressLint;
4
+import android.app.Dialog;
5
+import android.content.Context;
6
+import android.content.DialogInterface;
7
+import android.view.LayoutInflater;
8
+import android.view.View;
9
+import android.view.Window;
10
+import android.view.animation.Animation;
11
+import android.view.animation.AnimationUtils;
12
+
13
+import com.reactnativenavigation.R;
14
+import com.reactnativenavigation.activities.BaseReactActivity;
15
+import com.reactnativenavigation.controllers.ModalController;
16
+import com.reactnativenavigation.core.objects.Screen;
17
+import com.reactnativenavigation.views.RctView;
18
+import com.reactnativenavigation.views.ScreenStack;
19
+
20
+/**
21
+ * Created by guyc on 02/05/16.
22
+ */
23
+public class RnnModal extends Dialog implements DialogInterface.OnDismissListener {
24
+
25
+    private ScreenStack mScreenStack;
26
+    private Screen mScreen;
27
+    private View mContentView;
28
+
29
+    public RnnModal(BaseReactActivity context, Screen screen) {
30
+        super(context, R.style.Modal);
31
+        mScreen = screen;
32
+        ModalController.getInstance().add(this, screen.navigatorId);
33
+        init(context);
34
+    }
35
+
36
+    @SuppressLint("InflateParams")
37
+    private void init(final Context context) {
38
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
39
+        mContentView = LayoutInflater.from(context).inflate(R.layout.modal_layout, null, false);
40
+        mScreenStack = (ScreenStack) mContentView.findViewById(R.id.screenStack);
41
+        setContentView(mContentView);
42
+        mScreenStack.push(mScreen, new RctView.OnDisplayedListener() {
43
+            @Override
44
+            public void onDisplayed() {
45
+                Animation animation = AnimationUtils.loadAnimation(context, R.anim.slide_up);
46
+                mContentView.setAnimation(animation);
47
+                mContentView.animate();
48
+            }
49
+        });
50
+    }
51
+
52
+    public void push(Screen screen) {
53
+        mScreenStack.push(screen);
54
+    }
55
+
56
+    public Screen pop() {
57
+        return mScreenStack.pop();
58
+    }
59
+
60
+    @Override
61
+    public void onBackPressed() {
62
+        if (mScreenStack.getStackSize() > 1) {
63
+            mScreenStack.pop();
64
+        } else {
65
+            ModalController.getInstance().remove(mScreen.navigatorId);
66
+            super.onBackPressed();
67
+        }
68
+    }
69
+
70
+    @Override
71
+    public void onDismiss(DialogInterface dialog) {
72
+        ModalController.getInstance().remove(mScreen.navigatorId);
73
+    }
74
+}

+ 59
- 7
android/app/src/main/java/com/reactnativenavigation/modules/RctActivityModule.java Целия файл

@@ -12,7 +12,9 @@ import com.facebook.react.bridge.ReadableMap;
12 12
 import com.reactnativenavigation.activities.BaseReactActivity;
13 13
 import com.reactnativenavigation.activities.SingleScreenActivity;
14 14
 import com.reactnativenavigation.activities.TabActivity;
15
+import com.reactnativenavigation.controllers.ModalController;
15 16
 import com.reactnativenavigation.core.objects.Screen;
17
+import com.reactnativenavigation.modal.RnnModal;
16 18
 import com.reactnativenavigation.utils.ContextProvider;
17 19
 
18 20
 import java.util.ArrayList;
@@ -71,27 +73,77 @@ public class RctActivityModule extends ReactContextBaseJavaModule {
71 73
     }
72 74
 
73 75
     @ReactMethod
74
-    public void navigatorPush(final ReadableMap screen) {
76
+    public void navigatorPush(final ReadableMap skreen) {
77
+        final Screen screen = new Screen(skreen);
75 78
         final BaseReactActivity context = ContextProvider.getActivityContext();
76
-         if (context != null && !context.isFinishing()) {
77
-             context.runOnUiThread(new Runnable() {
79
+        if (context == null || context.isFinishing()) {
80
+            return;
81
+        }
82
+
83
+        // First, check is the screen should be displayed in a Modal
84
+        ModalController modalController = ModalController.getInstance();
85
+        if (modalController.isModalDisplayed(screen.navigatorId)) {
86
+            final RnnModal modal = modalController.get(screen.navigatorId);
87
+            if (modal != null) {
88
+                context.runOnUiThread(new Runnable() {
89
+                    @Override
90
+                    public void run() {
91
+                        modal.push(screen);
92
+                    }
93
+                });
94
+            }
95
+            return;
96
+        }
97
+
98
+        // No Modal is displayed, Push to activity
99
+         context.runOnUiThread(new Runnable() {
78 100
                  @Override
79 101
                  public void run() {
80
-                     context.push(new Screen(screen));
102
+                     context.push(screen);
81 103
                  }
82 104
              });
83
-        }
84 105
     }
85 106
 
86 107
     @ReactMethod
87 108
     public void navigatorPop(final ReadableMap navigator) {
88
-        final String navID = navigator.getString("navigatorID");
109
+        final String navigatorId = navigator.getString("navigatorID");
110
+        final BaseReactActivity context = ContextProvider.getActivityContext();
111
+        if (context == null || context.isFinishing()) {
112
+            return;
113
+        }
114
+
115
+        // First, check if the screen should be popped from a Modal
116
+        ModalController modalController = ModalController.getInstance();
117
+        if (modalController.isModalDisplayed(navigatorId)) {
118
+            final RnnModal modal = modalController.get(navigatorId);
119
+            if (modal != null) {
120
+                context.runOnUiThread(new Runnable() {
121
+                    @Override
122
+                    public void run() {
123
+                        modal.pop();
124
+                    }
125
+                });
126
+            }
127
+            return;
128
+        }
129
+
130
+        context.runOnUiThread(new Runnable() {
131
+            @Override
132
+            public void run() {
133
+                context.pop(navigatorId);
134
+            }
135
+        });
136
+
137
+    }
138
+
139
+    @ReactMethod
140
+    public void showModal(final ReadableMap screen) {
89 141
         final BaseReactActivity context = ContextProvider.getActivityContext();
90 142
         if (context != null && !context.isFinishing()) {
91 143
             context.runOnUiThread(new Runnable() {
92 144
                 @Override
93 145
                 public void run() {
94
-                    context.pop(navID);
146
+                    new RnnModal(context, new Screen(screen)).show();
95 147
                 }
96 148
             });
97 149
         }

+ 20
- 0
android/app/src/main/java/com/reactnativenavigation/utils/RefUtils.java Целия файл

@@ -0,0 +1,20 @@
1
+package com.reactnativenavigation.utils;
2
+
3
+import java.lang.ref.WeakReference;
4
+
5
+/**
6
+ * Created by guyc on 06/05/16.
7
+ */
8
+public class RefUtils {
9
+    /**
10
+     * Extract the object within a WeakReference object
11
+     * @param wr the WeakReference to extract
12
+     * @return the object within the WR or null when object not available.
13
+     */
14
+    public static <T> T get(WeakReference<T> wr) {
15
+        if (wr != null) {
16
+            return wr.get();
17
+        }
18
+        return null;
19
+    }
20
+}

+ 26
- 0
android/app/src/main/java/com/reactnativenavigation/views/RctView.java Целия файл

@@ -1,6 +1,7 @@
1 1
 package com.reactnativenavigation.views;
2 2
 
3 3
 import android.os.Bundle;
4
+import android.view.ViewTreeObserver;
4 5
 import android.widget.FrameLayout;
5 6
 
6 7
 import com.facebook.react.ReactInstanceManager;
@@ -15,11 +16,26 @@ public class RctView extends FrameLayout {
15 16
 
16 17
     private ReactRootView mReactRootView;
17 18
 
19
+    /**
20
+     * Interface used to run some code when the {@link ReactRootView} is visible.
21
+     */
22
+    public interface OnDisplayedListener {
23
+        /**
24
+         * This method will be invoked when the {@link ReactRootView} is visible.
25
+         */
26
+        public void onDisplayed();
27
+    }
28
+
18 29
     public ReactRootView getReactRootView() {
19 30
         return mReactRootView;
20 31
     }
21 32
 
22 33
     public RctView(BaseReactActivity ctx, ReactInstanceManager rctInstanceManager, Screen screen) {
34
+        this(ctx, rctInstanceManager, screen, null);
35
+    }
36
+
37
+    public RctView(BaseReactActivity ctx, ReactInstanceManager rctInstanceManager, Screen screen,
38
+                   final OnDisplayedListener onDisplayedListener) {
23 39
         super(ctx);
24 40
         setLayoutParams(new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
25 41
 
@@ -34,6 +50,16 @@ public class RctView extends FrameLayout {
34 50
 
35 51
         mReactRootView.startReactApplication(rctInstanceManager, componentName, passProps);
36 52
 
53
+        if (onDisplayedListener != null) {
54
+            mReactRootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
55
+                @Override
56
+                public void onGlobalLayout() {
57
+                    onDisplayedListener.onDisplayed();
58
+                    mReactRootView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
59
+                }
60
+            });
61
+        }
62
+
37 63
         addView(mReactRootView);
38 64
     }
39 65
 }

+ 9
- 3
android/app/src/main/java/com/reactnativenavigation/views/RnnToolBar.java Целия файл

@@ -60,10 +60,17 @@ public class RnnToolBar extends Toolbar {
60 60
     }
61 61
 
62 62
     @SuppressWarnings({"ConstantConditions"})
63
-    @SuppressLint("PrivateResource")
64 63
     public void showBackButton(Screen screen) {
65 64
         ActionBar actionBar = ContextProvider.getActivityContext().getSupportActionBar();
66 65
 
66
+        Drawable backButton = setupBackButton(screen);
67
+        actionBar.setHomeAsUpIndicator(backButton);
68
+        actionBar.setDisplayHomeAsUpEnabled(true);
69
+    }
70
+
71
+    @SuppressLint("PrivateResource")
72
+    @SuppressWarnings({"ConstantConditions"})
73
+    private Drawable setupBackButton(Screen screen) {
67 74
         Resources resources = getResources();
68 75
         final Drawable backButton;
69 76
         if (screen.buttonsTintColor != null) {
@@ -76,8 +83,7 @@ public class RnnToolBar extends Toolbar {
76 83
                     R.drawable.abc_ic_ab_back_mtrl_am_alpha,
77 84
                     ContextProvider.getActivityContext().getTheme());
78 85
         }
79
-        actionBar.setHomeAsUpIndicator(backButton);
80
-        actionBar.setDisplayHomeAsUpEnabled(true);
86
+        return backButton;
81 87
     }
82 88
 
83 89
     @SuppressWarnings({"ConstantConditions"})

+ 18
- 3
android/app/src/main/java/com/reactnativenavigation/views/ScreenStack.java Целия файл

@@ -1,6 +1,8 @@
1 1
 package com.reactnativenavigation.views;
2 2
 
3 3
 import android.animation.LayoutTransition;
4
+import android.content.Context;
5
+import android.util.AttributeSet;
4 6
 import android.widget.FrameLayout;
5 7
 
6 8
 import com.facebook.react.ReactInstanceManager;
@@ -29,20 +31,33 @@ public class ScreenStack extends FrameLayout {
29 31
     private final Stack<ScreenView> mStack = new Stack<>();
30 32
     private final ReactInstanceManager mReactInstanceManager =
31 33
             RctManager.getInstance().getReactInstanceManager();
32
-    private final BaseReactActivity mReactActivity;
34
+    private BaseReactActivity mReactActivity;
33 35
 
34 36
     public ScreenStack(BaseReactActivity context) {
35 37
         super(context);
36
-        mReactActivity = context;
38
+        init(context);
39
+    }
40
+
41
+    public ScreenStack(Context context, AttributeSet attrs) {
42
+        super(context, attrs);
43
+        init(context);
44
+    }
45
+
46
+    private void init(Context context) {
47
+        mReactActivity = (BaseReactActivity) context;
37 48
         setLayoutTransition(new LayoutTransition());
38 49
     }
39 50
 
40 51
     public void push(Screen screen) {
52
+        push(screen, null);
53
+    }
54
+
55
+    public void push(Screen screen, RctView.OnDisplayedListener onDisplayed) {
41 56
         RctView oldView = null;
42 57
         if (!mStack.isEmpty()) {
43 58
             oldView = mStack.peek().view;
44 59
         }
45
-        RctView view = new RctView(mReactActivity, mReactInstanceManager, screen);
60
+        RctView view = new RctView(mReactActivity, mReactInstanceManager, screen, onDisplayed);
46 61
         addView(view, MATCH_PARENT, MATCH_PARENT);
47 62
         if (oldView != null) {
48 63
             ReactRootView reactRootView = oldView.getReactRootView();

+ 8
- 0
android/app/src/main/res/anim/slide_down.xml Целия файл

@@ -0,0 +1,8 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<set xmlns:android="http://schemas.android.com/apk/res/android"
3
+     android:interpolator="@android:anim/accelerate_interpolator">
4
+    <translate
5
+        android:fromYDelta="0"
6
+        android:toYDelta="100%"
7
+        android:duration="190"/>
8
+</set>

+ 9
- 0
android/app/src/main/res/anim/slide_up.xml Целия файл

@@ -0,0 +1,9 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<set xmlns:android="http://schemas.android.com/apk/res/android"
3
+    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
4
+    android:shareInterpolator="true">
5
+    <translate
6
+        android:fromYDelta="100%"
7
+        android:toYDelta="0"
8
+        android:duration="350"/>
9
+</set>

+ 25
- 0
android/app/src/main/res/layout/modal_layout.xml Целия файл

@@ -0,0 +1,25 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
+    xmlns:app="http://schemas.android.com/apk/res-auto"
4
+    android:background="@android:color/transparent"
5
+    android:orientation="vertical"
6
+    android:layout_width="match_parent"
7
+    android:layout_height="match_parent">
8
+
9
+    <android.support.design.widget.AppBarLayout
10
+        android:id="@+id/appbar"
11
+        android:layout_width="match_parent"
12
+        android:layout_height="wrap_content"
13
+        android:fitsSystemWindows="true">
14
+        <com.reactnativenavigation.views.RnnToolBar
15
+            android:id="@+id/toolbar"
16
+            android:layout_width="match_parent"
17
+            android:layout_height="?attr/actionBarSize"
18
+            app:layout_scrollFlags="scroll|enterAlways"/>
19
+    </android.support.design.widget.AppBarLayout>
20
+
21
+    <com.reactnativenavigation.views.ScreenStack
22
+        android:id="@+id/screenStack"
23
+        android:layout_width="match_parent"
24
+        android:layout_height="match_parent"/>
25
+</LinearLayout>

+ 10
- 0
android/app/src/main/res/values/styles.xml Целия файл

@@ -0,0 +1,10 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<resources>
3
+    <style name="Modal" parent="@android:style/Theme.Translucent.NoTitleBar">
4
+        <item name="android:windowAnimationStyle">@style/modalAnimations</item>
5
+    </style>
6
+
7
+    <style name="modalAnimations">
8
+        <item name="android:windowExitAnimation">@anim/slide_down</item>
9
+    </style>
10
+</resources>

+ 12
- 0
example-redux/src/screens/FirstTabScreen.js Целия файл

@@ -72,18 +72,30 @@ class FirstTabScreen extends Component {
72 72
           <Text style={styles.button}>Push Screen</Text>
73 73
         </TouchableOpacity>
74 74
 
75
+        <TouchableOpacity onPress={ this.onShowModalPress.bind(this) }>
76
+          <Text style={styles.button}>Modal Screen</Text>
77
+        </TouchableOpacity>
75 78
       </View>
76 79
     );
77 80
   }
81
+
78 82
   onIncrementPress() {
79 83
     this.props.dispatch(counterActions.increment());
80 84
   }
85
+
81 86
   onPushPress() {
82 87
     this.props.navigator.push({
83 88
       title: "More",
84 89
       screen: "example.PushedScreen"
85 90
     });
86 91
   }
92
+
93
+  onShowModalPress() {
94
+    this.props.navigator.showModal({
95
+      title: "Modal Screen",
96
+      screen: "example.PushedScreen"
97
+    });
98
+  }
87 99
 }
88 100
 
89 101
 const styles = StyleSheet.create({

+ 8
- 2
src/platformSpecific.android.js Целия файл

@@ -51,6 +51,11 @@ function navigatorPop(navigator, params) {
51 51
   RctActivity.navigatorPop(navigator);
52 52
 }
53 53
 
54
+function showModal(params) {
55
+  addNavigatorParams(params)
56
+  RctActivity.showModal(params);
57
+}
58
+
54 59
 function addNavigatorParams(screen, navigator = null, idx = '') {
55 60
   screen.navigatorID = navigator ? navigator.navigatorID : utils.getRandomId() + '_nav' + idx;
56 61
   screen.screenInstanceID = utils.getRandomId();
@@ -80,8 +85,9 @@ function addToolbarStyleParams(screen) {
80 85
 }
81 86
 
82 87
 export default {
83
-  startSingleScreenApp,
84 88
   startTabBasedApp,
89
+  startSingleScreenApp,
85 90
   navigatorPush,
86
-  navigatorPop
91
+  navigatorPop,
92
+  showModal
87 93
 }