mirror of
https://github.com/status-im/react-native.git
synced 2025-02-26 08:05:34 +00:00
[ReactNative] OSS CameraRoll
This commit is contained in:
parent
320429e355
commit
78ec0df464
115
Examples/UIExplorer/CameraRollExample.ios.js
Normal file
115
Examples/UIExplorer/CameraRollExample.ios.js
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* @providesModule CameraRollExample
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var React = require('react-native');
|
||||||
|
var {
|
||||||
|
CameraRoll,
|
||||||
|
Image,
|
||||||
|
Slider,
|
||||||
|
StyleSheet,
|
||||||
|
SwitchIOS,
|
||||||
|
Text,
|
||||||
|
View,
|
||||||
|
} = React;
|
||||||
|
|
||||||
|
var CameraRollView = require('./CameraRollView.ios');
|
||||||
|
|
||||||
|
var CAMERA_ROLL_VIEW = 'camera_roll_view';
|
||||||
|
|
||||||
|
var CameraRollExample = React.createClass({
|
||||||
|
|
||||||
|
getInitialState() {
|
||||||
|
return {
|
||||||
|
groupTypes: 'SavedPhotos',
|
||||||
|
sliderValue: 1,
|
||||||
|
bigImages: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
<SwitchIOS
|
||||||
|
onValueChange={this._onSwitchChange}
|
||||||
|
value={this.state.bigImages} />
|
||||||
|
<Text>{(this.state.bigImages ? 'Big' : 'Small') + ' Images'}</Text>
|
||||||
|
<Slider
|
||||||
|
value={this.state.sliderValue}
|
||||||
|
onValueChange={this._onSliderChange}
|
||||||
|
/>
|
||||||
|
<Text>{'Group Type: ' + this.state.groupTypes}</Text>
|
||||||
|
<CameraRollView
|
||||||
|
ref={CAMERA_ROLL_VIEW}
|
||||||
|
batchSize={5}
|
||||||
|
groupTypes={this.state.groupTypes}
|
||||||
|
renderImage={this._renderImage}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
_renderImage(asset) {
|
||||||
|
var imageSize = this.state.bigImages ? 150 : 75;
|
||||||
|
var imageStyle = [styles.image, {width: imageSize, height: imageSize}];
|
||||||
|
var location = asset.node.location.longitude ?
|
||||||
|
JSON.stringify(asset.node.location) : 'Unknown location';
|
||||||
|
return (
|
||||||
|
<View key={asset} style={styles.row}>
|
||||||
|
<Image
|
||||||
|
source={asset.node.image}
|
||||||
|
style={imageStyle}
|
||||||
|
/>
|
||||||
|
<View style={styles.info}>
|
||||||
|
<Text style={styles.url}>{asset.node.image.uri}</Text>
|
||||||
|
<Text>{location}</Text>
|
||||||
|
<Text>{asset.node.group_name}</Text>
|
||||||
|
<Text>{new Date(asset.node.timestamp).toString()}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onSliderChange(value) {
|
||||||
|
var options = CameraRoll.GroupTypesOptions;
|
||||||
|
var index = Math.floor(value * options.length * 0.99);
|
||||||
|
var groupTypes = options[index];
|
||||||
|
if (groupTypes !== this.state.groupTypes) {
|
||||||
|
this.setState({groupTypes: groupTypes});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_onSwitchChange(value) {
|
||||||
|
this.refs[CAMERA_ROLL_VIEW].rendererChanged();
|
||||||
|
this.setState({ bigImages: value });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var styles = StyleSheet.create({
|
||||||
|
row: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
fontSize: 9,
|
||||||
|
marginBottom: 14,
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
margin: 4,
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.title = '<CameraRollView>';
|
||||||
|
exports.description = 'Example component that uses CameraRoll to list user\'s photos';
|
||||||
|
exports.examples = [
|
||||||
|
{
|
||||||
|
title: 'Photos',
|
||||||
|
render() { return <CameraRollExample />; }
|
||||||
|
}
|
||||||
|
];
|
231
Examples/UIExplorer/CameraRollView.ios.js
Normal file
231
Examples/UIExplorer/CameraRollView.ios.js
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* @providesModule CameraRollView
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var React = require('react-native');
|
||||||
|
var {
|
||||||
|
ActivityIndicatorIOS,
|
||||||
|
CameraRoll,
|
||||||
|
Image,
|
||||||
|
ListView,
|
||||||
|
ListViewDataSource,
|
||||||
|
StyleSheet,
|
||||||
|
View,
|
||||||
|
} = React;
|
||||||
|
|
||||||
|
var groupByEveryN = require('groupByEveryN');
|
||||||
|
var logError = require('logError');
|
||||||
|
|
||||||
|
var propTypes = {
|
||||||
|
/**
|
||||||
|
* The group where the photos will be fetched from. Possible
|
||||||
|
* values are 'Album', 'All', 'Event', 'Faces', 'Library', 'PhotoStream'
|
||||||
|
* and SavedPhotos.
|
||||||
|
*/
|
||||||
|
groupTypes: React.PropTypes.oneOf([
|
||||||
|
'Album',
|
||||||
|
'All',
|
||||||
|
'Event',
|
||||||
|
'Faces',
|
||||||
|
'Library',
|
||||||
|
'PhotoStream',
|
||||||
|
'SavedPhotos',
|
||||||
|
]),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of images that will be fetched in one page.
|
||||||
|
*/
|
||||||
|
batchSize: React.PropTypes.number,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function that takes a single image as a parameter and renders it.
|
||||||
|
*/
|
||||||
|
renderImage: React.PropTypes.func,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* imagesPerRow: Number of images to be shown in each row.
|
||||||
|
*/
|
||||||
|
imagesPerRow: React.PropTypes.number,
|
||||||
|
};
|
||||||
|
|
||||||
|
var CameraRollView = React.createClass({
|
||||||
|
propTypes: propTypes,
|
||||||
|
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
groupTypes: 'SavedPhotos',
|
||||||
|
batchSize: 5,
|
||||||
|
imagesPerRow: 1,
|
||||||
|
renderImage: function(asset) {
|
||||||
|
var imageSize = 150;
|
||||||
|
var imageStyle = [styles.image, {width: imageSize, height: imageSize}];
|
||||||
|
return (
|
||||||
|
<Image
|
||||||
|
source={asset.node.image}
|
||||||
|
style={imageStyle}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
var ds = new ListViewDataSource({rowHasChanged: this._rowHasChanged});
|
||||||
|
|
||||||
|
return {
|
||||||
|
assets: [],
|
||||||
|
groupTypes: this.props.groupTypes,
|
||||||
|
lastCursor: null,
|
||||||
|
noMore: false,
|
||||||
|
loadingMore: false,
|
||||||
|
dataSource: ds,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This should be called when the image renderer is changed to tell the
|
||||||
|
* component to re-render its assets.
|
||||||
|
*/
|
||||||
|
rendererChanged: function() {
|
||||||
|
var ds = new ListViewDataSource({rowHasChanged: this._rowHasChanged});
|
||||||
|
this.state.dataSource = ds.cloneWithRows(
|
||||||
|
groupByEveryN(this.state.assets, this.props.imagesPerRow)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
this.fetch();
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillReceiveProps: function(nextProps) {
|
||||||
|
if (this.props.groupTypes !== nextProps.groupTypes) {
|
||||||
|
this.fetch(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_fetch: function(clear) {
|
||||||
|
if (clear) {
|
||||||
|
this.setState(this.getInitialState(), this.fetch);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fetchParams = {
|
||||||
|
first: this.props.batchSize,
|
||||||
|
groupTypes: this.props.groupTypes,
|
||||||
|
};
|
||||||
|
if (this.state.lastCursor) {
|
||||||
|
fetchParams.after = this.state.lastCursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
CameraRoll.getPhotos(fetchParams, this._appendAssets, logError);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches more images from the camera roll. If clear is set to true, it will
|
||||||
|
* set the component to its initial state and re-fetch the images.
|
||||||
|
*/
|
||||||
|
fetch: function(clear) {
|
||||||
|
if (!this.state.loadingMore) {
|
||||||
|
this.setState({loadingMore: true}, () => { this._fetch(clear); });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
return (
|
||||||
|
<ListView
|
||||||
|
renderRow={this._renderRow}
|
||||||
|
renderFooter={this._renderFooterSpinner}
|
||||||
|
onEndReached={this._onEndReached}
|
||||||
|
style={styles.container}
|
||||||
|
dataSource={this.state.dataSource}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
_rowHasChanged: function(r1, r2) {
|
||||||
|
if (r1.length !== r2.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < r1.length; i++) {
|
||||||
|
if (r1[i] !== r2[i]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
_renderFooterSpinner: function() {
|
||||||
|
if (!this.state.noMore) {
|
||||||
|
return <ActivityIndicatorIOS style={styles.spinner} />;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
// rowData is an array of images
|
||||||
|
_renderRow: function(rowData, sectionID, rowID) {
|
||||||
|
var images = rowData.map((image) => {
|
||||||
|
if (image === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.props.renderImage(image);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.row}>
|
||||||
|
{images}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
_appendAssets: function(data) {
|
||||||
|
var assets = data.edges;
|
||||||
|
var newState = { loadingMore: false };
|
||||||
|
|
||||||
|
if (!data.page_info.has_next_page) {
|
||||||
|
newState.noMore = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assets.length > 0) {
|
||||||
|
newState.lastCursor = data.page_info.end_cursor;
|
||||||
|
newState.assets = this.state.assets.concat(assets);
|
||||||
|
newState.dataSource = this.state.dataSource.cloneWithRows(
|
||||||
|
groupByEveryN(newState.assets, this.props.imagesPerRow)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState(newState);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onEndReached: function() {
|
||||||
|
if (!this.state.noMore) {
|
||||||
|
this.fetch();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
var styles = StyleSheet.create({
|
||||||
|
row: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
fontSize: 9,
|
||||||
|
marginBottom: 14,
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
margin: 4,
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = CameraRollView;
|
@ -36,6 +36,7 @@ var EXAMPLES = [
|
|||||||
require('./TabBarExample'),
|
require('./TabBarExample'),
|
||||||
require('./SwitchExample'),
|
require('./SwitchExample'),
|
||||||
require('./SliderExample'),
|
require('./SliderExample'),
|
||||||
|
require('./CameraRollExample.ios'),
|
||||||
];
|
];
|
||||||
|
|
||||||
var UIExplorerList = React.createClass({
|
var UIExplorerList = React.createClass({
|
||||||
|
149
Libraries/CameraRoll/CameraRoll.js
Normal file
149
Libraries/CameraRoll/CameraRoll.js
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* @providesModule CameraRoll
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var ReactPropTypes = require('ReactPropTypes');
|
||||||
|
var RKCameraRollManager = require('NativeModules').RKCameraRollManager;
|
||||||
|
|
||||||
|
var createStrictShapeTypeChecker = require('createStrictShapeTypeChecker');
|
||||||
|
var deepFreezeAndThrowOnMutationInDev =
|
||||||
|
require('deepFreezeAndThrowOnMutationInDev');
|
||||||
|
var invariant = require('invariant');
|
||||||
|
|
||||||
|
var GROUP_TYPES_OPTIONS = [
|
||||||
|
'Album',
|
||||||
|
'All',
|
||||||
|
'Event',
|
||||||
|
'Faces',
|
||||||
|
'Library',
|
||||||
|
'PhotoStream',
|
||||||
|
'SavedPhotos', // default
|
||||||
|
];
|
||||||
|
|
||||||
|
deepFreezeAndThrowOnMutationInDev(GROUP_TYPES_OPTIONS);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shape of the param arg for the `getPhotos` function.
|
||||||
|
*/
|
||||||
|
var getPhotosParamChecker = createStrictShapeTypeChecker({
|
||||||
|
/**
|
||||||
|
* The number of photos wanted in reverse order of the photo application
|
||||||
|
* (i.e. most recent first for SavedPhotos).
|
||||||
|
*/
|
||||||
|
first: ReactPropTypes.number.isRequired,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A cursor that matches `page_info { end_cursor }` returned from a previous
|
||||||
|
* call to `getPhotos`
|
||||||
|
*/
|
||||||
|
after: ReactPropTypes.string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies which group types to filter the results to.
|
||||||
|
*/
|
||||||
|
groupTypes: ReactPropTypes.oneOf(GROUP_TYPES_OPTIONS),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies filter on group names, like 'Recent Photos' or custom album
|
||||||
|
* titles.
|
||||||
|
*/
|
||||||
|
groupName: ReactPropTypes.string,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shape of the return value of the `getPhotos` function.
|
||||||
|
*/
|
||||||
|
var getPhotosReturnChecker = createStrictShapeTypeChecker({
|
||||||
|
edges: ReactPropTypes.arrayOf(createStrictShapeTypeChecker({
|
||||||
|
node: createStrictShapeTypeChecker({
|
||||||
|
type: ReactPropTypes.string.isRequired,
|
||||||
|
group_name: ReactPropTypes.string.isRequired,
|
||||||
|
image: createStrictShapeTypeChecker({
|
||||||
|
uri: ReactPropTypes.string.isRequired,
|
||||||
|
height: ReactPropTypes.number.isRequired,
|
||||||
|
width: ReactPropTypes.number.isRequired,
|
||||||
|
isStored: ReactPropTypes.bool,
|
||||||
|
}).isRequired,
|
||||||
|
timestamp: ReactPropTypes.number.isRequired,
|
||||||
|
location: createStrictShapeTypeChecker({
|
||||||
|
latitude: ReactPropTypes.number,
|
||||||
|
longitude: ReactPropTypes.number,
|
||||||
|
altitude: ReactPropTypes.number,
|
||||||
|
heading: ReactPropTypes.number,
|
||||||
|
speed: ReactPropTypes.number,
|
||||||
|
}),
|
||||||
|
}).isRequired,
|
||||||
|
})).isRequired,
|
||||||
|
page_info: createStrictShapeTypeChecker({
|
||||||
|
has_next_page: ReactPropTypes.bool.isRequired,
|
||||||
|
start_cursor: ReactPropTypes.string,
|
||||||
|
end_cursor: ReactPropTypes.string,
|
||||||
|
}).isRequired,
|
||||||
|
});
|
||||||
|
|
||||||
|
class CameraRoll {
|
||||||
|
/**
|
||||||
|
* Saves the image with tag `tag` to the camera roll.
|
||||||
|
*
|
||||||
|
* @param {string} tag - Can be any of the three kinds of tags we accept:
|
||||||
|
* 1. URL
|
||||||
|
* 2. assets-library tag
|
||||||
|
* 3. tag returned from storing an image in memory
|
||||||
|
*/
|
||||||
|
static saveImageWithTag(tag, successCallback, errorCallback) {
|
||||||
|
invariant(
|
||||||
|
typeof tag === 'string',
|
||||||
|
'CameraRoll.saveImageWithTag tag must be a valid string.'
|
||||||
|
);
|
||||||
|
RKCameraRollManager.saveImageWithTag(
|
||||||
|
tag,
|
||||||
|
(imageTag) => {
|
||||||
|
successCallback && successCallback(imageTag);
|
||||||
|
},
|
||||||
|
(errorMessage) => {
|
||||||
|
errorCallback && errorCallback(errorMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes `callback` with photo identifier objects from the local camera
|
||||||
|
* roll of the device matching shape defined by `getPhotosReturnChecker`.
|
||||||
|
*
|
||||||
|
* @param {object} params - See `getPhotosParamChecker`.
|
||||||
|
* @param {function} callback - Invoked with arg of shape defined by
|
||||||
|
* `getPhotosReturnChecker` on success.
|
||||||
|
* @param {function} errorCallback - Invoked with error message on error.
|
||||||
|
*/
|
||||||
|
static getPhotos(params, callback, errorCallback) {
|
||||||
|
var metaCallback = callback;
|
||||||
|
if (__DEV__) {
|
||||||
|
getPhotosParamChecker({params}, 'params', 'CameraRoll.getPhotos');
|
||||||
|
invariant(
|
||||||
|
typeof callback === 'function',
|
||||||
|
'CameraRoll.getPhotos callback must be a valid function.'
|
||||||
|
);
|
||||||
|
invariant(
|
||||||
|
typeof errorCallback === 'function',
|
||||||
|
'CameraRoll.getPhotos errorCallback must be a valid function.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (__DEV__) {
|
||||||
|
metaCallback = (response) => {
|
||||||
|
getPhotosReturnChecker(
|
||||||
|
{response},
|
||||||
|
'response',
|
||||||
|
'CameraRoll.getPhotos callback'
|
||||||
|
);
|
||||||
|
callback(response);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
RKCameraRollManager.getPhotos(params, metaCallback, errorCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CameraRoll.GroupTypesOptions = GROUP_TYPES_OPTIONS;
|
||||||
|
|
||||||
|
module.exports = CameraRoll;
|
7
Libraries/Image/RCTCameraRollManager.h
Normal file
7
Libraries/Image/RCTCameraRollManager.h
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||||
|
|
||||||
|
#import "RCTBridgeModule.h"
|
||||||
|
|
||||||
|
@interface RCTCameraRollManager : NSObject <RCTBridgeModule>
|
||||||
|
|
||||||
|
@end
|
148
Libraries/Image/RCTCameraRollManager.m
Normal file
148
Libraries/Image/RCTCameraRollManager.m
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||||
|
|
||||||
|
#import "RCTCameraRollManager.h"
|
||||||
|
|
||||||
|
#import <AssetsLibrary/AssetsLibrary.h>
|
||||||
|
#import <CoreLocation/CoreLocation.h>
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
#import "RCTImageLoader.h"
|
||||||
|
#import "RCTLog.h"
|
||||||
|
|
||||||
|
@implementation RCTCameraRollManager
|
||||||
|
|
||||||
|
- (void)saveImageWithTag:(NSString *)imageTag successCallback:(RCTResponseSenderBlock)successCallback errorCallback:(RCTResponseSenderBlock)errorCallback
|
||||||
|
{
|
||||||
|
RCT_EXPORT();
|
||||||
|
|
||||||
|
[RCTImageLoader loadImageWithTag:imageTag callback:^(NSError *loadError, UIImage *loadedImage) {
|
||||||
|
if (loadError) {
|
||||||
|
errorCallback(@[[loadError localizedDescription]]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
[[RCTImageLoader assetsLibrary] writeImageToSavedPhotosAlbum:[loadedImage CGImage] metadata:nil completionBlock:^(NSURL *assetURL, NSError *saveError) {
|
||||||
|
if (saveError) {
|
||||||
|
NSString *errorMessage = [NSString stringWithFormat:@"Error saving cropped image: %@", saveError];
|
||||||
|
RCTLogWarn(@"%@", errorMessage);
|
||||||
|
errorCallback(@[errorMessage]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
successCallback(@[[assetURL absoluteString]]);
|
||||||
|
}];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)callCallback:(RCTResponseSenderBlock)callback withAssets:(NSArray *)assets hasNextPage:(BOOL)hasNextPage
|
||||||
|
{
|
||||||
|
if (![assets count]) {
|
||||||
|
callback(@[@{
|
||||||
|
@"edges": assets,
|
||||||
|
@"page_info": @{
|
||||||
|
@"has_next_page": @NO}
|
||||||
|
}]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback(@[@{
|
||||||
|
@"edges": assets,
|
||||||
|
@"page_info": @{
|
||||||
|
@"start_cursor": assets[0][@"node"][@"image"][@"uri"],
|
||||||
|
@"end_cursor": assets[assets.count - 1][@"node"][@"image"][@"uri"],
|
||||||
|
@"has_next_page": @(hasNextPage)}
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)getPhotos:(NSDictionary *)params callback:(RCTResponseSenderBlock)callback errorCallback:(RCTResponseSenderBlock)errorCallback
|
||||||
|
{
|
||||||
|
RCT_EXPORT();
|
||||||
|
|
||||||
|
NSUInteger first = [params[@"first"] integerValue];
|
||||||
|
NSString *afterCursor = params[@"after"];
|
||||||
|
NSString *groupTypesStr = params[@"groupTypes"];
|
||||||
|
NSString *groupName = params[@"groupName"];
|
||||||
|
ALAssetsGroupType groupTypes;
|
||||||
|
if ([groupTypesStr isEqualToString:@"Album"]) {
|
||||||
|
groupTypes = ALAssetsGroupAlbum;
|
||||||
|
} else if ([groupTypesStr isEqualToString:@"All"]) {
|
||||||
|
groupTypes = ALAssetsGroupAll;
|
||||||
|
} else if ([groupTypesStr isEqualToString:@"Event"]) {
|
||||||
|
groupTypes = ALAssetsGroupEvent;
|
||||||
|
} else if ([groupTypesStr isEqualToString:@"Faces"]) {
|
||||||
|
groupTypes = ALAssetsGroupFaces;
|
||||||
|
} else if ([groupTypesStr isEqualToString:@"Library"]) {
|
||||||
|
groupTypes = ALAssetsGroupLibrary;
|
||||||
|
} else if ([groupTypesStr isEqualToString:@"PhotoStream"]) {
|
||||||
|
groupTypes = ALAssetsGroupPhotoStream;
|
||||||
|
} else {
|
||||||
|
groupTypes = ALAssetsGroupSavedPhotos;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL __block foundAfter = NO;
|
||||||
|
BOOL __block hasNextPage = NO;
|
||||||
|
BOOL __block calledCallback = NO;
|
||||||
|
NSMutableArray *assets = [[NSMutableArray alloc] init];
|
||||||
|
|
||||||
|
[[RCTImageLoader assetsLibrary] enumerateGroupsWithTypes:groupTypes usingBlock:^(ALAssetsGroup *group, BOOL *stopGroups) {
|
||||||
|
if (group && (groupName == nil || [groupName isEqualToString:[group valueForProperty:ALAssetsGroupPropertyName]])) {
|
||||||
|
[group setAssetsFilter:ALAssetsFilter.allPhotos];
|
||||||
|
[group enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stopAssets) {
|
||||||
|
if (result) {
|
||||||
|
NSString *uri = [(NSURL *)[result valueForProperty:ALAssetPropertyAssetURL] absoluteString];
|
||||||
|
if (afterCursor && !foundAfter) {
|
||||||
|
if ([afterCursor isEqualToString:uri]) {
|
||||||
|
foundAfter = YES;
|
||||||
|
}
|
||||||
|
return; // Skip until we get to the first one
|
||||||
|
}
|
||||||
|
if (first == [assets count]) {
|
||||||
|
*stopAssets = YES;
|
||||||
|
*stopGroups = YES;
|
||||||
|
hasNextPage = YES;
|
||||||
|
RCTAssert(calledCallback == NO, @"Called the callback before we finished processing the results.");
|
||||||
|
[self callCallback:callback withAssets:assets hasNextPage:hasNextPage];
|
||||||
|
calledCallback = YES;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CGSize dimensions = [result defaultRepresentation].dimensions;
|
||||||
|
CLLocation *loc = [result valueForProperty:ALAssetPropertyLocation];
|
||||||
|
NSDate *date = [result valueForProperty:ALAssetPropertyDate];
|
||||||
|
[assets addObject:@{
|
||||||
|
@"node": @{
|
||||||
|
@"type": [result valueForProperty:ALAssetPropertyType],
|
||||||
|
@"group_name": [group valueForProperty:ALAssetsGroupPropertyName],
|
||||||
|
@"image": @{
|
||||||
|
@"uri": uri,
|
||||||
|
@"height": @(dimensions.height),
|
||||||
|
@"width": @(dimensions.width),
|
||||||
|
@"isStored": @YES,
|
||||||
|
},
|
||||||
|
@"timestamp": @([date timeIntervalSince1970]),
|
||||||
|
@"location": loc ?
|
||||||
|
@{
|
||||||
|
@"latitude": @(loc.coordinate.latitude),
|
||||||
|
@"longitude": @(loc.coordinate.longitude),
|
||||||
|
@"altitude": @(loc.altitude),
|
||||||
|
@"heading": @(loc.course),
|
||||||
|
@"speed": @(loc.speed),
|
||||||
|
} : @{},
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
} else {
|
||||||
|
// Sometimes the enumeration continues even if we set stop above, so we guard against calling the callback
|
||||||
|
// multiple times here.
|
||||||
|
if (!calledCallback) {
|
||||||
|
[self callCallback:callback withAssets:assets hasNextPage:hasNextPage];
|
||||||
|
calledCallback = YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} failureBlock:^(NSError *error) {
|
||||||
|
if (error.code != ALAssetsLibraryAccessUserDeniedError) {
|
||||||
|
RCTLogError(@"Failure while iterating through asset groups %@", error);
|
||||||
|
}
|
||||||
|
errorCallback(@[error.description]);
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
@ -10,6 +10,8 @@
|
|||||||
1304D5AB1AA8C4A30002E2BE /* RCTStaticImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5A81AA8C4A30002E2BE /* RCTStaticImage.m */; };
|
1304D5AB1AA8C4A30002E2BE /* RCTStaticImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5A81AA8C4A30002E2BE /* RCTStaticImage.m */; };
|
||||||
1304D5AC1AA8C4A30002E2BE /* RCTStaticImageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */; };
|
1304D5AC1AA8C4A30002E2BE /* RCTStaticImageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */; };
|
||||||
1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */; };
|
1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */; };
|
||||||
|
143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879341AAD238D00F088A5 /* RCTCameraRollManager.m */; };
|
||||||
|
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879371AAD32A300F088A5 /* RCTImageLoader.m */; };
|
||||||
58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */; };
|
58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */; };
|
||||||
58B511901A9E6BD600147676 /* RCTNetworkImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118C1A9E6BD600147676 /* RCTNetworkImageView.m */; };
|
58B511901A9E6BD600147676 /* RCTNetworkImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118C1A9E6BD600147676 /* RCTNetworkImageView.m */; };
|
||||||
58B511911A9E6BD600147676 /* RCTNetworkImageViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118E1A9E6BD600147676 /* RCTNetworkImageViewManager.m */; };
|
58B511911A9E6BD600147676 /* RCTNetworkImageViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118E1A9E6BD600147676 /* RCTNetworkImageViewManager.m */; };
|
||||||
@ -34,6 +36,10 @@
|
|||||||
1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTStaticImageManager.m; sourceTree = "<group>"; };
|
1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTStaticImageManager.m; sourceTree = "<group>"; };
|
||||||
1304D5B01AA8C50D0002E2BE /* RCTGIFImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTGIFImage.h; sourceTree = "<group>"; };
|
1304D5B01AA8C50D0002E2BE /* RCTGIFImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTGIFImage.h; sourceTree = "<group>"; };
|
||||||
1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTGIFImage.m; sourceTree = "<group>"; };
|
1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTGIFImage.m; sourceTree = "<group>"; };
|
||||||
|
143879331AAD238D00F088A5 /* RCTCameraRollManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTCameraRollManager.h; sourceTree = "<group>"; };
|
||||||
|
143879341AAD238D00F088A5 /* RCTCameraRollManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCameraRollManager.m; sourceTree = "<group>"; };
|
||||||
|
143879361AAD32A300F088A5 /* RCTImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageLoader.h; sourceTree = "<group>"; };
|
||||||
|
143879371AAD32A300F088A5 /* RCTImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageLoader.m; sourceTree = "<group>"; };
|
||||||
58B5115D1A9E6B3D00147676 /* libRCTImage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTImage.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
58B5115D1A9E6B3D00147676 /* libRCTImage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTImage.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
58B511891A9E6BD600147676 /* RCTImageDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageDownloader.h; sourceTree = "<group>"; };
|
58B511891A9E6BD600147676 /* RCTImageDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageDownloader.h; sourceTree = "<group>"; };
|
||||||
58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageDownloader.m; sourceTree = "<group>"; };
|
58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageDownloader.m; sourceTree = "<group>"; };
|
||||||
@ -57,6 +63,10 @@
|
|||||||
58B511541A9E6B3D00147676 = {
|
58B511541A9E6B3D00147676 = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
143879361AAD32A300F088A5 /* RCTImageLoader.h */,
|
||||||
|
143879371AAD32A300F088A5 /* RCTImageLoader.m */,
|
||||||
|
143879331AAD238D00F088A5 /* RCTCameraRollManager.h */,
|
||||||
|
143879341AAD238D00F088A5 /* RCTCameraRollManager.m */,
|
||||||
1304D5B01AA8C50D0002E2BE /* RCTGIFImage.h */,
|
1304D5B01AA8C50D0002E2BE /* RCTGIFImage.h */,
|
||||||
1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */,
|
1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */,
|
||||||
58B511891A9E6BD600147676 /* RCTImageDownloader.h */,
|
58B511891A9E6BD600147676 /* RCTImageDownloader.h */,
|
||||||
@ -142,6 +152,8 @@
|
|||||||
1304D5AC1AA8C4A30002E2BE /* RCTStaticImageManager.m in Sources */,
|
1304D5AC1AA8C4A30002E2BE /* RCTStaticImageManager.m in Sources */,
|
||||||
58B511901A9E6BD600147676 /* RCTNetworkImageView.m in Sources */,
|
58B511901A9E6BD600147676 /* RCTNetworkImageView.m in Sources */,
|
||||||
1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */,
|
1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */,
|
||||||
|
143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */,
|
||||||
|
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */,
|
||||||
1304D5AB1AA8C4A30002E2BE /* RCTStaticImage.m in Sources */,
|
1304D5AB1AA8C4A30002E2BE /* RCTStaticImage.m in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
13
Libraries/Image/RCTImageLoader.h
Normal file
13
Libraries/Image/RCTImageLoader.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
@class ALAssetsLibrary;
|
||||||
|
@class UIImage;
|
||||||
|
|
||||||
|
@interface RCTImageLoader : NSObject
|
||||||
|
|
||||||
|
+ (ALAssetsLibrary *)assetsLibrary;
|
||||||
|
+ (void)loadImageWithTag:(NSString *)tag callback:(void (^)(NSError *error, UIImage *image))callback;
|
||||||
|
|
||||||
|
@end
|
98
Libraries/Image/RCTImageLoader.m
Normal file
98
Libraries/Image/RCTImageLoader.m
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||||
|
|
||||||
|
#import "RCTImageLoader.h"
|
||||||
|
|
||||||
|
#import <AssetsLibrary/AssetsLibrary.h>
|
||||||
|
#import <Photos/PHAsset.h>
|
||||||
|
#import <Photos/PHFetchResult.h>
|
||||||
|
#import <Photos/PHImageManager.h>
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
#import "RCTConvert.h"
|
||||||
|
#import "RCTImageDownloader.h"
|
||||||
|
#import "RCTLog.h"
|
||||||
|
|
||||||
|
NSError *errorWithMessage(NSString *message) {
|
||||||
|
NSDictionary *errorInfo = @{NSLocalizedDescriptionKey: message};
|
||||||
|
NSError *error = [[NSError alloc] initWithDomain:RCTErrorDomain code:0 userInfo:errorInfo];
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
@implementation RCTImageLoader
|
||||||
|
|
||||||
|
+ (ALAssetsLibrary *)assetsLibrary
|
||||||
|
{
|
||||||
|
static ALAssetsLibrary *assetsLibrary = nil;
|
||||||
|
static dispatch_once_t onceToken;
|
||||||
|
dispatch_once(&onceToken, ^{
|
||||||
|
assetsLibrary = [[ALAssetsLibrary alloc] init];
|
||||||
|
});
|
||||||
|
return assetsLibrary;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (void)loadImageWithTag:(NSString *)imageTag callback:(void (^)(NSError *error, UIImage *image))callback
|
||||||
|
{
|
||||||
|
if ([imageTag hasPrefix:@"assets-library"]) {
|
||||||
|
[[RCTImageLoader assetsLibrary] assetForURL:[NSURL URLWithString:imageTag] resultBlock:^(ALAsset *asset) {
|
||||||
|
if (asset) {
|
||||||
|
ALAssetRepresentation *representation = [asset defaultRepresentation];
|
||||||
|
ALAssetOrientation orientation = [representation orientation];
|
||||||
|
UIImage *image = [UIImage imageWithCGImage:[representation fullResolutionImage] scale:1.0f orientation:(UIImageOrientation)orientation];
|
||||||
|
callback(nil, image);
|
||||||
|
} else {
|
||||||
|
NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@ with no error message.", imageTag];
|
||||||
|
NSError *error = errorWithMessage(errorText);
|
||||||
|
callback(error, nil);
|
||||||
|
}
|
||||||
|
} failureBlock:^(NSError *loadError) {
|
||||||
|
NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@.\niOS Error: %@", imageTag, loadError];
|
||||||
|
NSError *error = errorWithMessage(errorText);
|
||||||
|
callback(error, nil);
|
||||||
|
}];
|
||||||
|
} else if ([imageTag hasPrefix:@"ph://"]) {
|
||||||
|
// Using PhotoKit for iOS 8+
|
||||||
|
// 'ph://' prefix is used by FBMediaKit to differentiate between assets-library. It is prepended to the local ID so that it
|
||||||
|
// is in the form of NSURL which is what assets-library is based on.
|
||||||
|
// This means if we use any FB standard photo picker, we will get this prefix =(
|
||||||
|
NSString *phAssetID = [imageTag substringFromIndex:[@"ph://" length]];
|
||||||
|
PHFetchResult *results = [PHAsset fetchAssetsWithLocalIdentifiers:@[phAssetID] options:nil];
|
||||||
|
if (results.count == 0) {
|
||||||
|
NSString *errorText = [NSString stringWithFormat:@"Failed to fetch PHAsset with local identifier %@ with no error message.", phAssetID];
|
||||||
|
NSError *error = errorWithMessage(errorText);
|
||||||
|
callback(error, nil);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PHAsset *asset = [results firstObject];
|
||||||
|
[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeDefault options:nil resultHandler:^(UIImage *result, NSDictionary *info) {
|
||||||
|
if (result) {
|
||||||
|
callback(nil, result);
|
||||||
|
} else {
|
||||||
|
NSString *errorText = [NSString stringWithFormat:@"Failed to load PHAsset with local identifier %@ with no error message.", phAssetID];
|
||||||
|
NSError *error = errorWithMessage(errorText);
|
||||||
|
callback(error, nil);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
} else if ([imageTag hasPrefix:@"http"]) {
|
||||||
|
NSURL *url = [NSURL URLWithString:imageTag];
|
||||||
|
if (!url) {
|
||||||
|
NSString *errorMessage = [NSString stringWithFormat:@"Invalid URL: %@", imageTag];
|
||||||
|
callback(errorWithMessage(errorMessage), nil);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
[[RCTImageDownloader sharedInstance] downloadDataForURL:url block:^(NSData *data, NSError *error) {
|
||||||
|
if (error) {
|
||||||
|
callback(error, nil);
|
||||||
|
} else {
|
||||||
|
callback(nil, [UIImage imageWithData:data]);
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
} else {
|
||||||
|
NSString *errorMessage = [NSString stringWithFormat:@"Unrecognized tag protocol: %@", imageTag];
|
||||||
|
NSError *error = errorWithMessage(errorMessage);
|
||||||
|
callback(error, nil);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#import "RCTConvert.h"
|
#import "RCTConvert.h"
|
||||||
#import "RCTGIFImage.h"
|
#import "RCTGIFImage.h"
|
||||||
|
#import "RCTImageLoader.h"
|
||||||
#import "RCTStaticImage.h"
|
#import "RCTStaticImage.h"
|
||||||
|
|
||||||
@implementation RCTStaticImageManager
|
@implementation RCTStaticImageManager
|
||||||
@ -39,5 +40,19 @@ RCT_CUSTOM_VIEW_PROPERTY(tintColor, RCTStaticImage *)
|
|||||||
view.tintColor = defaultView.tintColor;
|
view.tintColor = defaultView.tintColor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
RCT_CUSTOM_VIEW_PROPERTY(imageTag, RCTStaticImage *)
|
||||||
|
{
|
||||||
|
if (json) {
|
||||||
|
[RCTImageLoader loadImageWithTag:[RCTConvert NSString:json] callback:^(NSError *error, UIImage *image) {
|
||||||
|
if (error) {
|
||||||
|
RCTLogWarn(@"%@", error.localizedDescription);
|
||||||
|
} else {
|
||||||
|
view.image = image;
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
} else {
|
||||||
|
view.image = defaultView.image;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
46
Libraries/Utilities/groupByEveryN.js
Normal file
46
Libraries/Utilities/groupByEveryN.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* @providesModule groupByEveryN
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Useful method to split an array into groups of the same number of elements.
|
||||||
|
* You can use it to generate grids, rows, pages...
|
||||||
|
*
|
||||||
|
* If the input length is not a multiple of the count, it'll fill the last
|
||||||
|
* array with null so you can display a placeholder.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* groupByEveryN([1, 2, 3, 4, 5], 3)
|
||||||
|
* => [[1, 2, 3], [4, 5, null]]
|
||||||
|
*
|
||||||
|
* groupByEveryN([1, 2, 3], 2).map(elems => {
|
||||||
|
* return <Row>{elems.map(elem => <Elem>{elem}</Elem>)}</Row>;
|
||||||
|
* })
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function groupByEveryN(array, n) {
|
||||||
|
var result = [];
|
||||||
|
var temp = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < array.length; ++i) {
|
||||||
|
if (i > 0 && i % n === 0) {
|
||||||
|
result.push(temp);
|
||||||
|
temp = [];
|
||||||
|
}
|
||||||
|
temp.push(array[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (temp.length > 0) {
|
||||||
|
while (temp.length !== n) {
|
||||||
|
temp.push(null);
|
||||||
|
}
|
||||||
|
result.push(temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = groupByEveryN;
|
1
Libraries/react-native/react-native.js
vendored
1
Libraries/react-native/react-native.js
vendored
@ -8,6 +8,7 @@
|
|||||||
var ReactNative = {
|
var ReactNative = {
|
||||||
...require('React'),
|
...require('React'),
|
||||||
AppRegistry: require('AppRegistry'),
|
AppRegistry: require('AppRegistry'),
|
||||||
|
CameraRoll: require('CameraRoll'),
|
||||||
DatePickerIOS: require('DatePickerIOS'),
|
DatePickerIOS: require('DatePickerIOS'),
|
||||||
ExpandingText: require('ExpandingText'),
|
ExpandingText: require('ExpandingText'),
|
||||||
Image: require('Image'),
|
Image: require('Image'),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user