mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-27 19:25:11 +08:00
Updates from Tue 7 Apr
- [AdsManager] Correct back button functionality | Eric Vicenti - [ReactNative] Replace Backstack with BackAndroid | Eric Vicenti - [ReactNative] Better error message for EADDRINUSE | Alex Kotliarskyi - [ReactNative] npm install --save chalk | Alex Kotliarskyi - Removed redundant views and shadow views | Nick Lockwood - [ReactNative] Fix variable shadowing in RCTText | Tadeu Zagallo - [react-packager] check in image-size module | Amjad Masad - Refactored RCTLog and added facility to prepend extra data to the log message | Nick Lockwood - [ReactNative] Fix crash on image download | Tadeu Zagallo - [React Native] #WIP Modify RCTShadowText measure function to honor maxNumberOfLines property | Alex Akers - Add promise support to AsyncStorage | Spencer Ahrens - [ReactNative] Revert high-level Subscribable | Eric Vicenti - [ReactNative] wrong deprecated prop check in RCTConvert | Kevin Gozali - [ReactNative][MAdMan] Add type for image source, flowify AdsManagerObjectiveTypes | Philipp von Weitershausen
This commit is contained in:
@@ -71,7 +71,7 @@
|
||||
|
||||
UIView *view = viewRegistry[reactTag];
|
||||
if (!view) {
|
||||
RCTLogWarn(@"React tag %@ is not registered with the view registry", reactTag);
|
||||
RCTLogWarn(@"React tag #%@ is not registered with the view registry", reactTag);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,308 +11,25 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Subscribable wraps EventEmitter in a clean interface, and provides a mixin
|
||||
* so components can easily subscribe to events and not worry about cleanup on
|
||||
* unmount.
|
||||
*
|
||||
* Also acts as a basic store because it records the last data that it emitted,
|
||||
* and provides a way to populate the initial data. The most recent data can be
|
||||
* fetched from the Subscribable by calling `get()`
|
||||
*
|
||||
* Advantages over EventEmitter + Subscibable.Mixin.addListenerOn:
|
||||
* - Cleaner usage: no strings to identify the event
|
||||
* - Lifespan pattern enforces cleanup
|
||||
* - More logical: Subscribable.Mixin now uses a Subscribable class
|
||||
* - Subscribable saves the last data and makes it available with `.get()`
|
||||
*
|
||||
* Legacy Subscribable.Mixin.addListenerOn allowed automatic subscription to
|
||||
* EventEmitters. Now we should avoid EventEmitters and wrap with Subscribable
|
||||
* instead:
|
||||
*
|
||||
* ```
|
||||
* AppState.networkReachability = new Subscribable(
|
||||
* RCTDeviceEventEmitter,
|
||||
* 'reachabilityDidChange',
|
||||
* (resp) => resp.network_reachability,
|
||||
* RCTReachability.getCurrentReachability
|
||||
* );
|
||||
*
|
||||
* var myComponent = React.createClass({
|
||||
* mixins: [Subscribable.Mixin],
|
||||
* getInitialState: function() {
|
||||
* return {
|
||||
* isConnected: AppState.networkReachability.get() !== 'none'
|
||||
* };
|
||||
* },
|
||||
* componentDidMount: function() {
|
||||
* this._reachSubscription = this.subscribeTo(
|
||||
* AppState.networkReachability,
|
||||
* (reachability) => {
|
||||
* this.setState({ isConnected: reachability !== 'none' })
|
||||
* }
|
||||
* );
|
||||
* },
|
||||
* render: function() {
|
||||
* return (
|
||||
* <Text>
|
||||
* {this.state.isConnected ? 'Network Connected' : 'No network'}
|
||||
* </Text>
|
||||
* <Text onPress={() => this._reachSubscription.remove()}>
|
||||
* End reachability subscription
|
||||
* </Text>
|
||||
* );
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
|
||||
var EventEmitter = require('EventEmitter');
|
||||
|
||||
var invariant = require('invariant');
|
||||
var logError = require('logError');
|
||||
|
||||
var SUBSCRIBABLE_INTERNAL_EVENT = 'subscriptionEvent';
|
||||
|
||||
type Data = Object;
|
||||
type EventMapping = (_: Data) => Data;
|
||||
|
||||
class Subscribable {
|
||||
_eventMapping: EventMapping;
|
||||
_lastData: Data;
|
||||
|
||||
/**
|
||||
* Creates a new Subscribable object
|
||||
*
|
||||
* @param {EventEmitter} eventEmitter Emitter to trigger subscription events.
|
||||
* @param {string} eventName Name of emitted event that triggers subscription
|
||||
* events.
|
||||
* @param {function} eventMapping (optional) Function to convert the output
|
||||
* of the eventEmitter to the subscription output.
|
||||
* @param {function} getInitData (optional) Async function to grab the initial
|
||||
* data to publish. Signature `function(successCallback, errorCallback)`.
|
||||
* The resolved data will be transformed with the eventMapping before it
|
||||
* gets emitted.
|
||||
*/
|
||||
constructor(eventEmitter: EventEmitter, eventName: string, eventMapping?: EventMapping, getInitData?: Function) {
|
||||
|
||||
this._internalEmitter = new EventEmitter();
|
||||
this._eventMapping = eventMapping || (data => data);
|
||||
|
||||
this._upstreamSubscription = eventEmitter.addListener(
|
||||
eventName,
|
||||
this._handleEmit,
|
||||
this
|
||||
);
|
||||
|
||||
// Asyncronously get the initial data, if provided
|
||||
getInitData && getInitData(this._handleInitData.bind(this), logError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last data emitted from the Subscribable, or undefined
|
||||
*/
|
||||
get(): Data {
|
||||
return this._lastData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from the upstream EventEmitter
|
||||
*/
|
||||
cleanup() {
|
||||
this._upstreamSubscription && this._upstreamSubscription.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new listener to the subscribable. This should almost never be used
|
||||
* directly, and instead through Subscribable.Mixin.subscribeTo
|
||||
*
|
||||
* @param {object} lifespan Object with `addUnmountCallback` that accepts
|
||||
* a handler to be called when the component unmounts. This is required and
|
||||
* desirable because it enforces cleanup. There is no easy way to leave the
|
||||
* subsciption hanging
|
||||
* {
|
||||
* addUnmountCallback: function(newUnmountHanlder) {...},
|
||||
* }
|
||||
* @param {function} callback Handler to call when Subscribable has data
|
||||
* updates
|
||||
* @param {object} context Object to bind the handler on, as "this"
|
||||
*
|
||||
* @return {object} the subscription object:
|
||||
* {
|
||||
* remove: function() {...},
|
||||
* }
|
||||
* Call `remove` to terminate the subscription before unmounting
|
||||
*/
|
||||
subscribe(lifespan: { addUnmountCallback: Function }, callback: Function, context: Object) {
|
||||
invariant(
|
||||
typeof lifespan.addUnmountCallback === 'function',
|
||||
'Must provide a valid lifespan, which provides a way to add a ' +
|
||||
'callback for when subscription can be cleaned up. This is used ' +
|
||||
'automatically by Subscribable.Mixin'
|
||||
);
|
||||
invariant(
|
||||
typeof callback === 'function',
|
||||
'Must provide a valid subscription handler.'
|
||||
);
|
||||
|
||||
// Add a listener to the internal EventEmitter
|
||||
var subscription = this._internalEmitter.addListener(
|
||||
SUBSCRIBABLE_INTERNAL_EVENT,
|
||||
callback,
|
||||
context
|
||||
);
|
||||
|
||||
// Clean up subscription upon the lifespan unmount callback
|
||||
lifespan.addUnmountCallback(() => {
|
||||
subscription.remove();
|
||||
});
|
||||
|
||||
return subscription;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for the initial data resolution. Currently behaves the same as
|
||||
* `_handleEmit`, but we may eventually want to keep track of the difference
|
||||
*/
|
||||
_handleInitData(dataInput: Data) {
|
||||
var emitData = this._eventMapping(dataInput);
|
||||
this._lastData = emitData;
|
||||
this._internalEmitter.emit(SUBSCRIBABLE_INTERNAL_EVENT, emitData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle new data emissions. Pass the data through our eventMapping
|
||||
* transformation, store it for later `get()`ing, and emit it for subscribers
|
||||
*/
|
||||
_handleEmit(dataInput: Data) {
|
||||
var emitData = this._eventMapping(dataInput);
|
||||
this._lastData = emitData;
|
||||
this._internalEmitter.emit(SUBSCRIBABLE_INTERNAL_EVENT, emitData);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Subscribable provides a mixin for safely subscribing a component to an
|
||||
* eventEmitter
|
||||
*
|
||||
* This will be replaced with the observe interface that will be coming soon to
|
||||
* React Core
|
||||
*/
|
||||
|
||||
var Subscribable = {};
|
||||
|
||||
Subscribable.Mixin = {
|
||||
|
||||
/**
|
||||
* @return {object} lifespan Object with `addUnmountCallback` that accepts
|
||||
* a handler to be called when the component unmounts
|
||||
* {
|
||||
* addUnmountCallback: function(newUnmountHanlder) {...},
|
||||
* }
|
||||
*/
|
||||
_getSubscribableLifespan: function() {
|
||||
if (!this._subscribableLifespan) {
|
||||
this._subscribableLifespan = {
|
||||
addUnmountCallback: (cb) => {
|
||||
this._endSubscribableLifespanCallbacks.push(cb);
|
||||
},
|
||||
};
|
||||
}
|
||||
return this._subscribableLifespan;
|
||||
},
|
||||
|
||||
_endSubscribableLifespan: function() {
|
||||
this._endSubscribableLifespanCallbacks.forEach(cb => cb());
|
||||
},
|
||||
|
||||
/**
|
||||
* Components use `subscribeTo` for listening to Subscribable stores. Cleanup
|
||||
* is automatic on component unmount.
|
||||
*
|
||||
* To stop listening to the subscribable and end the subscription early,
|
||||
* components should store the returned subscription object and invoke the
|
||||
* `remove()` function on it
|
||||
*
|
||||
* @param {Subscribable} subscription to subscribe to.
|
||||
* @param {function} listener Function to invoke when event occurs.
|
||||
* @param {object} context Object to bind the handler on, as "this"
|
||||
*
|
||||
* @return {object} the subscription object:
|
||||
* {
|
||||
* remove: function() {...},
|
||||
* }
|
||||
* Call `remove` to terminate the subscription before unmounting
|
||||
*/
|
||||
subscribeTo: function(subscribable, handler, context) {
|
||||
invariant(
|
||||
subscribable instanceof Subscribable,
|
||||
'Must provide a Subscribable'
|
||||
);
|
||||
return subscribable.subscribe(
|
||||
this._getSubscribableLifespan(),
|
||||
handler,
|
||||
context
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a Subscribable store, scoped to the component, that can be passed to
|
||||
* children. The component will automatically clean up the subscribable's
|
||||
* subscription to the eventEmitter when unmounting.
|
||||
*
|
||||
* `provideSubscribable` will always return the same Subscribable for any
|
||||
* particular emitter/eventName combo, so it can be called directly from
|
||||
* render, and it will never create duplicate Subscribables.
|
||||
*
|
||||
* @param {EventEmitter} eventEmitter Emitter to trigger subscription events.
|
||||
* @param {string} eventName Name of emitted event that triggers subscription
|
||||
* events.
|
||||
* @param {function} eventMapping (optional) Function to convert the output
|
||||
* of the eventEmitter to the subscription output.
|
||||
* @param {function} getInitData (optional) Async function to grab the initial
|
||||
* data to publish. Signature `function(successCallback, errorCallback)`.
|
||||
* The resolved data will be transformed with the eventMapping before it
|
||||
* gets emitted.
|
||||
*/
|
||||
provideSubscribable: function(eventEmitter, eventName, eventMapping, getInitData) {
|
||||
this._localSubscribables = this._localSubscribables || {};
|
||||
this._localSubscribables[eventEmitter] =
|
||||
this._localSubscribables[eventEmitter] || {};
|
||||
if (!this._localSubscribables[eventEmitter][eventName]) {
|
||||
this._localSubscribables[eventEmitter][eventName] =
|
||||
new Subscribable(eventEmitter, eventName, eventMapping, getInitData);
|
||||
}
|
||||
return this._localSubscribables[eventEmitter][eventName];
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes any local Subscribables created with `provideSubscribable`, so the
|
||||
* component can unmount without leaving any dangling listeners on
|
||||
* eventEmitters
|
||||
*/
|
||||
_cleanupLocalSubscribables: function() {
|
||||
if (!this._localSubscribables) {
|
||||
return;
|
||||
}
|
||||
Object.keys(this._localSubscribables).forEach((eventEmitter) => {
|
||||
var emitterSubscribables = this._localSubscribables[eventEmitter];
|
||||
if (emitterSubscribables) {
|
||||
Object.keys(emitterSubscribables).forEach((eventName) => {
|
||||
emitterSubscribables[eventName].cleanup();
|
||||
});
|
||||
}
|
||||
});
|
||||
this._localSubscribables = null;
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this._endSubscribableLifespanCallbacks = [];
|
||||
|
||||
// DEPRECATED addListenerOn* usage:
|
||||
this._subscribableSubscriptions = [];
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
// Resolve the lifespan, which will cause Subscribable to clean any
|
||||
// remaining subscriptions
|
||||
this._endSubscribableLifespan && this._endSubscribableLifespan();
|
||||
|
||||
this._cleanupLocalSubscribables();
|
||||
|
||||
// DEPRECATED addListenerOn* usage uses _subscribableSubscriptions array
|
||||
// instead of lifespan
|
||||
this._subscribableSubscriptions.forEach(
|
||||
(subscription) => subscription.remove()
|
||||
);
|
||||
@@ -320,9 +37,6 @@ Subscribable.Mixin = {
|
||||
},
|
||||
|
||||
/**
|
||||
* DEPRECATED - Use `Subscribable` and `Mixin.subscribeTo` instead.
|
||||
* `addListenerOn` subscribes the component to an `EventEmitter`.
|
||||
*
|
||||
* Special form of calling `addListener` that *guarantees* that a
|
||||
* subscription *must* be tied to a component instance, and therefore will
|
||||
* be cleaned up when the component is unmounted. It is impossible to create
|
||||
@@ -335,7 +49,12 @@ Subscribable.Mixin = {
|
||||
* @param {function} listener Function to invoke when event occurs.
|
||||
* @param {object} context Object to use as listener context.
|
||||
*/
|
||||
addListenerOn: function(eventEmitter, eventType, listener, context) {
|
||||
addListenerOn: function(
|
||||
eventEmitter: EventEmitter,
|
||||
eventType: string,
|
||||
listener: Function,
|
||||
context: Object
|
||||
) {
|
||||
this._subscribableSubscriptions.push(
|
||||
eventEmitter.addListener(eventType, listener, context)
|
||||
);
|
||||
|
||||
@@ -27,12 +27,14 @@
|
||||
'use strict';
|
||||
|
||||
var AnimationsDebugModule = require('NativeModules').AnimationsDebugModule;
|
||||
var Backstack = require('Backstack');
|
||||
var BackAndroid = require('BackAndroid');
|
||||
var Dimensions = require('Dimensions');
|
||||
var InteractionMixin = require('InteractionMixin');
|
||||
var NavigatorSceneConfigs = require('NavigatorSceneConfigs');
|
||||
var NavigatorNavigationBar = require('NavigatorNavigationBar');
|
||||
var NavigatorBreadcrumbNavigationBar = require('NavigatorBreadcrumbNavigationBar');
|
||||
var NavigatorInterceptor = require('NavigatorInterceptor');
|
||||
var NavigatorNavigationBar = require('NavigatorNavigationBar');
|
||||
var NavigatorSceneConfigs = require('NavigatorSceneConfigs');
|
||||
var NavigatorStaticContextContainer = require('NavigatorStaticContextContainer');
|
||||
var PanResponder = require('PanResponder');
|
||||
var React = require('React');
|
||||
var StaticContainer = require('StaticContainer.react');
|
||||
@@ -41,6 +43,7 @@ var Subscribable = require('Subscribable');
|
||||
var TimerMixin = require('react-timer-mixin');
|
||||
var View = require('View');
|
||||
|
||||
var getNavigatorContext = require('getNavigatorContext');
|
||||
var clamp = require('clamp');
|
||||
var invariant = require('invariant');
|
||||
var keyMirror = require('keyMirror');
|
||||
@@ -59,8 +62,6 @@ function getuid() {
|
||||
return __uid++;
|
||||
}
|
||||
|
||||
var nextComponentUid = 0;
|
||||
|
||||
// styles moved to the top of the file so getDefaultProps can refer to it
|
||||
var styles = StyleSheet.create({
|
||||
container: {
|
||||
@@ -185,12 +186,11 @@ var Navigator = React.createClass({
|
||||
|
||||
/**
|
||||
* Required function which renders the scene for a given route. Will be
|
||||
* invoked with the route, the navigator object, and a ref handler that
|
||||
* will allow a ref to your scene to be provided by props.onItemRef
|
||||
* invoked with the route and the navigator object
|
||||
*
|
||||
* ```
|
||||
* (route, navigator, onRef) =>
|
||||
* <MySceneComponent title={route.title} ref={onRef} />
|
||||
* (route, navigator) =>
|
||||
* <MySceneComponent title={route.title} />
|
||||
* ```
|
||||
*/
|
||||
renderScene: PropTypes.func.isRequired,
|
||||
@@ -242,19 +242,18 @@ var Navigator = React.createClass({
|
||||
* Styles to apply to the container of each scene
|
||||
*/
|
||||
sceneStyle: View.propTypes.style,
|
||||
},
|
||||
|
||||
/**
|
||||
* Should the backstack back button "jump" back instead of pop? Set to true
|
||||
* if a jump forward might happen after the android back button is pressed,
|
||||
* so the scenes will remain mounted
|
||||
*/
|
||||
shouldJumpOnBackstackPop: PropTypes.bool,
|
||||
contextTypes: {
|
||||
navigator: PropTypes.object,
|
||||
},
|
||||
|
||||
statics: {
|
||||
BreadcrumbNavigationBar: NavigatorBreadcrumbNavigationBar,
|
||||
NavigationBar: NavigatorNavigationBar,
|
||||
SceneConfigs: NavigatorSceneConfigs,
|
||||
Interceptor: NavigatorInterceptor,
|
||||
getContext: getNavigatorContext,
|
||||
},
|
||||
|
||||
mixins: [TimerMixin, InteractionMixin, Subscribable.Mixin],
|
||||
@@ -303,7 +302,20 @@ var Navigator = React.createClass({
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this.navigatorActions = {
|
||||
this.parentNavigator = getNavigatorContext(this) || this.props.navigator;
|
||||
this.navigatorContext = {
|
||||
setHandlerForRoute: this.setHandlerForRoute,
|
||||
request: this.request,
|
||||
|
||||
parentNavigator: this.parentNavigator,
|
||||
getCurrentRoutes: this.getCurrentRoutes,
|
||||
// We want to bubble focused routes to the top navigation stack. If we
|
||||
// are a child navigator, this allows us to call props.navigator.on*Focus
|
||||
// of the topmost Navigator
|
||||
onWillFocus: this.props.onWillFocus,
|
||||
onDidFocus: this.props.onDidFocus,
|
||||
|
||||
// Legacy, imperitive nav actions. Use request when possible.
|
||||
jumpBack: this.jumpBack,
|
||||
jumpForward: this.jumpForward,
|
||||
jumpTo: this.jumpTo,
|
||||
@@ -317,14 +329,8 @@ var Navigator = React.createClass({
|
||||
resetTo: this.resetTo,
|
||||
popToRoute: this.popToRoute,
|
||||
popToTop: this.popToTop,
|
||||
parentNavigator: this.props.navigator,
|
||||
getCurrentRoutes: this.getCurrentRoutes,
|
||||
// We want to bubble focused routes to the top navigation stack. If we
|
||||
// are a child navigator, this allows us to call props.navigator.on*Focus
|
||||
// of the topmost Navigator
|
||||
onWillFocus: this.props.onWillFocus,
|
||||
onDidFocus: this.props.onDidFocus,
|
||||
};
|
||||
this._handlers = {};
|
||||
|
||||
this.panGesture = PanResponder.create({
|
||||
onStartShouldSetPanResponderCapture: this._handleStartShouldSetPanResponderCapture,
|
||||
@@ -336,17 +342,50 @@ var Navigator = React.createClass({
|
||||
});
|
||||
this._itemRefs = {};
|
||||
this._interactionHandle = null;
|
||||
this._backstackComponentKey = 'jsnavstack' + nextComponentUid;
|
||||
nextComponentUid++;
|
||||
|
||||
Backstack.eventEmitter && this.addListenerOn(
|
||||
Backstack.eventEmitter,
|
||||
'popNavigation',
|
||||
this._onBackstackPopState);
|
||||
|
||||
this._emitWillFocus(this.state.presentedIndex);
|
||||
},
|
||||
|
||||
request: function(action, arg1, arg2) {
|
||||
if (this.parentNavigator) {
|
||||
return this.parentNavigator.request.apply(null, arguments);
|
||||
}
|
||||
return this._handleRequest.apply(null, arguments);
|
||||
},
|
||||
|
||||
_handleRequest: function(action, arg1, arg2) {
|
||||
var childHandler = this._handlers[this.state.presentedIndex];
|
||||
if (childHandler && childHandler(action, arg1, arg2)) {
|
||||
return true;
|
||||
}
|
||||
switch (action) {
|
||||
case 'pop':
|
||||
return this._handlePop();
|
||||
case 'push':
|
||||
return this._handlePush(arg1);
|
||||
default:
|
||||
invariant(false, 'Unsupported request type ' + action);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
_handlePop: function() {
|
||||
if (this.state.presentedIndex === 0) {
|
||||
return false;
|
||||
}
|
||||
this.pop();
|
||||
return true;
|
||||
},
|
||||
|
||||
_handlePush: function(route) {
|
||||
this.push(route);
|
||||
return true;
|
||||
},
|
||||
|
||||
setHandlerForRoute: function(route, handler) {
|
||||
this._handlers[this.state.routeStack.indexOf(route)] = handler;
|
||||
},
|
||||
|
||||
_configureSpring: function(animationConfig) {
|
||||
var config = this.spring.getSpringConfig();
|
||||
config.friction = animationConfig.springFriction;
|
||||
@@ -361,30 +400,28 @@ var Navigator = React.createClass({
|
||||
animationConfig && this._configureSpring(animationConfig);
|
||||
this.spring.addListener(this);
|
||||
this.onSpringUpdate();
|
||||
|
||||
// Fill up the Backstack with routes that have been provided in
|
||||
// initialRouteStack
|
||||
this._fillBackstackRange(0, this.state.routeStack.indexOf(this.props.initialRoute));
|
||||
this._emitDidFocus(this.state.presentedIndex);
|
||||
if (this.parentNavigator) {
|
||||
this.parentNavigator.setHandler(this._handleRequest);
|
||||
} else {
|
||||
// There is no navigator in our props or context, so this is the
|
||||
// top-level navigator. We will handle back button presses here
|
||||
BackAndroid.addEventListener('hardwareBackPress', this._handleBackPress);
|
||||
}
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
Backstack.removeComponentHistory(this._backstackComponentKey);
|
||||
if (this.parentNavigator) {
|
||||
this.parentNavigator.setHandler(null);
|
||||
} else {
|
||||
BackAndroid.removeEventListener('hardwareBackPress', this._handleBackPress);
|
||||
}
|
||||
},
|
||||
|
||||
_onBackstackPopState: function(componentKey, stateKey, state) {
|
||||
if (componentKey !== this._backstackComponentKey) {
|
||||
return;
|
||||
}
|
||||
if (!this._canNavigate()) {
|
||||
// A bit hacky: if we can't actually handle the pop, just push it back on the stack
|
||||
Backstack.pushNavigation(componentKey, stateKey, state);
|
||||
} else {
|
||||
if (this.props.shouldJumpOnBackstackPop) {
|
||||
this._jumpToWithoutBackstack(state.fromRoute);
|
||||
} else {
|
||||
this._popToRouteWithoutBackstack(state.fromRoute);
|
||||
}
|
||||
_handleBackPress: function() {
|
||||
var didPop = this.request('pop');
|
||||
if (!didPop) {
|
||||
BackAndroid.exitApp();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -737,41 +774,6 @@ var Navigator = React.createClass({
|
||||
return !this.state.isAnimating;
|
||||
},
|
||||
|
||||
_jumpNWithoutBackstack: function(n) {
|
||||
var destIndex = this._getDestIndexWithinBounds(n);
|
||||
if (!this._canNavigate()) {
|
||||
return; // It's busy animating or transitioning.
|
||||
}
|
||||
var requestTransitionAndResetUpdatingRange = () => {
|
||||
this._requestTransitionTo(destIndex);
|
||||
this._resetUpdatingRange();
|
||||
};
|
||||
this.setState({
|
||||
updatingRangeStart: destIndex,
|
||||
updatingRangeLength: 1,
|
||||
toIndex: destIndex,
|
||||
}, requestTransitionAndResetUpdatingRange);
|
||||
},
|
||||
|
||||
_fillBackstackRange: function(start, end) {
|
||||
invariant(
|
||||
start <= end,
|
||||
'Can only fill the backstack forward. Provide end index greater than start'
|
||||
);
|
||||
for (var i = 0; i < (end - start); i++) {
|
||||
var fromIndex = start + i;
|
||||
var toIndex = start + i + 1;
|
||||
Backstack.pushNavigation(
|
||||
this._backstackComponentKey,
|
||||
toIndex,
|
||||
{
|
||||
fromRoute: this.state.routeStack[fromIndex],
|
||||
toRoute: this.state.routeStack[toIndex],
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
_getDestIndexWithinBounds: function(n) {
|
||||
var currentIndex = this.state.presentedIndex;
|
||||
var destIndex = currentIndex + n;
|
||||
@@ -788,20 +790,19 @@ var Navigator = React.createClass({
|
||||
},
|
||||
|
||||
_jumpN: function(n) {
|
||||
var currentIndex = this.state.presentedIndex;
|
||||
var destIndex = this._getDestIndexWithinBounds(n);
|
||||
if (!this._canNavigate()) {
|
||||
return; // It's busy animating or transitioning.
|
||||
}
|
||||
if (n > 0) {
|
||||
this._fillBackstackRange(currentIndex, currentIndex + n);
|
||||
} else {
|
||||
var landingBeforeIndex = currentIndex + n + 1;
|
||||
Backstack.resetToBefore(
|
||||
this._backstackComponentKey,
|
||||
landingBeforeIndex
|
||||
);
|
||||
}
|
||||
this._jumpNWithoutBackstack(n);
|
||||
var requestTransitionAndResetUpdatingRange = () => {
|
||||
this._requestTransitionTo(destIndex);
|
||||
this._resetUpdatingRange();
|
||||
};
|
||||
this.setState({
|
||||
updatingRangeStart: destIndex,
|
||||
updatingRangeLength: 1,
|
||||
toIndex: destIndex,
|
||||
}, requestTransitionAndResetUpdatingRange);
|
||||
},
|
||||
|
||||
jumpTo: function(route) {
|
||||
@@ -813,15 +814,6 @@ var Navigator = React.createClass({
|
||||
this._jumpN(destIndex - this.state.presentedIndex);
|
||||
},
|
||||
|
||||
_jumpToWithoutBackstack: function(route) {
|
||||
var destIndex = this.state.routeStack.indexOf(route);
|
||||
invariant(
|
||||
destIndex !== -1,
|
||||
'Cannot jump to route that is not in the route stack'
|
||||
);
|
||||
this._jumpNWithoutBackstack(destIndex - this.state.presentedIndex);
|
||||
},
|
||||
|
||||
jumpForward: function() {
|
||||
this._jumpN(1);
|
||||
},
|
||||
@@ -852,11 +844,6 @@ var Navigator = React.createClass({
|
||||
toRoute: route,
|
||||
fromRoute: this.state.routeStack[this.state.routeStack.length - 1],
|
||||
};
|
||||
Backstack.pushNavigation(
|
||||
this._backstackComponentKey,
|
||||
this.state.routeStack.length,
|
||||
navigationState);
|
||||
|
||||
this.setState({
|
||||
idStack: nextIDStack,
|
||||
routeStack: nextStack,
|
||||
@@ -867,14 +854,7 @@ var Navigator = React.createClass({
|
||||
}, requestTransitionAndResetUpdatingRange);
|
||||
},
|
||||
|
||||
_manuallyPopBackstack: function(n) {
|
||||
Backstack.resetToBefore(this._backstackComponentKey, this.state.routeStack.length - n);
|
||||
},
|
||||
|
||||
/**
|
||||
* Like popN, but doesn't also update the Backstack.
|
||||
*/
|
||||
_popNWithoutBackstack: function(n) {
|
||||
popN: function(n) {
|
||||
if (n === 0 || !this._canNavigate()) {
|
||||
return;
|
||||
}
|
||||
@@ -888,17 +868,10 @@ var Navigator = React.createClass({
|
||||
);
|
||||
},
|
||||
|
||||
popN: function(n) {
|
||||
if (n === 0 || !this._canNavigate()) {
|
||||
return;
|
||||
}
|
||||
this._popNWithoutBackstack(n);
|
||||
this._manuallyPopBackstack(n);
|
||||
},
|
||||
|
||||
pop: function() {
|
||||
if (this.props.navigator && this.state.routeStack.length === 1) {
|
||||
return this.props.navigator.pop();
|
||||
// TODO (t6707686): remove this parentNavigator call after transitioning call sites to `.request('pop')`
|
||||
if (this.parentNavigator && this.state.routeStack.length === 1) {
|
||||
return this.parentNavigator.pop();
|
||||
}
|
||||
this.popN(1);
|
||||
},
|
||||
@@ -970,14 +943,6 @@ var Navigator = React.createClass({
|
||||
return this.state.routeStack.length - indexOfRoute - 1;
|
||||
},
|
||||
|
||||
/**
|
||||
* Like popToRoute, but doesn't update the Backstack, presumably because it's already up to date.
|
||||
*/
|
||||
_popToRouteWithoutBackstack: function(route) {
|
||||
var numToPop = this._getNumToPopForRoute(route);
|
||||
this._popNWithoutBackstack(numToPop);
|
||||
},
|
||||
|
||||
popToRoute: function(route) {
|
||||
var numToPop = this._getNumToPopForRoute(route);
|
||||
this.popN(numToPop);
|
||||
@@ -1003,7 +968,7 @@ var Navigator = React.createClass({
|
||||
return this.state.routeStack;
|
||||
},
|
||||
|
||||
_onItemRef: function(itemId, ref) {
|
||||
_handleItemRef: function(itemId, ref) {
|
||||
this._itemRefs[itemId] = ref;
|
||||
var itemIndex = this.state.idStack.indexOf(itemId);
|
||||
if (itemIndex === -1) {
|
||||
@@ -1036,23 +1001,33 @@ var Navigator = React.createClass({
|
||||
this.state.updatingRangeLength !== 0 &&
|
||||
i >= this.state.updatingRangeStart &&
|
||||
i <= this.state.updatingRangeStart + this.state.updatingRangeLength;
|
||||
var sceneNavigatorContext = {
|
||||
...this.navigatorContext,
|
||||
route,
|
||||
setHandler: (handler) => {
|
||||
this.navigatorContext.setHandlerForRoute(route, handler);
|
||||
},
|
||||
};
|
||||
var child = this.props.renderScene(
|
||||
route,
|
||||
this.navigatorActions,
|
||||
this._onItemRef.bind(null, this.state.idStack[i])
|
||||
sceneNavigatorContext
|
||||
);
|
||||
|
||||
var initialSceneStyle =
|
||||
i === this.state.presentedIndex ? styles.presentNavItem : styles.futureNavItem;
|
||||
return (
|
||||
<StaticContainer key={'nav' + i} shouldUpdate={shouldUpdateChild}>
|
||||
<NavigatorStaticContextContainer
|
||||
navigatorContext={sceneNavigatorContext}
|
||||
key={'nav' + i}
|
||||
shouldUpdate={shouldUpdateChild}>
|
||||
<View
|
||||
key={this.state.idStack[i]}
|
||||
ref={'scene_' + i}
|
||||
style={[initialSceneStyle, this.props.sceneStyle]}>
|
||||
{child}
|
||||
{React.cloneElement(child, {
|
||||
ref: this._handleItemRef.bind(null, this.state.idStack[i]),
|
||||
})}
|
||||
</View>
|
||||
</StaticContainer>
|
||||
</NavigatorStaticContextContainer>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1081,7 +1056,7 @@ var Navigator = React.createClass({
|
||||
}
|
||||
return React.cloneElement(this.props.navigationBar, {
|
||||
ref: (navBar) => { this._navBar = navBar; },
|
||||
navigator: this.navigatorActions,
|
||||
navigator: this.navigatorContext,
|
||||
navState: this.state,
|
||||
});
|
||||
},
|
||||
|
||||
95
Libraries/CustomComponents/Navigator/NavigatorInterceptor.js
Normal file
95
Libraries/CustomComponents/Navigator/NavigatorInterceptor.js
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
*
|
||||
* Facebook, Inc. (“Facebook”) owns all right, title and interest, including
|
||||
* all intellectual property and other proprietary rights, in and to the React
|
||||
* Native CustomComponents software (the “Software”). Subject to your
|
||||
* compliance with these terms, you are hereby granted a non-exclusive,
|
||||
* worldwide, royalty-free copyright license to (1) use and copy the Software;
|
||||
* and (2) reproduce and distribute the Software as part of your own software
|
||||
* (“Your Software”). Facebook reserves all rights not expressly granted to
|
||||
* you in this license agreement.
|
||||
*
|
||||
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
|
||||
* OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR
|
||||
* EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* @providesModule NavigatorInterceptor
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('React');
|
||||
|
||||
var getNavigatorContext = require('getNavigatorContext');
|
||||
|
||||
var NavigatorInterceptor = React.createClass({
|
||||
|
||||
contextTypes: {
|
||||
navigator: React.PropTypes.object,
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this.navigator = getNavigatorContext(this);
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.navigator.setHandler(this._navigatorHandleRequest);
|
||||
},
|
||||
|
||||
childContextTypes: {
|
||||
navigator: React.PropTypes.object,
|
||||
},
|
||||
|
||||
getChildContext: function() {
|
||||
return {
|
||||
navigator: {
|
||||
...this.navigator,
|
||||
setHandler: (handler) => {
|
||||
this._childNavigationHandler = handler;
|
||||
},
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
this.navigator.setHandler(null);
|
||||
},
|
||||
|
||||
_navigatorHandleRequest: function(action, arg1, arg2) {
|
||||
if (this._interceptorHandle(action, arg1, arg2)) {
|
||||
return true;
|
||||
}
|
||||
if (this._childNavigationHandler && this._childNavigationHandler(action, arg1, arg2)) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
_interceptorHandle: function(action, arg1, arg2) {
|
||||
if (this.props.onRequest && this.props.onRequest(action, arg1, arg2)) {
|
||||
return true;
|
||||
}
|
||||
switch (action) {
|
||||
case 'pop':
|
||||
return this.props.onPopRequest && this.props.onPopRequest(action, arg1, arg2);
|
||||
case 'push':
|
||||
return this.props.onPushRequest && this.props.onPushRequest(action, arg1, arg2);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return this.props.children;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
module.exports = NavigatorInterceptor;
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
*
|
||||
* Facebook, Inc. (“Facebook”) owns all right, title and interest, including
|
||||
* all intellectual property and other proprietary rights, in and to the React
|
||||
* Native CustomComponents software (the “Software”). Subject to your
|
||||
* compliance with these terms, you are hereby granted a non-exclusive,
|
||||
* worldwide, royalty-free copyright license to (1) use and copy the Software;
|
||||
* and (2) reproduce and distribute the Software as part of your own software
|
||||
* (“Your Software”). Facebook reserves all rights not expressly granted to
|
||||
* you in this license agreement.
|
||||
*
|
||||
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
|
||||
* OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR
|
||||
* EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* @providesModule NavigatorStaticContextContainer
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('React');
|
||||
var StaticContainer = require('StaticContainer.react');
|
||||
|
||||
var PropTypes = React.PropTypes;
|
||||
|
||||
var NavigatorStaticContextContainer = React.createClass({
|
||||
|
||||
childContextTypes: {
|
||||
navigator: PropTypes.object,
|
||||
},
|
||||
|
||||
getChildContext: function() {
|
||||
return {
|
||||
navigator: this.props.navigatorContext,
|
||||
};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<StaticContainer {...this.props} />
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = NavigatorStaticContextContainer;
|
||||
37
Libraries/CustomComponents/Navigator/getNavigatorContext.js
Normal file
37
Libraries/CustomComponents/Navigator/getNavigatorContext.js
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
*
|
||||
* Facebook, Inc. (“Facebook”) owns all right, title and interest, including
|
||||
* all intellectual property and other proprietary rights, in and to the React
|
||||
* Native CustomComponents software (the “Software”). Subject to your
|
||||
* compliance with these terms, you are hereby granted a non-exclusive,
|
||||
* worldwide, royalty-free copyright license to (1) use and copy the Software;
|
||||
* and (2) reproduce and distribute the Software as part of your own software
|
||||
* (“Your Software”). Facebook reserves all rights not expressly granted to
|
||||
* you in this license agreement.
|
||||
*
|
||||
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
|
||||
* OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR
|
||||
* EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* @providesModule getNavigatorContext
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
|
||||
var ReactInstanceMap = require('ReactInstanceMap');
|
||||
|
||||
function getNavigatorContext(el) {
|
||||
// TODO (t6707746): replace with `el.context.navigator` when parent context is supported
|
||||
return ReactInstanceMap.get(el)._context.navigator;
|
||||
}
|
||||
|
||||
module.exports = getNavigatorContext;
|
||||
17
Libraries/Image/ImageSource.js
Normal file
17
Libraries/Image/ImageSource.js
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* 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 ImageSource
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
type ImageSource = {
|
||||
uri: string;
|
||||
isStatic: boolean;
|
||||
};
|
||||
@@ -77,13 +77,14 @@ static NSString *RCTCacheKeyForURL(NSURL *url)
|
||||
|
||||
__weak RCTImageDownloader *weakSelf = self;
|
||||
RCTCachedDataDownloadBlock runBlocks = ^(BOOL cached, NSData *data, NSError *error) {
|
||||
|
||||
RCTImageDownloader *strongSelf = weakSelf;
|
||||
NSArray *blocks = strongSelf->_pendingBlocks[cacheKey];
|
||||
[strongSelf->_pendingBlocks removeObjectForKey:cacheKey];
|
||||
for (RCTCachedDataDownloadBlock block in blocks) {
|
||||
block(cached, data, error);
|
||||
}
|
||||
dispatch_async(_processingQueue, ^{
|
||||
RCTImageDownloader *strongSelf = weakSelf;
|
||||
NSArray *blocks = strongSelf->_pendingBlocks[cacheKey];
|
||||
[strongSelf->_pendingBlocks removeObjectForKey:cacheKey];
|
||||
for (RCTCachedDataDownloadBlock block in blocks) {
|
||||
block(cached, data, error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if ([_cache hasDataForKey:cacheKey]) {
|
||||
|
||||
@@ -119,7 +119,9 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
|
||||
|
||||
[_jsQueue addOperationWithBlock:^{
|
||||
if (!self.valid) {
|
||||
NSError *error = [NSError errorWithDomain:@"WS" code:1 userInfo:@{NSLocalizedDescriptionKey:@"socket closed"}];
|
||||
NSError *error = [NSError errorWithDomain:@"WS" code:1 userInfo:@{
|
||||
NSLocalizedDescriptionKey: @"socket closed"
|
||||
}];
|
||||
callback(error, nil);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -27,49 +27,73 @@ var RCTAsyncStorage = RCTAsyncRocksDBStorage || RCTAsyncLocalStorage;
|
||||
* operates globally.
|
||||
*
|
||||
* This JS code is a simple facad over the native iOS implementation to provide
|
||||
* a clear JS API, real Error objects, and simple non-multi functions.
|
||||
* a clear JS API, real Error objects, and simple non-multi functions. Each
|
||||
* method returns a `Promise` object.
|
||||
*/
|
||||
var AsyncStorage = {
|
||||
/**
|
||||
* Fetches `key` and passes the result to `callback`, along with an `Error` if
|
||||
* there is any.
|
||||
* there is any. Returns a `Promise` object.
|
||||
*/
|
||||
getItem: function(
|
||||
key: string,
|
||||
callback: (error: ?Error, result: ?string) => void
|
||||
): void {
|
||||
RCTAsyncStorage.multiGet([key], function(errors, result) {
|
||||
// Unpack result to get value from [[key,value]]
|
||||
var value = (result && result[0] && result[0][1]) ? result[0][1] : null;
|
||||
callback((errors && convertError(errors[0])) || null, value);
|
||||
): Promise {
|
||||
return new Promise((resolve, reject) => {
|
||||
RCTAsyncStorage.multiGet([key], function(errors, result) {
|
||||
// Unpack result to get value from [[key,value]]
|
||||
var value = (result && result[0] && result[0][1]) ? result[0][1] : null;
|
||||
callback && callback((errors && convertError(errors[0])) || null, value);
|
||||
if (errors) {
|
||||
reject(convertError(errors[0]));
|
||||
} else {
|
||||
resolve(value);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets `value` for `key` and calls `callback` on completion, along with an
|
||||
* `Error` if there is any.
|
||||
* `Error` if there is any. Returns a `Promise` object.
|
||||
*/
|
||||
setItem: function(
|
||||
key: string,
|
||||
value: string,
|
||||
callback: ?(error: ?Error) => void
|
||||
): void {
|
||||
RCTAsyncStorage.multiSet([[key,value]], function(errors) {
|
||||
callback && callback((errors && convertError(errors[0])) || null);
|
||||
): Promise {
|
||||
return new Promise((resolve, reject) => {
|
||||
RCTAsyncStorage.multiSet([[key,value]], function(errors) {
|
||||
callback && callback((errors && convertError(errors[0])) || null);
|
||||
if (errors) {
|
||||
reject(convertError(errors[0]));
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a `Promise` object.
|
||||
*/
|
||||
removeItem: function(
|
||||
key: string,
|
||||
callback: ?(error: ?Error) => void
|
||||
): void {
|
||||
RCTAsyncStorage.multiRemove([key], function(errors) {
|
||||
callback && callback((errors && convertError(errors[0])) || null);
|
||||
): Promise {
|
||||
return new Promise((resolve, reject) => {
|
||||
RCTAsyncStorage.multiRemove([key], function(errors) {
|
||||
callback && callback((errors && convertError(errors[0])) || null);
|
||||
if (errors) {
|
||||
reject(convertError(errors[0]));
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Merges existing value with input value, assuming they are stringified json.
|
||||
* Merges existing value with input value, assuming they are stringified json. Returns a `Promise` object.
|
||||
*
|
||||
* Not supported by all native implementations.
|
||||
*/
|
||||
@@ -77,29 +101,50 @@ var AsyncStorage = {
|
||||
key: string,
|
||||
value: string,
|
||||
callback: ?(error: ?Error) => void
|
||||
): void {
|
||||
RCTAsyncStorage.multiMerge([[key,value]], function(errors) {
|
||||
callback && callback((errors && convertError(errors[0])) || null);
|
||||
): Promise {
|
||||
return new Promise((resolve, reject) => {
|
||||
RCTAsyncStorage.multiMerge([[key,value]], function(errors) {
|
||||
callback && callback((errors && convertError(errors[0])) || null);
|
||||
if (errors) {
|
||||
reject(convertError(errors[0]));
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Erases *all* AsyncStorage for all clients, libraries, etc. You probably
|
||||
* don't want to call this - use removeItem or multiRemove to clear only your
|
||||
* own keys instead.
|
||||
* own keys instead. Returns a `Promise` object.
|
||||
*/
|
||||
clear: function(callback: ?(error: ?Error) => void) {
|
||||
RCTAsyncStorage.clear(function(error) {
|
||||
callback && callback(convertError(error));
|
||||
clear: function(callback: ?(error: ?Error) => void): Promise {
|
||||
return new Promise((resolve, reject) => {
|
||||
RCTAsyncStorage.clear(function(error) {
|
||||
callback && callback(convertError(error));
|
||||
if (error && convertError(error)){
|
||||
reject(convertError(error));
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets *all* keys known to the system, for all callers, libraries, etc.
|
||||
* Gets *all* keys known to the system, for all callers, libraries, etc. Returns a `Promise` object.
|
||||
*/
|
||||
getAllKeys: function(callback: (error: ?Error) => void) {
|
||||
RCTAsyncStorage.getAllKeys(function(error, keys) {
|
||||
callback(convertError(error), keys);
|
||||
getAllKeys: function(callback: (error: ?Error) => void): Promise {
|
||||
return new Promise((resolve, reject) => {
|
||||
RCTAsyncStorage.getAllKeys(function(error, keys) {
|
||||
callback && callback(convertError(error), keys);
|
||||
if (error) {
|
||||
reject(convertError(error));
|
||||
} else {
|
||||
resolve(keys);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@@ -115,67 +160,90 @@ var AsyncStorage = {
|
||||
|
||||
/**
|
||||
* multiGet invokes callback with an array of key-value pair arrays that
|
||||
* matches the input format of multiSet.
|
||||
* matches the input format of multiSet. Returns a `Promise` object.
|
||||
*
|
||||
* multiGet(['k1', 'k2'], cb) -> cb([['k1', 'val1'], ['k2', 'val2']])
|
||||
*/
|
||||
multiGet: function(
|
||||
keys: Array<string>,
|
||||
callback: (errors: ?Array<Error>, result: ?Array<Array<string>>) => void
|
||||
): void {
|
||||
RCTAsyncStorage.multiGet(keys, function(errors, result) {
|
||||
callback(
|
||||
(errors && errors.map((error) => convertError(error))) || null,
|
||||
result
|
||||
);
|
||||
): Promise {
|
||||
return new Promise((resolve, reject) => {
|
||||
RCTAsyncStorage.multiGet(keys, function(errors, result) {
|
||||
var error = (errors && errors.map((error) => convertError(error))) || null;
|
||||
callback && callback(error, result);
|
||||
if (errors) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* multiSet and multiMerge take arrays of key-value array pairs that match
|
||||
* the output of multiGet, e.g.
|
||||
* the output of multiGet, e.g. Returns a `Promise` object.
|
||||
*
|
||||
* multiSet([['k1', 'val1'], ['k2', 'val2']], cb);
|
||||
*/
|
||||
multiSet: function(
|
||||
keyValuePairs: Array<Array<string>>,
|
||||
callback: ?(errors: ?Array<Error>) => void
|
||||
): void {
|
||||
RCTAsyncStorage.multiSet(keyValuePairs, function(errors) {
|
||||
callback && callback(
|
||||
(errors && errors.map((error) => convertError(error))) || null
|
||||
);
|
||||
): Promise {
|
||||
return new Promise((resolve, reject) => {
|
||||
RCTAsyncStorage.multiSet(keyValuePairs, function(errors) {
|
||||
var error = (errors && errors.map((error) => convertError(error))) || null;
|
||||
callback && callback(error);
|
||||
if (errors) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete all the keys in the `keys` array.
|
||||
* Delete all the keys in the `keys` array. Returns a `Promise` object.
|
||||
*/
|
||||
multiRemove: function(
|
||||
keys: Array<string>,
|
||||
callback: ?(errors: ?Array<Error>) => void
|
||||
): void {
|
||||
RCTAsyncStorage.multiRemove(keys, function(errors) {
|
||||
callback && callback(
|
||||
(errors && errors.map((error) => convertError(error))) || null
|
||||
);
|
||||
): Promise {
|
||||
return new Promise((resolve, reject) => {
|
||||
RCTAsyncStorage.multiRemove(keys, function(errors) {
|
||||
var error = (errors && errors.map((error) => convertError(error))) || null;
|
||||
callback && callback(error);
|
||||
if (errors) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Merges existing values with input values, assuming they are stringified
|
||||
* json.
|
||||
* json. Returns a `Promise` object.
|
||||
*
|
||||
* Not supported by all native implementations.
|
||||
*/
|
||||
multiMerge: function(
|
||||
keyValuePairs: Array<Array<string>>,
|
||||
callback: ?(errors: ?Array<Error>) => void
|
||||
): void {
|
||||
RCTAsyncStorage.multiMerge(keyValuePairs, function(errors) {
|
||||
callback && callback(
|
||||
(errors && errors.map((error) => convertError(error))) || null
|
||||
);
|
||||
): Promise {
|
||||
return new Promise((resolve, reject) => {
|
||||
RCTAsyncStorage.multiMerge(keyValuePairs, function(errors) {
|
||||
var error = (errors && errors.map((error) => convertError(error))) || null;
|
||||
callback && callback(error);
|
||||
if (errors) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
- (UIView *)view
|
||||
{
|
||||
return [[UIView alloc] init];
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (RCTShadowView *)shadowView
|
||||
@@ -26,4 +26,3 @@
|
||||
RCT_EXPORT_SHADOW_PROPERTY(text, NSString)
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ extern NSString *const RCTReactTagAttributeName;
|
||||
@property (nonatomic, copy) NSString *fontStyle;
|
||||
@property (nonatomic, assign) BOOL isHighlighted;
|
||||
@property (nonatomic, assign) CGFloat lineHeight;
|
||||
@property (nonatomic, assign) NSInteger maxNumberOfLines;
|
||||
@property (nonatomic, assign) NSUInteger maximumNumberOfLines;
|
||||
@property (nonatomic, assign) CGSize shadowOffset;
|
||||
@property (nonatomic, assign) NSTextAlignment textAlign;
|
||||
|
||||
@@ -31,6 +31,8 @@ extern NSString *const RCTReactTagAttributeName;
|
||||
@property (nonatomic, strong) UIFont *font;
|
||||
@property (nonatomic, assign) NSLineBreakMode truncationMode;
|
||||
|
||||
- (NSAttributedString *)attributedString;
|
||||
@property (nonatomic, copy, readonly) NSAttributedString *attributedString;
|
||||
@property (nonatomic, strong, readonly) NSLayoutManager *layoutManager;
|
||||
@property (nonatomic, strong, readonly) NSTextContainer *textContainer;
|
||||
|
||||
@end
|
||||
|
||||
@@ -20,9 +20,17 @@ NSString *const RCTReactTagAttributeName = @"ReactTagAttributeName";
|
||||
static css_dim_t RCTMeasure(void *context, float width)
|
||||
{
|
||||
RCTShadowText *shadowText = (__bridge RCTShadowText *)context;
|
||||
CGSize computedSize = [[shadowText attributedString] boundingRectWithSize:(CGSize){isnan(width) ? CGFLOAT_MAX : width, CGFLOAT_MAX}
|
||||
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
|
||||
context:nil].size;
|
||||
|
||||
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:[shadowText attributedString]];
|
||||
[textStorage addLayoutManager:shadowText.layoutManager];
|
||||
|
||||
shadowText.textContainer.size = CGSizeMake(isnan(width) ? CGFLOAT_MAX : width, CGFLOAT_MAX);
|
||||
shadowText.layoutManager.textStorage = textStorage;
|
||||
[shadowText.layoutManager ensureLayoutForTextContainer:shadowText.textContainer];
|
||||
|
||||
CGSize computedSize = [shadowText.layoutManager usedRectForTextContainer:shadowText.textContainer].size;
|
||||
|
||||
[textStorage removeLayoutManager:shadowText.layoutManager];
|
||||
|
||||
css_dim_t result;
|
||||
result.dimensions[CSS_WIDTH] = RCTCeilPixelValue(computedSize.width);
|
||||
@@ -30,8 +38,9 @@ static css_dim_t RCTMeasure(void *context, float width)
|
||||
return result;
|
||||
}
|
||||
|
||||
@implementation RCTShadowText
|
||||
{
|
||||
@implementation RCTShadowText {
|
||||
NSLayoutManager *_layoutManager;
|
||||
NSTextContainer *_textContainer;
|
||||
NSAttributedString *_cachedAttributedString;
|
||||
UIFont *_font;
|
||||
}
|
||||
@@ -41,7 +50,15 @@ static css_dim_t RCTMeasure(void *context, float width)
|
||||
if ((self = [super init])) {
|
||||
_fontSize = NAN;
|
||||
_isHighlighted = NO;
|
||||
|
||||
_textContainer = [[NSTextContainer alloc] init];
|
||||
_textContainer.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
_textContainer.lineFragmentPadding = 0.0;
|
||||
|
||||
_layoutManager = [[NSLayoutManager alloc] init];
|
||||
[_layoutManager addTextContainer:_textContainer];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -201,11 +218,31 @@ RCT_TEXT_PROPERTY(FontFamily, _fontFamily, NSString *);
|
||||
RCT_TEXT_PROPERTY(FontSize, _fontSize, CGFloat);
|
||||
RCT_TEXT_PROPERTY(FontWeight, _fontWeight, NSString *);
|
||||
RCT_TEXT_PROPERTY(LineHeight, _lineHeight, CGFloat);
|
||||
RCT_TEXT_PROPERTY(MaxNumberOfLines, _maxNumberOfLines, NSInteger);
|
||||
RCT_TEXT_PROPERTY(ShadowOffset, _shadowOffset, CGSize);
|
||||
RCT_TEXT_PROPERTY(TextAlign, _textAlign, NSTextAlignment);
|
||||
RCT_TEXT_PROPERTY(TruncationMode, _truncationMode, NSLineBreakMode);
|
||||
RCT_TEXT_PROPERTY(IsHighlighted, _isHighlighted, BOOL);
|
||||
RCT_TEXT_PROPERTY(Font, _font, UIFont *);
|
||||
|
||||
- (NSLineBreakMode)truncationMode
|
||||
{
|
||||
return _textContainer.lineBreakMode;
|
||||
}
|
||||
|
||||
- (void)setTruncationMode:(NSLineBreakMode)truncationMode
|
||||
{
|
||||
_textContainer.lineBreakMode = truncationMode;
|
||||
[self dirtyText];
|
||||
}
|
||||
|
||||
- (NSUInteger)maximumNumberOfLines
|
||||
{
|
||||
return _textContainer.maximumNumberOfLines;
|
||||
}
|
||||
|
||||
- (void)setMaximumNumberOfLines:(NSUInteger)maximumNumberOfLines
|
||||
{
|
||||
_textContainer.maximumNumberOfLines = maximumNumberOfLines;
|
||||
[self dirtyText];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -11,9 +11,10 @@
|
||||
|
||||
@interface RCTText : UIView
|
||||
|
||||
@property (nonatomic, strong) NSLayoutManager *layoutManager;
|
||||
@property (nonatomic, strong) NSTextContainer *textContainer;
|
||||
@property (nonatomic, copy) NSAttributedString *attributedText;
|
||||
@property (nonatomic, assign) NSLineBreakMode lineBreakMode;
|
||||
@property (nonatomic, assign) NSUInteger numberOfLines;
|
||||
|
||||
@property (nonatomic, assign) UIEdgeInsets contentInset;
|
||||
|
||||
@end
|
||||
|
||||
@@ -23,15 +23,7 @@
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
if ((self = [super initWithFrame:frame])) {
|
||||
_textContainer = [[NSTextContainer alloc] init];
|
||||
_textContainer.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
_textContainer.lineFragmentPadding = 0.0;
|
||||
|
||||
_layoutManager = [[NSLayoutManager alloc] init];
|
||||
[_layoutManager addTextContainer:_textContainer];
|
||||
|
||||
_textStorage = [[NSTextStorage alloc] init];
|
||||
[_textStorage addLayoutManager:_layoutManager];
|
||||
|
||||
self.contentMode = UIViewContentModeRedraw;
|
||||
}
|
||||
@@ -50,25 +42,31 @@
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (NSUInteger)numberOfLines
|
||||
- (void)setTextContainer:(NSTextContainer *)textContainer
|
||||
{
|
||||
return _textContainer.maximumNumberOfLines;
|
||||
}
|
||||
if ([_textContainer isEqual:textContainer]) return;
|
||||
|
||||
_textContainer = textContainer;
|
||||
|
||||
for (NSInteger i = _layoutManager.textContainers.count - 1; i >= 0; i--) {
|
||||
[_layoutManager removeTextContainerAtIndex:i];
|
||||
}
|
||||
[_layoutManager addTextContainer:_textContainer];
|
||||
|
||||
- (void)setNumberOfLines:(NSUInteger)numberOfLines
|
||||
{
|
||||
_textContainer.maximumNumberOfLines = numberOfLines;
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (NSLineBreakMode)lineBreakMode
|
||||
- (void)setLayoutManager:(NSLayoutManager *)layoutManager
|
||||
{
|
||||
return _textContainer.lineBreakMode;
|
||||
}
|
||||
if ([_layoutManager isEqual:layoutManager]) return;
|
||||
|
||||
_layoutManager = layoutManager;
|
||||
|
||||
for (NSLayoutManager *existingLayoutManager in _textStorage.layoutManagers) {
|
||||
[_textStorage removeLayoutManager:existingLayoutManager];
|
||||
}
|
||||
[_textStorage addLayoutManager:_layoutManager];
|
||||
|
||||
- (void)setLineBreakMode:(NSLineBreakMode)lineBreakMode
|
||||
{
|
||||
_textContainer.lineBreakMode = lineBreakMode;
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
|
||||
@@ -33,15 +33,6 @@
|
||||
#pragma mark - View properties
|
||||
|
||||
RCT_REMAP_VIEW_PROPERTY(containerBackgroundColor, backgroundColor, UIColor)
|
||||
RCT_CUSTOM_VIEW_PROPERTY(numberOfLines, NSInteger, RCTText)
|
||||
{
|
||||
NSLineBreakMode truncationMode = NSLineBreakByClipping;
|
||||
view.numberOfLines = json ? [RCTConvert NSInteger:json] : defaultView.numberOfLines;
|
||||
if (view.numberOfLines > 0) {
|
||||
truncationMode = NSLineBreakByTruncatingTail;
|
||||
}
|
||||
view.lineBreakMode = truncationMode;
|
||||
}
|
||||
|
||||
#pragma mark - Shadow properties
|
||||
|
||||
@@ -65,8 +56,8 @@ RCT_CUSTOM_SHADOW_PROPERTY(containerBackgroundColor, UIColor, RCTShadowText)
|
||||
RCT_CUSTOM_SHADOW_PROPERTY(numberOfLines, NSInteger, RCTShadowText)
|
||||
{
|
||||
NSLineBreakMode truncationMode = NSLineBreakByClipping;
|
||||
view.maxNumberOfLines = json ? [RCTConvert NSInteger:json] : defaultView.maxNumberOfLines;
|
||||
if (view.maxNumberOfLines > 0) {
|
||||
view.maximumNumberOfLines = json ? [RCTConvert NSInteger:json] : defaultView.maximumNumberOfLines;
|
||||
if (view.maximumNumberOfLines > 0) {
|
||||
truncationMode = NSLineBreakByTruncatingTail;
|
||||
}
|
||||
view.truncationMode = truncationMode;
|
||||
@@ -124,12 +115,16 @@ RCT_CUSTOM_SHADOW_PROPERTY(numberOfLines, NSInteger, RCTShadowText)
|
||||
};
|
||||
}
|
||||
|
||||
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowView *)shadowView
|
||||
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowText *)shadowView
|
||||
{
|
||||
NSNumber *reactTag = shadowView.reactTag;
|
||||
UIEdgeInsets padding = shadowView.paddingAsInsets;
|
||||
|
||||
return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||
((RCTText *)viewRegistry[reactTag]).contentInset = padding;
|
||||
RCTText *text = (RCTText *)viewRegistry[reactTag];
|
||||
text.contentInset = padding;
|
||||
text.layoutManager = shadowView.layoutManager;
|
||||
text.textContainer = shadowView.textContainer;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
21
Libraries/Utilities/BackAndroid.ios.js
Normal file
21
Libraries/Utilities/BackAndroid.ios.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* iOS stub for BackAndroid.android.js
|
||||
*
|
||||
* @providesModule BackAndroid
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var warning = require('warning');
|
||||
|
||||
function platformWarn() {
|
||||
warning(false, 'BackAndroid is not supported on this platform.');
|
||||
}
|
||||
|
||||
var BackAndroid = {
|
||||
exitApp: platformWarn,
|
||||
addEventListener: platformWarn,
|
||||
removeEventListener: platformWarn,
|
||||
};
|
||||
|
||||
module.exports = BackAndroid;
|
||||
@@ -1,16 +0,0 @@
|
||||
/**
|
||||
* To lower the risk of breaking things on iOS, we are stubbing out the
|
||||
* BackStack for now. See Backstack.android.js
|
||||
*
|
||||
* @providesModule Backstack
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var Backstack = {
|
||||
pushNavigation: () => {},
|
||||
resetToBefore: () => {},
|
||||
removeComponentHistory: () => {},
|
||||
};
|
||||
|
||||
module.exports = Backstack;
|
||||
Reference in New Issue
Block a user