mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30999e37d1 | ||
|
|
00ed7c69de | ||
|
|
6c16d293cb | ||
|
|
75d63f9503 | ||
|
|
14f43a1117 | ||
|
|
0cd2f77116 | ||
|
|
9c034a9904 | ||
|
|
b0a6010191 |
+4
-2
@@ -21,8 +21,10 @@ DerivedData
|
||||
*.xcuserstate
|
||||
project.xcworkspace
|
||||
|
||||
# Xcode, Gradle
|
||||
build/
|
||||
# Gradle
|
||||
/build/
|
||||
/Examples/**/android/app/build/
|
||||
/ReactAndroid/build/
|
||||
|
||||
# Android
|
||||
.idea
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react-native');
|
||||
var {
|
||||
Alert,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
View,
|
||||
} = React;
|
||||
|
||||
var UIExplorerBlock = require('./UIExplorerBlock');
|
||||
|
||||
// corporate ipsum > lorem ipsum
|
||||
var alertMessage = 'Credibly reintermediate next-generation potentialities after goal-oriented ' +
|
||||
'catalysts for change. Dynamically revolutionize.';
|
||||
|
||||
/**
|
||||
* Simple alert examples.
|
||||
*/
|
||||
var SimpleAlertExampleBlock = React.createClass({
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<TouchableHighlight style={styles.wrapper}
|
||||
onPress={() => Alert.alert(
|
||||
'Alert Title',
|
||||
alertMessage,
|
||||
)}>
|
||||
<View style={styles.button}>
|
||||
<Text>Alert with message and default button</Text>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
<TouchableHighlight style={styles.wrapper}
|
||||
onPress={() => Alert.alert(
|
||||
'Alert Title',
|
||||
alertMessage,
|
||||
[
|
||||
{text: 'OK', onPress: () => console.log('OK Pressed!')},
|
||||
]
|
||||
)}>
|
||||
<View style={styles.button}>
|
||||
<Text>Alert with one button</Text>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
<TouchableHighlight style={styles.wrapper}
|
||||
onPress={() => Alert.alert(
|
||||
'Alert Title',
|
||||
alertMessage,
|
||||
[
|
||||
{text: 'Cancel', onPress: () => console.log('Cancel Pressed!')},
|
||||
{text: 'OK', onPress: () => console.log('OK Pressed!')},
|
||||
]
|
||||
)}>
|
||||
<View style={styles.button}>
|
||||
<Text>Alert with two buttons</Text>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
<TouchableHighlight style={styles.wrapper}
|
||||
onPress={() => Alert.alert(
|
||||
'Alert Title',
|
||||
null,
|
||||
[
|
||||
{text: 'Foo', onPress: () => console.log('Foo Pressed!')},
|
||||
{text: 'Bar', onPress: () => console.log('Bar Pressed!')},
|
||||
{text: 'Baz', onPress: () => console.log('Baz Pressed!')},
|
||||
]
|
||||
)}>
|
||||
<View style={styles.button}>
|
||||
<Text>Alert with three buttons</Text>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
<TouchableHighlight style={styles.wrapper}
|
||||
onPress={() => Alert.alert(
|
||||
'Foo Title',
|
||||
alertMessage,
|
||||
'..............'.split('').map((dot, index) => ({
|
||||
text: 'Button ' + index,
|
||||
onPress: () => console.log('Pressed ' + index)
|
||||
}))
|
||||
)}>
|
||||
<View style={styles.button}>
|
||||
<Text>Alert with too many buttons</Text>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
var AlertExample = React.createClass({
|
||||
statics: {
|
||||
title: 'Alert',
|
||||
description: 'Alerts display a concise and informative message ' +
|
||||
'and prompt the user to make a decision.',
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<UIExplorerBlock title={'Alert'}>
|
||||
<SimpleAlertExampleBlock />
|
||||
</UIExplorerBlock>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
wrapper: {
|
||||
borderRadius: 5,
|
||||
marginBottom: 5,
|
||||
},
|
||||
button: {
|
||||
backgroundColor: '#eeeeee',
|
||||
padding: 10,
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
AlertExample,
|
||||
SimpleAlertExampleBlock,
|
||||
}
|
||||
@@ -24,77 +24,15 @@ var {
|
||||
AlertIOS,
|
||||
} = React;
|
||||
|
||||
var { SimpleAlertExampleBlock } = require('./AlertExample');
|
||||
|
||||
exports.framework = 'React';
|
||||
exports.title = 'AlertIOS';
|
||||
exports.description = 'iOS alerts and action sheets';
|
||||
exports.examples = [{
|
||||
title: 'Alerts',
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<TouchableHighlight style={styles.wrapper}
|
||||
onPress={() => AlertIOS.alert(
|
||||
'Foo Title',
|
||||
'My Alert Msg'
|
||||
)}>
|
||||
<View style={styles.button}>
|
||||
<Text>Alert with message and default button</Text>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
<TouchableHighlight style={styles.wrapper}
|
||||
onPress={() => AlertIOS.alert(
|
||||
'Foo Title',
|
||||
null,
|
||||
[
|
||||
{text: 'Button', onPress: () => console.log('Button Pressed!')},
|
||||
]
|
||||
)}>
|
||||
<View style={styles.button}>
|
||||
<Text>Alert with only one button</Text>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
<TouchableHighlight style={styles.wrapper}
|
||||
onPress={() => AlertIOS.alert(
|
||||
'Foo Title',
|
||||
'My Alert Msg',
|
||||
[
|
||||
{text: 'Foo', onPress: () => console.log('Foo Pressed!')},
|
||||
{text: 'Bar', onPress: () => console.log('Bar Pressed!')},
|
||||
]
|
||||
)}>
|
||||
<View style={styles.button}>
|
||||
<Text>Alert with two buttons</Text>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
<TouchableHighlight style={styles.wrapper}
|
||||
onPress={() => AlertIOS.alert(
|
||||
'Foo Title',
|
||||
null,
|
||||
[
|
||||
{text: 'Foo', onPress: () => console.log('Foo Pressed!')},
|
||||
{text: 'Bar', onPress: () => console.log('Bar Pressed!')},
|
||||
{text: 'Baz', onPress: () => console.log('Baz Pressed!')},
|
||||
]
|
||||
)}>
|
||||
<View style={styles.button}>
|
||||
<Text>Alert with 3 buttons</Text>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
<TouchableHighlight style={styles.wrapper}
|
||||
onPress={() => AlertIOS.alert(
|
||||
'Foo Title',
|
||||
'My Alert Msg',
|
||||
'..............'.split('').map((dot, index) => ({
|
||||
text: 'Button ' + index,
|
||||
onPress: () => console.log('Pressed ' + index)
|
||||
}))
|
||||
)}>
|
||||
<View style={styles.button}>
|
||||
<Text>Alert with too many buttons</Text>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
</View>
|
||||
);
|
||||
return <SimpleAlertExampleBlock />;
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -34,10 +34,12 @@ var COMPONENTS = [
|
||||
require('./TouchableExample'),
|
||||
require('./ViewExample'),
|
||||
require('./ViewPagerAndroidExample.android'),
|
||||
require('./WebViewExample'),
|
||||
];
|
||||
|
||||
var APIS = [
|
||||
require('./AccessibilityAndroidExample.android'),
|
||||
require('./AlertExample').AlertExample,
|
||||
require('./BorderExample'),
|
||||
require('./ClipboardExample'),
|
||||
require('./GeolocationExample'),
|
||||
|
||||
@@ -32,15 +32,14 @@ var WebViewState = keyMirror({
|
||||
});
|
||||
|
||||
/**
|
||||
* Note that WebView is only supported on iOS for now,
|
||||
* see https://facebook.github.io/react-native/docs/known-issues.html
|
||||
* Renders a native WebView.
|
||||
*/
|
||||
var WebView = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
...View.propTypes,
|
||||
renderError: PropTypes.func, // view to show if there's an error
|
||||
renderLoading: PropTypes.func, // loading indicator to show
|
||||
renderError: PropTypes.func,
|
||||
renderLoading: PropTypes.func,
|
||||
url: PropTypes.string,
|
||||
html: PropTypes.string,
|
||||
automaticallyAdjustContentInsets: PropTypes.bool,
|
||||
@@ -48,6 +47,11 @@ var WebView = React.createClass({
|
||||
onNavigationStateChange: PropTypes.func,
|
||||
startInLoadingState: PropTypes.bool, // force WebView to show loadingView on first load
|
||||
style: View.propTypes.style,
|
||||
|
||||
/**
|
||||
* Used on Android only, JS is enabled by default for WebView on iOS
|
||||
* @platform android
|
||||
*/
|
||||
javaScriptEnabledAndroid: PropTypes.bool,
|
||||
|
||||
/**
|
||||
@@ -56,10 +60,11 @@ var WebView = React.createClass({
|
||||
injectedJavaScript: PropTypes.string,
|
||||
|
||||
/**
|
||||
* Sets the user-agent for this WebView. The user-agent can also be set in native through
|
||||
* WebViewConfig, but this can and will overwrite that config.
|
||||
* Sets the user-agent for this WebView. The user-agent can also be set in native using
|
||||
* WebViewConfig. This prop will overwrite that config.
|
||||
*/
|
||||
userAgent: PropTypes.string,
|
||||
|
||||
/**
|
||||
* Used to locate this view in end-to-end tests.
|
||||
*/
|
||||
|
||||
@@ -77,9 +77,6 @@ var defaultRenderError = (errorDomain, errorCode, errorDesc) => (
|
||||
|
||||
/**
|
||||
* Renders a native WebView.
|
||||
*
|
||||
* Note that WebView is only supported on iOS for now,
|
||||
* see https://facebook.github.io/react-native/docs/known-issues.html
|
||||
*/
|
||||
var WebView = React.createClass({
|
||||
statics: {
|
||||
@@ -91,9 +88,21 @@ var WebView = React.createClass({
|
||||
...View.propTypes,
|
||||
url: PropTypes.string,
|
||||
html: PropTypes.string,
|
||||
/**
|
||||
* Function that returns a view to show if there's an error.
|
||||
*/
|
||||
renderError: PropTypes.func, // view to show if there's an error
|
||||
renderLoading: PropTypes.func, // loading indicator to show
|
||||
/**
|
||||
* Function that returns a loading indicator.
|
||||
*/
|
||||
renderLoading: PropTypes.func,
|
||||
/**
|
||||
* @platform ios
|
||||
*/
|
||||
bounces: PropTypes.bool,
|
||||
/**
|
||||
* @platform ios
|
||||
*/
|
||||
scrollEnabled: PropTypes.bool,
|
||||
automaticallyAdjustContentInsets: PropTypes.bool,
|
||||
contentInset: EdgeInsetsPropType,
|
||||
@@ -102,7 +111,7 @@ var WebView = React.createClass({
|
||||
style: View.propTypes.style,
|
||||
|
||||
/**
|
||||
* Used for android only, JS is enabled by default for WebView on iOS
|
||||
* Used on Android only, JS is enabled by default for WebView on iOS
|
||||
* @platform android
|
||||
*/
|
||||
javaScriptEnabledAndroid: PropTypes.bool,
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule Alert
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var AlertIOS = require('AlertIOS');
|
||||
var Platform = require('Platform');
|
||||
var DialogModuleAndroid = require('NativeModules').DialogManagerAndroid;
|
||||
|
||||
import type { AlertType, AlertButtonStyle } from 'AlertIOS';
|
||||
|
||||
type Buttons = Array<{
|
||||
text?: string;
|
||||
onPress?: ?Function;
|
||||
style?: AlertButtonStyle;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Launches an alert dialog with the specified title and message.
|
||||
*
|
||||
* Optionally provide a list of buttons. Tapping any button will fire the
|
||||
* respective onPress callback and dismiss the alert. By default, the only
|
||||
* button will be an 'OK' button.
|
||||
*
|
||||
* The last button in the list will be considered the 'Primary' button.
|
||||
*
|
||||
* ## iOS
|
||||
*
|
||||
* On iOS you can specify any number of buttons. Each button can optionally
|
||||
* specify a style and you can also specify type of the alert.
|
||||
* Refer to `AlertIOS` for details.
|
||||
*
|
||||
* ## Android
|
||||
*
|
||||
* On Android at most three buttons can be specified. Android has a concept
|
||||
* of a 'neutral', 'negative' and a 'positive' button:
|
||||
*
|
||||
* - If you specify one button, it will be the 'positive' one (such as 'OK')
|
||||
* - Two buttons mean 'negative', 'positive' (such as 'Cancel', 'OK')
|
||||
* - Three buttons mean 'neutral', 'negative', 'positive' (such as 'Later', 'Cancel', 'OK')
|
||||
*
|
||||
* ```
|
||||
* Alert.alert(
|
||||
* 'Alert Title',
|
||||
* 'My Alert Msg',
|
||||
* [
|
||||
* {text: 'Ask me later', onPress: () => console.log('Ask me later pressed')},
|
||||
* {text: 'Cancel', onPress: () => console.log('Cancel Pressed')},
|
||||
* {text: 'OK', onPress: () => console.log('OK Pressed')},
|
||||
* ]
|
||||
* )
|
||||
* ```
|
||||
*/
|
||||
class Alert {
|
||||
|
||||
static alert(
|
||||
title: ?string,
|
||||
message?: ?string,
|
||||
buttons?: Buttons,
|
||||
type?: AlertType
|
||||
): void {
|
||||
if (Platform.OS === 'ios') {
|
||||
AlertIOS.alert(title, message, buttons, type);
|
||||
} else if (Platform.OS === 'android') {
|
||||
AlertAndroid.alert(title, message, buttons);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around the Android native module.
|
||||
*/
|
||||
class AlertAndroid {
|
||||
|
||||
static alert(
|
||||
title: ?string,
|
||||
message?: ?string,
|
||||
buttons?: Buttons,
|
||||
): void {
|
||||
var config = {
|
||||
title: title || '',
|
||||
message: message || '',
|
||||
};
|
||||
// At most three buttons (neutral, negative, positive). Ignore rest.
|
||||
// The text 'OK' should be probably localized. iOS Alert does that in native.
|
||||
var validButtons: Buttons = buttons ? buttons.slice(0, 3) : [{text: 'OK'}];
|
||||
var buttonPositive = validButtons.pop();
|
||||
var buttonNegative = validButtons.pop();
|
||||
var buttonNeutral = validButtons.pop();
|
||||
if (buttonNeutral) {
|
||||
config = {...config, buttonNeutral: buttonNeutral.text || '' }
|
||||
}
|
||||
if (buttonNegative) {
|
||||
config = {...config, buttonNegative: buttonNegative.text || '' }
|
||||
}
|
||||
if (buttonPositive) {
|
||||
config = {...config, buttonPositive: buttonPositive.text || '' }
|
||||
}
|
||||
DialogModuleAndroid.showAlert(
|
||||
config,
|
||||
(errorMessage) => console.warn(message),
|
||||
(action, buttonKey) => {
|
||||
if (action !== DialogModuleAndroid.buttonClicked) {
|
||||
return;
|
||||
}
|
||||
if (buttonKey === DialogModuleAndroid.buttonNeutral) {
|
||||
buttonNeutral.onPress && buttonNeutral.onPress();
|
||||
} else if (buttonKey === DialogModuleAndroid.buttonNegative) {
|
||||
buttonNegative.onPress && buttonNegative.onPress();
|
||||
} else if (buttonKey === DialogModuleAndroid.buttonPositive) {
|
||||
buttonPositive.onPress && buttonPositive.onPress();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Alert;
|
||||
@@ -14,14 +14,14 @@
|
||||
var RCTAlertManager = require('NativeModules').AlertManager;
|
||||
var invariant = require('invariant');
|
||||
|
||||
type AlertType = $Enum<{
|
||||
export type AlertType = $Enum<{
|
||||
'default': string;
|
||||
'plain-text': string;
|
||||
'secure-text': string;
|
||||
'login-password': string;
|
||||
}>;
|
||||
|
||||
type AlertButtonStyle = $Enum<{
|
||||
export type AlertButtonStyle = $Enum<{
|
||||
'default': string;
|
||||
'cancel': string;
|
||||
'destructive': string;
|
||||
@@ -32,20 +32,19 @@ type AlertButtonStyle = $Enum<{
|
||||
*
|
||||
* Optionally provide a list of buttons. Tapping any button will fire the
|
||||
* respective onPress callback and dismiss the alert. By default, the only
|
||||
* button will be an 'OK' button
|
||||
* button will be an 'OK' button.
|
||||
*
|
||||
* ```
|
||||
* AlertIOS.alert(
|
||||
* 'Foo Title',
|
||||
* 'My Alert Msg',
|
||||
* [
|
||||
* {text: 'OK', onPress: () => console.log('OK Pressed!')},
|
||||
* {text: 'Cancel', onPress: () => console.log('Cancel Pressed!'), style: 'cancel'},
|
||||
* {text: 'OK', onPress: () => console.log('OK Pressed')},
|
||||
* {text: 'Cancel', onPress: () => console.log('Cancel Pressed'), style: 'cancel'},
|
||||
* ]
|
||||
* )
|
||||
* ```
|
||||
*/
|
||||
|
||||
class AlertIOS {
|
||||
static alert(
|
||||
title: ?string,
|
||||
|
||||
+1
@@ -51,6 +51,7 @@ var ReactNative = {
|
||||
// APIs
|
||||
get ActionSheetIOS() { return require('ActionSheetIOS'); },
|
||||
get AdSupportIOS() { return require('AdSupportIOS'); },
|
||||
get Alert() { return require('Alert'); },
|
||||
get AlertIOS() { return require('AlertIOS'); },
|
||||
get Animated() { return require('Animated'); },
|
||||
get AppRegistry() { return require('AppRegistry'); },
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "React"
|
||||
s.version = "0.15.0"
|
||||
s.version = "0.17.0"
|
||||
s.summary = "Build high quality mobile apps using React."
|
||||
s.description = <<-DESC
|
||||
React Native apps are built using the React JS
|
||||
|
||||
@@ -263,7 +263,7 @@ dependencies {
|
||||
testCompile "org.powermock:powermock-classloading-xstream:${POWERMOCK_VERSION}"
|
||||
testCompile "org.mockito:mockito-core:${MOCKITO_CORE_VERSION}"
|
||||
testCompile "org.easytesting:fest-assert-core:${FEST_ASSERT_CORE_VERSION}"
|
||||
testCompile("org.robolectric:robolectric:${ROBOLECTRIC_VERSION}")
|
||||
testCompile "org.robolectric:robolectric:${ROBOLECTRIC_VERSION}"
|
||||
}
|
||||
|
||||
apply from: 'release.gradle'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
VERSION_NAME=0.12.0-SNAPSHOT
|
||||
VERSION_NAME=0.17.1
|
||||
GROUP=com.facebook.react
|
||||
|
||||
POM_NAME=ReactNative
|
||||
|
||||
@@ -184,7 +184,7 @@ public abstract class ReactInstanceManager {
|
||||
/**
|
||||
* Path to the JS bundle file to be loaded from the file system.
|
||||
*
|
||||
* Example: {@code "assets://index.android.js" or "/sdcard/main.jsbundle}
|
||||
* Example: {@code "assets://index.android.js" or "/sdcard/main.jsbundle"}
|
||||
*/
|
||||
public Builder setJSBundleFile(String jsBundleFile) {
|
||||
mJSBundleFile = jsBundleFile;
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.common.build;
|
||||
|
||||
import com.facebook.react.BuildConfig;
|
||||
|
||||
/**
|
||||
* Convenience class for accessing auto-generated BuildConfig so that a) other modules can just
|
||||
* depend on this module instead of having to manually depend on generating their own build config
|
||||
* and b) we don't have to deal with IntelliJ getting confused about the autogenerated BuildConfig
|
||||
* class all over the place.
|
||||
*/
|
||||
public class ReactBuildConfig {
|
||||
|
||||
public static final boolean DEBUG = BuildConfig.DEBUG;
|
||||
public static final boolean IS_INTERNAL_BUILD = BuildConfig.IS_INTERNAL_BUILD;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.dialog;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.DialogFragment;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
|
||||
/**
|
||||
* A fragment used to display the dialog.
|
||||
*/
|
||||
/* package */ class AlertFragment extends DialogFragment implements DialogInterface.OnClickListener {
|
||||
|
||||
/* package */ static final String ARG_TITLE = "title";
|
||||
/* package */ static final String ARG_MESSAGE = "message";
|
||||
/* package */ static final String ARG_BUTTON_POSITIVE = "button_positive";
|
||||
/* package */ static final String ARG_BUTTON_NEGATIVE = "button_negative";
|
||||
/* package */ static final String ARG_BUTTON_NEUTRAL = "button_neutral";
|
||||
|
||||
private final @Nullable DialogModule.AlertFragmentListener mListener;
|
||||
|
||||
public AlertFragment(@Nullable DialogModule.AlertFragmentListener listener, Bundle arguments) {
|
||||
mListener = listener;
|
||||
setArguments(arguments);
|
||||
}
|
||||
|
||||
public static Dialog createDialog(
|
||||
Context activityContext, Bundle arguments, DialogInterface.OnClickListener fragment) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activityContext)
|
||||
.setTitle(arguments.getString(ARG_TITLE))
|
||||
.setMessage(arguments.getString(ARG_MESSAGE));
|
||||
|
||||
if (arguments.containsKey(ARG_BUTTON_POSITIVE)) {
|
||||
builder.setPositiveButton(arguments.getString(ARG_BUTTON_POSITIVE), fragment);
|
||||
}
|
||||
if (arguments.containsKey(ARG_BUTTON_NEGATIVE)) {
|
||||
builder.setNegativeButton(arguments.getString(ARG_BUTTON_NEGATIVE), fragment);
|
||||
}
|
||||
if (arguments.containsKey(ARG_BUTTON_NEUTRAL)) {
|
||||
builder.setNeutralButton(arguments.getString(ARG_BUTTON_NEUTRAL), fragment);
|
||||
}
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
return createDialog(getActivity(), getArguments(), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (mListener != null) {
|
||||
mListener.onClick(dialog, which);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
super.onDismiss(dialog);
|
||||
if (mListener != null) {
|
||||
mListener.onDismiss(dialog);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.dialog;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.DialogInterface.OnDismissListener;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.bridge.LifecycleEventListener;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.common.MapBuilder;
|
||||
|
||||
public class DialogModule extends ReactContextBaseJavaModule implements LifecycleEventListener {
|
||||
|
||||
/* package */ static final String FRAGMENT_TAG =
|
||||
"com.facebook.catalyst.react.dialog.DialogModule";
|
||||
|
||||
/* package */ static final String NAME = "DialogManagerAndroid";
|
||||
|
||||
/* package */ static final String ACTION_BUTTON_CLICKED = "buttonClicked";
|
||||
/* package */ static final String ACTION_DISMISSED = "dismissed";
|
||||
/* package */ static final String KEY_TITLE = "title";
|
||||
/* package */ static final String KEY_MESSAGE = "message";
|
||||
/* package */ static final String KEY_BUTTON_POSITIVE = "buttonPositive";
|
||||
/* package */ static final String KEY_BUTTON_NEGATIVE = "buttonNegative";
|
||||
/* package */ static final String KEY_BUTTON_NEUTRAL = "buttonNeutral";
|
||||
|
||||
/* package */ static final Map<String, Object> CONSTANTS = MapBuilder.<String, Object>of(
|
||||
ACTION_BUTTON_CLICKED, ACTION_BUTTON_CLICKED,
|
||||
ACTION_DISMISSED, ACTION_DISMISSED,
|
||||
KEY_BUTTON_POSITIVE, DialogInterface.BUTTON_POSITIVE,
|
||||
KEY_BUTTON_NEGATIVE, DialogInterface.BUTTON_NEGATIVE,
|
||||
KEY_BUTTON_NEUTRAL, DialogInterface.BUTTON_NEUTRAL);
|
||||
|
||||
private boolean mIsInForeground;
|
||||
|
||||
public DialogModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to allow this module to work with both the standard FragmentManager
|
||||
* and the Support FragmentManager (for apps that need to use it for legacy reasons).
|
||||
* Since the two APIs don't share a common interface there's unfortunately some
|
||||
* code duplication.
|
||||
*/
|
||||
private class FragmentManagerHelper {
|
||||
|
||||
// Exactly one of the two is null
|
||||
private final @Nullable android.app.FragmentManager mFragmentManager;
|
||||
private final @Nullable android.support.v4.app.FragmentManager mSupportFragmentManager;
|
||||
|
||||
private @Nullable Object mFragmentToShow;
|
||||
|
||||
private boolean isUsingSupportLibrary() {
|
||||
return mSupportFragmentManager != null;
|
||||
}
|
||||
|
||||
public FragmentManagerHelper(android.support.v4.app.FragmentManager supportFragmentManager) {
|
||||
mFragmentManager = null;
|
||||
mSupportFragmentManager = supportFragmentManager;
|
||||
}
|
||||
public FragmentManagerHelper(android.app.FragmentManager fragmentManager) {
|
||||
mFragmentManager = fragmentManager;
|
||||
mSupportFragmentManager = null;
|
||||
}
|
||||
|
||||
public void showPendingAlert() {
|
||||
if (mFragmentToShow == null) {
|
||||
return;
|
||||
}
|
||||
if (isUsingSupportLibrary()) {
|
||||
((SupportAlertFragment) mFragmentToShow).show(mSupportFragmentManager, FRAGMENT_TAG);
|
||||
} else {
|
||||
((AlertFragment) mFragmentToShow).show(mFragmentManager, FRAGMENT_TAG);
|
||||
}
|
||||
mFragmentToShow = null;
|
||||
}
|
||||
|
||||
private void dismissExisting() {
|
||||
if (isUsingSupportLibrary()) {
|
||||
SupportAlertFragment oldFragment =
|
||||
(SupportAlertFragment) mSupportFragmentManager.findFragmentByTag(FRAGMENT_TAG);
|
||||
if (oldFragment != null) {
|
||||
oldFragment.dismiss();
|
||||
}
|
||||
} else {
|
||||
AlertFragment oldFragment =
|
||||
(AlertFragment) mFragmentManager.findFragmentByTag(FRAGMENT_TAG);
|
||||
if (oldFragment != null) {
|
||||
oldFragment.dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void showNewAlert(boolean isInForeground, Bundle arguments, Callback actionCallback) {
|
||||
dismissExisting();
|
||||
|
||||
AlertFragmentListener actionListener =
|
||||
actionCallback != null ? new AlertFragmentListener(actionCallback) : null;
|
||||
|
||||
if (isUsingSupportLibrary()) {
|
||||
SupportAlertFragment alertFragment = new SupportAlertFragment(actionListener, arguments);
|
||||
if (isInForeground) {
|
||||
alertFragment.show(mSupportFragmentManager, FRAGMENT_TAG);
|
||||
} else {
|
||||
mFragmentToShow = alertFragment;
|
||||
}
|
||||
} else {
|
||||
AlertFragment alertFragment = new AlertFragment(actionListener, arguments);
|
||||
if (isInForeground) {
|
||||
alertFragment.show(mFragmentManager, FRAGMENT_TAG);
|
||||
} else {
|
||||
mFragmentToShow = alertFragment;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ class AlertFragmentListener implements OnClickListener, OnDismissListener {
|
||||
|
||||
private final Callback mCallback;
|
||||
private boolean mCallbackConsumed = false;
|
||||
|
||||
public AlertFragmentListener(Callback callback) {
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (!mCallbackConsumed) {
|
||||
if (getReactApplicationContext().hasActiveCatalystInstance()) {
|
||||
mCallback.invoke(ACTION_BUTTON_CLICKED, which);
|
||||
mCallbackConsumed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
if (!mCallbackConsumed) {
|
||||
if (getReactApplicationContext().hasActiveCatalystInstance()) {
|
||||
mCallback.invoke(ACTION_DISMISSED);
|
||||
mCallbackConsumed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getConstants() {
|
||||
return CONSTANTS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
getReactApplicationContext().addLifecycleEventListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostPause() {
|
||||
// Don't show the dialog if the host is paused.
|
||||
mIsInForeground = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostDestroy() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostResume() {
|
||||
mIsInForeground = true;
|
||||
// Check if a dialog has been created while the host was paused, so that we can show it now.
|
||||
FragmentManagerHelper fragmentManagerHelper = getFragmentManagerHelper();
|
||||
Assertions.assertNotNull(
|
||||
fragmentManagerHelper,
|
||||
"Attached DialogModule to host with pending alert but no FragmentManager " +
|
||||
"(not attached to an Activity).");
|
||||
fragmentManagerHelper.showPendingAlert();
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void showAlert(
|
||||
ReadableMap options,
|
||||
Callback errorCallback,
|
||||
Callback actionCallback) {
|
||||
FragmentManagerHelper fragmentManagerHelper = getFragmentManagerHelper();
|
||||
if (fragmentManagerHelper == null) {
|
||||
errorCallback.invoke("Tried to show an alert while not attached to an Activity");
|
||||
return;
|
||||
}
|
||||
|
||||
final Bundle args = new Bundle();
|
||||
if (options.hasKey(KEY_TITLE)) {
|
||||
args.putString(AlertFragment.ARG_TITLE, options.getString(KEY_TITLE));
|
||||
}
|
||||
if (options.hasKey(KEY_MESSAGE)) {
|
||||
args.putString(AlertFragment.ARG_MESSAGE, options.getString(KEY_MESSAGE));
|
||||
}
|
||||
if (options.hasKey(KEY_BUTTON_POSITIVE)) {
|
||||
args.putString(AlertFragment.ARG_BUTTON_POSITIVE, options.getString(KEY_BUTTON_POSITIVE));
|
||||
}
|
||||
if (options.hasKey(KEY_BUTTON_NEGATIVE)) {
|
||||
args.putString(AlertFragment.ARG_BUTTON_NEGATIVE, options.getString(KEY_BUTTON_NEGATIVE));
|
||||
}
|
||||
if (options.hasKey(KEY_BUTTON_NEUTRAL)) {
|
||||
args.putString(AlertFragment.ARG_BUTTON_NEUTRAL, options.getString(KEY_BUTTON_NEUTRAL));
|
||||
}
|
||||
|
||||
fragmentManagerHelper.showNewAlert(mIsInForeground, args, actionCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new helper to work with either the FragmentManager or the legacy support
|
||||
* FragmentManager transparently. Returns null if we're not attached to an Activity.
|
||||
*
|
||||
* DO NOT HOLD LONG-LIVED REFERENCES TO THE OBJECT RETURNED BY THIS METHOD, AS THIS WILL CAUSE
|
||||
* MEMORY LEAKS.
|
||||
*/
|
||||
private @Nullable FragmentManagerHelper getFragmentManagerHelper() {
|
||||
Activity activity = getCurrentActivity();
|
||||
if (activity == null) {
|
||||
return null;
|
||||
}
|
||||
if (activity instanceof FragmentActivity) {
|
||||
return new FragmentManagerHelper(((FragmentActivity) activity).getSupportFragmentManager());
|
||||
} else {
|
||||
return new FragmentManagerHelper(activity.getFragmentManager());
|
||||
}
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.dialog;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
|
||||
import android.support.v4.app.DialogFragment;
|
||||
|
||||
/**
|
||||
* {@link AlertFragment} for apps that use the Support FragmentActivity and FragmentManager
|
||||
* for legacy reasons.
|
||||
*/
|
||||
/* package */ class SupportAlertFragment extends DialogFragment implements DialogInterface.OnClickListener {
|
||||
|
||||
private final @Nullable DialogModule.AlertFragmentListener mListener;
|
||||
|
||||
public SupportAlertFragment(@Nullable DialogModule.AlertFragmentListener listener, Bundle arguments) {
|
||||
mListener = listener;
|
||||
setArguments(arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
return AlertFragment.createDialog(getActivity(), getArguments(), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (mListener != null) {
|
||||
mListener.onClick(dialog, which);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
super.onDismiss(dialog);
|
||||
if (mListener != null) {
|
||||
mListener.onDismiss(dialog);
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
-9
@@ -1,4 +1,11 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.netinfo;
|
||||
|
||||
@@ -10,6 +17,7 @@ import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.support.v4.net.ConnectivityManagerCompat;
|
||||
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.bridge.LifecycleEventListener;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
@@ -17,25 +25,32 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.bridge.WritableNativeMap;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
|
||||
import static com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter;
|
||||
|
||||
/**
|
||||
* Module that monitors and provides information about the connectivity state of the device.
|
||||
*/
|
||||
public class ConnectivityModule extends ReactContextBaseJavaModule
|
||||
public class NetInfoModule extends ReactContextBaseJavaModule
|
||||
implements LifecycleEventListener {
|
||||
|
||||
private static final String CONNECTION_TYPE_NONE = "NONE";
|
||||
private static final String CONNECTION_TYPE_UNKNOWN = "UNKNOWN";
|
||||
|
||||
private static final String MISSING_PERMISSION_MESSAGE =
|
||||
"To use NetInfo on Android, add the following to your AndroidManifest.xml:\n" +
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />";
|
||||
|
||||
private final ConnectivityManager mConnectivityManager;
|
||||
private final ConnectivityManagerCompat mConnectivityManagerCompat;
|
||||
private final ConnectivityBroadcastReceiver mConnectivityBroadcastReceiver;
|
||||
|
||||
private boolean mNoNetworkPermission = false;
|
||||
|
||||
private String mConnectivity = "";
|
||||
|
||||
public ConnectivityModule(ReactApplicationContext reactContext) {
|
||||
public NetInfoModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
mConnectivityManager =
|
||||
(ConnectivityManager) reactContext.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
@@ -69,11 +84,23 @@ public class ConnectivityModule extends ReactContextBaseJavaModule
|
||||
|
||||
@ReactMethod
|
||||
public void getCurrentConnectivity(Callback successCallback, Callback errorCallback) {
|
||||
if (mNoNetworkPermission) {
|
||||
if (errorCallback == null) {
|
||||
FLog.e(ReactConstants.TAG, MISSING_PERMISSION_MESSAGE);
|
||||
return;
|
||||
}
|
||||
errorCallback.invoke(MISSING_PERMISSION_MESSAGE);
|
||||
return;
|
||||
}
|
||||
successCallback.invoke(createConnectivityEventMap());
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void isConnectionMetered(Callback successCallback) {
|
||||
if (mNoNetworkPermission) {
|
||||
FLog.e(ReactConstants.TAG, MISSING_PERMISSION_MESSAGE);
|
||||
return;
|
||||
}
|
||||
successCallback.invoke(mConnectivityManagerCompat.isActiveNetworkMetered(mConnectivityManager));
|
||||
}
|
||||
|
||||
@@ -98,15 +125,21 @@ public class ConnectivityModule extends ReactContextBaseJavaModule
|
||||
}
|
||||
|
||||
private String getCurrentConnectionType() {
|
||||
NetworkInfo networkInfo = mConnectivityManager.getActiveNetworkInfo();
|
||||
if (networkInfo == null || !networkInfo.isConnected()) {
|
||||
return CONNECTION_TYPE_NONE;
|
||||
} else if (ConnectivityManager.isNetworkTypeValid(networkInfo.getType())) {
|
||||
return networkInfo.getTypeName().toUpperCase();
|
||||
} else {
|
||||
try {
|
||||
NetworkInfo networkInfo = mConnectivityManager.getActiveNetworkInfo();
|
||||
if (networkInfo == null || !networkInfo.isConnected()) {
|
||||
return CONNECTION_TYPE_NONE;
|
||||
} else if (ConnectivityManager.isNetworkTypeValid(networkInfo.getType())) {
|
||||
return networkInfo.getTypeName().toUpperCase();
|
||||
} else {
|
||||
return CONNECTION_TYPE_UNKNOWN;
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
mNoNetworkPermission = true;
|
||||
return CONNECTION_TYPE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
private void sendConnectivityChangedEvent() {
|
||||
getReactApplicationContext().getJSModule(RCTDeviceEventEmitter.class)
|
||||
.emit("networkStatusDidChange", createConnectivityEventMap());
|
||||
@@ -17,10 +17,11 @@ import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.JavaScriptModule;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.modules.dialog.DialogModule;
|
||||
import com.facebook.react.modules.fresco.FrescoModule;
|
||||
import com.facebook.react.modules.intent.IntentModule;
|
||||
import com.facebook.react.modules.location.LocationModule;
|
||||
import com.facebook.react.modules.netinfo.ConnectivityModule;
|
||||
import com.facebook.react.modules.netinfo.NetInfoModule;
|
||||
import com.facebook.react.modules.network.NetworkingModule;
|
||||
import com.facebook.react.modules.storage.AsyncStorageModule;
|
||||
import com.facebook.react.modules.toast.ToastModule;
|
||||
@@ -41,6 +42,7 @@ import com.facebook.react.views.toolbar.ReactToolbarManager;
|
||||
import com.facebook.react.views.view.ReactViewManager;
|
||||
import com.facebook.react.views.viewpager.ReactViewPagerManager;
|
||||
import com.facebook.react.views.swiperefresh.SwipeRefreshLayoutManager;
|
||||
import com.facebook.react.views.webview.ReactWebViewManager;
|
||||
import com.facebook.react.modules.clipboard.ClipboardModule;
|
||||
|
||||
/**
|
||||
@@ -53,11 +55,12 @@ public class MainReactPackage implements ReactPackage {
|
||||
return Arrays.<NativeModule>asList(
|
||||
new AsyncStorageModule(reactContext),
|
||||
new ClipboardModule(reactContext),
|
||||
new DialogModule(reactContext),
|
||||
new FrescoModule(reactContext),
|
||||
new IntentModule(reactContext),
|
||||
new LocationModule(reactContext),
|
||||
new NetworkingModule(reactContext),
|
||||
new ConnectivityModule(reactContext),
|
||||
new NetInfoModule(reactContext),
|
||||
new WebSocketModule(reactContext),
|
||||
new ToastModule(reactContext));
|
||||
}
|
||||
@@ -84,6 +87,7 @@ public class MainReactPackage implements ReactPackage {
|
||||
new ReactViewPagerManager(),
|
||||
new ReactTextInlineImageViewManager(),
|
||||
new ReactVirtualTextViewManager(),
|
||||
new SwipeRefreshLayoutManager());
|
||||
new SwipeRefreshLayoutManager(),
|
||||
new ReactWebViewManager());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,327 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.views.webview;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Build;
|
||||
import android.os.SystemClock;
|
||||
import android.text.TextUtils;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
|
||||
import com.facebook.catalyst.views.webview.events.TopLoadingErrorEvent;
|
||||
import com.facebook.catalyst.views.webview.events.TopLoadingFinishEvent;
|
||||
import com.facebook.catalyst.views.webview.events.TopLoadingStartEvent;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.LifecycleEventListener;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.common.MapBuilder;
|
||||
import com.facebook.react.common.build.ReactBuildConfig;
|
||||
import com.facebook.react.uimanager.ReactProp;
|
||||
import com.facebook.react.uimanager.SimpleViewManager;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
import com.facebook.react.uimanager.events.EventDispatcher;
|
||||
|
||||
/**
|
||||
* Manages instances of {@link WebView}
|
||||
*
|
||||
* Can accept following commands:
|
||||
* - GO_BACK
|
||||
* - GO_FORWARD
|
||||
* - RELOAD
|
||||
*
|
||||
* {@link WebView} instances could emit following direct events:
|
||||
* - topLoadingFinish
|
||||
* - topLoadingStart
|
||||
* - topLoadingError
|
||||
*
|
||||
* Each event will carry the following properties:
|
||||
* - target - view's react tag
|
||||
* - url - url set for the webview
|
||||
* - loading - whether webview is in a loading state
|
||||
* - title - title of the current page
|
||||
* - canGoBack - boolean, whether there is anything on a history stack to go back
|
||||
* - canGoForward - boolean, whether it is possible to request GO_FORWARD command
|
||||
*/
|
||||
public class ReactWebViewManager extends SimpleViewManager<WebView> {
|
||||
|
||||
private static final String REACT_CLASS = "RCTWebView";
|
||||
|
||||
private static final String HTML_ENCODING = "UTF-8";
|
||||
private static final String HTML_MIME_TYPE = "text/html";
|
||||
|
||||
public static final int COMMAND_GO_BACK = 1;
|
||||
public static final int COMMAND_GO_FORWARD = 2;
|
||||
public static final int COMMAND_RELOAD = 3;
|
||||
|
||||
// Use `webView.loadUrl("about:blank")` to reliably reset the view
|
||||
// state and release page resources (including any running JavaScript).
|
||||
private static final String BLANK_URL = "about:blank";
|
||||
|
||||
private WebViewConfig mWebViewConfig;
|
||||
|
||||
static {
|
||||
if (ReactBuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
WebView.setWebContentsDebuggingEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ReactWebViewClient extends WebViewClient {
|
||||
|
||||
private boolean mLastLoadFailed = false;
|
||||
|
||||
@Override
|
||||
public void onPageFinished(WebView webView, String url) {
|
||||
super.onPageFinished(webView, url);
|
||||
|
||||
if (!mLastLoadFailed) {
|
||||
ReactWebView reactWebView = (ReactWebView) webView;
|
||||
reactWebView.callInjectedJavaScript();
|
||||
emitFinishEvent(webView, url);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageStarted(WebView webView, String url, Bitmap favicon) {
|
||||
super.onPageStarted(webView, url, favicon);
|
||||
mLastLoadFailed = false;
|
||||
|
||||
ReactContext reactContext = (ReactContext) ((ReactWebView) webView).getContext();
|
||||
EventDispatcher eventDispatcher =
|
||||
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
|
||||
eventDispatcher.dispatchEvent(
|
||||
new TopLoadingStartEvent(
|
||||
webView.getId(),
|
||||
SystemClock.uptimeMillis(),
|
||||
createWebViewEvent(webView, url)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedError(
|
||||
WebView webView,
|
||||
int errorCode,
|
||||
String description,
|
||||
String failingUrl) {
|
||||
super.onReceivedError(webView, errorCode, description, failingUrl);
|
||||
mLastLoadFailed = true;
|
||||
|
||||
// In case of an error JS side expect to get a finish event first, and then get an error event
|
||||
// Android WebView does it in the opposite way, so we need to simulate that behavior
|
||||
emitFinishEvent(webView, failingUrl);
|
||||
|
||||
ReactContext reactContext = (ReactContext) ((ReactWebView) webView).getContext();
|
||||
WritableMap eventData = createWebViewEvent(webView, failingUrl);
|
||||
eventData.putDouble("code", errorCode);
|
||||
eventData.putString("description", description);
|
||||
|
||||
EventDispatcher eventDispatcher =
|
||||
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
|
||||
eventDispatcher.dispatchEvent(
|
||||
new TopLoadingErrorEvent(webView.getId(), SystemClock.uptimeMillis(), eventData));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doUpdateVisitedHistory(WebView webView, String url, boolean isReload) {
|
||||
super.doUpdateVisitedHistory(webView, url, isReload);
|
||||
|
||||
ReactContext reactContext = (ReactContext) webView.getContext();
|
||||
EventDispatcher eventDispatcher =
|
||||
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
|
||||
eventDispatcher.dispatchEvent(
|
||||
new TopLoadingStartEvent(
|
||||
webView.getId(),
|
||||
SystemClock.uptimeMillis(),
|
||||
createWebViewEvent(webView, url)));
|
||||
}
|
||||
|
||||
private void emitFinishEvent(WebView webView, String url) {
|
||||
ReactContext reactContext = (ReactContext) webView.getContext();
|
||||
|
||||
EventDispatcher eventDispatcher =
|
||||
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
|
||||
eventDispatcher.dispatchEvent(
|
||||
new TopLoadingFinishEvent(
|
||||
webView.getId(),
|
||||
SystemClock.uptimeMillis(),
|
||||
createWebViewEvent(webView, url)));
|
||||
}
|
||||
|
||||
private WritableMap createWebViewEvent(WebView webView, String url) {
|
||||
WritableMap event = Arguments.createMap();
|
||||
event.putDouble("target", webView.getId());
|
||||
// Don't use webView.getUrl() here, the URL isn't updated to the new value yet in callbacks
|
||||
// like onPageFinished
|
||||
event.putString("url", url);
|
||||
event.putBoolean("loading", !mLastLoadFailed && webView.getProgress() != 100);
|
||||
event.putString("title", webView.getTitle());
|
||||
event.putBoolean("canGoBack", webView.canGoBack());
|
||||
event.putBoolean("canGoForward", webView.canGoForward());
|
||||
return event;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass of {@link WebView} that implements {@link LifecycleEventListener} interface in order
|
||||
* to call {@link WebView#destroy} on activty destroy event and also to clear the client
|
||||
*/
|
||||
private static class ReactWebView extends WebView implements LifecycleEventListener {
|
||||
private @Nullable String injectedJS;
|
||||
|
||||
/**
|
||||
* WebView must be created with an context of the current activity
|
||||
*
|
||||
* Activity Context is required for creation of dialogs internally by WebView
|
||||
* Reactive Native needed for access to ReactNative internal system functionality
|
||||
*
|
||||
*/
|
||||
public ReactWebView(ThemedReactContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostResume() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostPause() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostDestroy() {
|
||||
cleanupCallbacksAndDestroy();
|
||||
}
|
||||
|
||||
public void setInjectedJavaScript(@Nullable String js) {
|
||||
injectedJS = js;
|
||||
}
|
||||
|
||||
public void callInjectedJavaScript() {
|
||||
if (
|
||||
getSettings().getJavaScriptEnabled() &&
|
||||
injectedJS != null &&
|
||||
!TextUtils.isEmpty(injectedJS)) {
|
||||
loadUrl("javascript:(function() {\n" + injectedJS + ";\n})();");
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanupCallbacksAndDestroy() {
|
||||
setWebViewClient(null);
|
||||
destroy();
|
||||
}
|
||||
}
|
||||
|
||||
public ReactWebViewManager() {
|
||||
mWebViewConfig = new WebViewConfig() {
|
||||
public void configWebView(WebView webView) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public ReactWebViewManager(WebViewConfig webViewConfig) {
|
||||
mWebViewConfig = webViewConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return REACT_CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected WebView createViewInstance(ThemedReactContext reactContext) {
|
||||
ReactWebView webView = new ReactWebView(reactContext);
|
||||
reactContext.addLifecycleEventListener(webView);
|
||||
mWebViewConfig.configWebView(webView);
|
||||
return webView;
|
||||
}
|
||||
|
||||
@ReactProp(name = "javaScriptEnabledAndroid")
|
||||
public void setJavaScriptEnabled(WebView view, boolean enabled) {
|
||||
view.getSettings().setJavaScriptEnabled(enabled);
|
||||
}
|
||||
|
||||
@ReactProp(name = "userAgent")
|
||||
public void setUserAgent(WebView view, @Nullable String userAgent) {
|
||||
if (userAgent != null) {
|
||||
// TODO(8496850): Fix incorrect behavior when property is unset (uA == null)
|
||||
view.getSettings().setUserAgentString(userAgent);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = "injectedJavaScript")
|
||||
public void setInjectedJavaScript(WebView view, @Nullable String injectedJavaScript) {
|
||||
((ReactWebView) view).setInjectedJavaScript(injectedJavaScript);
|
||||
}
|
||||
|
||||
@ReactProp(name = "html")
|
||||
public void setHtml(WebView view, @Nullable String html) {
|
||||
if (html != null) {
|
||||
view.loadData(html, HTML_MIME_TYPE, HTML_ENCODING);
|
||||
} else {
|
||||
view.loadUrl(BLANK_URL);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = "url")
|
||||
public void setUrl(WebView view, @Nullable String url) {
|
||||
// TODO(8495359): url and html are coupled as they both call loadUrl, therefore in case when
|
||||
// property url is removed in favor of property html being added in single transaction we may
|
||||
// end up in a state when blank url is loaded as it depends on the order of update operations!
|
||||
if (url != null) {
|
||||
view.loadUrl(url);
|
||||
} else {
|
||||
view.loadUrl(BLANK_URL);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addEventEmitters(ThemedReactContext reactContext, WebView view) {
|
||||
// Do not register default touch emitter and let WebView implementation handle touches
|
||||
view.setWebViewClient(new ReactWebViewClient());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Map<String, Integer> getCommandsMap() {
|
||||
return MapBuilder.of(
|
||||
"goBack", COMMAND_GO_BACK,
|
||||
"goForward", COMMAND_GO_FORWARD,
|
||||
"reload", COMMAND_RELOAD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveCommand(WebView root, int commandId, @Nullable ReadableArray args) {
|
||||
switch (commandId) {
|
||||
case COMMAND_GO_BACK:
|
||||
root.goBack();
|
||||
break;
|
||||
case COMMAND_GO_FORWARD:
|
||||
root.goForward();
|
||||
break;
|
||||
case COMMAND_RELOAD:
|
||||
root.reload();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDropViewInstance(ThemedReactContext reactContext, WebView webView) {
|
||||
super.onDropViewInstance(reactContext, webView);
|
||||
reactContext.removeLifecycleEventListener((ReactWebView) webView);
|
||||
((ReactWebView) webView).cleanupCallbacksAndDestroy();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.views.webview;
|
||||
|
||||
import android.webkit.WebView;
|
||||
|
||||
/**
|
||||
* Implement this interface in order to config your {@link WebView}. An instance of that
|
||||
* implementation will have to be given as a constructor argument to {@link ReactWebViewManager}.
|
||||
*/
|
||||
public interface WebViewConfig {
|
||||
|
||||
void configWebView(WebView webView);
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.catalyst.views.webview.events;
|
||||
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.events.Event;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
|
||||
/**
|
||||
* Event emitted when there is an error in loading.
|
||||
*/
|
||||
public class TopLoadingErrorEvent extends Event<TopLoadingErrorEvent> {
|
||||
|
||||
public static final String EVENT_NAME = "topLoadingError";
|
||||
private WritableMap mEventData;
|
||||
|
||||
public TopLoadingErrorEvent(int viewId, long timestampMs, WritableMap eventData) {
|
||||
super(viewId, timestampMs);
|
||||
mEventData = eventData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return EVENT_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canCoalesce() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getCoalescingKey() {
|
||||
// All events for a given view can be coalesced.
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), mEventData);
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.catalyst.views.webview.events;
|
||||
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.events.Event;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
|
||||
/**
|
||||
* Event emitted when loading is completed.
|
||||
*/
|
||||
public class TopLoadingFinishEvent extends Event<TopLoadingFinishEvent> {
|
||||
|
||||
public static final String EVENT_NAME = "topLoadingFinish";
|
||||
private WritableMap mEventData;
|
||||
|
||||
public TopLoadingFinishEvent(int viewId, long timestampMs, WritableMap eventData) {
|
||||
super(viewId, timestampMs);
|
||||
mEventData = eventData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return EVENT_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canCoalesce() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getCoalescingKey() {
|
||||
// All events for a given view can be coalesced.
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), mEventData);
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.catalyst.views.webview.events;
|
||||
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.events.Event;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
|
||||
/**
|
||||
* Event emitted when loading has started
|
||||
*/
|
||||
public class TopLoadingStartEvent extends Event<TopLoadingStartEvent> {
|
||||
|
||||
public static final String EVENT_NAME = "topLoadingStart";
|
||||
private WritableMap mEventData;
|
||||
|
||||
public TopLoadingStartEvent(int viewId, long timestampMs, WritableMap eventData) {
|
||||
super(viewId, timestampMs);
|
||||
mEventData = eventData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return EVENT_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canCoalesce() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getCoalescingKey() {
|
||||
// All events for a given view can be coalesced.
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), mEventData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.dialog;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.app.Activity;
|
||||
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.SimpleMap;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.powermock.api.mockito.PowerMockito;
|
||||
import org.powermock.core.classloader.annotations.PowerMockIgnore;
|
||||
import org.robolectric.Robolectric;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.util.ActivityController;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
|
||||
public class DialogModuleTest {
|
||||
|
||||
private ActivityController<Activity> mActivityController;
|
||||
private Activity mActivity;
|
||||
private DialogModule mDialogModule;
|
||||
|
||||
final static class SimpleCallback implements Callback {
|
||||
private Object[] mArgs;
|
||||
private int mCalls;
|
||||
|
||||
@Override
|
||||
public void invoke(Object... args) {
|
||||
mCalls++;
|
||||
mArgs = args;
|
||||
}
|
||||
|
||||
public int getCalls() {
|
||||
return mCalls;
|
||||
}
|
||||
|
||||
public Object[] getArgs() {
|
||||
return mArgs;
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mActivityController = Robolectric.buildActivity(Activity.class);
|
||||
mActivity = mActivityController
|
||||
.create()
|
||||
.start()
|
||||
.resume()
|
||||
.get();
|
||||
|
||||
final ReactApplicationContext context = PowerMockito.mock(ReactApplicationContext.class);
|
||||
PowerMockito.when(context.hasActiveCatalystInstance()).thenReturn(true);
|
||||
PowerMockito.when(context, "getCurrentActivity").thenReturn(mActivity);
|
||||
|
||||
mDialogModule = new DialogModule(context);
|
||||
mDialogModule.onHostResume();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
mActivityController.pause().stop().destroy();
|
||||
|
||||
mActivityController = null;
|
||||
mDialogModule = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAllOptions() {
|
||||
final SimpleMap options = new SimpleMap();
|
||||
options.putString("title", "Title");
|
||||
options.putString("message", "Message");
|
||||
options.putString("buttonPositive", "OK");
|
||||
options.putString("buttonNegative", "Cancel");
|
||||
options.putString("buttonNeutral", "Later");
|
||||
|
||||
mDialogModule.showAlert(options, null, null);
|
||||
|
||||
final AlertFragment fragment = getFragment();
|
||||
assertNotNull("Fragment was not displayed", fragment);
|
||||
|
||||
final AlertDialog dialog = (AlertDialog) fragment.getDialog();
|
||||
assertEquals("OK", dialog.getButton(DialogInterface.BUTTON_POSITIVE).getText().toString());
|
||||
assertEquals("Cancel", dialog.getButton(DialogInterface.BUTTON_NEGATIVE).getText().toString());
|
||||
assertEquals("Later", dialog.getButton(DialogInterface.BUTTON_NEUTRAL).getText().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCallbackPositive() {
|
||||
final SimpleMap options = new SimpleMap();
|
||||
options.putString("buttonPositive", "OK");
|
||||
|
||||
final SimpleCallback actionCallback = new SimpleCallback();
|
||||
mDialogModule.showAlert(options, null, actionCallback);
|
||||
|
||||
final AlertDialog dialog = (AlertDialog) getFragment().getDialog();
|
||||
dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick();
|
||||
|
||||
assertEquals(1, actionCallback.getCalls());
|
||||
assertEquals(DialogModule.ACTION_BUTTON_CLICKED, actionCallback.getArgs()[0]);
|
||||
assertEquals(DialogInterface.BUTTON_POSITIVE, actionCallback.getArgs()[1]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCallbackNegative() {
|
||||
final SimpleMap options = new SimpleMap();
|
||||
options.putString("buttonNegative", "Cancel");
|
||||
|
||||
final SimpleCallback actionCallback = new SimpleCallback();
|
||||
mDialogModule.showAlert(options, null, actionCallback);
|
||||
|
||||
final AlertDialog dialog = (AlertDialog) getFragment().getDialog();
|
||||
dialog.getButton(DialogInterface.BUTTON_NEGATIVE).performClick();
|
||||
|
||||
assertEquals(1, actionCallback.getCalls());
|
||||
assertEquals(DialogModule.ACTION_BUTTON_CLICKED, actionCallback.getArgs()[0]);
|
||||
assertEquals(DialogInterface.BUTTON_NEGATIVE, actionCallback.getArgs()[1]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCallbackNeutral() {
|
||||
final SimpleMap options = new SimpleMap();
|
||||
options.putString("buttonNeutral", "Later");
|
||||
|
||||
final SimpleCallback actionCallback = new SimpleCallback();
|
||||
mDialogModule.showAlert(options, null, actionCallback);
|
||||
|
||||
final AlertDialog dialog = (AlertDialog) getFragment().getDialog();
|
||||
dialog.getButton(DialogInterface.BUTTON_NEUTRAL).performClick();
|
||||
|
||||
assertEquals(1, actionCallback.getCalls());
|
||||
assertEquals(DialogModule.ACTION_BUTTON_CLICKED, actionCallback.getArgs()[0]);
|
||||
assertEquals(DialogInterface.BUTTON_NEUTRAL, actionCallback.getArgs()[1]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCallbackDismiss() {
|
||||
final SimpleMap options = new SimpleMap();
|
||||
|
||||
final SimpleCallback actionCallback = new SimpleCallback();
|
||||
mDialogModule.showAlert(options, null, actionCallback);
|
||||
|
||||
getFragment().getDialog().dismiss();
|
||||
|
||||
assertEquals(1, actionCallback.getCalls());
|
||||
assertEquals(DialogModule.ACTION_DISMISSED, actionCallback.getArgs()[0]);
|
||||
}
|
||||
|
||||
private AlertFragment getFragment() {
|
||||
return (AlertFragment) mActivity.getFragmentManager()
|
||||
.findFragmentByTag(DialogModule.FRAGMENT_TAG);
|
||||
}
|
||||
}
|
||||
@@ -74,5 +74,5 @@ android {
|
||||
dependencies {
|
||||
compile fileTree(dir: "libs", include: ["*.jar"])
|
||||
compile "com.android.support:appcompat-v7:23.0.1"
|
||||
compile "com.facebook.react:react-native:0.13.0"
|
||||
compile "com.facebook.react:react-native:0.17.+"
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-native",
|
||||
"version": "0.12.0",
|
||||
"version": "0.17.0",
|
||||
"description": "A framework for building native apps using React",
|
||||
"license": "BSD-3-Clause",
|
||||
"repository": {
|
||||
|
||||
Reference in New Issue
Block a user