diff --git a/Libraries/Components/ScrollView/RecyclerViewBackedScrollView.android.js b/Libraries/Components/ScrollView/RecyclerViewBackedScrollView.android.js index 86e60af82709fc..cc7246daf75ee2 100644 --- a/Libraries/Components/ScrollView/RecyclerViewBackedScrollView.android.js +++ b/Libraries/Components/ScrollView/RecyclerViewBackedScrollView.android.js @@ -75,6 +75,30 @@ var RecyclerViewBackedScrollView = React.createClass({ this.props.onContentSizeChange(width, height); }, + /** + * A helper function to scroll to a specific point in the scrollview. + * This is currently used to help focus on child textviews, but can also + * be used to quickly scroll to any element we want to focus. Syntax: + * + * scrollResponderScrollTo(options: {x: number = 0; y: number = 0; animated: boolean = true}) + * + * Note: The weird argument signature is due to the fact that, for historical reasons, + * the function also accepts separate arguments as as alternative to the options object. + * This is deprecated due to ambiguity (y before x), and SHOULD NOT BE USED. + */ + scrollTo: function( + y?: number | { x?: number, y?: number, animated?: boolean }, + x?: number, + animated?: boolean + ) { + if (typeof y === 'number') { + console.warn('`scrollTo(y, x, animated)` is deprecated. Use `scrollTo({x: 5, y: 5, animated: true})` instead.'); + } else { + ({x, y, animated} = y || {}); + } + this.getScrollResponder().scrollResponderScrollTo({x: x || 0, y: y || 0, animated: animated !== false}); + }, + render: function() { var recyclerProps = { ...this.props, diff --git a/Libraries/CustomComponents/NavigationExperimental/NavigationCardStack.js b/Libraries/CustomComponents/NavigationExperimental/NavigationCardStack.js index 41d2d7154e2075..dc518628a1864a 100644 --- a/Libraries/CustomComponents/NavigationExperimental/NavigationCardStack.js +++ b/Libraries/CustomComponents/NavigationExperimental/NavigationCardStack.js @@ -133,13 +133,7 @@ class NavigationCardStack extends React.Component { renderOverlay } = this.props; - let overlay = null; - if (renderOverlay) { - overlay = renderOverlay({ - ...props, - scene: props.scene, - }); - } + const overlay = renderOverlay && renderOverlay(props); const scenes = props.scenes.map( scene => this._renderScene({ diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m index fe7abb3d1493c0..ec78c3a5fac087 100644 --- a/Libraries/Image/RCTImageLoader.m +++ b/Libraries/Image/RCTImageLoader.m @@ -296,10 +296,16 @@ - (RCTImageLoaderCancellationBlock)_loadImageOrDataWithURLRequest:(NSURLRequest __block dispatch_block_t cancelLoad = nil; __weak RCTImageLoader *weakSelf = self; - // Add missing png extension - if (request.URL.fileURL && request.URL.pathExtension.length == 0) { + { NSMutableURLRequest *mutableRequest = [request mutableCopy]; - mutableRequest.URL = [NSURL fileURLWithPath:[request.URL.path stringByAppendingPathExtension:@"png"]]; + [NSURLProtocol setProperty:@"RCTImageLoader" + forKey:@"trackingName" + inRequest:mutableRequest]; + + // Add missing png extension + if (request.URL.fileURL && request.URL.pathExtension.length == 0) { + mutableRequest.URL = [NSURL fileURLWithPath:[request.URL.path stringByAppendingPathExtension:@"png"]]; + } request = mutableRequest; } diff --git a/Libraries/Network/RCTNetworking.m b/Libraries/Network/RCTNetworking.m index 5be3de7f901037..ea912e0f82c647 100644 --- a/Libraries/Network/RCTNetworking.m +++ b/Libraries/Network/RCTNetworking.m @@ -223,6 +223,12 @@ - (RCTURLRequestCancellationBlock)buildRequest:(NSDictionary *)q request.allHTTPHeaderFields = [self stripNullsInRequestHeaders:[RCTConvert NSDictionary:query[@"headers"]]]; request.timeoutInterval = [RCTConvert NSTimeInterval:query[@"timeout"]]; NSDictionary *data = [RCTConvert NSDictionary:RCTNilIfNull(query[@"data"])]; + NSString *trackingName = data[@"trackingName"]; + if (trackingName) { + [NSURLProtocol setProperty:trackingName + forKey:@"trackingName" + inRequest:request]; + } return [self processDataForHTTPQuery:data callback:^(NSError *error, NSDictionary *result) { if (error) { RCTLogError(@"Error processing request body: %@", error); diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index e9e1f608da4026..e3d9ea0d3ea41b 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -57,6 +57,7 @@ @implementation RCTBatchedBridge @synthesize flowIDMapLock = _flowIDMapLock; @synthesize loading = _loading; @synthesize valid = _valid; +@synthesize performanceLogger = _performanceLogger; - (instancetype)initWithParentBridge:(RCTBridge *)bridge { @@ -68,6 +69,10 @@ - (instancetype)initWithParentBridge:(RCTBridge *)bridge launchOptions:bridge.launchOptions]) { _parentBridge = bridge; + _performanceLogger = [RCTPerformanceLogger new]; + [_performanceLogger markStartForTag:RCTPLBridgeStartup]; + [_performanceLogger markStartForTag:RCTPLTTI]; + /** * Set Initial State */ diff --git a/React/Base/RCTBridge+Private.h b/React/Base/RCTBridge+Private.h index e7693685b7af30..f69e2beec57461 100644 --- a/React/Base/RCTBridge+Private.h +++ b/React/Base/RCTBridge+Private.h @@ -10,14 +10,9 @@ #import "RCTBridge.h" @class RCTModuleData; -@class RCTPerformanceLogger; @protocol RCTJavaScriptExecutor; @interface RCTBridge () -{ -@public - RCTPerformanceLogger *_performanceLogger; -} // Private designated initializer - (instancetype)initWithDelegate:(id)delegate @@ -134,9 +129,9 @@ @interface RCTBatchedBridge : RCTBridge -@property (nonatomic, weak) RCTBridge *parentBridge; -@property (nonatomic, weak) id javaScriptExecutor; -@property (nonatomic, assign) BOOL moduleSetupComplete; +@property (nonatomic, weak, readonly) RCTBridge *parentBridge; +@property (nonatomic, weak, readonly) id javaScriptExecutor; +@property (nonatomic, assign, readonly) BOOL moduleSetupComplete; - (instancetype)initWithParentBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER; diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 030ca38259f80c..f515a87315555e 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -132,15 +132,13 @@ - (instancetype)initWithDelegate:(id)delegate launchOptions:(NSDictionary *)launchOptions { if (self = [super init]) { - _performanceLogger = [RCTPerformanceLogger new]; - [_performanceLogger markStartForTag:RCTPLBridgeStartup]; - [_performanceLogger markStartForTag:RCTPLTTI]; - _delegate = delegate; _bundleURL = bundleURL; _moduleProvider = block; _launchOptions = [launchOptions copy]; + [self setUp]; + RCTExecuteOnMainQueue(^{ [self bindKeys]; }); } return self; @@ -182,6 +180,11 @@ - (void)bindKeys #endif } +- (RCTPerformanceLogger *)performanceLogger +{ + return self.batchedBridge.performanceLogger; +} + - (NSArray *)moduleClasses { return self.batchedBridge.moduleClasses; diff --git a/React/Base/RCTPerformanceLogger.h b/React/Base/RCTPerformanceLogger.h index 9477a0ec679d83..ab37e1d54e2e81 100644 --- a/React/Base/RCTPerformanceLogger.h +++ b/React/Base/RCTPerformanceLogger.h @@ -85,7 +85,7 @@ typedef NS_ENUM(NSUInteger, RCTPLTag) { - (NSArray *)valuesForTags; /** - * Returns a duration (stop_time - start_time) for given RCTPLTag. + * Returns a duration in ms (stop_time - start_time) for given RCTPLTag. */ - (int64_t)durationForTag:(RCTPLTag)tag; diff --git a/React/Base/RCTRootView.m b/React/Base/RCTRootView.m index 20fc643a1a1bdb..fe0b3ed2de4e5f 100644 --- a/React/Base/RCTRootView.m +++ b/React/Base/RCTRootView.m @@ -14,7 +14,7 @@ #import #import "RCTAssert.h" -#import "RCTBridge+Private.h" +#import "RCTBridge.h" #import "RCTEventDispatcher.h" #import "RCTKeyCommands.h" #import "RCTLog.h" @@ -92,7 +92,7 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge object:self]; if (!_bridge.loading) { - [self bundleFinishedLoading:_bridge.batchedBridge]; + [self bundleFinishedLoading:_bridge]; } [self showLoadingView]; @@ -259,7 +259,7 @@ - (void)setAppProperties:(NSDictionary *)appProperties _appProperties = [appProperties copy]; if (_contentView && _bridge.valid && !_bridge.loading) { - [self runApplication:_bridge.batchedBridge]; + [self runApplication:_bridge]; } } @@ -340,7 +340,7 @@ - (instancetype)initWithFrame:(CGRect)frame - (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex { [super insertReactSubview:subview atIndex:atIndex]; - [_bridge->_performanceLogger markStopForTag:RCTPLTTI]; + [_bridge.performanceLogger markStopForTag:RCTPLTTI]; dispatch_async(dispatch_get_main_queue(), ^{ if (!self->_contentHasAppeared) { self->_contentHasAppeared = YES; diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/core/Timing.java b/ReactAndroid/src/main/java/com/facebook/react/modules/core/Timing.java index e95bf245e007dc..2fc348e8d5c4b5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/core/Timing.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/core/Timing.java @@ -22,7 +22,6 @@ import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.bridge.WritableArray; -import com.facebook.react.common.MapBuilder; import com.facebook.react.common.SystemClock; import com.facebook.react.devsupport.DevSupportManager; import com.facebook.react.uimanager.ReactChoreographer; @@ -105,7 +104,7 @@ public void doFrame(long frameTimeNanos) { timer.mTargetTime = frameTimeMillis + timer.mInterval; mTimers.add(timer); } else { - mTimerIdsToTimers.remove(timer.mCallbackID); + mTimerIdsToTimers.remove(timer.mExecutorToken); } } } @@ -386,7 +385,7 @@ public void deleteTimer(ExecutorToken executorToken, int timerId) { return; } // We may have already called/removed it - mTimerIdsToTimers.remove(timerId); + mTimerIdsToTimers.remove(executorToken); mTimers.remove(timer); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index b04af822a124cb..033ea83e051e79 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -264,9 +264,11 @@ public void manageChildren( public void setChildren( int viewTag, ReadableArray childrenTags) { - FLog.d( - ReactConstants.TAG, - "(UIManager.setChildren) tag: " + viewTag + ", children: " + childrenTags); + if (DEBUG) { + FLog.d( + ReactConstants.TAG, + "(UIManager.setChildren) tag: " + viewTag + ", children: " + childrenTags); + } mUIImplementation.setChildren(viewTag, childrenTags); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewGroupManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewGroupManager.java index a5094e7d5b8db4..6a728db8f98e56 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewGroupManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewGroupManager.java @@ -9,15 +9,16 @@ package com.facebook.react.uimanager; -import android.view.View; -import android.view.ViewGroup; - import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.List; import java.util.WeakHashMap; +import android.view.View; +import android.view.ViewGroup; + /** * Class providing children management API for view managers of classes extending ViewGroup. */ @@ -45,6 +46,19 @@ public void addView(T parent, View child, int index) { reorderChildrenByZIndex(parent); } + /** + * Convenience method for batching a set of addView calls + * Note that this adds the views to the beginning of the ViewGroup + * + * @param parent the parent ViewGroup + * @param views the set of views to add + */ + public void addViews(T parent, List views) { + for (int i = 0, size = views.size(); i < size; i++) { + addView(parent, views.get(i), i); + } + } + public static void setViewZIndex(View view, int zIndex) { mZIndexHash.put(view, zIndex); // zIndex prop gets set BEFORE the view is added, so parent may be null. diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTRenderableViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTRenderableViewManager.java index 44ba41ba63030d..5af6d179453a06 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTRenderableViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTRenderableViewManager.java @@ -52,11 +52,11 @@ public String getName() { @Override public ReactShadowNode createShadowNodeInstance() { - if (mClassName == CLASS_GROUP) { + if (CLASS_GROUP.equals(mClassName)) { return new ARTGroupShadowNode(); - } else if (mClassName == CLASS_SHAPE) { + } else if (CLASS_SHAPE.equals(mClassName)) { return new ARTShapeShadowNode(); - } else if (mClassName == CLASS_TEXT) { + } else if (CLASS_TEXT.equals(mClassName)) { return new ARTTextShadowNode(); } else { throw new IllegalStateException("Unexpected type " + mClassName); @@ -65,11 +65,11 @@ public ReactShadowNode createShadowNodeInstance() { @Override public Class getShadowNodeClass() { - if (mClassName == CLASS_GROUP) { + if (CLASS_GROUP.equals(mClassName)) { return ARTGroupShadowNode.class; - } else if (mClassName == CLASS_SHAPE) { + } else if (CLASS_SHAPE.equals(mClassName)) { return ARTShapeShadowNode.class; - } else if (mClassName == CLASS_TEXT) { + } else if (CLASS_TEXT.equals(mClassName)) { return ARTTextShadowNode.class; } else { throw new IllegalStateException("Unexpected type " + mClassName); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/ReactViewPager.java b/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/ReactViewPager.java index 3f9d8961aba0d0..c5a2ea12f71557 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/ReactViewPager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/ReactViewPager.java @@ -28,11 +28,12 @@ * views to custom {@link PagerAdapter} instance which is used by {@link NativeViewHierarchyManager} * to add children nodes according to react views hierarchy. */ -/* package */ class ReactViewPager extends ViewPager { +public class ReactViewPager extends ViewPager { private class Adapter extends PagerAdapter { private final List mViews = new ArrayList<>(); + private boolean mIsViewPagerInIntentionallyInconsistentState = false; void addView(View child, int index) { mViews.add(index, child); @@ -57,6 +58,32 @@ void removeViewAt(int index) { setOffscreenPageLimit(mViews.size()); } + /** + * Replace a set of views to the ViewPager adapter and update the ViewPager + */ + void setViews(List views) { + mViews.clear(); + mViews.addAll(views); + notifyDataSetChanged(); + + // we want to make sure we return POSITION_NONE for every view here, since this is only + // called after a removeAllViewsFromAdapter + mIsViewPagerInIntentionallyInconsistentState = false; + } + + /** + * Remove all the views from the adapter and de-parents them from the ViewPager + * After calling this, it is expected that notifyDataSetChanged should be called soon + * afterwards. + */ + void removeAllViewsFromAdapter(ViewPager pager) { + mViews.clear(); + pager.removeAllViews(); + // set this, so that when the next addViews is called, we return POSITION_NONE for every + // entry so we can remove whichever views we need to and add the ones that we need to. + mIsViewPagerInIntentionallyInconsistentState = true; + } + View getViewAt(int index) { return mViews.get(index); } @@ -68,7 +95,9 @@ public int getCount() { @Override public int getItemPosition(Object object) { - return mViews.contains(object) ? mViews.indexOf(object) : POSITION_NONE; + // if we've removed all views, we want to return POSITION_NONE intentionally + return mIsViewPagerInIntentionallyInconsistentState || !mViews.contains(object) ? + POSITION_NONE : mViews.indexOf(object); } @Override @@ -190,4 +219,12 @@ public void setScrollEnabled(boolean scrollEnabled) { /*package*/ View getViewFromAdapter(int index) { return getAdapter().getViewAt(index); } + + public void setViews(List views) { + getAdapter().setViews(views); + } + + public void removeAllViewsFromAdapter() { + getAdapter().removeAllViewsFromAdapter(this); + } } diff --git a/docs/UsingNavigators.md b/docs/UsingNavigators.md index 16ef78f7899f72..5aee3bb6811dac 100644 --- a/docs/UsingNavigators.md +++ b/docs/UsingNavigators.md @@ -51,7 +51,7 @@ Notice the `export default` in front of the component declaration. This will _ex ```javascript import React, { Component } from 'react'; -import { View, Text } from 'react-native'; +import { AppRegistry } from 'react-native'; import MyScene from './MyScene';