mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Compare commits
22
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
daccc4709c | ||
|
|
85535e60cb | ||
|
|
44eedabf52 | ||
|
|
4890d56eb1 | ||
|
|
619a576f47 | ||
|
|
e9883fd68c | ||
|
|
70bb4624f1 | ||
|
|
936d72a9c7 | ||
|
|
9347583d8a | ||
|
|
71785a06b3 | ||
|
|
fa68531ba2 | ||
|
|
516d97397b | ||
|
|
8b1a34d0ee | ||
|
|
bc72cf1514 | ||
|
|
5db36da8a1 | ||
|
|
d32cf68ff8 | ||
|
|
2b087c2c94 | ||
|
|
7f98028e71 | ||
|
|
f1fdd8914a | ||
|
|
75f4033ca0 | ||
|
|
7f42a44751 | ||
|
|
b570c23cfe |
@@ -57,6 +57,11 @@ exports.examples = [
|
||||
onPress={() => { _scrollView.scrollTo({y: 0}); }}>
|
||||
<Text>Scroll to top</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={styles.button}
|
||||
onPress={() => { _scrollView.scrollToEnd({animated: true}); }}>
|
||||
<Text>Scroll to bottom</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -79,6 +84,11 @@ exports.examples = [
|
||||
onPress={() => { _scrollView.scrollTo({x: 0}); }}>
|
||||
<Text>Scroll to start</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={styles.button}
|
||||
onPress={() => { _scrollView.scrollToEnd({animated: true}); }}>
|
||||
<Text>Scroll to end</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -57,6 +57,20 @@ class ScrollViewSimpleExample extends React.Component {
|
||||
{this.makeItems(NUM_ITEMS, [styles.itemWrapper, styles.horizontalItemWrapper])}
|
||||
</ScrollView>
|
||||
);
|
||||
items.push(
|
||||
<ScrollView
|
||||
key={'scrollViewSnap'}
|
||||
horizontal
|
||||
snapToInterval={210}
|
||||
pagingEnabled
|
||||
>
|
||||
{this.makeItems(NUM_ITEMS, [
|
||||
styles.itemWrapper,
|
||||
styles.horizontalItemWrapper,
|
||||
styles.horizontalPagingItemWrapper,
|
||||
])}
|
||||
</ScrollView>
|
||||
);
|
||||
|
||||
var verticalScrollView = (
|
||||
<ScrollView style={styles.verticalScrollView}>
|
||||
@@ -83,7 +97,10 @@ var styles = StyleSheet.create({
|
||||
},
|
||||
horizontalItemWrapper: {
|
||||
padding: 50
|
||||
}
|
||||
},
|
||||
horizontalPagingItemWrapper: {
|
||||
width: 200,
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = ScrollViewSimpleExample;
|
||||
|
||||
@@ -87,6 +87,7 @@ exports.examples = [
|
||||
backgroundColor: 'rgb(180, 64, 119)',
|
||||
width: 200,
|
||||
height: 100,
|
||||
borderRadius: 20,
|
||||
transform: [{scale: mScale}]
|
||||
};
|
||||
return (
|
||||
|
||||
@@ -377,11 +377,11 @@ var ScrollResponderMixin = {
|
||||
},
|
||||
|
||||
/**
|
||||
* 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
|
||||
* A helper function to scroll to a specific point in the ScrollView.
|
||||
* This is currently used to help focus 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})
|
||||
* `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.
|
||||
@@ -404,6 +404,26 @@ var ScrollResponderMixin = {
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Scrolls to the end of the ScrollView, either immediately or with a smooth
|
||||
* animation.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* `scrollResponderScrollToEnd({animated: true})`
|
||||
*/
|
||||
scrollResponderScrollToEnd: function(
|
||||
options?: { animated?: boolean },
|
||||
) {
|
||||
// Default to true
|
||||
const animated = (options && options.animated) !== false;
|
||||
UIManager.dispatchViewManagerCommand(
|
||||
this.scrollResponderGetScrollableNode(),
|
||||
UIManager.RCTScrollView.Commands.scrollToEnd,
|
||||
[animated],
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Deprecated, do not use.
|
||||
*/
|
||||
|
||||
@@ -294,8 +294,8 @@ const ScrollView = React.createClass({
|
||||
* When set, causes the scroll view to stop at multiples of the value of
|
||||
* `snapToInterval`. This can be used for paginating through children
|
||||
* that have lengths smaller than the scroll view. Used in combination
|
||||
* with `snapToAlignment`.
|
||||
* @platform ios
|
||||
* with `snapToAlignment` on ios.
|
||||
* Supported for horizontal scrollview on android. Use in combination with `pagingEnabled`.
|
||||
*/
|
||||
snapToInterval: PropTypes.number,
|
||||
/**
|
||||
@@ -382,11 +382,11 @@ const ScrollView = React.createClass({
|
||||
/**
|
||||
* Scrolls to a given x, y offset, either immediately or with a smooth animation.
|
||||
*
|
||||
* Syntax:
|
||||
* Example:
|
||||
*
|
||||
* `scrollTo(options: {x: number = 0; y: number = 0; animated: boolean = true})`
|
||||
* `scrollTo({x: 0; y: 0; animated: true})`
|
||||
*
|
||||
* Note: The weird argument signature is due to the fact that, for historical reasons,
|
||||
* Note: The weird function 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.
|
||||
*/
|
||||
@@ -404,7 +404,25 @@ const ScrollView = React.createClass({
|
||||
},
|
||||
|
||||
/**
|
||||
* Deprecated, do not use.
|
||||
* If this is a vertical ScrollView scrolls to the bottom.
|
||||
* If this is a horizontal ScrollView scrolls to the right.
|
||||
*
|
||||
* Use `scrollToEnd({animated: true})` for smooth animated scrolling,
|
||||
* `scrollToEnd({animated: false})` for immediate scrolling.
|
||||
* If no options are passed, `animated` defaults to true.
|
||||
*/
|
||||
scrollToEnd: function(
|
||||
options?: { animated?: boolean },
|
||||
) {
|
||||
// Default to true
|
||||
const animated = (options && options.animated) !== false;
|
||||
this.getScrollResponder().scrollResponderScrollToEnd({
|
||||
animated: animated,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Deprecated, use `scrollTo` instead.
|
||||
*/
|
||||
scrollWithoutAnimationTo: function(y: number = 0, x: number = 0) {
|
||||
console.warn('`scrollWithoutAnimationTo` is deprecated. Use `scrollTo` instead');
|
||||
|
||||
@@ -287,6 +287,29 @@ var ListView = React.createClass({
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* If this is a vertical ListView scrolls to the bottom.
|
||||
* If this is a horizontal ListView scrolls to the right.
|
||||
*
|
||||
* Use `scrollToEnd({animated: true})` for smooth animated scrolling,
|
||||
* `scrollToEnd({animated: false})` for immediate scrolling.
|
||||
* If no options are passed, `animated` defaults to true.
|
||||
*
|
||||
* See `ScrollView#scrollToEnd`.
|
||||
*/
|
||||
scrollToEnd: function(options?: { animated?: boolean }) {
|
||||
if (this._scrollComponent) {
|
||||
if (this._scrollComponent.scrollToEnd) {
|
||||
this._scrollComponent.scrollToEnd(options);
|
||||
} else {
|
||||
console.warn(
|
||||
'The scroll component used by the ListView does not support ' +
|
||||
'scrollToEnd. Check the renderScrollComponent prop of your ListView.'
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
setNativeProps: function(props: Object) {
|
||||
if (this._scrollComponent) {
|
||||
this._scrollComponent.setNativeProps(props);
|
||||
|
||||
@@ -20,13 +20,12 @@
|
||||
|
||||
#import "RCTImageUtils.h"
|
||||
|
||||
static const NSUInteger RCTMaxCachableDecodedImageSizeInBytes = 1048576; // 1MB
|
||||
static const NSUInteger RCTMaxCachableDecodedImageSizeInBytes = 1048576 * 4; // 4MB
|
||||
|
||||
static NSString *RCTCacheKeyForImage(NSString *imageTag, CGSize size, CGFloat scale,
|
||||
RCTResizeMode resizeMode, NSString *responseDate)
|
||||
{
|
||||
return [NSString stringWithFormat:@"%@|%g|%g|%g|%zd|%@",
|
||||
imageTag, size.width, size.height, scale, resizeMode, responseDate];
|
||||
return [NSString stringWithFormat:@"%@", imageTag];
|
||||
}
|
||||
|
||||
@implementation RCTImageCache
|
||||
@@ -38,7 +37,7 @@ static NSString *RCTCacheKeyForImage(NSString *imageTag, CGSize size, CGFloat sc
|
||||
- (instancetype)init
|
||||
{
|
||||
_decodedImageCache = [NSCache new];
|
||||
_decodedImageCache.totalCostLimit = 5 * 1024 * 1024; // 5MB
|
||||
_decodedImageCache.totalCostLimit = 32 * 1024 * 1024; // 32MB
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(clearCache)
|
||||
|
||||
@@ -386,6 +386,9 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
|
||||
} else {
|
||||
// Use networking module to load image
|
||||
cancelLoad = [strongSelf _loadURLRequest:request
|
||||
size:size
|
||||
scale:scale
|
||||
resizeMode:resizeMode
|
||||
progressBlock:progressHandler
|
||||
completionBlock:completionHandler];
|
||||
}
|
||||
@@ -402,6 +405,9 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
|
||||
}
|
||||
|
||||
- (RCTImageLoaderCancellationBlock)_loadURLRequest:(NSURLRequest *)request
|
||||
size:(CGSize)size
|
||||
scale:(CGFloat)scale
|
||||
resizeMode:(RCTResizeMode)resizeMode
|
||||
progressBlock:(RCTImageLoaderProgressBlock)progressHandler
|
||||
completionBlock:(void (^)(NSError *error, id imageOrData, NSString *fetchDate))completionHandler
|
||||
{
|
||||
@@ -413,6 +419,17 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
UIImage *image = [[self imageCache] imageForUrl:request.URL.absoluteString
|
||||
size:size
|
||||
scale:scale
|
||||
resizeMode:resizeMode
|
||||
responseDate:@""];
|
||||
if (image) {
|
||||
completionHandler(nil, image, @"");
|
||||
return ^{ };
|
||||
}
|
||||
|
||||
|
||||
RCTNetworking *networking = [_bridge networking];
|
||||
|
||||
// Check if networking module can load image
|
||||
@@ -457,9 +474,9 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
|
||||
|
||||
// Download image
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
__block RCTNetworkTask *task =
|
||||
[networking networkTaskWithRequest:request
|
||||
completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) {
|
||||
__block RCTNetworkTask *task = [networking networkTaskWithRequest:request
|
||||
completionBlock:^(NSURLResponse *response, NSData *data, NSError *error)
|
||||
{
|
||||
__typeof(self) strongSelf = weakSelf;
|
||||
if (!strongSelf) {
|
||||
return;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
#import "RCTTextManager.h"
|
||||
|
||||
#import <yoga/Yoga.h>
|
||||
#import <React/Yoga.h>
|
||||
#import <React/RCTAccessibilityManager.h>
|
||||
#import <React/RCTAssert.h>
|
||||
#import <React/RCTConvert.h>
|
||||
|
||||
+9
-2
@@ -4,7 +4,7 @@ package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "React"
|
||||
s.version = package['version']
|
||||
s.version = "0.41.101"
|
||||
s.summary = package['description']
|
||||
s.description = <<-DESC
|
||||
React Native apps are built using the React JS
|
||||
@@ -34,19 +34,26 @@ Pod::Spec.new do |s|
|
||||
ss.dependency 'React/yoga'
|
||||
ss.dependency 'React/cxxreact'
|
||||
ss.source_files = "React/**/*.{c,h,m,mm,S}"
|
||||
ss.exclude_files = "**/__tests__/*", "IntegrationTests/*", "ReactCommon/yoga/*"
|
||||
ss.exclude_files = "**/__tests__/*", "IntegrationTests/*", "React/**/RCTTVView.*", "ReactCommon/yoga/*"
|
||||
ss.frameworks = "JavaScriptCore"
|
||||
ss.libraries = "stdc++"
|
||||
end
|
||||
|
||||
s.subspec 'tvOS' do |ss|
|
||||
ss.dependency 'React/Core'
|
||||
ss.source_files = "React/**/RCTTVView.{h, m}"
|
||||
end
|
||||
|
||||
s.subspec 'jschelpers' do |ss|
|
||||
ss.source_files = 'ReactCommon/jschelpers/{JavaScriptCore,JSCWrapper}.{cpp,h}'
|
||||
ss.private_header_files = "ReactCommon/jschelpers/{JavaScriptCore,JSCWrapper}.h"
|
||||
ss.header_dir = 'jschelpers'
|
||||
end
|
||||
|
||||
s.subspec 'cxxreact' do |ss|
|
||||
ss.dependency 'React/jschelpers'
|
||||
ss.source_files = 'ReactCommon/cxxreact/{JSBundleType,oss-compat-util}.{cpp,h}'
|
||||
ss.private_header_files = "ReactCommon/cxxreact/{JSBundleType,oss-compat-util}.h"
|
||||
ss.header_dir = 'cxxreact'
|
||||
end
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <yoga/Yoga.h>
|
||||
#import <React/Yoga.h>
|
||||
#import <React/RCTAnimationType.h>
|
||||
#import <React/RCTBorderStyle.h>
|
||||
#import <React/RCTDefines.h>
|
||||
|
||||
@@ -11,8 +11,13 @@
|
||||
|
||||
#import <sys/stat.h>
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#import <React/JSBundleType.h>
|
||||
#import <React/JavaScriptCore.h>
|
||||
#else
|
||||
#import <cxxreact/JSBundleType.h>
|
||||
#import <jschelpers/JavaScriptCore.h>
|
||||
#endif
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTConvert.h"
|
||||
|
||||
@@ -480,10 +480,10 @@ SEL RCTParseMethodSignature(NSString *methodSignature, NSArray<RCTMethodArgument
|
||||
expectedCount -= 2;
|
||||
}
|
||||
|
||||
RCTLogError(@"%@.%@ was called with %zd arguments, but expects %zd. \
|
||||
If you haven\'t changed this method yourself, this usually means that \
|
||||
your versions of the native code and JavaScript code are out of sync. \
|
||||
Updating both should make this error go away.",
|
||||
RCTLogError(@"%@.%@ was called with %zd arguments but expects %zd arguments. "
|
||||
@"If you haven\'t changed this method yourself, this usually means that "
|
||||
@"your versions of the native code and JavaScript code are out of sync. "
|
||||
@"Updating both should make this error go away.",
|
||||
RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName,
|
||||
actualCount, expectedCount);
|
||||
return nil;
|
||||
|
||||
@@ -9,7 +9,11 @@
|
||||
|
||||
#include "RCTJSCErrorHandling.h"
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#import <React/JavaScriptCore.h>
|
||||
#else
|
||||
#import <jschelpers/JavaScriptCore.h>
|
||||
#endif
|
||||
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTJSStackFrame.h"
|
||||
|
||||
@@ -17,8 +17,13 @@
|
||||
|
||||
#import <UIKit/UIDevice.h>
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#import <React/JSBundleType.h>
|
||||
#import <React/JavaScriptCore.h>
|
||||
#else
|
||||
#import <cxxreact/JSBundleType.h>
|
||||
#import <jschelpers/JavaScriptCore.h>
|
||||
#endif
|
||||
|
||||
#import "JSCSamplingProfiler.h"
|
||||
#import "RCTAssert.h"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
#import <yoga/Yoga.h>
|
||||
#import <React/Yoga.h>
|
||||
|
||||
#import "RCTAccessibilityManager.h"
|
||||
#import "RCTAnimationType.h"
|
||||
|
||||
@@ -588,6 +588,11 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
||||
_scrollView.contentOffset = contentOffset;
|
||||
}
|
||||
|
||||
- (BOOL)isHorizontal:(UIScrollView *)scrollView
|
||||
{
|
||||
return scrollView.contentSize.width > self.frame.size.width;
|
||||
}
|
||||
|
||||
- (void)scrollToOffset:(CGPoint)offset
|
||||
{
|
||||
[self scrollToOffset:offset animated:YES];
|
||||
@@ -602,6 +607,26 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is a vertical scroll view, scrolls to the bottom.
|
||||
* If this is a horizontal scroll view, scrolls to the right.
|
||||
*/
|
||||
- (void)scrollToEnd:(BOOL)animated
|
||||
{
|
||||
BOOL isHorizontal = [self isHorizontal:_scrollView];
|
||||
CGPoint offset;
|
||||
if (isHorizontal) {
|
||||
offset = CGPointMake(_scrollView.contentSize.width - _scrollView.bounds.size.width, 0);
|
||||
} else {
|
||||
offset = CGPointMake(0, _scrollView.contentSize.height - _scrollView.bounds.size.height);
|
||||
}
|
||||
if (!CGPointEqualToPoint(_scrollView.contentOffset, offset)) {
|
||||
// Ensure at least one scroll event will fire
|
||||
_allowNextScrollNoMatterWhat = YES;
|
||||
[_scrollView setContentOffset:offset animated:animated];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated
|
||||
{
|
||||
[_scrollView zoomToRect:rect animated:animated];
|
||||
@@ -727,7 +752,7 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, onScroll)
|
||||
CGFloat snapToIntervalF = (CGFloat)self.snapToInterval;
|
||||
|
||||
// Find which axis to snap
|
||||
BOOL isHorizontal = (scrollView.contentSize.width > self.frame.size.width);
|
||||
BOOL isHorizontal = [self isHorizontal:scrollView];
|
||||
|
||||
// What is the current offset?
|
||||
CGFloat targetContentOffsetAlongAxis = isHorizontal ? targetContentOffset->x : targetContentOffset->y;
|
||||
|
||||
@@ -147,6 +147,21 @@ RCT_EXPORT_METHOD(scrollTo:(nonnull NSNumber *)reactTag
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(scrollToEnd:(nonnull NSNumber *)reactTag
|
||||
animated:(BOOL)animated)
|
||||
{
|
||||
[self.bridge.uiManager addUIBlock:
|
||||
^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry){
|
||||
UIView *view = viewRegistry[reactTag];
|
||||
if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
|
||||
[(id<RCTScrollableProtocol>)view scrollToEnd:animated];
|
||||
} else {
|
||||
RCTLogError(@"tried to scrollTo: on non-RCTScrollableProtocol view %@ "
|
||||
"with tag #%@", view, reactTag);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(zoomToRect:(nonnull NSNumber *)reactTag
|
||||
withRect:(CGRect)rect
|
||||
animated:(BOOL)animated)
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
|
||||
- (void)scrollToOffset:(CGPoint)offset;
|
||||
- (void)scrollToOffset:(CGPoint)offset animated:(BOOL)animated;
|
||||
/**
|
||||
* If this is a vertical scroll view, scrolls to the bottom.
|
||||
* If this is a horizontal scroll view, scrolls to the right.
|
||||
*/
|
||||
- (void)scrollToEnd:(BOOL)animated;
|
||||
- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated;
|
||||
|
||||
- (void)addScrollListener:(NSObject<UIScrollViewDelegate> *)scrollListener;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <yoga/Yoga.h>
|
||||
#import <React/Yoga.h>
|
||||
#import <React/RCTComponent.h>
|
||||
#import <React/RCTRootView.h>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
VERSION_NAME=1000.0.0-master
|
||||
VERSION_NAME=0.41.101
|
||||
GROUP=com.facebook.react
|
||||
|
||||
POM_NAME=ReactNative
|
||||
|
||||
+9
@@ -80,6 +80,15 @@ public class RecyclerViewBackedScrollViewManager extends
|
||||
scrollView.scrollTo(data.mDestX, data.mDestY, data.mAnimated);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scrollToEnd(
|
||||
RecyclerViewBackedScrollView scrollView,
|
||||
ReactScrollViewCommandHelper.ScrollToEndCommandData data) {
|
||||
// Not implemented.
|
||||
// RecyclerViewBackedScrollView is deprecated and will be removed.
|
||||
// People should use a standard ScrollView or ListView instead.
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable
|
||||
Map getExportedCustomDirectEventTypeConstants() {
|
||||
|
||||
+11
-1
@@ -48,6 +48,7 @@ public class ReactHorizontalScrollView extends HorizontalScrollView implements
|
||||
private @Nullable String mScrollPerfTag;
|
||||
private @Nullable Drawable mEndBackground;
|
||||
private int mEndFillColor = Color.TRANSPARENT;
|
||||
private int mSnapInterval = 0;
|
||||
|
||||
public ReactHorizontalScrollView(Context context) {
|
||||
this(context, null);
|
||||
@@ -88,6 +89,8 @@ public class ReactHorizontalScrollView extends HorizontalScrollView implements
|
||||
mPagingEnabled = pagingEnabled;
|
||||
}
|
||||
|
||||
public void setSnapInterval(int snapInterval) { mSnapInterval = snapInterval; }
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
MeasureSpecAssertions.assertExplicitMeasureSpec(widthMeasureSpec, heightMeasureSpec);
|
||||
@@ -295,6 +298,13 @@ public class ReactHorizontalScrollView extends HorizontalScrollView implements
|
||||
postOnAnimationDelayed(mPostTouchRunnable, ReactScrollViewHelper.MOMENTUM_DELAY);
|
||||
}
|
||||
|
||||
private int getSnapInterval() {
|
||||
if (mSnapInterval != 0) {
|
||||
return mSnapInterval;
|
||||
}
|
||||
return getWidth();
|
||||
}
|
||||
|
||||
/**
|
||||
* This will smooth scroll us to the nearest page boundary
|
||||
* It currently just looks at where the content is relative to the page and slides to the nearest
|
||||
@@ -302,7 +312,7 @@ public class ReactHorizontalScrollView extends HorizontalScrollView implements
|
||||
* scrolling.
|
||||
*/
|
||||
private void smoothScrollToPage(int velocity) {
|
||||
int width = getWidth();
|
||||
int width = getSnapInterval();
|
||||
int currentX = getScrollX();
|
||||
// TODO (t11123799) - Should we do anything beyond linear accounting of the velocity
|
||||
int predictedX = currentX + velocity;
|
||||
|
||||
+22
@@ -12,9 +12,11 @@ package com.facebook.react.views.scroll;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.util.DisplayMetrics;
|
||||
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.module.annotations.ReactModule;
|
||||
import com.facebook.react.uimanager.DisplayMetricsHolder;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.ViewGroupManager;
|
||||
@@ -58,6 +60,12 @@ public class ReactHorizontalScrollViewManager
|
||||
view.setScrollEnabled(value);
|
||||
}
|
||||
|
||||
@ReactProp(name = "snapToInterval")
|
||||
public void setSnapToInterval(ReactHorizontalScrollView view, int snapToInterval) {
|
||||
DisplayMetrics screenDisplayMetrics = DisplayMetricsHolder.getScreenDisplayMetrics();
|
||||
view.setSnapInterval((int)(snapToInterval * screenDisplayMetrics.density));
|
||||
}
|
||||
|
||||
@ReactProp(name = "showsHorizontalScrollIndicator")
|
||||
public void setShowsHorizontalScrollIndicator(ReactHorizontalScrollView view, boolean value) {
|
||||
view.setHorizontalScrollBarEnabled(value);
|
||||
@@ -117,6 +125,20 @@ public class ReactHorizontalScrollViewManager
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scrollToEnd(
|
||||
ReactHorizontalScrollView scrollView,
|
||||
ReactScrollViewCommandHelper.ScrollToEndCommandData data) {
|
||||
// ScrollView always has one child - the scrollable area
|
||||
int right =
|
||||
scrollView.getChildAt(0).getWidth() + scrollView.getPaddingRight();
|
||||
if (data.mAnimated) {
|
||||
scrollView.smoothScrollTo(right, scrollView.getScrollY());
|
||||
} else {
|
||||
scrollView.scrollTo(right, scrollView.getScrollY());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When set, fills the rest of the scrollview with a color to avoid setting a background and
|
||||
* creating unnecessary overdraw.
|
||||
|
||||
+19
-1
@@ -25,9 +25,11 @@ import com.facebook.react.common.MapBuilder;
|
||||
public class ReactScrollViewCommandHelper {
|
||||
|
||||
public static final int COMMAND_SCROLL_TO = 1;
|
||||
public static final int COMMAND_SCROLL_TO_END = 2;
|
||||
|
||||
public interface ScrollCommandHandler<T> {
|
||||
void scrollTo(T scrollView, ScrollToCommandData data);
|
||||
void scrollToEnd(T scrollView, ScrollToEndCommandData data);
|
||||
}
|
||||
|
||||
public static class ScrollToCommandData {
|
||||
@@ -42,10 +44,21 @@ public class ReactScrollViewCommandHelper {
|
||||
}
|
||||
}
|
||||
|
||||
public static class ScrollToEndCommandData {
|
||||
|
||||
public final boolean mAnimated;
|
||||
|
||||
ScrollToEndCommandData(boolean animated) {
|
||||
mAnimated = animated;
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<String,Integer> getCommandsMap() {
|
||||
return MapBuilder.of(
|
||||
"scrollTo",
|
||||
COMMAND_SCROLL_TO);
|
||||
COMMAND_SCROLL_TO,
|
||||
"scrollToEnd",
|
||||
COMMAND_SCROLL_TO_END);
|
||||
}
|
||||
|
||||
public static <T> void receiveCommand(
|
||||
@@ -64,6 +77,11 @@ public class ReactScrollViewCommandHelper {
|
||||
viewManager.scrollTo(scrollView, new ScrollToCommandData(destX, destY, animated));
|
||||
return;
|
||||
}
|
||||
case COMMAND_SCROLL_TO_END: {
|
||||
boolean animated = args.getBoolean(0);
|
||||
viewManager.scrollToEnd(scrollView, new ScrollToEndCommandData(animated));
|
||||
return;
|
||||
}
|
||||
default:
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Unsupported command %d received by %s.",
|
||||
|
||||
+14
@@ -131,6 +131,20 @@ public class ReactScrollViewManager
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scrollToEnd(
|
||||
ReactScrollView scrollView,
|
||||
ReactScrollViewCommandHelper.ScrollToEndCommandData data) {
|
||||
// ScrollView always has one child - the scrollable area
|
||||
int bottom =
|
||||
scrollView.getChildAt(0).getHeight() + scrollView.getPaddingBottom();
|
||||
if (data.mAnimated) {
|
||||
scrollView.smoothScrollTo(scrollView.getScrollX(), bottom);
|
||||
} else {
|
||||
scrollView.scrollTo(scrollView.getScrollX(), bottom);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Map getExportedCustomDirectEventTypeConstants() {
|
||||
return createExportedCustomDirectEventTypeConstants();
|
||||
|
||||
@@ -12,8 +12,8 @@ package com.facebook.react.views.view;
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.PaintDrawable;
|
||||
import android.graphics.drawable.RippleDrawable;
|
||||
import android.os.Build;
|
||||
import android.util.TypedValue;
|
||||
@@ -23,6 +23,8 @@ import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.SoftAssertions;
|
||||
import com.facebook.react.uimanager.ViewProps;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Utility class that helps with converting android drawable description used in JS to an actual
|
||||
* instance of {@link Drawable}.
|
||||
@@ -31,9 +33,16 @@ public class ReactDrawableHelper {
|
||||
|
||||
private static final TypedValue sResolveOutValue = new TypedValue();
|
||||
|
||||
public static Drawable createDrawableFromJSDescription(
|
||||
Context context,
|
||||
ReadableMap drawableDescriptionDict) {
|
||||
return createDrawableFromJSDescription(context, drawableDescriptionDict, null);
|
||||
}
|
||||
|
||||
public static Drawable createDrawableFromJSDescription(
|
||||
Context context,
|
||||
ReadableMap drawableDescriptionDict) {
|
||||
ReadableMap drawableDescriptionDict,
|
||||
@Nullable float[] cornerRadii) {
|
||||
String type = drawableDescriptionDict.getString("type");
|
||||
if ("ThemeAttrAndroid".equals(type)) {
|
||||
String attr = drawableDescriptionDict.getString("attribute");
|
||||
@@ -75,11 +84,14 @@ public class ReactDrawableHelper {
|
||||
"couldn't be resolved into a drawable");
|
||||
}
|
||||
}
|
||||
Drawable mask = null;
|
||||
PaintDrawable mask = null;
|
||||
if (!drawableDescriptionDict.hasKey("borderless") ||
|
||||
drawableDescriptionDict.isNull("borderless") ||
|
||||
!drawableDescriptionDict.getBoolean("borderless")) {
|
||||
mask = new ColorDrawable(Color.WHITE);
|
||||
drawableDescriptionDict.isNull("borderless") ||
|
||||
!drawableDescriptionDict.getBoolean("borderless")) {
|
||||
mask = new PaintDrawable(Color.WHITE);
|
||||
if (cornerRadii != null) {
|
||||
mask.setCornerRadii(cornerRadii);
|
||||
}
|
||||
}
|
||||
ColorStateList colorStateList = new ColorStateList(
|
||||
new int[][] {new int[]{}},
|
||||
|
||||
+31
-25
@@ -255,6 +255,25 @@ public class ReactViewBackgroundDrawable extends Drawable {
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ float[] getBorderRadii() {
|
||||
float defaultBorderRadius = !YogaConstants.isUndefined(mBorderRadius) ? mBorderRadius : 0;
|
||||
float topLeftRadius = mBorderCornerRadii != null && !YogaConstants.isUndefined(mBorderCornerRadii[0]) ? mBorderCornerRadii[0] : defaultBorderRadius;
|
||||
float topRightRadius = mBorderCornerRadii != null && !YogaConstants.isUndefined(mBorderCornerRadii[1]) ? mBorderCornerRadii[1] : defaultBorderRadius;
|
||||
float bottomRightRadius = mBorderCornerRadii != null && !YogaConstants.isUndefined(mBorderCornerRadii[2]) ? mBorderCornerRadii[2] : defaultBorderRadius;
|
||||
float bottomLeftRadius = mBorderCornerRadii != null && !YogaConstants.isUndefined(mBorderCornerRadii[3]) ? mBorderCornerRadii[3] : defaultBorderRadius;
|
||||
|
||||
return new float[] {
|
||||
topLeftRadius,
|
||||
topLeftRadius,
|
||||
topRightRadius,
|
||||
topRightRadius,
|
||||
bottomRightRadius,
|
||||
bottomRightRadius,
|
||||
bottomLeftRadius,
|
||||
bottomLeftRadius
|
||||
};
|
||||
}
|
||||
|
||||
private void updatePath() {
|
||||
if (!mNeedUpdatePathForBorderRadius) {
|
||||
return;
|
||||
@@ -277,25 +296,12 @@ public class ReactViewBackgroundDrawable extends Drawable {
|
||||
mTempRectForBorderRadius.inset(fullBorderWidth * 0.5f, fullBorderWidth * 0.5f);
|
||||
}
|
||||
|
||||
float defaultBorderRadius = !YogaConstants.isUndefined(mBorderRadius) ? mBorderRadius : 0;
|
||||
float topLeftRadius = mBorderCornerRadii != null && !YogaConstants.isUndefined(mBorderCornerRadii[0]) ? mBorderCornerRadii[0] : defaultBorderRadius;
|
||||
float topRightRadius = mBorderCornerRadii != null && !YogaConstants.isUndefined(mBorderCornerRadii[1]) ? mBorderCornerRadii[1] : defaultBorderRadius;
|
||||
float bottomRightRadius = mBorderCornerRadii != null && !YogaConstants.isUndefined(mBorderCornerRadii[2]) ? mBorderCornerRadii[2] : defaultBorderRadius;
|
||||
float bottomLeftRadius = mBorderCornerRadii != null && !YogaConstants.isUndefined(mBorderCornerRadii[3]) ? mBorderCornerRadii[3] : defaultBorderRadius;
|
||||
float[] borderRadii = getBorderRadii();
|
||||
|
||||
mPathForBorderRadius.addRoundRect(
|
||||
mTempRectForBorderRadius,
|
||||
new float[] {
|
||||
topLeftRadius,
|
||||
topLeftRadius,
|
||||
topRightRadius,
|
||||
topRightRadius,
|
||||
bottomRightRadius,
|
||||
bottomRightRadius,
|
||||
bottomLeftRadius,
|
||||
bottomLeftRadius
|
||||
},
|
||||
Path.Direction.CW);
|
||||
mTempRectForBorderRadius,
|
||||
borderRadii,
|
||||
Path.Direction.CW);
|
||||
|
||||
float extraRadiusForOutline = 0;
|
||||
|
||||
@@ -306,14 +312,14 @@ public class ReactViewBackgroundDrawable extends Drawable {
|
||||
mPathForBorderRadiusOutline.addRoundRect(
|
||||
mTempRectForBorderRadiusOutline,
|
||||
new float[] {
|
||||
topLeftRadius + extraRadiusForOutline,
|
||||
topLeftRadius + extraRadiusForOutline,
|
||||
topRightRadius + extraRadiusForOutline,
|
||||
topRightRadius + extraRadiusForOutline,
|
||||
bottomRightRadius + extraRadiusForOutline,
|
||||
bottomRightRadius + extraRadiusForOutline,
|
||||
bottomLeftRadius + extraRadiusForOutline,
|
||||
bottomLeftRadius + extraRadiusForOutline
|
||||
borderRadii[0] + extraRadiusForOutline,
|
||||
borderRadii[1] + extraRadiusForOutline,
|
||||
borderRadii[2] + extraRadiusForOutline,
|
||||
borderRadii[3] + extraRadiusForOutline,
|
||||
borderRadii[4] + extraRadiusForOutline,
|
||||
borderRadii[5] + extraRadiusForOutline,
|
||||
borderRadii[6] + extraRadiusForOutline,
|
||||
borderRadii[7] + extraRadiusForOutline
|
||||
},
|
||||
Path.Direction.CW);
|
||||
}
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
|
||||
package com.facebook.react.views.view;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
@@ -22,6 +20,7 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.common.annotations.VisibleForTesting;
|
||||
import com.facebook.react.touch.ReactHitSlopView;
|
||||
import com.facebook.react.touch.ReactInterceptingViewGroup;
|
||||
@@ -32,6 +31,8 @@ import com.facebook.react.uimanager.ReactClippingViewGroup;
|
||||
import com.facebook.react.uimanager.ReactClippingViewGroupHelper;
|
||||
import com.facebook.react.uimanager.ReactPointerEventsView;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Backing for a React View. Has support for borders, but since borders aren't common, lazy
|
||||
* initializes most of the storage needed for them.
|
||||
@@ -96,6 +97,7 @@ public class ReactViewGroup extends ViewGroup implements
|
||||
private @Nullable ReactViewBackgroundDrawable mReactBackgroundDrawable;
|
||||
private @Nullable OnInterceptTouchEventListener mOnInterceptTouchEventListener;
|
||||
private boolean mNeedsOffscreenAlphaCompositing = false;
|
||||
private @Nullable ReadableMap mNativeBackground;
|
||||
|
||||
public ReactViewGroup(Context context) {
|
||||
super(context);
|
||||
@@ -136,16 +138,33 @@ public class ReactViewGroup extends ViewGroup implements
|
||||
"This method is not supported for ReactViewGroup instances");
|
||||
}
|
||||
|
||||
public void setTranslucentBackgroundDrawable(@Nullable Drawable background) {
|
||||
public void setNativeBackground(@Nullable ReadableMap nativeBackground) {
|
||||
mNativeBackground = nativeBackground;
|
||||
refreshTranslucentBackgroundDrawable();
|
||||
}
|
||||
|
||||
public void refreshTranslucentBackgroundDrawable() {
|
||||
// it's required to call setBackground to null, as in some of the cases we may set new
|
||||
// background to be a layer drawable that contains a drawable that has been previously setup
|
||||
// as a background previously. This will not work correctly as the drawable callback logic is
|
||||
// messed up in AOSP
|
||||
|
||||
Drawable background = null;
|
||||
if (mNativeBackground != null) {
|
||||
float[] cornerRadii = null;
|
||||
if (mReactBackgroundDrawable != null) {
|
||||
cornerRadii = mReactBackgroundDrawable.getBorderRadii();
|
||||
}
|
||||
background = ReactDrawableHelper.createDrawableFromJSDescription(getContext(), mNativeBackground, cornerRadii);
|
||||
}
|
||||
|
||||
super.setBackground(null);
|
||||
if (mReactBackgroundDrawable != null && background != null) {
|
||||
LayerDrawable layerDrawable =
|
||||
new LayerDrawable(new Drawable[] {mReactBackgroundDrawable, background});
|
||||
super.setBackground(layerDrawable);
|
||||
} else if (mReactBackgroundDrawable != null && background == null) {
|
||||
super.setBackground(mReactBackgroundDrawable);
|
||||
} else if (background != null) {
|
||||
super.setBackground(background);
|
||||
}
|
||||
@@ -209,10 +228,12 @@ public class ReactViewGroup extends ViewGroup implements
|
||||
|
||||
public void setBorderRadius(float borderRadius) {
|
||||
getOrCreateReactViewBackground().setRadius(borderRadius);
|
||||
refreshTranslucentBackgroundDrawable();
|
||||
}
|
||||
|
||||
public void setBorderRadius(float borderRadius, int position) {
|
||||
getOrCreateReactViewBackground().setRadius(borderRadius, position);
|
||||
refreshTranslucentBackgroundDrawable();
|
||||
}
|
||||
|
||||
public void setBorderStyle(@Nullable String style) {
|
||||
|
||||
@@ -104,8 +104,7 @@ public class ReactViewManager extends ViewGroupManager<ReactViewGroup> {
|
||||
|
||||
@ReactProp(name = "nativeBackgroundAndroid")
|
||||
public void setNativeBackground(ReactViewGroup view, @Nullable ReadableMap bg) {
|
||||
view.setTranslucentBackgroundDrawable(bg == null ?
|
||||
null : ReactDrawableHelper.createDrawableFromJSDescription(view.getContext(), bg));
|
||||
view.setNativeBackground(bg);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
|
||||
@@ -9,7 +9,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <React/JSCWrapper.h>
|
||||
#else
|
||||
#include <jschelpers/JSCWrapper.h>
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__)
|
||||
|
||||
|
||||
@@ -2520,19 +2520,22 @@ static void YGNodelayoutImpl(const YGNodeRef node,
|
||||
|
||||
// If the user didn't specify a width or height for the node, set the
|
||||
// dimensions based on the children.
|
||||
if (measureModeMainDim == YGMeasureModeUndefined) {
|
||||
if (measureModeMainDim == YGMeasureModeUndefined ||
|
||||
(node->style.overflow != YGOverflowScroll && measureModeMainDim == YGMeasureModeAtMost)) {
|
||||
// Clamp the size to the min/max size, if specified, and make sure it
|
||||
// doesn't go below the padding and border amount.
|
||||
node->layout.measuredDimensions[dim[mainAxis]] =
|
||||
YGNodeBoundAxis(node, mainAxis, maxLineMainDim, mainAxisParentSize, parentWidth);
|
||||
} else if (measureModeMainDim == YGMeasureModeAtMost) {
|
||||
} else if (measureModeMainDim == YGMeasureModeAtMost &&
|
||||
node->style.overflow == YGOverflowScroll) {
|
||||
node->layout.measuredDimensions[dim[mainAxis]] = fmaxf(
|
||||
fminf(availableInnerMainDim + paddingAndBorderAxisMain,
|
||||
YGNodeBoundAxisWithinMinAndMax(node, mainAxis, maxLineMainDim, mainAxisParentSize)),
|
||||
paddingAndBorderAxisMain);
|
||||
}
|
||||
|
||||
if (measureModeCrossDim == YGMeasureModeUndefined) {
|
||||
if (measureModeCrossDim == YGMeasureModeUndefined ||
|
||||
(node->style.overflow != YGOverflowScroll && measureModeCrossDim == YGMeasureModeAtMost)) {
|
||||
// Clamp the size to the min/max size, if specified, and make sure it
|
||||
// doesn't go below the padding and border amount.
|
||||
node->layout.measuredDimensions[dim[crossAxis]] =
|
||||
@@ -2541,7 +2544,8 @@ static void YGNodelayoutImpl(const YGNodeRef node,
|
||||
totalLineCrossDim + paddingAndBorderAxisCross,
|
||||
crossAxisParentSize,
|
||||
parentWidth);
|
||||
} else if (measureModeCrossDim == YGMeasureModeAtMost) {
|
||||
} else if (measureModeCrossDim == YGMeasureModeAtMost &&
|
||||
node->style.overflow == YGOverflowScroll) {
|
||||
node->layout.measuredDimensions[dim[crossAxis]] =
|
||||
fmaxf(fminf(availableInnerCrossDim + paddingAndBorderAxisCross,
|
||||
YGNodeBoundAxisWithinMinAndMax(node,
|
||||
|
||||
+7
-6
@@ -44,8 +44,9 @@ Run:
|
||||
git checkout -b <version_you_are_releasing>-stable
|
||||
# e.g. git checkout -b 0.22-stable
|
||||
|
||||
node ./scripts/bump-oss-version.js <exact-version_you_are_releasing>
|
||||
# e.g. node ./scripts/bump-oss-version.js 0.22.0-rc
|
||||
./scripts/bump-oss-version.js <exact-version_you_are_releasing>
|
||||
# e.g. ./scripts/bump-oss-version.js 0.22.0-rc
|
||||
# You can use the --remote option to specify a Git remote other than the default "origin"
|
||||
```
|
||||
|
||||
Circle CI will automatically run the tests and publish to npm with the version you have specified (e.g `0.22.0-rc`) and tag `next` meaning that this version will not be installed for users by default.
|
||||
@@ -112,8 +113,8 @@ git cherry-pick commitHash1
|
||||
If everything worked:
|
||||
|
||||
```bash
|
||||
node ./scripts/bump-oss-version.js <exact_version_you_are_releasing>
|
||||
# e.g. node ./scripts/bump-oss-version.js 0.28.0-rc.1
|
||||
./scripts/bump-oss-version.js <exact_version_you_are_releasing>
|
||||
# e.g. ./scripts/bump-oss-version.js 0.28.0-rc.1
|
||||
````
|
||||
|
||||
-------------------
|
||||
@@ -141,8 +142,8 @@ git cherry-pick commitHash1
|
||||
If everything worked:
|
||||
|
||||
```bash
|
||||
node ./scripts/bump-oss-version.js <exact_version_you_are_releasing>
|
||||
# e.g. node ./scripts/bump-oss-version.js 0.22.0
|
||||
./scripts/bump-oss-version.js <exact_version_you_are_releasing>
|
||||
# e.g. ./scripts/bump-oss-version.js 0.22.0
|
||||
```
|
||||
|
||||
#### Update the release notes
|
||||
|
||||
@@ -42,6 +42,7 @@ const documentedCommands = [
|
||||
require('./library/library'),
|
||||
require('./bundle/bundle'),
|
||||
require('./bundle/unbundle'),
|
||||
require('./eject/eject'),
|
||||
require('./link/link'),
|
||||
require('./link/unlink'),
|
||||
require('./install/install'),
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const copyProjectTemplateAndReplace = require('../generator/copyProjectTemplateAndReplace');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
/**
|
||||
* The eject command re-creates the `android` and `ios` native folders. Because native code can be
|
||||
* difficult to maintain, this new script allows an `app.json` to be defined for the project, which
|
||||
* is used to configure the native app.
|
||||
*
|
||||
* The `app.json` config may contain the following keys:
|
||||
*
|
||||
* - `name` - The short name used for the project, should be TitleCase
|
||||
* - `displayName` - The app's name on the home screen
|
||||
*/
|
||||
|
||||
function eject() {
|
||||
|
||||
const doesIOSExist = fs.existsSync(path.resolve('ios'));
|
||||
const doesAndroidExist = fs.existsSync(path.resolve('android'));
|
||||
if (doesIOSExist && doesAndroidExist) {
|
||||
console.error(
|
||||
'Both the iOS and Android folders already exist! Please delete `ios` and/or `android` ' +
|
||||
'before ejecting.'
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let appConfig = null;
|
||||
try {
|
||||
appConfig = require(path.resolve('app.json'));
|
||||
} catch(e) {
|
||||
console.error(
|
||||
`Eject requires an \`app.json\` config file to be located at ` +
|
||||
`${path.resolve('app.json')}, and it must at least specify a \`name\` for the project ` +
|
||||
`name, and a \`displayName\` for the app's home screen label.`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const appName = appConfig.name;
|
||||
if (!appName) {
|
||||
console.error(
|
||||
`App \`name\` must be defined in the \`app.json\` config file to define the project name. `+
|
||||
`It must not contain any spaces or dashes.`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
const displayName = appConfig.displayName;
|
||||
if (!displayName) {
|
||||
console.error(
|
||||
`App \`displayName\` must be defined in the \`app.json\` config file, to define the label ` +
|
||||
`of the app on the home screen.`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const templateOptions = { displayName };
|
||||
|
||||
if (!doesIOSExist) {
|
||||
console.log('Generating the iOS folder.');
|
||||
copyProjectTemplateAndReplace(
|
||||
path.resolve('node_modules', 'react-native', 'local-cli', 'templates', 'HelloWorld', 'ios'),
|
||||
path.resolve('ios'),
|
||||
appName,
|
||||
templateOptions
|
||||
);
|
||||
}
|
||||
|
||||
if (!doesAndroidExist) {
|
||||
console.log('Generating the Android folder.');
|
||||
copyProjectTemplateAndReplace(
|
||||
path.resolve('node_modules', 'react-native', 'local-cli', 'templates', 'HelloWorld', 'android'),
|
||||
path.resolve('android'),
|
||||
appName,
|
||||
templateOptions
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
name: 'eject',
|
||||
description: 'Re-create the iOS and Android folders and native code',
|
||||
func: eject,
|
||||
options: [],
|
||||
};
|
||||
@@ -27,10 +27,12 @@ function copyProjectTemplateAndReplace(srcPath, destPath, newProjectName, option
|
||||
if (!destPath) { throw new Error('Need a path to copy to'); }
|
||||
if (!newProjectName) { throw new Error('Need a project name'); }
|
||||
|
||||
options = options || {};
|
||||
|
||||
walk(srcPath).forEach(absoluteSrcFilePath => {
|
||||
|
||||
// 'react-native upgrade'
|
||||
if (options && options.upgrade) {
|
||||
if (options.upgrade) {
|
||||
// Don't upgrade these files
|
||||
const fileName = path.basename(absoluteSrcFilePath);
|
||||
// This also includes __tests__/index.*.js
|
||||
@@ -44,7 +46,7 @@ function copyProjectTemplateAndReplace(srcPath, destPath, newProjectName, option
|
||||
.replace(/helloworld/g, newProjectName.toLowerCase());
|
||||
|
||||
let contentChangedCallback = null;
|
||||
if (options && options.upgrade && (!options.force)) {
|
||||
if (options.upgrade && (!options.force)) {
|
||||
contentChangedCallback = (_, contentChanged) => {
|
||||
return upgradeFileContentChangedCallback(
|
||||
absoluteSrcFilePath,
|
||||
@@ -57,6 +59,7 @@ function copyProjectTemplateAndReplace(srcPath, destPath, newProjectName, option
|
||||
absoluteSrcFilePath,
|
||||
path.resolve(destPath, relativeRenamedPath),
|
||||
{
|
||||
'Hello App Display Name': options.displayName || newProjectName,
|
||||
'HelloWorld': newProjectName,
|
||||
'helloworld': newProjectName.toLowerCase(),
|
||||
},
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
/captures/*
|
||||
preLoadedCapture.js
|
||||
bundle.js
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,3 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">HelloWorld</string>
|
||||
<string name="app_name">Hello App Display Name</string>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "HelloWorld",
|
||||
"displayName": "HelloWorld"
|
||||
}
|
||||
@@ -966,6 +966,10 @@
|
||||
INFOPLIST_FILE = HelloWorldTests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
OTHER_LDFLAGS = (
|
||||
"-ObjC",
|
||||
"-lc++",
|
||||
);
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/HelloWorld.app/HelloWorld";
|
||||
};
|
||||
@@ -979,6 +983,10 @@
|
||||
INFOPLIST_FILE = HelloWorldTests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
OTHER_LDFLAGS = (
|
||||
"-ObjC",
|
||||
"-lc++",
|
||||
);
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/HelloWorld.app/HelloWorld";
|
||||
};
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Hello App Display Name</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-native",
|
||||
"version": "1000.0.0",
|
||||
"version": "0.41.101",
|
||||
"description": "A framework for building native apps using React",
|
||||
"license": "BSD-3-Clause",
|
||||
"repository": {
|
||||
@@ -113,7 +113,7 @@
|
||||
"react-native": "local-cli/wrong-react-native.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "~15.4.0-rc.4"
|
||||
"react": "~15.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"absolute-path": "^0.0.0",
|
||||
@@ -203,9 +203,9 @@
|
||||
"jest-repl": "18.0.0",
|
||||
"jest-runtime": "18.0.0",
|
||||
"mock-fs": "^3.11.0",
|
||||
"react": "~15.4.0-rc.4",
|
||||
"react-dom": "~15.4.0-rc.4",
|
||||
"react-test-renderer": "~15.4.0-rc.4",
|
||||
"react": "~15.4.0",
|
||||
"react-dom": "~15.4.0",
|
||||
"react-test-renderer": "~15.4.0",
|
||||
"shelljs": "0.6.0",
|
||||
"sinon": "^2.0.0-pre.2"
|
||||
}
|
||||
|
||||
Regular → Executable
+14
-5
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
@@ -17,6 +18,13 @@
|
||||
/*eslint-disable no-undef */
|
||||
require(`shelljs/global`);
|
||||
|
||||
const minimist = require('minimist');
|
||||
|
||||
let argv = minimist(process.argv.slice(2), {
|
||||
alias: {remote: 'r'},
|
||||
default: {remote: 'origin'},
|
||||
});
|
||||
|
||||
// - check we are in release branch, e.g. 0.33-stable
|
||||
let branch = exec(`git symbolic-ref --short HEAD`, {silent: true}).stdout.trim();
|
||||
|
||||
@@ -30,7 +38,7 @@ let versionMajor = branch.slice(0, branch.indexOf(`-stable`));
|
||||
|
||||
// - check that argument version matches branch
|
||||
// e.g. 0.33.1 or 0.33.0-rc4
|
||||
let version = process.argv[2];
|
||||
let version = argv._[0];
|
||||
if (!version || version.indexOf(versionMajor) !== 0) {
|
||||
echo(`You must pass a tag like ${versionMajor}.[X]-rc[Y] to bump a version`);
|
||||
exit(1);
|
||||
@@ -77,17 +85,18 @@ if (exec(`git tag v${version}`).code) {
|
||||
}
|
||||
|
||||
// Push newly created tag
|
||||
exec(`git push origin v${version}`);
|
||||
let remote = argv.remote;
|
||||
exec(`git push ${remote} v${version}`);
|
||||
|
||||
// Tag latest if doing stable release
|
||||
if (version.indexOf(`rc`) === -1) {
|
||||
exec(`git tag -d latest`);
|
||||
exec(`git push origin :latest`);
|
||||
exec(`git push ${remote} :latest`);
|
||||
exec(`git tag latest`);
|
||||
exec(`git push origin latest`);
|
||||
exec(`git push ${remote} latest`);
|
||||
}
|
||||
|
||||
exec(`git push origin ${branch} --follow-tags`);
|
||||
exec(`git push ${remote} ${branch} --follow-tags`);
|
||||
|
||||
exit(0);
|
||||
/*eslint-enable no-undef */
|
||||
|
||||
Reference in New Issue
Block a user