Janic Duplessis 4c245160bd Replace deprecated ActivityIndicatorIOS and ProgressBar with ActivityIndicator
Summary:
This replaces ActivityIndicatorIOS and indeterminate ProgressBar that were deprecated recently with ActivityIndicator across the codebase and examples and a few other cleanups.

This also make a small tweak to ActivityIndicator so it uses the Android theme color instead of gray when no color is specified.

Use Slider instead of SliderIOS in CameraRoll example.

Remove the line about unifying ActivityIndicator and ProgressBar.

**Test plan**
Tested the affected components in UIExplorer on iOS and Android, tested the changes made in Movies example on iOS and Android.
Closes https://github.com/facebook/react-native/pull/8082

Differential Revision: D3429770

fbshipit-source-id: 3b2e1196a8b9fe00d47a7aa1bbc079b094796421
2016-06-13 22:28:24 -07:00

465 lines
12 KiB
JavaScript

/**
* 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 WebView
* @noflow
*/
'use strict';
var ActivityIndicator = require('ActivityIndicator');
var EdgeInsetsPropType = require('EdgeInsetsPropType');
var React = require('React');
var ReactNative = require('ReactNative');
var StyleSheet = require('StyleSheet');
var Text = require('Text');
var UIManager = require('UIManager');
var View = require('View');
var ScrollView = require('ScrollView');
var deprecatedPropType = require('deprecatedPropType');
var invariant = require('fbjs/lib/invariant');
var keyMirror = require('fbjs/lib/keyMirror');
var processDecelerationRate = require('processDecelerationRate');
var requireNativeComponent = require('requireNativeComponent');
var resolveAssetSource = require('resolveAssetSource');
var PropTypes = React.PropTypes;
var RCTWebViewManager = require('NativeModules').WebViewManager;
var BGWASH = 'rgba(255,255,255,0.8)';
var RCT_WEBVIEW_REF = 'webview';
var WebViewState = keyMirror({
IDLE: null,
LOADING: null,
ERROR: null,
});
const NavigationType = keyMirror({
click: true,
formsubmit: true,
backforward: true,
reload: true,
formresubmit: true,
other: true,
});
const JSNavigationScheme = 'react-js-navigation';
type ErrorEvent = {
domain: any;
code: any;
description: any;
}
type Event = Object;
var defaultRenderLoading = () => (
<View style={styles.loadingView}>
<ActivityIndicator />
</View>
);
var defaultRenderError = (errorDomain, errorCode, errorDesc) => (
<View style={styles.errorContainer}>
<Text style={styles.errorTextTitle}>
Error loading page
</Text>
<Text style={styles.errorText}>
{'Domain: ' + errorDomain}
</Text>
<Text style={styles.errorText}>
{'Error Code: ' + errorCode}
</Text>
<Text style={styles.errorText}>
{'Description: ' + errorDesc}
</Text>
</View>
);
/**
* Renders a native WebView.
*/
var WebView = React.createClass({
statics: {
JSNavigationScheme: JSNavigationScheme,
NavigationType: NavigationType,
},
propTypes: {
...View.propTypes,
html: deprecatedPropType(
PropTypes.string,
'Use the `source` prop instead.'
),
url: deprecatedPropType(
PropTypes.string,
'Use the `source` prop instead.'
),
/**
* Loads static html or a uri (with optional headers) in the WebView.
*/
source: PropTypes.oneOfType([
PropTypes.shape({
/*
* The URI to load in the WebView. Can be a local or remote file.
*/
uri: PropTypes.string,
/*
* The HTTP Method to use. Defaults to GET if not specified.
* NOTE: On Android, only GET and POST are supported.
*/
method: PropTypes.string,
/*
* Additional HTTP headers to send with the request.
* NOTE: On Android, this can only be used with GET requests.
*/
headers: PropTypes.object,
/*
* The HTTP body to send with the request. This must be a valid
* UTF-8 string, and will be sent exactly as specified, with no
* additional encoding (e.g. URL-escaping or base64) applied.
* NOTE: On Android, this can only be used with POST requests.
*/
body: PropTypes.string,
}),
PropTypes.shape({
/*
* A static HTML page to display in the WebView.
*/
html: PropTypes.string,
/*
* The base URL to be used for any relative links in the HTML.
*/
baseUrl: PropTypes.string,
}),
/*
* Used internally by packager.
*/
PropTypes.number,
]),
/**
* Function that returns a view to show if there's an error.
*/
renderError: PropTypes.func, // view to show if there's an error
/**
* Function that returns a loading indicator.
*/
renderLoading: PropTypes.func,
/**
* Invoked when load finish
*/
onLoad: PropTypes.func,
/**
* Invoked when load either succeeds or fails
*/
onLoadEnd: PropTypes.func,
/**
* Invoked on load start
*/
onLoadStart: PropTypes.func,
/**
* Invoked when load fails
*/
onError: PropTypes.func,
/**
* @platform ios
*/
bounces: PropTypes.bool,
/**
* A floating-point number that determines how quickly the scroll view
* decelerates after the user lifts their finger. You may also use string
* shortcuts `"normal"` and `"fast"` which match the underlying iOS settings
* for `UIScrollViewDecelerationRateNormal` and
* `UIScrollViewDecelerationRateFast` respectively.
* - normal: 0.998
* - fast: 0.99 (the default for iOS WebView)
* @platform ios
*/
decelerationRate: ScrollView.propTypes.decelerationRate,
/**
* @platform ios
*/
scrollEnabled: PropTypes.bool,
automaticallyAdjustContentInsets: PropTypes.bool,
contentInset: EdgeInsetsPropType,
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
*/
javaScriptEnabled: PropTypes.bool,
/**
* Used on Android only, controls whether DOM Storage is enabled or not
* @platform android
*/
domStorageEnabled: PropTypes.bool,
/**
* Sets the JS to be injected when the webpage loads.
*/
injectedJavaScript: PropTypes.string,
/**
* Sets whether the webpage scales to fit the view and the user can change the scale.
*/
scalesPageToFit: PropTypes.bool,
/**
* Allows custom handling of any webview requests by a JS handler. Return true
* or false from this method to continue loading the request.
* @platform ios
*/
onShouldStartLoadWithRequest: PropTypes.func,
/**
* Determines whether HTML5 videos play inline or use the native full-screen
* controller.
* default value `false`
* **NOTE** : "In order for video to play inline, not only does this
* property need to be set to true, but the video element in the HTML
* document must also include the webkit-playsinline attribute."
* @platform ios
*/
allowsInlineMediaPlayback: PropTypes.bool,
/**
* Determines whether HTML5 audio & videos require the user to tap before they can
* start playing. The default value is `false`.
*/
mediaPlaybackRequiresUserAction: PropTypes.bool,
},
getInitialState: function() {
return {
viewState: WebViewState.IDLE,
lastErrorEvent: (null: ?ErrorEvent),
startInLoadingState: true,
};
},
componentWillMount: function() {
if (this.props.startInLoadingState) {
this.setState({viewState: WebViewState.LOADING});
}
},
render: function() {
var otherView = null;
if (this.state.viewState === WebViewState.LOADING) {
otherView = (this.props.renderLoading || defaultRenderLoading)();
} else if (this.state.viewState === WebViewState.ERROR) {
var errorEvent = this.state.lastErrorEvent;
invariant(
errorEvent != null,
'lastErrorEvent expected to be non-null'
);
otherView = (this.props.renderError || defaultRenderError)(
errorEvent.domain,
errorEvent.code,
errorEvent.description
);
} else if (this.state.viewState !== WebViewState.IDLE) {
console.error(
'RCTWebView invalid state encountered: ' + this.state.loading
);
}
var webViewStyles = [styles.container, styles.webView, this.props.style];
if (this.state.viewState === WebViewState.LOADING ||
this.state.viewState === WebViewState.ERROR) {
// if we're in either LOADING or ERROR states, don't show the webView
webViewStyles.push(styles.hidden);
}
var onShouldStartLoadWithRequest = this.props.onShouldStartLoadWithRequest && ((event: Event) => {
var shouldStart = this.props.onShouldStartLoadWithRequest &&
this.props.onShouldStartLoadWithRequest(event.nativeEvent);
RCTWebViewManager.startLoadWithResult(!!shouldStart, event.nativeEvent.lockIdentifier);
});
var decelerationRate = processDecelerationRate(this.props.decelerationRate);
var source = this.props.source || {};
if (this.props.html) {
source.html = this.props.html;
} else if (this.props.url) {
source.uri = this.props.url;
}
var webView =
<RCTWebView
ref={RCT_WEBVIEW_REF}
key="webViewKey"
style={webViewStyles}
source={resolveAssetSource(source)}
injectedJavaScript={this.props.injectedJavaScript}
bounces={this.props.bounces}
scrollEnabled={this.props.scrollEnabled}
decelerationRate={decelerationRate}
contentInset={this.props.contentInset}
automaticallyAdjustContentInsets={this.props.automaticallyAdjustContentInsets}
onLoadingStart={this._onLoadingStart}
onLoadingFinish={this._onLoadingFinish}
onLoadingError={this._onLoadingError}
onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
scalesPageToFit={this.props.scalesPageToFit}
allowsInlineMediaPlayback={this.props.allowsInlineMediaPlayback}
mediaPlaybackRequiresUserAction={this.props.mediaPlaybackRequiresUserAction}
/>;
return (
<View style={styles.container}>
{webView}
{otherView}
</View>
);
},
/**
* Go forward one page in the webview's history.
*/
goForward: function() {
UIManager.dispatchViewManagerCommand(
this.getWebViewHandle(),
UIManager.RCTWebView.Commands.goForward,
null
);
},
/**
* Go back one page in the webview's history.
*/
goBack: function() {
UIManager.dispatchViewManagerCommand(
this.getWebViewHandle(),
UIManager.RCTWebView.Commands.goBack,
null
);
},
/**
* Reloads the current page.
*/
reload: function() {
UIManager.dispatchViewManagerCommand(
this.getWebViewHandle(),
UIManager.RCTWebView.Commands.reload,
null
);
},
stopLoading: function() {
UIManager.dispatchViewManagerCommand(
this.getWebViewHandle(),
UIManager.RCTWebView.Commands.stopLoading,
null
);
},
/**
* We return an event with a bunch of fields including:
* url, title, loading, canGoBack, canGoForward
*/
_updateNavigationState: function(event: Event) {
if (this.props.onNavigationStateChange) {
this.props.onNavigationStateChange(event.nativeEvent);
}
},
/**
* Returns the native webview node.
*/
getWebViewHandle: function(): any {
return ReactNative.findNodeHandle(this.refs[RCT_WEBVIEW_REF]);
},
_onLoadingStart: function(event: Event) {
var onLoadStart = this.props.onLoadStart;
onLoadStart && onLoadStart(event);
this._updateNavigationState(event);
},
_onLoadingError: function(event: Event) {
event.persist(); // persist this event because we need to store it
var {onError, onLoadEnd} = this.props;
onError && onError(event);
onLoadEnd && onLoadEnd(event);
console.warn('Encountered an error loading page', event.nativeEvent);
this.setState({
lastErrorEvent: event.nativeEvent,
viewState: WebViewState.ERROR
});
},
_onLoadingFinish: function(event: Event) {
var {onLoad, onLoadEnd} = this.props;
onLoad && onLoad(event);
onLoadEnd && onLoadEnd(event);
this.setState({
viewState: WebViewState.IDLE,
});
this._updateNavigationState(event);
},
});
var RCTWebView = requireNativeComponent('RCTWebView', WebView, {
nativeOnly: {
onLoadingStart: true,
onLoadingError: true,
onLoadingFinish: true,
},
});
var styles = StyleSheet.create({
container: {
flex: 1,
},
errorContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: BGWASH,
},
errorText: {
fontSize: 14,
textAlign: 'center',
marginBottom: 2,
},
errorTextTitle: {
fontSize: 15,
fontWeight: '500',
marginBottom: 10,
},
hidden: {
height: 0,
flex: 0, // disable 'flex:1' when hiding a View
},
loadingView: {
backgroundColor: BGWASH,
flex: 1,
justifyContent: 'center',
alignItems: 'center',
height: 100,
},
webView: {
backgroundColor: '#ffffff',
}
});
module.exports = WebView;