Browse Source

Add Modal implementation

Guy Carmeli 8 years ago
parent
commit
c68ac978e8

+ 54
- 0
android/app/src/main/java/com/reactnativenavigation/controllers/ModalController.java View File

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 View File

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 View File

12
 import com.reactnativenavigation.activities.BaseReactActivity;
12
 import com.reactnativenavigation.activities.BaseReactActivity;
13
 import com.reactnativenavigation.activities.SingleScreenActivity;
13
 import com.reactnativenavigation.activities.SingleScreenActivity;
14
 import com.reactnativenavigation.activities.TabActivity;
14
 import com.reactnativenavigation.activities.TabActivity;
15
+import com.reactnativenavigation.controllers.ModalController;
15
 import com.reactnativenavigation.core.objects.Screen;
16
 import com.reactnativenavigation.core.objects.Screen;
17
+import com.reactnativenavigation.modal.RnnModal;
16
 import com.reactnativenavigation.utils.ContextProvider;
18
 import com.reactnativenavigation.utils.ContextProvider;
17
 
19
 
18
 import java.util.ArrayList;
20
 import java.util.ArrayList;
71
     }
73
     }
72
 
74
 
73
     @ReactMethod
75
     @ReactMethod
74
-    public void navigatorPush(final ReadableMap screen) {
76
+    public void navigatorPush(final ReadableMap skreen) {
77
+        final Screen screen = new Screen(skreen);
75
         final BaseReactActivity context = ContextProvider.getActivityContext();
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
                  @Override
100
                  @Override
79
                  public void run() {
101
                  public void run() {
80
-                     context.push(new Screen(screen));
102
+                     context.push(screen);
81
                  }
103
                  }
82
              });
104
              });
83
-        }
84
     }
105
     }
85
 
106
 
86
     @ReactMethod
107
     @ReactMethod
87
     public void navigatorPop(final ReadableMap navigator) {
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
         final BaseReactActivity context = ContextProvider.getActivityContext();
141
         final BaseReactActivity context = ContextProvider.getActivityContext();
90
         if (context != null && !context.isFinishing()) {
142
         if (context != null && !context.isFinishing()) {
91
             context.runOnUiThread(new Runnable() {
143
             context.runOnUiThread(new Runnable() {
92
                 @Override
144
                 @Override
93
                 public void run() {
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 View File

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 View File

1
 package com.reactnativenavigation.views;
1
 package com.reactnativenavigation.views;
2
 
2
 
3
 import android.os.Bundle;
3
 import android.os.Bundle;
4
+import android.view.ViewTreeObserver;
4
 import android.widget.FrameLayout;
5
 import android.widget.FrameLayout;
5
 
6
 
6
 import com.facebook.react.ReactInstanceManager;
7
 import com.facebook.react.ReactInstanceManager;
15
 
16
 
16
     private ReactRootView mReactRootView;
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
     public ReactRootView getReactRootView() {
29
     public ReactRootView getReactRootView() {
19
         return mReactRootView;
30
         return mReactRootView;
20
     }
31
     }
21
 
32
 
22
     public RctView(BaseReactActivity ctx, ReactInstanceManager rctInstanceManager, Screen screen) {
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
         super(ctx);
39
         super(ctx);
24
         setLayoutParams(new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
40
         setLayoutParams(new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
25
 
41
 
34
 
50
 
35
         mReactRootView.startReactApplication(rctInstanceManager, componentName, passProps);
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
         addView(mReactRootView);
63
         addView(mReactRootView);
38
     }
64
     }
39
 }
65
 }

+ 9
- 3
android/app/src/main/java/com/reactnativenavigation/views/RnnToolBar.java View File

60
     }
60
     }
61
 
61
 
62
     @SuppressWarnings({"ConstantConditions"})
62
     @SuppressWarnings({"ConstantConditions"})
63
-    @SuppressLint("PrivateResource")
64
     public void showBackButton(Screen screen) {
63
     public void showBackButton(Screen screen) {
65
         ActionBar actionBar = ContextProvider.getActivityContext().getSupportActionBar();
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
         Resources resources = getResources();
74
         Resources resources = getResources();
68
         final Drawable backButton;
75
         final Drawable backButton;
69
         if (screen.buttonsTintColor != null) {
76
         if (screen.buttonsTintColor != null) {
76
                     R.drawable.abc_ic_ab_back_mtrl_am_alpha,
83
                     R.drawable.abc_ic_ab_back_mtrl_am_alpha,
77
                     ContextProvider.getActivityContext().getTheme());
84
                     ContextProvider.getActivityContext().getTheme());
78
         }
85
         }
79
-        actionBar.setHomeAsUpIndicator(backButton);
80
-        actionBar.setDisplayHomeAsUpEnabled(true);
86
+        return backButton;
81
     }
87
     }
82
 
88
 
83
     @SuppressWarnings({"ConstantConditions"})
89
     @SuppressWarnings({"ConstantConditions"})

+ 18
- 3
android/app/src/main/java/com/reactnativenavigation/views/ScreenStack.java View File

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

+ 8
- 0
android/app/src/main/res/anim/slide_down.xml View File

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 View File

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 View File

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 View File

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 View File

72
           <Text style={styles.button}>Push Screen</Text>
72
           <Text style={styles.button}>Push Screen</Text>
73
         </TouchableOpacity>
73
         </TouchableOpacity>
74
 
74
 
75
+        <TouchableOpacity onPress={ this.onShowModalPress.bind(this) }>
76
+          <Text style={styles.button}>Modal Screen</Text>
77
+        </TouchableOpacity>
75
       </View>
78
       </View>
76
     );
79
     );
77
   }
80
   }
81
+
78
   onIncrementPress() {
82
   onIncrementPress() {
79
     this.props.dispatch(counterActions.increment());
83
     this.props.dispatch(counterActions.increment());
80
   }
84
   }
85
+
81
   onPushPress() {
86
   onPushPress() {
82
     this.props.navigator.push({
87
     this.props.navigator.push({
83
       title: "More",
88
       title: "More",
84
       screen: "example.PushedScreen"
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
 const styles = StyleSheet.create({
101
 const styles = StyleSheet.create({

+ 8
- 2
src/platformSpecific.android.js View File

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