mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Compare commits
253
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53f688f07c | ||
|
|
ff6d9cc03e | ||
|
|
91df57c0a5 | ||
|
|
e7bb56d676 | ||
|
|
444712052a | ||
|
|
035c181e77 | ||
|
|
dad45aa8f2 | ||
|
|
039dab078a | ||
|
|
b0b9a56ef8 | ||
|
|
ed0b45ef8e | ||
|
|
864fdd1ebd | ||
|
|
7e53ee1095 | ||
|
|
6f71b790f1 | ||
|
|
772010a9af | ||
|
|
bc409ba2da | ||
|
|
7c36138caa | ||
|
|
e6ad91b5a2 | ||
|
|
6ec4d65aec | ||
|
|
251a4086a7 | ||
|
|
dee96fbedc | ||
|
|
582655e6a8 | ||
|
|
eced31ffaf | ||
|
|
26df016600 | ||
|
|
880a06a4b2 | ||
|
|
3d489c3a6a | ||
|
|
98aed73e1c | ||
|
|
32aeeea313 | ||
|
|
17ef86d6bb | ||
|
|
58ea08dfdb | ||
|
|
59bb57a002 | ||
|
|
55f8f3e370 | ||
|
|
afea653cbc | ||
|
|
ce7de93275 | ||
|
|
ae5de54f00 | ||
|
|
66717d802b | ||
|
|
71da2917e5 | ||
|
|
ff17789e98 | ||
|
|
8c18a24277 | ||
|
|
be70e32de2 | ||
|
|
aff4cfb3b3 | ||
|
|
2f5681d4f2 | ||
|
|
3a8f9e1080 | ||
|
|
54b1402c43 | ||
|
|
56fef9b622 | ||
|
|
666833d2d2 | ||
|
|
d061de1107 | ||
|
|
279a6cd52f | ||
|
|
44ff93e7bf | ||
|
|
64c809345f | ||
|
|
4036eca570 | ||
|
|
a859d9e931 | ||
|
|
5227b10455 | ||
|
|
8ad09987e5 | ||
|
|
394dfae868 | ||
|
|
c771fb3072 | ||
|
|
62ac37275f | ||
|
|
68c52814d6 | ||
|
|
6a5b61eb88 | ||
|
|
cafeac0000 | ||
|
|
a5c693dd53 | ||
|
|
852eba2a91 | ||
|
|
586b1ee5df | ||
|
|
59563ff203 | ||
|
|
0b5179dce7 | ||
|
|
43d6b1bbfa | ||
|
|
9960878ba4 | ||
|
|
ea4ea90d77 | ||
|
|
e1b2c84a81 | ||
|
|
f1ab581337 | ||
|
|
c6c97cbd9d | ||
|
|
8e7cfcd053 | ||
|
|
088c193a22 | ||
|
|
5c4bb894bf | ||
|
|
b4d4cf4444 | ||
|
|
34c91800d2 | ||
|
|
71adc24974 | ||
|
|
774bbc2f3a | ||
|
|
c15ec71238 | ||
|
|
54b5b62f78 | ||
|
|
330b0538a9 | ||
|
|
d6f46c693a | ||
|
|
523769faa7 | ||
|
|
c76c22249d | ||
|
|
02e39719c2 | ||
|
|
93488a7898 | ||
|
|
5e2882846c | ||
|
|
06a154e061 | ||
|
|
4ac3c4a853 | ||
|
|
e612690413 | ||
|
|
82fad33af7 | ||
|
|
93c56a0e90 | ||
|
|
3506f55ece | ||
|
|
fb46e39cad | ||
|
|
dbe37e7898 | ||
|
|
4849aabecc | ||
|
|
1b55e4657c | ||
|
|
1d6d1189f0 | ||
|
|
77154a7581 | ||
|
|
89c1747c33 | ||
|
|
1b29690496 | ||
|
|
d41cedb8fc | ||
|
|
4d9b05cfb2 | ||
|
|
67498a4b6d | ||
|
|
8d592650c9 | ||
|
|
17c59ace46 | ||
|
|
d03bf216b1 | ||
|
|
61ad255629 | ||
|
|
ac5e2a41bb | ||
|
|
123516d556 | ||
|
|
d760544652 | ||
|
|
73b80773ba | ||
|
|
1076f4a172 | ||
|
|
419549b116 | ||
|
|
31f9a690f3 | ||
|
|
607527c0d4 | ||
|
|
9dc036d2b9 | ||
|
|
59e670e453 | ||
|
|
b09e92e1b3 | ||
|
|
81317bd27a | ||
|
|
45d2c691a3 | ||
|
|
716f7e8ef2 | ||
|
|
e3f33ea261 | ||
|
|
2be0525b2a | ||
|
|
ec6d41414d | ||
|
|
c0a2c96d9b | ||
|
|
7827b35443 | ||
|
|
3759511951 | ||
|
|
e59b28e6ca | ||
|
|
6949be63e4 | ||
|
|
16a48ae0c3 | ||
|
|
f01272b031 | ||
|
|
a2524bac33 | ||
|
|
dcd82a2450 | ||
|
|
b39b97546a | ||
|
|
8bff2d30ab | ||
|
|
5977f494fc | ||
|
|
01f90054cc | ||
|
|
1511d8ca1d | ||
|
|
256f69f4e6 | ||
|
|
b00c49725c | ||
|
|
24bfdda499 | ||
|
|
da250671e5 | ||
|
|
2608efccb6 | ||
|
|
1091ecd8e0 | ||
|
|
c0b5e6ed67 | ||
|
|
4a3857ef1d | ||
|
|
8e2ec64763 | ||
|
|
0ed3048e39 | ||
|
|
8fb9cc8fc1 | ||
|
|
79e33078f2 | ||
|
|
74002b784b | ||
|
|
f47e8ac6a1 | ||
|
|
3d40d622f9 | ||
|
|
84df87ef64 | ||
|
|
15054ade7d | ||
|
|
0788e0b683 | ||
|
|
b3c72a5632 | ||
|
|
83b8a30beb | ||
|
|
643769fc8e | ||
|
|
d96b750e56 | ||
|
|
36697ae6a1 | ||
|
|
72b713262f | ||
|
|
ca1bc35940 | ||
|
|
c38d7fab97 | ||
|
|
1e52b8297c | ||
|
|
d0e40c581d | ||
|
|
4ec5161685 | ||
|
|
8ddd3e864e | ||
|
|
cb609f986c | ||
|
|
02958376ad | ||
|
|
498ef4f935 | ||
|
|
5e844c75f8 | ||
|
|
ce0e839d27 | ||
|
|
bbb756d2cb | ||
|
|
dfee3f30af | ||
|
|
a0f3d29c0b | ||
|
|
6ab4faa68b | ||
|
|
f006571a41 | ||
|
|
3f2199ddf0 | ||
|
|
c43b2da158 | ||
|
|
080746c4c3 | ||
|
|
62e238fd6a | ||
|
|
ceb948d151 | ||
|
|
21a8145fe8 | ||
|
|
a4383f0a50 | ||
|
|
d9ee81c127 | ||
|
|
2d18cae041 | ||
|
|
529687f923 | ||
|
|
10a9b94b9c | ||
|
|
10cc72bb88 | ||
|
|
aae9f0255f | ||
|
|
1b94586fe6 | ||
|
|
3df34f1ba8 | ||
|
|
f8ef2c4b77 | ||
|
|
92c7eb25a6 | ||
|
|
9a48b7c336 | ||
|
|
c2ae790396 | ||
|
|
45644aab35 | ||
|
|
855f7be4b8 | ||
|
|
6494746613 | ||
|
|
a041d770e4 | ||
|
|
31a02fb758 | ||
|
|
8486a68b91 | ||
|
|
ad4f589b65 | ||
|
|
663ae6cbf7 | ||
|
|
4b90db10da | ||
|
|
589df04846 | ||
|
|
9493e96e13 | ||
|
|
c0654cfc96 | ||
|
|
ea70525744 | ||
|
|
f03c7b59fd | ||
|
|
7aeae9778f | ||
|
|
0206347df3 | ||
|
|
f4e2a670d3 | ||
|
|
08e79deebd | ||
|
|
a21e226b68 | ||
|
|
1ab0c3b374 | ||
|
|
19e11232fe | ||
|
|
a102e6f587 | ||
|
|
793c3564ff | ||
|
|
49f7b68772 | ||
|
|
39bea42932 | ||
|
|
05015e8d36 | ||
|
|
3d22547e31 | ||
|
|
a92f107712 | ||
|
|
28f6eba22d | ||
|
|
e0b2c2e34e | ||
|
|
88a880b1fc | ||
|
|
76e0f49338 | ||
|
|
8e03dfc99e | ||
|
|
44c651037a | ||
|
|
c70cbbaf61 | ||
|
|
b721c12516 | ||
|
|
3f55bd1380 | ||
|
|
c73ceff914 | ||
|
|
5b425278bb | ||
|
|
aabdc9cc9f | ||
|
|
baf5b7b4d5 | ||
|
|
261100d9d0 | ||
|
|
c456accbed | ||
|
|
42e9189b77 | ||
|
|
393ead59dc | ||
|
|
144f8598c2 | ||
|
|
12a68a8183 | ||
|
|
0e98bc9651 | ||
|
|
6f04687436 | ||
|
|
31aacd78e8 | ||
|
|
3561c9ed2b | ||
|
|
f39816eb38 | ||
|
|
aacd2d90a4 | ||
|
|
da359c312a | ||
|
|
62d0586e59 | ||
|
|
a31d3a9d4f |
@@ -0,0 +1,6 @@
|
||||
# Force LF line endings for Bash scripts. On Windows the rest of the source
|
||||
# files will typically have CR+LF endings (Git default on Windows), but Bash
|
||||
# scripts need to have LF endings to work (under Cygwin), thus override to force
|
||||
# that.
|
||||
gradlew text eol=lf
|
||||
*.sh text eol=lf
|
||||
@@ -72,7 +72,12 @@ env:
|
||||
- TEST_TYPE=build_website
|
||||
- TEST_TYPE=e2e
|
||||
global:
|
||||
# $GITHUB_TOKEN
|
||||
- secure: "HlmG8M2DmBUSBh6KH1yVIe/8gR4iibg4WfcHq1x/xYQxGbvleq7NOo04V6eFHnl9cvZCu+PKH0841WLnGR7c4BBf47GVu/o16nXzggPumHKy++lDzxFPlJ1faMDfjg/5vjbAxRUe7D3y98hQSeGHH4tedc8LvTaFLVu7iiGqvjU="
|
||||
# $APPETIZE_TOKEN
|
||||
- secure: "egsvVSpszTzrNd6bN62DsVAzMiSZI/OHgdizfPryqvqWBf655ztE6XFQSEFNpuIAzSKDDF25ioT8iPfVsbC1iK6HDWHfmqYxML0L+OoU0gi+hV2oKUBFZDZ1fwSnFoWuBdNdMDpLlUxvJp6N1WyfNOB2dxuZUt8eTt48Hi3+Hpc="
|
||||
# $S3_TOKEN
|
||||
- secure: "lY8JZPA0A7zT7L5KF9BBg34XYWIeR/RJiEvE7l7oVr88KnEPtyd//79eHhhVKnUnav7zsk5QJwkcX0MxKTp/dp4G0Am+zOX+sfA8kQrJ+2/+FzFW7AEsW/kHByfaIEIly9DQvUFt4I4oMm8nQZysJLahDgNWglyI3RTuJp//hcY="
|
||||
|
||||
branches:
|
||||
only:
|
||||
|
||||
+1
-1
@@ -46,8 +46,8 @@ Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe
|
||||
|
||||
## How to Get in Touch
|
||||
|
||||
* IRC - [#reactnative on freenode](http://webchat.freenode.net/?channels=reactnative)
|
||||
* [Facebook group](https://www.facebook.com/groups/react.native.community/)
|
||||
* Reactiflux — [#react-native](http://join.reactiflux.com/)
|
||||
|
||||
## Style Guide
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ var {
|
||||
ProgressBarAndroid,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextInput,
|
||||
View,
|
||||
} = React;
|
||||
var TimerMixin = require('react-timer-mixin');
|
||||
@@ -271,7 +270,7 @@ var SearchScreen = React.createClass({
|
||||
style = [style, styles.rowSeparatorHide];
|
||||
}
|
||||
return (
|
||||
<View key={"SEP_" + sectionID + "_" + rowID} style={style}/>
|
||||
<View key={'SEP_' + sectionID + '_' + rowID} style={style}/>
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
@@ -26,10 +26,6 @@ dependencies {
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
compile 'com.android.support:appcompat-v7:23.0.1'
|
||||
|
||||
// Depend on pre-built React Native
|
||||
compile 'com.facebook.react:react-native:0.11.+'
|
||||
|
||||
// Depend on React Native source.
|
||||
// This is useful for testing your changes when working on React Native.
|
||||
// compile project(':ReactAndroid')
|
||||
// Build React Native from source
|
||||
compile project(':ReactAndroid')
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ var AccessibilityIOSExample = React.createClass({
|
||||
Accessibility label example
|
||||
</Text>
|
||||
</View>
|
||||
<View accessibilityTraits={["button", "selected"]}
|
||||
<View accessibilityTraits={['button', 'selected']}
|
||||
accessible={true}>
|
||||
<Text>
|
||||
Accessibility traits example
|
||||
|
||||
@@ -24,9 +24,9 @@ var {
|
||||
} = React;
|
||||
|
||||
var BUTTONS = [
|
||||
'Button Index: 0',
|
||||
'Button Index: 1',
|
||||
'Button Index: 2',
|
||||
'Option 0',
|
||||
'Option 1',
|
||||
'Option 2',
|
||||
'Destruct',
|
||||
'Cancel',
|
||||
];
|
||||
@@ -47,7 +47,7 @@ var ActionSheetExample = React.createClass({
|
||||
Click to show the ActionSheet
|
||||
</Text>
|
||||
<Text>
|
||||
Clicked button at index: "{this.state.clicked}"
|
||||
Clicked button: {this.state.clicked}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
@@ -99,7 +99,7 @@ var ShareActionSheetExample = React.createClass({
|
||||
} else {
|
||||
text = 'You didn\'t share';
|
||||
}
|
||||
this.setState({text})
|
||||
this.setState({text});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -40,7 +40,7 @@ var AppStateSubscription = React.createClass({
|
||||
AppStateIOS.removeEventListener('memoryWarning', this._handleMemoryWarning);
|
||||
},
|
||||
_handleMemoryWarning: function() {
|
||||
this.setState({memoryWarnings: this.state.memoryWarnings + 1})
|
||||
this.setState({memoryWarnings: this.state.memoryWarnings + 1});
|
||||
},
|
||||
_handleAppStateChange: function(appState) {
|
||||
var previousAppStates = this.state.previousAppStates.slice();
|
||||
@@ -92,7 +92,7 @@ exports.examples = [
|
||||
},
|
||||
{
|
||||
title: 'Memory Warnings',
|
||||
description: "In the simulator, hit Shift+Command+M to simulate a memory warning.",
|
||||
description: 'In the simulator, hit Shift+Command+M to simulate a memory warning.',
|
||||
render(): ReactElement { return <AppStateSubscription showMemoryWarnings={true} />; }
|
||||
},
|
||||
];
|
||||
|
||||
@@ -24,6 +24,8 @@ var {
|
||||
ActivityIndicatorIOS
|
||||
} = React;
|
||||
|
||||
var base64Icon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAQAAACSR7JhAAADtUlEQVR4Ac3YA2Bj6QLH0XPT1Fzbtm29tW3btm3bfLZtv7e2ObZnms7d8Uw098tuetPzrxv8wiISrtVudrG2JXQZ4VOv+qUfmqCGGl1mqLhoA52oZlb0mrjsnhKpgeUNEs91Z0pd1kvihA3ULGVHiQO2narKSHKkEMulm9VgUyE60s1aWoMQUbpZOWE+kaqs4eLEjdIlZTcFZB0ndc1+lhB1lZrIuk5P2aib1NBpZaL+JaOGIt0ls47SKzLC7CqrlGF6RZ09HGoNy1lYl2aRSWL5GuzqWU1KafRdoRp0iOQEiDzgZPnG6DbldcomadViflnl/cL93tOoVbsOLVM2jylvdWjXolWX1hmfZbGR/wjypDjFLSZIRov09BgYmtUqPQPlQrPapecLgTIy0jMgPKtTeob2zWtrGH3xvjUkPCtNg/tm1rjwrMa+mdUkPd3hWbH0jArPGiU9ufCsNNWFZ40wpwn+62/66R2RUtoso1OB34tnLOcy7YB1fUdc9e0q3yru8PGM773vXsuZ5YIZX+5xmHwHGVvlrGPN6ZSiP1smOsMMde40wKv2VmwPPVXNut4sVpUreZiLBHi0qln/VQeI/LTMYXpsJtFiclUN+5HVZazim+Ky+7sAvxWnvjXrJFneVtLWLyPJu9K3cXLWeOlbMTlrIelbMDlrLenrjEQOtIF+fuI9xRp9ZBFp6+b6WT8RrxEpdK64BuvHgDk+vUy+b5hYk6zfyfs051gRoNO1usU12WWRWL73/MMEy9pMi9qIrR4ZpV16Rrvduxazmy1FSvuFXRkqTnE7m2kdb5U8xGjLw/spRr1uTov4uOgQE+0N/DvFrG/Jt7i/FzwxbA9kDanhf2w+t4V97G8lrT7wc08aA2QNUkuTfW/KimT01wdlfK4yEw030VfT0RtZbzjeMprNq8m8tnSTASrTLti64oBNdpmMQm0eEwvfPwRbUBywG5TzjPCsdwk3IeAXjQblLCoXnDVeoAz6SfJNk5TTzytCNZk/POtTSV40NwOFWzw86wNJRpubpXsn60NJFlHeqlYRbslqZm2jnEZ3qcSKgm0kTli3zZVS7y/iivZTweYXJ26Y+RTbV1zh3hYkgyFGSTKPfRVbRqWWVReaxYeSLarYv1Qqsmh1s95S7G+eEWK0f3jYKTbV6bOwepjfhtafsvUsqrQvrGC8YhmnO9cSCk3yuY984F1vesdHYhWJ5FvASlacshUsajFt2mUM9pqzvKGcyNJW0arTKN1GGGzQlH0tXwLDgQTurS8eIQAAAABJRU5ErkJggg==';
|
||||
|
||||
var ImageCapInsetsExample = require('./ImageCapInsetsExample');
|
||||
|
||||
var NetworkImageExample = React.createClass({
|
||||
@@ -335,6 +337,18 @@ exports.examples = [
|
||||
},
|
||||
platform: 'ios',
|
||||
},
|
||||
{
|
||||
title: 'Base64 image',
|
||||
render: function() {
|
||||
return (
|
||||
<Image
|
||||
style={styles.base64}
|
||||
source={{uri: base64Icon, scale: 3}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
platform: 'ios',
|
||||
},
|
||||
{
|
||||
title: 'Cap Insets',
|
||||
description:
|
||||
@@ -399,4 +413,9 @@ var styles = StyleSheet.create({
|
||||
flex: 1,
|
||||
height: 200,
|
||||
},
|
||||
base64: {
|
||||
flex: 1,
|
||||
height: 50,
|
||||
resizeMode: 'contain',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -21,8 +21,7 @@ var {
|
||||
ScrollView,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
TouchableOpacity
|
||||
} = React;
|
||||
|
||||
var _getRandomRoute = function() {
|
||||
@@ -82,19 +81,19 @@ var BreadcrumbNavSample = React.createClass({
|
||||
return (
|
||||
<ScrollView style={styles.scene}>
|
||||
<NavButton
|
||||
onPress={() => { navigator.push(_getRandomRoute()) }}
|
||||
onPress={() => { navigator.push(_getRandomRoute()); }}
|
||||
text="Push"
|
||||
/>
|
||||
<NavButton
|
||||
onPress={() => { navigator.immediatelyResetRouteStack([_getRandomRoute(), _getRandomRoute()]) }}
|
||||
onPress={() => { navigator.immediatelyResetRouteStack([_getRandomRoute(), _getRandomRoute()]); }}
|
||||
text="Reset w/ 2 scenes"
|
||||
/>
|
||||
<NavButton
|
||||
onPress={() => { navigator.popToTop() }}
|
||||
onPress={() => { navigator.popToTop(); }}
|
||||
text="Pop to top"
|
||||
/>
|
||||
<NavButton
|
||||
onPress={() => { navigator.replace(_getRandomRoute()) }}
|
||||
onPress={() => { navigator.replace(_getRandomRoute()); }}
|
||||
text="Replace"
|
||||
/>
|
||||
<NavButton
|
||||
|
||||
@@ -23,7 +23,6 @@ var {
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} = React;
|
||||
|
||||
var cssVar = require('cssVar');
|
||||
|
||||
@@ -134,7 +134,7 @@ var TabBarExample = React.createClass({
|
||||
<Navigator
|
||||
ref={this._setNavigatorRef}
|
||||
style={styles.container}
|
||||
initialRoute={{ message: "First Scene", }}
|
||||
initialRoute={{ message: 'First Scene', }}
|
||||
renderScene={this.renderScene}
|
||||
configureScene={(route) => {
|
||||
if (route.sceneConfig) {
|
||||
|
||||
@@ -56,7 +56,7 @@ var SwitchAndroidExample = React.createClass({
|
||||
onValueChange={(value) => this.setState({eventSwitchIsOn: value})}
|
||||
style={{marginBottom: 10}}
|
||||
value={this.state.eventSwitchIsOn} />
|
||||
<Text>{this.state.eventSwitchIsOn ? "On" : "Off"}</Text>
|
||||
<Text>{this.state.eventSwitchIsOn ? 'On' : 'Off'}</Text>
|
||||
</UIExplorerBlock>
|
||||
<UIExplorerBlock title="Switches are controlled components">
|
||||
<SwitchAndroid />
|
||||
|
||||
@@ -107,7 +107,7 @@ var EventSwitchExample = React.createClass({
|
||||
onValueChange={(value) => this.setState({eventSwitchIsOn: value})}
|
||||
style={{marginBottom: 10}}
|
||||
value={this.state.eventSwitchIsOn} />
|
||||
<Text>{this.state.eventSwitchIsOn ? "On" : "Off"}</Text>
|
||||
<Text>{this.state.eventSwitchIsOn ? 'On' : 'Off'}</Text>
|
||||
</View>
|
||||
<View>
|
||||
<SwitchIOS
|
||||
@@ -118,7 +118,7 @@ var EventSwitchExample = React.createClass({
|
||||
onValueChange={(value) => this.setState({eventSwitchRegressionIsOn: value})}
|
||||
style={{marginBottom: 10}}
|
||||
value={this.state.eventSwitchRegressionIsOn} />
|
||||
<Text>{this.state.eventSwitchRegressionIsOn ? "On" : "Off"}</Text>
|
||||
<Text>{this.state.eventSwitchRegressionIsOn ? 'On' : 'Off'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -19,9 +19,7 @@ var React = require('react-native');
|
||||
var {
|
||||
AlertIOS,
|
||||
Platform,
|
||||
Text,
|
||||
ToastAndroid,
|
||||
TouchableHighlight,
|
||||
View,
|
||||
} = React;
|
||||
var TimerMixin = require('react-timer-mixin');
|
||||
|
||||
@@ -31,7 +31,7 @@ var ToastExample = React.createClass({
|
||||
|
||||
statics: {
|
||||
title: 'Toast Example',
|
||||
description: 'Example that demostrates the use of an Android Toast to provide feedback.',
|
||||
description: 'Example that demonstrates the use of an Android Toast to provide feedback.',
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
||||
@@ -101,6 +101,12 @@ var ToolbarAndroidExample = React.createClass({
|
||||
title="Bunny and Hawk"
|
||||
style={styles.toolbar} />
|
||||
</UIExplorerBlock>
|
||||
<UIExplorerBlock title="Toolbar with custom overflowIcon">
|
||||
<ToolbarAndroid
|
||||
actions={toolbarActions}
|
||||
overflowIcon={require('./bunny.png')}
|
||||
style={styles.toolbar} />
|
||||
</UIExplorerBlock>
|
||||
</UIExplorerPage>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
14D6D7291B2222EF001FB087 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14AADF041AC3DB95002390C9 /* libReact.a */; };
|
||||
14DC67F41AB71881001358AB /* libRCTPushNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14DC67F11AB71876001358AB /* libRCTPushNotification.a */; };
|
||||
3578590A1B28D2CF00341EDB /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 357859011B28D2C500341EDB /* libRCTLinking.a */; };
|
||||
3D36915B1BDA8CBB007B22D8 /* uie_thumb_big.png in Resources */ = {isa = PBXBuildFile; fileRef = 3D36915A1BDA8CBB007B22D8 /* uie_thumb_big.png */; settings = {ASSET_TAGS = (); }; };
|
||||
3DB99D0C1BA0340600302749 /* UIExplorerIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DB99D0B1BA0340600302749 /* UIExplorerIntegrationTests.m */; };
|
||||
834C36EC1AF8DED70019C93C /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 834C36D21AF8DA610019C93C /* libRCTSettings.a */; };
|
||||
83636F8F1B53F22C009F943E /* RCTUIManagerScenarioTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */; };
|
||||
@@ -222,6 +223,7 @@
|
||||
14DC67E71AB71876001358AB /* RCTPushNotification.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTPushNotification.xcodeproj; path = ../../Libraries/PushNotificationIOS/RCTPushNotification.xcodeproj; sourceTree = "<group>"; };
|
||||
14E0EEC81AB118F7000DECC3 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = ../../Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj; sourceTree = "<group>"; };
|
||||
357858F81B28D2C400341EDB /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = ../../Libraries/LinkingIOS/RCTLinking.xcodeproj; sourceTree = "<group>"; };
|
||||
3D36915A1BDA8CBB007B22D8 /* uie_thumb_big.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = uie_thumb_big.png; path = UIExplorer/Images.xcassets/uie_thumb_big.imageset/uie_thumb_big.png; sourceTree = SOURCE_ROOT; };
|
||||
3DB99D0B1BA0340600302749 /* UIExplorerIntegrationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIExplorerIntegrationTests.m; sourceTree = "<group>"; };
|
||||
58005BE41ABA80530062E044 /* RCTTest.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTTest.xcodeproj; path = ../../Libraries/RCTTest/RCTTest.xcodeproj; sourceTree = "<group>"; };
|
||||
83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUIManagerScenarioTests.m; sourceTree = "<group>"; };
|
||||
@@ -436,6 +438,7 @@
|
||||
143BC5971B21E3E100462512 /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3D36915A1BDA8CBB007B22D8 /* uie_thumb_big.png */,
|
||||
143BC5981B21E3E100462512 /* Info.plist */,
|
||||
);
|
||||
name = "Supporting Files";
|
||||
@@ -817,6 +820,7 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
3D36915B1BDA8CBB007B22D8 /* uie_thumb_big.png in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -966,6 +970,7 @@
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.facebook.internal.uiexplorer.local;
|
||||
PRODUCT_NAME = UIExplorer;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -985,6 +990,7 @@
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.facebook.internal.uiexplorer.local;
|
||||
PRODUCT_NAME = UIExplorer;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
* Load from pre-bundled file on disk. To re-generate the static bundle, `cd`
|
||||
* to your Xcode project folder and run
|
||||
*
|
||||
* $ curl 'http://localhost:8081/Examples/UIExplorer/UIExplorerApp.ios.bundle' -o main.jsbundle
|
||||
* $ curl 'http://localhost:8081/Examples/UIExplorer/UIExplorerApp.ios.bundle?platform=ios' -o main.jsbundle
|
||||
*
|
||||
* then add the `main.jsbundle` file to your project and uncomment this line:
|
||||
*/
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x",
|
||||
"filename" : "blue_square.png"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 128 B |
+21
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x",
|
||||
"filename" : "red_square.png"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 129 B |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -36,7 +36,7 @@
|
||||
#endif
|
||||
|
||||
NSOperatingSystemVersion version = [NSProcessInfo processInfo].operatingSystemVersion;
|
||||
RCTAssert(version.majorVersion == 8 || version.minorVersion >= 3, @"Tests should be run on iOS 8.3+, found %zd.%zd.%zd", version.majorVersion, version.minorVersion, version.patchVersion);
|
||||
RCTAssert((version.majorVersion == 8 && version.minorVersion >= 3) || version.majorVersion >= 9, @"Tests should be run on iOS 8.3+, found %zd.%zd.%zd", version.majorVersion, version.minorVersion, version.patchVersion);
|
||||
_runner = RCTInitRunnerForApp(@"Examples/UIExplorer/UIExplorerIntegrationTests/js/IntegrationTestsApp", nil);
|
||||
}
|
||||
|
||||
@@ -62,9 +62,12 @@
|
||||
RCT_TEST(IntegrationTestHarnessTest)
|
||||
RCT_TEST(TimersTest)
|
||||
RCT_TEST(AsyncStorageTest)
|
||||
RCT_TEST(LayoutEventsTest)
|
||||
RCT_TEST(AppEventsTest)
|
||||
RCT_TEST(ImageSnapshotTest)
|
||||
RCT_TEST(SimpleSnapshotTest)
|
||||
RCT_TEST(PromiseTest)
|
||||
|
||||
// Disable due to flakiness: #8686784
|
||||
//RCT_TEST(LayoutEventsTest)
|
||||
//RCT_TEST(PromiseTest)
|
||||
|
||||
@end
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
#endif
|
||||
|
||||
NSOperatingSystemVersion version = [NSProcessInfo processInfo].operatingSystemVersion;
|
||||
RCTAssert(version.majorVersion == 8 || version.minorVersion >= 3, @"Snapshot tests should be run on iOS 8.3+, found %zd.%zd.%zd", version.majorVersion, version.minorVersion, version.patchVersion);
|
||||
RCTAssert((version.majorVersion == 8 && version.minorVersion >= 3) || version.majorVersion >= 9, @"Tests should be run on iOS 8.3+, found %zd.%zd.%zd", version.majorVersion, version.minorVersion, version.patchVersion);
|
||||
_runner = RCTInitRunnerForApp(@"Examples/UIExplorer/UIExplorerApp.ios", nil);
|
||||
_runner.recordMode = NO;
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 128 B |
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
var React = require('react-native');
|
||||
var {
|
||||
Image,
|
||||
View,
|
||||
} = React;
|
||||
|
||||
var { TestModule } = React.addons;
|
||||
|
||||
var ImageSnapshotTest = React.createClass({
|
||||
componentDidMount() {
|
||||
if (!TestModule.verifySnapshot) {
|
||||
throw new Error('TestModule.verifySnapshot not defined.');
|
||||
}
|
||||
},
|
||||
|
||||
done(success) {
|
||||
TestModule.markTestPassed(success);
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Image
|
||||
source={require('image!blue_square')}
|
||||
defaultSource={require('image!red_square')}
|
||||
onLoad={() => TestModule.verifySnapshot(this.done)} />
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
ImageSnapshotTest.displayName = 'ImageSnapshotTest';
|
||||
|
||||
module.exports = ImageSnapshotTest;
|
||||
@@ -31,6 +31,7 @@ var TESTS = [
|
||||
require('./LayoutEventsTest'),
|
||||
require('./AppEventsTest'),
|
||||
require('./SimpleSnapshotTest'),
|
||||
require('./ImageSnapshotTest'),
|
||||
require('./PromiseTest'),
|
||||
];
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 129 B |
@@ -19,7 +19,7 @@ var React = require('react-native');
|
||||
var {
|
||||
AppRegistry,
|
||||
Settings,
|
||||
SnapshotView,
|
||||
SnapshotViewIOS,
|
||||
StyleSheet,
|
||||
} = React;
|
||||
|
||||
@@ -85,9 +85,9 @@ COMPONENTS.concat(APIS).forEach((Example) => {
|
||||
render: function() {
|
||||
var Renderable = UIExplorerListBase.makeRenderable(Example);
|
||||
return (
|
||||
<SnapshotView>
|
||||
<SnapshotViewIOS>
|
||||
<Renderable />
|
||||
</SnapshotView>
|
||||
</SnapshotViewIOS>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
@property (nonatomic, strong, readonly) RCTBridge *batchedBridge;
|
||||
|
||||
- (void)_handleBuffer:(id)buffer;
|
||||
- (void)handleBuffer:(id)buffer;
|
||||
- (void)setUp;
|
||||
|
||||
@end
|
||||
@@ -169,7 +169,7 @@ _Pragma("clang diagnostic pop")
|
||||
NSArray *args = @[@1234, @5678, @"stringy", @{@"a": @1}, @42];
|
||||
NSArray *buffer = @[@[testModuleID], @[testMethodID], @[args], @[], @1234567];
|
||||
|
||||
[_bridge.batchedBridge _handleBuffer:buffer];
|
||||
[_bridge.batchedBridge handleBuffer:buffer];
|
||||
|
||||
dispatch_sync(_methodQueue, ^{
|
||||
// clear the queue
|
||||
@@ -180,7 +180,7 @@ _Pragma("clang diagnostic pop")
|
||||
- (void)DISABLED_testBadArgumentsCount
|
||||
{
|
||||
//NSArray *bufferWithMissingArgument = @[@[@1], @[@0], @[@[@1234, @5678, @"stringy", @{@"a": @1}/*, @42*/]], @[], @1234567];
|
||||
//[_bridge _handleBuffer:bufferWithMissingArgument];
|
||||
//[_bridge handleBuffer:bufferWithMissingArgument];
|
||||
NSLog(@"WARNING: testBadArgumentsCount is temporarily disabled until we have a better way to test cases that we expect to trigger redbox errors");
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
- (void)testNativeLoggingHookExceptionBehavior
|
||||
{
|
||||
dispatch_semaphore_t doneSem = dispatch_semaphore_create(0);
|
||||
[_executor executeApplicationScript:@"var x = {toString: function() { throw 1; }}; nativeLoggingHook(x);"
|
||||
[_executor executeApplicationScript:[@"var x = {toString: function() { throw 1; }}; nativeLoggingHook(x);" dataUsingEncoding:NSUTF8StringEncoding]
|
||||
sourceURL:[NSURL URLWithString:@"file://"]
|
||||
onComplete:^(__unused id error){
|
||||
dispatch_semaphore_signal(doneSem);
|
||||
@@ -128,7 +128,7 @@ static uint64_t _get_time_nanoseconds(void)
|
||||
} \
|
||||
";
|
||||
|
||||
[_executor executeApplicationScript:script sourceURL:[NSURL URLWithString:@"http://localhost:8081/"] onComplete:^(__unused NSError *error) {
|
||||
[_executor executeApplicationScript:[script dataUsingEncoding:NSUTF8StringEncoding] sourceURL:[NSURL URLWithString:@"http://localhost:8081/"] onComplete:^(__unused NSError *error) {
|
||||
NSMutableArray *params = [NSMutableArray new];
|
||||
id data = @1;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
|
||||
@@ -25,14 +25,14 @@ typedef RCTImageLoaderCancellationBlock (^RCTImageURLLoaderLoadImageURLHandler)(
|
||||
|
||||
@end
|
||||
|
||||
typedef BOOL (^RCTImageDecoderCanDecodeImageDataHandler)(NSData *imageData);
|
||||
typedef RCTImageLoaderCancellationBlock (^RCTImageDecoderDecodeImageDataHandler)(NSData *imageData, CGSize size, CGFloat scale, UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler);
|
||||
typedef BOOL (^RCTImageDataDecoderCanDecodeImageDataHandler)(NSData *imageData);
|
||||
typedef RCTImageLoaderCancellationBlock (^RCTImageDataDecoderDecodeImageDataHandler)(NSData *imageData, CGSize size, CGFloat scale, UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler);
|
||||
|
||||
@interface RCTConcreteImageDecoder : NSObject <RCTImageDecoder>
|
||||
@interface RCTConcreteImageDecoder : NSObject <RCTImageDataDecoder>
|
||||
|
||||
- (instancetype)initWithPriority:(float)priority
|
||||
canDecodeImageDataHandler:(RCTImageDecoderCanDecodeImageDataHandler)canDecodeImageDataHandler
|
||||
decodeImageDataHandler:(RCTImageDecoderDecodeImageDataHandler)decodeImageDataHandler;
|
||||
canDecodeImageDataHandler:(RCTImageDataDecoderCanDecodeImageDataHandler)canDecodeImageDataHandler
|
||||
decodeImageDataHandler:(RCTImageDataDecoderDecodeImageDataHandler)decodeImageDataHandler;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
return _loadImageURLHandler(imageURL, size, scale, resizeMode, progressHandler, completionHandler);
|
||||
}
|
||||
|
||||
- (float)imageLoaderPriority
|
||||
- (float)loaderPriority
|
||||
{
|
||||
return _priority;
|
||||
}
|
||||
@@ -61,8 +61,8 @@
|
||||
|
||||
@implementation RCTConcreteImageDecoder
|
||||
{
|
||||
RCTImageDecoderCanDecodeImageDataHandler _canDecodeImageDataHandler;
|
||||
RCTImageDecoderDecodeImageDataHandler _decodeImageDataHandler;
|
||||
RCTImageDataDecoderCanDecodeImageDataHandler _canDecodeImageDataHandler;
|
||||
RCTImageDataDecoderDecodeImageDataHandler _decodeImageDataHandler;
|
||||
float _priority;
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (instancetype)initWithPriority:(float)priority canDecodeImageDataHandler:(RCTImageDecoderCanDecodeImageDataHandler)canDecodeImageDataHandler decodeImageDataHandler:(RCTImageDecoderDecodeImageDataHandler)decodeImageDataHandler
|
||||
- (instancetype)initWithPriority:(float)priority canDecodeImageDataHandler:(RCTImageDataDecoderCanDecodeImageDataHandler)canDecodeImageDataHandler decodeImageDataHandler:(RCTImageDataDecoderDecodeImageDataHandler)decodeImageDataHandler
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_canDecodeImageDataHandler = [canDecodeImageDataHandler copy];
|
||||
@@ -97,7 +97,7 @@
|
||||
return _decodeImageDataHandler(imageData, size, scale, resizeMode, completionHandler);
|
||||
}
|
||||
|
||||
- (float)imageDecoderPriority
|
||||
- (float)decoderPriority
|
||||
{
|
||||
return _priority;
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ RCTDefineImageDecoder(RCTImageLoaderTestsDecoder2)
|
||||
NSData *data = [NSData dataWithBytesNoCopy:blackGIF length:sizeof(blackGIF) freeWhenDone:NO];
|
||||
UIImage *image = [[UIImage alloc] initWithData:data];
|
||||
|
||||
id<RCTImageDecoder> decoder = [[RCTImageLoaderTestsDecoder1 alloc] initWithPriority:1.0 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) {
|
||||
id<RCTImageDataDecoder> decoder = [[RCTImageLoaderTestsDecoder1 alloc] initWithPriority:1.0 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) {
|
||||
return YES;
|
||||
} decodeImageDataHandler:^RCTImageLoaderCancellationBlock(NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler) {
|
||||
XCTAssertEqualObjects(imageData, data);
|
||||
@@ -118,7 +118,7 @@ RCTDefineImageDecoder(RCTImageLoaderTestsDecoder2)
|
||||
NSData *data = [NSData dataWithBytesNoCopy:blackGIF length:sizeof(blackGIF) freeWhenDone:NO];
|
||||
UIImage *image = [[UIImage alloc] initWithData:data];
|
||||
|
||||
id<RCTImageDecoder> decoder1 = [[RCTImageLoaderTestsDecoder1 alloc] initWithPriority:1.0 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) {
|
||||
id<RCTImageDataDecoder> decoder1 = [[RCTImageLoaderTestsDecoder1 alloc] initWithPriority:1.0 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) {
|
||||
return YES;
|
||||
} decodeImageDataHandler:^RCTImageLoaderCancellationBlock(NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler) {
|
||||
XCTAssertEqualObjects(imageData, data);
|
||||
@@ -126,7 +126,7 @@ RCTDefineImageDecoder(RCTImageLoaderTestsDecoder2)
|
||||
return nil;
|
||||
}];
|
||||
|
||||
id<RCTImageDecoder> decoder2 = [[RCTImageLoaderTestsDecoder2 alloc] initWithPriority:0.5 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) {
|
||||
id<RCTImageDataDecoder> decoder2 = [[RCTImageLoaderTestsDecoder2 alloc] initWithPriority:0.5 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) {
|
||||
return YES;
|
||||
} decodeImageDataHandler:^RCTImageLoaderCancellationBlock(__unused NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, __unused RCTImageLoaderCompletionBlock completionHandler) {
|
||||
XCTFail(@"Should not have used decoder2");
|
||||
|
||||
@@ -262,7 +262,7 @@ class FormUploader extends React.Component {
|
||||
|
||||
exports.framework = 'React';
|
||||
exports.title = 'XMLHttpRequest';
|
||||
exports.description = 'Example that demostrates upload and download requests ' +
|
||||
exports.description = 'Example that demonstrates upload and download requests ' +
|
||||
'using XMLHttpRequest.';
|
||||
exports.examples = [{
|
||||
title: 'File Download',
|
||||
|
||||
@@ -26,10 +26,6 @@ dependencies {
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
compile 'com.android.support:appcompat-v7:23.0.1'
|
||||
|
||||
// Depend on pre-built React Native
|
||||
compile 'com.facebook.react:react-native:0.11.+'
|
||||
|
||||
// Depend on React Native source.
|
||||
// This is useful for testing your changes when working on React Native.
|
||||
// compile project(':ReactAndroid')
|
||||
// Build React Native from source
|
||||
compile project(':ReactAndroid')
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ var ActionSheetIOS = {
|
||||
);
|
||||
RCTActionSheetManager.showActionSheetWithOptions(
|
||||
options,
|
||||
() => {}, // RKActionSheet compatibility hack
|
||||
callback
|
||||
);
|
||||
},
|
||||
|
||||
@@ -21,64 +21,136 @@
|
||||
|
||||
@implementation RCTActionSheetManager
|
||||
{
|
||||
NSMutableDictionary *_callbacks;
|
||||
// Use NSMapTable, as UIAlertViews do not implement <NSCopying>
|
||||
// which is required for NSDictionary keys
|
||||
NSMapTable *_callbacks;
|
||||
}
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_callbacks = [NSMutableDictionary new];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (dispatch_queue_t)methodQueue
|
||||
{
|
||||
return dispatch_get_main_queue();
|
||||
}
|
||||
|
||||
/*
|
||||
* The `anchor` option takes a view to set as the anchor for the share
|
||||
* popup to point to, on iPads running iOS 8. If it is not passed, it
|
||||
* defaults to centering the share popup on screen without any arrows.
|
||||
*/
|
||||
- (CGRect)sourceRectInView:(UIView *)sourceView
|
||||
anchorViewTag:(NSNumber *)anchorViewTag
|
||||
{
|
||||
if (anchorViewTag) {
|
||||
UIView *anchorView = [self.bridge.uiManager viewForReactTag:anchorViewTag];
|
||||
return [anchorView convertRect:anchorView.bounds toView:sourceView];
|
||||
} else {
|
||||
return (CGRect){sourceView.center, {1, 1}};
|
||||
}
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(showActionSheetWithOptions:(NSDictionary *)options
|
||||
failureCallback:(__unused RCTResponseSenderBlock)failureCallback
|
||||
callback:(RCTResponseSenderBlock)callback)
|
||||
{
|
||||
if (RCTRunningInAppExtension()) {
|
||||
RCTLogError(@"Unable to show action sheet from app extension");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_callbacks) {
|
||||
_callbacks = [NSMapTable strongToStrongObjectsMapTable];
|
||||
}
|
||||
|
||||
NSString *title = [RCTConvert NSString:options[@"title"]];
|
||||
NSArray *buttons = [RCTConvert NSStringArray:options[@"options"]];
|
||||
NSInteger destructiveButtonIndex = options[@"destructiveButtonIndex"] ? [RCTConvert NSInteger:options[@"destructiveButtonIndex"]] : -1;
|
||||
NSInteger cancelButtonIndex = options[@"cancelButtonIndex"] ? [RCTConvert NSInteger:options[@"cancelButtonIndex"]] : -1;
|
||||
|
||||
UIViewController *controller = RCTSharedApplication().delegate.window.rootViewController;
|
||||
if (controller == nil) {
|
||||
RCTLogError(@"Tried to display action sheet but there is no application window. options: %@", options);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* The `anchor` option takes a view to set as the anchor for the share
|
||||
* popup to point to, on iPads running iOS 8. If it is not passed, it
|
||||
* defaults to centering the share popup on screen without any arrows.
|
||||
*/
|
||||
NSNumber *anchorViewTag = [RCTConvert NSNumber:options[@"anchor"]];
|
||||
UIView *sourceView = controller.view;
|
||||
CGRect sourceRect = [self sourceRectInView:sourceView anchorViewTag:anchorViewTag];
|
||||
|
||||
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0
|
||||
|
||||
if ([UIAlertController class] == nil) {
|
||||
|
||||
UIActionSheet *actionSheet = [UIActionSheet new];
|
||||
|
||||
actionSheet.title = title;
|
||||
for (NSString *option in buttons) {
|
||||
[actionSheet addButtonWithTitle:option];
|
||||
}
|
||||
actionSheet.destructiveButtonIndex = destructiveButtonIndex;
|
||||
actionSheet.cancelButtonIndex = cancelButtonIndex;
|
||||
actionSheet.delegate = self;
|
||||
|
||||
[_callbacks setObject:callback forKey:actionSheet];
|
||||
|
||||
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
|
||||
[actionSheet showFromRect:sourceRect inView:sourceView animated:YES];
|
||||
} else {
|
||||
[actionSheet showInView:sourceView];
|
||||
}
|
||||
|
||||
} else
|
||||
|
||||
#endif
|
||||
|
||||
{
|
||||
UIAlertController *alertController =
|
||||
[UIAlertController alertControllerWithTitle:title
|
||||
message:nil
|
||||
preferredStyle:UIAlertControllerStyleActionSheet];
|
||||
|
||||
NSInteger index = 0;
|
||||
for (NSString *option in buttons) {
|
||||
UIAlertActionStyle style = UIAlertActionStyleDefault;
|
||||
if (index == destructiveButtonIndex) {
|
||||
style = UIAlertActionStyleDestructive;
|
||||
} else if (index == cancelButtonIndex) {
|
||||
style = UIAlertActionStyleCancel;
|
||||
}
|
||||
|
||||
NSInteger localIndex = index;
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:option
|
||||
style:style
|
||||
handler:^(__unused UIAlertAction *action){
|
||||
callback(@[@(localIndex)]);
|
||||
}]];
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
alertController.modalPresentationStyle = UIModalPresentationPopover;
|
||||
alertController.popoverPresentationController.sourceView = sourceView;
|
||||
alertController.popoverPresentationController.sourceRect = sourceRect;
|
||||
if (!anchorViewTag) {
|
||||
alertController.popoverPresentationController.permittedArrowDirections = 0;
|
||||
}
|
||||
[controller presentViewController:alertController animated:YES completion:nil];
|
||||
}
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options
|
||||
failureCallback:(RCTResponseErrorBlock)failureCallback
|
||||
successCallback:(RCTResponseSenderBlock)successCallback)
|
||||
{
|
||||
if (RCTRunningInAppExtension()) {
|
||||
RCTLogError(@"Unable to show action sheet from app extension");
|
||||
return;
|
||||
}
|
||||
|
||||
UIActionSheet *actionSheet = [UIActionSheet new];
|
||||
|
||||
actionSheet.title = options[@"title"];
|
||||
|
||||
for (NSString *option in options[@"options"]) {
|
||||
[actionSheet addButtonWithTitle:option];
|
||||
}
|
||||
|
||||
if (options[@"destructiveButtonIndex"]) {
|
||||
actionSheet.destructiveButtonIndex = [options[@"destructiveButtonIndex"] integerValue];
|
||||
}
|
||||
if (options[@"cancelButtonIndex"]) {
|
||||
actionSheet.cancelButtonIndex = [options[@"cancelButtonIndex"] integerValue];
|
||||
}
|
||||
|
||||
actionSheet.delegate = self;
|
||||
|
||||
_callbacks[RCTKeyForInstance(actionSheet)] = successCallback;
|
||||
|
||||
UIWindow *appWindow = RCTSharedApplication().delegate.window;
|
||||
if (appWindow == nil) {
|
||||
RCTLogError(@"Tried to display action sheet but there is no application window. options: %@", options);
|
||||
return;
|
||||
}
|
||||
[actionSheet showInView:appWindow];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options
|
||||
failureCallback:(RCTResponseSenderBlock)failureCallback
|
||||
successCallback:(RCTResponseSenderBlock)successCallback)
|
||||
{
|
||||
NSMutableArray *items = [NSMutableArray array];
|
||||
NSString *message = [RCTConvert NSString:options[@"message"]];
|
||||
if (message) {
|
||||
@@ -89,22 +161,18 @@ RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options
|
||||
[items addObject:URL];
|
||||
}
|
||||
if (items.count == 0) {
|
||||
failureCallback(@[@"No `url` or `message` to share"]);
|
||||
return;
|
||||
}
|
||||
if (RCTRunningInAppExtension()) {
|
||||
failureCallback(@[@"Unable to show action sheet from app extension"]);
|
||||
RCTLogError(@"No `url` or `message` to share");
|
||||
return;
|
||||
}
|
||||
|
||||
UIActivityViewController *share = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil];
|
||||
UIViewController *ctrl = RCTSharedApplication().delegate.window.rootViewController;
|
||||
UIActivityViewController *shareController = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil];
|
||||
UIViewController *controller = RCTSharedApplication().delegate.window.rootViewController;
|
||||
|
||||
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0
|
||||
|
||||
if (![UIActivityViewController instancesRespondToSelector:@selector(setCompletionWithItemsHandler:)]) {
|
||||
// Legacy iOS 7 implementation
|
||||
share.completionHandler = ^(NSString *activityType, BOOL completed) {
|
||||
shareController.completionHandler = ^(NSString *activityType, BOOL completed) {
|
||||
successCallback(@[@(completed), RCTNullIfNil(activityType)]);
|
||||
};
|
||||
} else
|
||||
@@ -113,45 +181,34 @@ RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options
|
||||
|
||||
{
|
||||
// iOS 8 version
|
||||
share.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, __unused NSArray *returnedItems, NSError *activityError) {
|
||||
shareController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, __unused NSArray *returnedItems, NSError *activityError) {
|
||||
if (activityError) {
|
||||
failureCallback(@[RCTNullIfNil(activityError.localizedDescription)]);
|
||||
failureCallback(activityError);
|
||||
} else {
|
||||
successCallback(@[@(completed), RCTNullIfNil(activityType)]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* The `anchor` option takes a view to set as the anchor for the share
|
||||
* popup to point to, on iPads running iOS 8. If it is not passed, it
|
||||
* defaults to centering the share popup on screen without any arrows.
|
||||
*/
|
||||
if ([share respondsToSelector:@selector(popoverPresentationController)]) {
|
||||
share.popoverPresentationController.sourceView = ctrl.view;
|
||||
shareController.modalPresentationStyle = UIModalPresentationPopover;
|
||||
NSNumber *anchorViewTag = [RCTConvert NSNumber:options[@"anchor"]];
|
||||
if (anchorViewTag) {
|
||||
UIView *anchorView = [self.bridge.uiManager viewForReactTag:anchorViewTag];
|
||||
share.popoverPresentationController.sourceRect = [anchorView convertRect:anchorView.bounds toView:ctrl.view];
|
||||
} else {
|
||||
CGRect sourceRect = CGRectMake(ctrl.view.center.x, ctrl.view.center.y, 1, 1);
|
||||
share.popoverPresentationController.sourceRect = sourceRect;
|
||||
share.popoverPresentationController.permittedArrowDirections = 0;
|
||||
if (!anchorViewTag) {
|
||||
shareController.popoverPresentationController.permittedArrowDirections = 0;
|
||||
}
|
||||
shareController.popoverPresentationController.sourceView = controller.view;
|
||||
shareController.popoverPresentationController.sourceRect = [self sourceRectInView:controller.view anchorViewTag:anchorViewTag];
|
||||
}
|
||||
|
||||
[ctrl presentViewController:share animated:YES completion:nil];
|
||||
[controller presentViewController:shareController animated:YES completion:nil];
|
||||
}
|
||||
|
||||
#pragma mark UIActionSheetDelegate Methods
|
||||
|
||||
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
|
||||
{
|
||||
NSString *key = RCTKeyForInstance(actionSheet);
|
||||
RCTResponseSenderBlock callback = _callbacks[key];
|
||||
RCTResponseSenderBlock callback = [_callbacks objectForKey:actionSheet];
|
||||
if (callback) {
|
||||
callback(@[@(buttonIndex)]);
|
||||
[_callbacks removeObjectForKey:key];
|
||||
[_callbacks removeObjectForKey:actionSheet];
|
||||
} else {
|
||||
RCTLogWarn(@"No callback registered for action sheet: %@", actionSheet.title);
|
||||
}
|
||||
@@ -159,11 +216,4 @@ RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options
|
||||
[RCTSharedApplication().delegate.window makeKeyWindow];
|
||||
}
|
||||
|
||||
#pragma mark Private
|
||||
|
||||
static NSString *RCTKeyForInstance(id instance)
|
||||
{
|
||||
return [NSString stringWithFormat:@"%p", instance];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -40,11 +40,16 @@ class Animated {
|
||||
__getChildren(): Array<Animated> { return []; }
|
||||
}
|
||||
|
||||
type AnimationConfig = {
|
||||
isInteraction?: bool;
|
||||
};
|
||||
|
||||
// Important note: start() and stop() will only be called at most once.
|
||||
// Once an animation has been stopped or finished its course, it will
|
||||
// not be reused.
|
||||
class Animation {
|
||||
__active: bool;
|
||||
__isInteraction: bool;
|
||||
__onEnd: ?EndCallback;
|
||||
start(
|
||||
fromValue: number,
|
||||
@@ -128,14 +133,14 @@ function _flush(rootNode: AnimatedValue): void {
|
||||
animatedStyles.forEach(animatedStyle => animatedStyle.update());
|
||||
}
|
||||
|
||||
type TimingAnimationConfig = {
|
||||
type TimingAnimationConfig = AnimationConfig & {
|
||||
toValue: number | AnimatedValue | {x: number, y: number} | AnimatedValueXY;
|
||||
easing?: (value: number) => number;
|
||||
duration?: number;
|
||||
delay?: number;
|
||||
};
|
||||
|
||||
type TimingAnimationConfigSingle = {
|
||||
type TimingAnimationConfigSingle = AnimationConfig & {
|
||||
toValue: number | AnimatedValue;
|
||||
easing?: (value: number) => number;
|
||||
duration?: number;
|
||||
@@ -163,6 +168,7 @@ class TimingAnimation extends Animation {
|
||||
this._easing = config.easing || easeInOut;
|
||||
this._duration = config.duration !== undefined ? config.duration : 500;
|
||||
this._delay = config.delay || 0;
|
||||
this.__isInteraction = config.isInteraction !== undefined ? config.isInteraction : true;
|
||||
}
|
||||
|
||||
start(
|
||||
@@ -223,12 +229,12 @@ class TimingAnimation extends Animation {
|
||||
}
|
||||
}
|
||||
|
||||
type DecayAnimationConfig = {
|
||||
type DecayAnimationConfig = AnimationConfig & {
|
||||
velocity: number | {x: number, y: number};
|
||||
deceleration?: number;
|
||||
};
|
||||
|
||||
type DecayAnimationConfigSingle = {
|
||||
type DecayAnimationConfigSingle = AnimationConfig & {
|
||||
velocity: number;
|
||||
deceleration?: number;
|
||||
};
|
||||
@@ -248,6 +254,7 @@ class DecayAnimation extends Animation {
|
||||
super();
|
||||
this._deceleration = config.deceleration || 0.998;
|
||||
this._velocity = config.velocity;
|
||||
this.__isInteraction = config.isInteraction !== undefined ? config.isInteraction : true;
|
||||
}
|
||||
|
||||
start(
|
||||
@@ -291,7 +298,7 @@ class DecayAnimation extends Animation {
|
||||
}
|
||||
}
|
||||
|
||||
type SpringAnimationConfig = {
|
||||
type SpringAnimationConfig = AnimationConfig & {
|
||||
toValue: number | AnimatedValue | {x: number, y: number} | AnimatedValueXY;
|
||||
overshootClamping?: bool;
|
||||
restDisplacementThreshold?: number;
|
||||
@@ -303,7 +310,7 @@ type SpringAnimationConfig = {
|
||||
friction?: number;
|
||||
};
|
||||
|
||||
type SpringAnimationConfigSingle = {
|
||||
type SpringAnimationConfigSingle = AnimationConfig & {
|
||||
toValue: number | AnimatedValue;
|
||||
overshootClamping?: bool;
|
||||
restDisplacementThreshold?: number;
|
||||
@@ -349,6 +356,7 @@ class SpringAnimation extends Animation {
|
||||
this._initialVelocity = config.velocity;
|
||||
this._lastVelocity = withDefault(config.velocity, 0);
|
||||
this._toValue = config.toValue;
|
||||
this.__isInteraction = config.isInteraction !== undefined ? config.isInteraction : true;
|
||||
|
||||
var springConfig;
|
||||
if (config.bounciness !== undefined || config.speed !== undefined) {
|
||||
@@ -611,7 +619,10 @@ class AnimatedValue extends AnimatedWithChildren {
|
||||
* class.
|
||||
*/
|
||||
animate(animation: Animation, callback: ?EndCallback): void {
|
||||
var handle = InteractionManager.createInteractionHandle();
|
||||
var handle = null;
|
||||
if (animation.__isInteraction) {
|
||||
handle = InteractionManager.createInteractionHandle();
|
||||
}
|
||||
var previousAnimation = this._animation;
|
||||
this._animation && this._animation.stop();
|
||||
this._animation = animation;
|
||||
@@ -622,7 +633,9 @@ class AnimatedValue extends AnimatedWithChildren {
|
||||
},
|
||||
(result) => {
|
||||
this._animation = null;
|
||||
InteractionManager.clearInteractionHandle(handle);
|
||||
if (handle !== null) {
|
||||
InteractionManager.clearInteractionHandle(handle);
|
||||
}
|
||||
callback && callback(result);
|
||||
},
|
||||
previousAnimation,
|
||||
|
||||
@@ -18,6 +18,7 @@ jest
|
||||
var Animated = require('Animated');
|
||||
|
||||
describe('Animated', () => {
|
||||
|
||||
it('works end to end', () => {
|
||||
var anim = new Animated.Value(0);
|
||||
|
||||
@@ -352,6 +353,54 @@ describe('Animated Events', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Animated Interactions', () => {
|
||||
/*eslint-disable no-shadow*/
|
||||
var Animated;
|
||||
/*eslint-enable*/
|
||||
var InteractionManager;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.mock('InteractionManager');
|
||||
Animated = require('Animated');
|
||||
InteractionManager = require('InteractionManager');
|
||||
});
|
||||
|
||||
afterEach(()=> {
|
||||
jest.dontMock('InteractionManager');
|
||||
});
|
||||
|
||||
it('registers an interaction by default', () => {
|
||||
InteractionManager.createInteractionHandle.mockReturnValue(777);
|
||||
|
||||
var value = new Animated.Value(0);
|
||||
var callback = jest.genMockFunction();
|
||||
Animated.timing(value, {
|
||||
toValue: 100,
|
||||
duration: 100,
|
||||
}).start(callback);
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(InteractionManager.createInteractionHandle).toBeCalled();
|
||||
expect(InteractionManager.clearInteractionHandle).toBeCalledWith(777);
|
||||
expect(callback).toBeCalledWith({finished: true});
|
||||
});
|
||||
|
||||
it('does not register an interaction when specified', () => {
|
||||
var value = new Animated.Value(0);
|
||||
var callback = jest.genMockFunction();
|
||||
Animated.timing(value, {
|
||||
toValue: 100,
|
||||
duration: 100,
|
||||
isInteraction: false,
|
||||
}).start(callback);
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(InteractionManager.createInteractionHandle).not.toBeCalled();
|
||||
expect(InteractionManager.clearInteractionHandle).not.toBeCalled();
|
||||
expect(callback).toBeCalledWith({finished: true});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Animated Tracking', () => {
|
||||
it('should track values', () => {
|
||||
var value1 = new Animated.Value(0);
|
||||
|
||||
@@ -15,11 +15,6 @@
|
||||
|
||||
@interface RCTBridge (RCTAssetsLibraryImageLoader)
|
||||
|
||||
/**
|
||||
* The shared Assets Library image loader
|
||||
*/
|
||||
@property (nonatomic, readonly) RCTAssetsLibraryImageLoader *assetsLibraryImageLoader;
|
||||
|
||||
/**
|
||||
* The shared asset library instance.
|
||||
*/
|
||||
|
||||
@@ -41,10 +41,15 @@ RCT_EXPORT_MODULE()
|
||||
|
||||
- (BOOL)canLoadImageURL:(NSURL *)requestURL
|
||||
{
|
||||
return [requestURL.scheme.lowercaseString isEqualToString:@"assets-library"];
|
||||
return [requestURL.scheme caseInsensitiveCompare:@"assets-library"] == NSOrderedSame;
|
||||
}
|
||||
|
||||
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
|
||||
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
|
||||
size:(CGSize)size
|
||||
scale:(CGFloat)scale
|
||||
resizeMode:(UIViewContentMode)resizeMode
|
||||
progressHandler:(RCTImageLoaderProgressBlock)progressHandler
|
||||
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
|
||||
{
|
||||
__block volatile uint32_t cancelled = 0;
|
||||
|
||||
@@ -69,7 +74,8 @@ RCT_EXPORT_MODULE()
|
||||
BOOL useMaximumSize = CGSizeEqualToSize(size, CGSizeZero);
|
||||
ALAssetRepresentation *representation = [asset defaultRepresentation];
|
||||
|
||||
#if RCT_DEV
|
||||
#if RCT_DEV
|
||||
|
||||
CGSize sizeBeingLoaded = size;
|
||||
if (useMaximumSize) {
|
||||
CGSize pointSize = representation.dimensions;
|
||||
@@ -78,7 +84,7 @@ RCT_EXPORT_MODULE()
|
||||
|
||||
CGSize screenSize;
|
||||
if ([[[UIDevice currentDevice] systemVersion] compare:@"8.0" options:NSNumericSearch] == NSOrderedDescending) {
|
||||
screenSize = UIScreen.mainScreen.nativeBounds.size;
|
||||
screenSize = [UIScreen mainScreen].nativeBounds.size;
|
||||
} else {
|
||||
CGSize mainScreenSize = [UIScreen mainScreen].bounds.size;
|
||||
CGFloat mainScreenScale = [[UIScreen mainScreen] scale];
|
||||
@@ -87,9 +93,11 @@ RCT_EXPORT_MODULE()
|
||||
CGFloat maximumPixelDimension = fmax(screenSize.width, screenSize.height);
|
||||
|
||||
if (sizeBeingLoaded.width > maximumPixelDimension || sizeBeingLoaded.height > maximumPixelDimension) {
|
||||
RCTLogInfo(@"[PERF ASSETS] Loading %@ at size %@, which is larger than screen size %@", representation.filename, NSStringFromCGSize(sizeBeingLoaded), NSStringFromCGSize(screenSize));
|
||||
RCTLogInfo(@"[PERF ASSETS] Loading %@ at size %@, which is larger than screen size %@",
|
||||
representation.filename, NSStringFromCGSize(sizeBeingLoaded), NSStringFromCGSize(screenSize));
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
UIImage *image;
|
||||
NSError *error = nil;
|
||||
@@ -106,8 +114,7 @@ RCT_EXPORT_MODULE()
|
||||
});
|
||||
} else {
|
||||
NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@ with no error message.", imageURL];
|
||||
NSError *error = RCTErrorWithMessage(errorText);
|
||||
completionHandler(error, nil);
|
||||
completionHandler(RCTErrorWithMessage(errorText), nil);
|
||||
}
|
||||
} failureBlock:^(NSError *loadError) {
|
||||
if (cancelled) {
|
||||
@@ -115,8 +122,7 @@ RCT_EXPORT_MODULE()
|
||||
}
|
||||
|
||||
NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@.\niOS Error: %@", imageURL, loadError];
|
||||
NSError *error = RCTErrorWithMessage(errorText);
|
||||
completionHandler(error, nil);
|
||||
completionHandler(RCTErrorWithMessage(errorText), nil);
|
||||
}];
|
||||
|
||||
return ^{
|
||||
@@ -128,14 +134,9 @@ RCT_EXPORT_MODULE()
|
||||
|
||||
@implementation RCTBridge (RCTAssetsLibraryImageLoader)
|
||||
|
||||
- (RCTAssetsLibraryImageLoader *)assetsLibraryImageLoader
|
||||
{
|
||||
return self.modules[RCTBridgeModuleNameForClass([RCTAssetsLibraryImageLoader class])];
|
||||
}
|
||||
|
||||
- (ALAssetsLibrary *)assetsLibrary
|
||||
{
|
||||
return [self.assetsLibraryImageLoader assetsLibrary];
|
||||
return [self.modules[RCTBridgeModuleNameForClass([RCTAssetsLibraryImageLoader class])] assetsLibrary];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -154,7 +155,11 @@ static dispatch_queue_t RCTAssetsLibraryImageLoaderQueue(void)
|
||||
// Why use a custom scaling method? Greater efficiency, reduced memory overhead:
|
||||
// http://www.mindsea.com/2012/12/downscaling-huge-alassets-without-fear-of-sigkill
|
||||
|
||||
static UIImage *RCTScaledImageForAsset(ALAssetRepresentation *representation, CGSize size, CGFloat scale, UIViewContentMode resizeMode, NSError **error)
|
||||
static UIImage *RCTScaledImageForAsset(ALAssetRepresentation *representation,
|
||||
CGSize size,
|
||||
CGFloat scale,
|
||||
UIViewContentMode resizeMode,
|
||||
NSError **error)
|
||||
{
|
||||
NSUInteger length = (NSUInteger)representation.size;
|
||||
NSMutableData *data = [NSMutableData dataWithLength:length];
|
||||
|
||||
@@ -35,14 +35,17 @@ RCT_EXPORT_METHOD(saveImageWithTag:(NSString *)imageTag
|
||||
errorCallback(loadError);
|
||||
return;
|
||||
}
|
||||
[_bridge.assetsLibrary writeImageToSavedPhotosAlbum:loadedImage.CGImage metadata:nil completionBlock:^(NSURL *assetURL, NSError *saveError) {
|
||||
if (saveError) {
|
||||
RCTLogWarn(@"Error saving cropped image: %@", saveError);
|
||||
errorCallback(saveError);
|
||||
} else {
|
||||
successCallback(@[assetURL.absoluteString]);
|
||||
}
|
||||
}];
|
||||
// It's unclear if writeImageToSavedPhotosAlbum is thread-safe
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[_bridge.assetsLibrary writeImageToSavedPhotosAlbum:loadedImage.CGImage metadata:nil completionBlock:^(NSURL *assetURL, NSError *saveError) {
|
||||
if (saveError) {
|
||||
RCTLogWarn(@"Error saving cropped image: %@", saveError);
|
||||
errorCallback(saveError);
|
||||
} else {
|
||||
successCallback(@[assetURL.absoluteString]);
|
||||
}
|
||||
}];
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
@@ -24,35 +24,44 @@ RCT_EXPORT_MODULE()
|
||||
|
||||
- (BOOL)canLoadImageURL:(NSURL *)requestURL
|
||||
{
|
||||
return [requestURL.scheme.lowercaseString isEqualToString:@"ph"];
|
||||
return [requestURL.scheme caseInsensitiveCompare:@"ph"] == NSOrderedSame;
|
||||
}
|
||||
|
||||
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
|
||||
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
|
||||
size:(CGSize)size
|
||||
scale:(CGFloat)scale
|
||||
resizeMode:(UIViewContentMode)resizeMode
|
||||
progressHandler:(RCTImageLoaderProgressBlock)progressHandler
|
||||
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
|
||||
{
|
||||
// Using PhotoKit for iOS 8+
|
||||
// The '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 an, NSURL which is what assets-library uses.
|
||||
NSString *phAssetID = [imageURL.absoluteString substringFromIndex:[@"ph://" length]];
|
||||
NSString *phAssetID = [imageURL.absoluteString 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 = RCTErrorWithMessage(errorText);
|
||||
completionHandler(error, nil);
|
||||
completionHandler(RCTErrorWithMessage(errorText), nil);
|
||||
return ^{};
|
||||
}
|
||||
|
||||
PHAsset *asset = [results firstObject];
|
||||
|
||||
PHImageRequestOptions *imageOptions = [PHImageRequestOptions new];
|
||||
imageOptions.progressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary *info) {
|
||||
static const double multiplier = 1e6;
|
||||
progressHandler(progress * multiplier, multiplier);
|
||||
};
|
||||
|
||||
if (progressHandler) {
|
||||
imageOptions.progressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary *info) {
|
||||
static const double multiplier = 1e6;
|
||||
progressHandler(progress * multiplier, multiplier);
|
||||
};
|
||||
}
|
||||
|
||||
// Note: PhotoKit defaults to a deliveryMode of PHImageRequestOptionsDeliveryModeOpportunistic
|
||||
// which means it may call back multiple times - we probably don't want that
|
||||
imageOptions.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
|
||||
|
||||
BOOL useMaximumSize = CGSizeEqualToSize(size, CGSizeZero);
|
||||
CGSize targetSize;
|
||||
|
||||
if (useMaximumSize) {
|
||||
targetSize = PHImageManagerMaximumSize;
|
||||
imageOptions.resizeMode = PHImageRequestOptionsResizeModeNone;
|
||||
@@ -66,7 +75,12 @@ RCT_EXPORT_MODULE()
|
||||
contentMode = PHImageContentModeAspectFit;
|
||||
}
|
||||
|
||||
PHImageRequestID requestID = [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:targetSize contentMode:contentMode options:imageOptions resultHandler:^(UIImage *result, NSDictionary *info) {
|
||||
PHImageRequestID requestID =
|
||||
[[PHImageManager defaultManager] requestImageForAsset:asset
|
||||
targetSize:targetSize
|
||||
contentMode:contentMode
|
||||
options:imageOptions
|
||||
resultHandler:^(UIImage *result, NSDictionary *info) {
|
||||
if (result) {
|
||||
completionHandler(nil, result);
|
||||
} else {
|
||||
|
||||
@@ -74,8 +74,8 @@ var ActivityIndicatorIOS = React.createClass({
|
||||
return (
|
||||
<View
|
||||
onLayout={onLayout}
|
||||
style={[styles.container, sizeStyle, style]}>
|
||||
<RCTActivityIndicatorView {...props} />
|
||||
style={[styles.container, style]}>
|
||||
<RCTActivityIndicatorView {...props} style={sizeStyle} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* @providesModule RecyclerViewBackedScrollView
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var React = require('React');
|
||||
var ScrollResponder = require('ScrollResponder');
|
||||
var ScrollView = require('ScrollView');
|
||||
|
||||
var requireNativeComponent = require('requireNativeComponent');
|
||||
|
||||
var INNERVIEW = 'InnerView';
|
||||
|
||||
/**
|
||||
* Wrapper around android native recycler view.
|
||||
*
|
||||
* It simply renders rows passed as children in a separate recycler view cells
|
||||
* similarily to how `ScrollView` is doing it. Thanks to the fact that it uses
|
||||
* native `RecyclerView` though, rows that are out of sight are going to be
|
||||
* automatically detached (similarily on how this would work with
|
||||
* `removeClippedSubviews = true` on a `ScrollView.js`).
|
||||
*
|
||||
* CAUTION: This is an experimental component and should only be used together
|
||||
* with javascript implementation of list view (see ListView.js). In order to
|
||||
* use it pass this component as `renderScrollComponent` to the list view. For
|
||||
* now only horizontal scrolling is supported.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* getInitialState: function() {
|
||||
* var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
|
||||
* return {
|
||||
* dataSource: ds.cloneWithRows(['row 1', 'row 2']),
|
||||
* };
|
||||
* },
|
||||
*
|
||||
* render: function() {
|
||||
* return (
|
||||
* <ListView
|
||||
* dataSource={this.state.dataSource}
|
||||
* renderRow={rowData => <Text>{rowData}</Text>}
|
||||
* renderScrollComponent={props => <RecyclerViewBackedScrollView {...props} />}
|
||||
* />
|
||||
* );
|
||||
* },
|
||||
* ```
|
||||
*/
|
||||
var RecyclerViewBackedScrollView = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
...ScrollView.propTypes,
|
||||
},
|
||||
|
||||
mixins: [ScrollResponder.Mixin],
|
||||
|
||||
getInitialState: function() {
|
||||
return this.scrollResponderMixinGetInitialState();
|
||||
},
|
||||
|
||||
getScrollResponder: function() {
|
||||
return this;
|
||||
},
|
||||
|
||||
getInnerViewNode: function(): any {
|
||||
return React.findNodeHandle(this.refs[INNERVIEW]);
|
||||
},
|
||||
|
||||
setNativeProps: function(props: Object) {
|
||||
this.refs[INNERVIEW].setNativeProps(props);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var props = {
|
||||
...this.props,
|
||||
onTouchStart: this.scrollResponderHandleTouchStart,
|
||||
onTouchMove: this.scrollResponderHandleTouchMove,
|
||||
onTouchEnd: this.scrollResponderHandleTouchEnd,
|
||||
onScrollBeginDrag: this.scrollResponderHandleScrollBeginDrag,
|
||||
onScrollEndDrag: this.scrollResponderHandleScrollEndDrag,
|
||||
onMomentumScrollBegin: this.scrollResponderHandleMomentumScrollBegin,
|
||||
onMomentumScrollEnd: this.scrollResponderHandleMomentumScrollEnd,
|
||||
onStartShouldSetResponder: this.scrollResponderHandleStartShouldSetResponder,
|
||||
onStartShouldSetResponderCapture: this.scrollResponderHandleStartShouldSetResponderCapture,
|
||||
onScrollShouldSetResponder: this.scrollResponderHandleScrollShouldSetResponder,
|
||||
onResponderGrant: this.scrollResponderHandleResponderGrant,
|
||||
onResponderRelease: this.scrollResponderHandleResponderRelease,
|
||||
onResponderReject: this.scrollResponderHandleResponderReject,
|
||||
onScroll: this.scrollResponderHandleScroll,
|
||||
style: ([{flex: 1}, this.props.style]: ?Array<any>),
|
||||
ref: INNERVIEW,
|
||||
};
|
||||
return (
|
||||
<NativeAndroidRecyclerView {...props}/>
|
||||
);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
var NativeAndroidRecyclerView = requireNativeComponent('AndroidRecyclerViewBackedScrollView', null);
|
||||
|
||||
module.exports = RecyclerViewBackedScrollView;
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* @providesModule RecyclerViewBackedScrollView
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
module.exports = require('UnimplementedView');
|
||||
@@ -108,7 +108,7 @@ var TextInput = React.createClass({
|
||||
autoFocus: PropTypes.bool,
|
||||
/**
|
||||
* Set the position of the cursor from where editing will begin.
|
||||
* @platorm android
|
||||
* @platform android
|
||||
*/
|
||||
textAlign: PropTypes.oneOf([
|
||||
'start',
|
||||
|
||||
@@ -103,6 +103,10 @@ var ToolbarAndroid = React.createClass({
|
||||
* Callback called when the icon is selected.
|
||||
*/
|
||||
onIconClicked: ReactPropTypes.func,
|
||||
/**
|
||||
* Sets the overflow icon.
|
||||
*/
|
||||
overflowIcon: optionalImageSource,
|
||||
/**
|
||||
* Sets the toolbar subtitle.
|
||||
*/
|
||||
@@ -135,6 +139,9 @@ var ToolbarAndroid = React.createClass({
|
||||
if (this.props.navIcon) {
|
||||
nativeProps.navIcon = resolveAssetSource(this.props.navIcon);
|
||||
}
|
||||
if (this.props.overflowIcon) {
|
||||
nativeProps.overflowIcon = resolveAssetSource(this.props.overflowIcon);
|
||||
}
|
||||
if (this.props.actions) {
|
||||
nativeProps.actions = [];
|
||||
for (var i = 0; i < this.props.actions.length; i++) {
|
||||
@@ -169,6 +176,7 @@ var toolbarAttributes = {
|
||||
actions: true,
|
||||
logo: true,
|
||||
navIcon: true,
|
||||
overflowIcon: true,
|
||||
subtitle: true,
|
||||
subtitleColor: true,
|
||||
title: true,
|
||||
|
||||
@@ -420,16 +420,18 @@ var TouchableMixin = {
|
||||
var positionOnActivate = this.state.touchable.positionOnActivate;
|
||||
var dimensionsOnActivate = this.state.touchable.dimensionsOnActivate;
|
||||
var pressRectOffset = this.touchableGetPressRectOffset ?
|
||||
this.touchableGetPressRectOffset() : null;
|
||||
var pressExpandLeft =
|
||||
pressRectOffset.left != null ? pressRectOffset.left : PRESS_EXPAND_PX;
|
||||
var pressExpandTop =
|
||||
pressRectOffset.top != null ? pressRectOffset.top : PRESS_EXPAND_PX;
|
||||
var pressExpandRight =
|
||||
pressRectOffset.right != null ? pressRectOffset.right : PRESS_EXPAND_PX;
|
||||
var pressExpandBottom =
|
||||
pressRectOffset.bottom != null ? pressRectOffset.bottom : PRESS_EXPAND_PX;
|
||||
|
||||
this.touchableGetPressRectOffset() : {
|
||||
left: PRESS_EXPAND_PX,
|
||||
right: PRESS_EXPAND_PX,
|
||||
top: PRESS_EXPAND_PX,
|
||||
bottom: PRESS_EXPAND_PX
|
||||
};
|
||||
|
||||
var pressExpandLeft = pressRectOffset.left;
|
||||
var pressExpandTop = pressRectOffset.top;
|
||||
var pressExpandRight = pressRectOffset.right;
|
||||
var pressExpandBottom = pressRectOffset.bottom;
|
||||
|
||||
var touch = TouchEventUtils.extractSingleTouch(e.nativeEvent);
|
||||
var pageX = touch && touch.pageX;
|
||||
var pageY = touch && touch.pageY;
|
||||
|
||||
@@ -25,7 +25,7 @@ var processColor = require('processColor');
|
||||
|
||||
var rippleBackgroundPropType = createStrictShapeTypeChecker({
|
||||
type: React.PropTypes.oneOf(['RippleAndroid']),
|
||||
color: PropTypes.string,
|
||||
color: PropTypes.number,
|
||||
borderless: PropTypes.bool,
|
||||
});
|
||||
|
||||
|
||||
@@ -387,6 +387,9 @@ var ListView = React.createClass({
|
||||
if (!props.scrollEventThrottle) {
|
||||
props.scrollEventThrottle = DEFAULT_SCROLL_CALLBACK_THROTTLE;
|
||||
}
|
||||
if (props.removeClippedSubviews === undefined) {
|
||||
props.removeClippedSubviews = true;
|
||||
}
|
||||
Object.assign(props, {
|
||||
onScroll: this._onScroll,
|
||||
stickyHeaderIndices: sectionHeaderIndices,
|
||||
|
||||
@@ -26,40 +26,71 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var NavigationEvent = require('NavigationEvent');
|
||||
var NavigationEventEmitter = require('NavigationEventEmitter');
|
||||
var NavigationTreeNode = require('NavigationTreeNode');
|
||||
|
||||
var emptyFunction = require('emptyFunction');
|
||||
var invariant = require('invariant');
|
||||
|
||||
import type * as NavigationEvent from 'NavigationEvent';
|
||||
import type * as EventSubscription from 'EventSubscription';
|
||||
|
||||
var {
|
||||
AT_TARGET,
|
||||
BUBBLING_PHASE,
|
||||
CAPTURING_PHASE,
|
||||
} = NavigationEvent;
|
||||
|
||||
/**
|
||||
* Class that contains the info and methods for app navigation.
|
||||
*/
|
||||
class NavigationContext {
|
||||
_eventEmitter: ?NavigationEventEmitter;
|
||||
__node: NavigationTreeNode;
|
||||
_bubbleEventEmitter: ?NavigationEventEmitter;
|
||||
_captureEventEmitter: ?NavigationEventEmitter;
|
||||
_currentRoute: any;
|
||||
_emitCounter: number;
|
||||
_emitQueue: Array<any>;
|
||||
|
||||
constructor() {
|
||||
this._eventEmitter = new NavigationEventEmitter(this);
|
||||
this._bubbleEventEmitter = new NavigationEventEmitter(this);
|
||||
this._captureEventEmitter = new NavigationEventEmitter(this);
|
||||
this._currentRoute = null;
|
||||
|
||||
// Sets the protected property `__node`.
|
||||
this.__node = new NavigationTreeNode(this);
|
||||
|
||||
this._emitCounter = 0;
|
||||
this._emitQueue = [];
|
||||
|
||||
this.addListener('willfocus', this._onFocus, this);
|
||||
this.addListener('didfocus', this._onFocus, this);
|
||||
}
|
||||
|
||||
// TODO: @flow does not like this getter. Will add @flow check back once
|
||||
// getter/setter is supported.
|
||||
/* $FlowFixMe - get/set properties not yet supported */
|
||||
get parent(): ?NavigationContext {
|
||||
var parent = this.__node.getParent();
|
||||
return parent ? parent.getValue() : null;
|
||||
}
|
||||
|
||||
/* $FlowFixMe - get/set properties not yet supported */
|
||||
get currentRoute(): any {
|
||||
return this._currentRoute;
|
||||
}
|
||||
|
||||
appendChild(childContext: NavigationContext): void {
|
||||
this.__node.appendChild(childContext.__node);
|
||||
}
|
||||
|
||||
addListener(
|
||||
eventType: string,
|
||||
listener: Function,
|
||||
context: ?Object
|
||||
context: ?Object,
|
||||
useCapture: ?boolean
|
||||
): EventSubscription {
|
||||
var emitter = this._eventEmitter;
|
||||
var emitter = useCapture ?
|
||||
this._captureEventEmitter :
|
||||
this._bubbleEventEmitter;
|
||||
if (emitter) {
|
||||
return emitter.addListener(eventType, listener, context);
|
||||
} else {
|
||||
@@ -68,19 +99,115 @@ class NavigationContext {
|
||||
}
|
||||
|
||||
emit(eventType: String, data: any, didEmitCallback: ?Function): void {
|
||||
var emitter = this._eventEmitter;
|
||||
if (emitter) {
|
||||
emitter.emit(eventType, data, didEmitCallback);
|
||||
if (this._emitCounter > 0) {
|
||||
// An event cycle that was previously created hasn't finished yet.
|
||||
// Put this event cycle into the queue and will finish them later.
|
||||
var args: any = Array.prototype.slice.call(arguments);
|
||||
this._emitQueue.push(args);
|
||||
return;
|
||||
}
|
||||
|
||||
this._emitCounter++;
|
||||
|
||||
var targets = [this];
|
||||
var parentTarget = this.parent;
|
||||
while (parentTarget) {
|
||||
targets.unshift(parentTarget);
|
||||
parentTarget = parentTarget.parent;
|
||||
}
|
||||
|
||||
var propagationStopped = false;
|
||||
var defaultPrevented = false;
|
||||
var callback = (event) => {
|
||||
propagationStopped = propagationStopped || event.isPropagationStopped();
|
||||
defaultPrevented = defaultPrevented || event.defaultPrevented;
|
||||
};
|
||||
|
||||
// capture phase
|
||||
targets.some((currentTarget) => {
|
||||
if (propagationStopped) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var extraInfo = {
|
||||
defaultPrevented,
|
||||
eventPhase: CAPTURING_PHASE,
|
||||
propagationStopped,
|
||||
target: this,
|
||||
};
|
||||
|
||||
currentTarget.__emit(eventType, data, callback, extraInfo);
|
||||
}, this);
|
||||
|
||||
// bubble phase
|
||||
targets.reverse().some((currentTarget) => {
|
||||
if (propagationStopped) {
|
||||
return true;
|
||||
}
|
||||
var extraInfo = {
|
||||
defaultPrevented,
|
||||
eventPhase: BUBBLING_PHASE,
|
||||
propagationStopped,
|
||||
target: this,
|
||||
};
|
||||
currentTarget.__emit(eventType, data, callback, extraInfo);
|
||||
}, this);
|
||||
|
||||
if (didEmitCallback) {
|
||||
var event = NavigationEvent.pool(eventType, this, data);
|
||||
propagationStopped && event.stopPropagation();
|
||||
defaultPrevented && event.preventDefault();
|
||||
didEmitCallback.call(this, event);
|
||||
event.dispose();
|
||||
}
|
||||
|
||||
this._emitCounter--;
|
||||
while (this._emitQueue.length) {
|
||||
var args: any = this._emitQueue.shift();
|
||||
this.emit.apply(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
var emitter = this._eventEmitter;
|
||||
// clean up everything.
|
||||
this._bubbleEventEmitter && this._bubbleEventEmitter.removeAllListeners();
|
||||
this._captureEventEmitter && this._captureEventEmitter.removeAllListeners();
|
||||
this._bubbleEventEmitter = null;
|
||||
this._captureEventEmitter = null;
|
||||
this._currentRoute = null;
|
||||
}
|
||||
|
||||
// This method `__method` is protected.
|
||||
__emit(
|
||||
eventType: String,
|
||||
data: any,
|
||||
didEmitCallback: ?Function,
|
||||
extraInfo: Object,
|
||||
): void {
|
||||
var emitter;
|
||||
switch (extraInfo.eventPhase) {
|
||||
case CAPTURING_PHASE: // phase = 1
|
||||
emitter = this._captureEventEmitter;
|
||||
break;
|
||||
case BUBBLING_PHASE: // phase = 3
|
||||
emitter = this._bubbleEventEmitter;
|
||||
break;
|
||||
default:
|
||||
invariant(false, 'invalid event phase %s', extraInfo.eventPhase);
|
||||
}
|
||||
|
||||
if (extraInfo.target === this) {
|
||||
// phase = 2
|
||||
extraInfo.eventPhase = AT_TARGET;
|
||||
}
|
||||
|
||||
if (emitter) {
|
||||
// clean up everything.
|
||||
emitter.removeAllListeners();
|
||||
this._eventEmitter = null;
|
||||
this._currentRoute = null;
|
||||
emitter.emit(
|
||||
eventType,
|
||||
data,
|
||||
didEmitCallback,
|
||||
extraInfo
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,13 +36,13 @@ class NavigationEventPool {
|
||||
this._list = [];
|
||||
}
|
||||
|
||||
get(type: string, target: Object, data: any): NavigationEvent {
|
||||
get(type: string, currentTarget: Object, data: any): NavigationEvent {
|
||||
var event;
|
||||
if (this._list.length > 0) {
|
||||
event = this._list.pop();
|
||||
event.constructor.call(event, type, target, data);
|
||||
event.constructor.call(event, type, currentTarget, data);
|
||||
} else {
|
||||
event = new NavigationEvent(type, target, data);
|
||||
event = new NavigationEvent(type, currentTarget, data);
|
||||
}
|
||||
return event;
|
||||
}
|
||||
@@ -54,21 +54,57 @@ class NavigationEventPool {
|
||||
|
||||
var _navigationEventPool = new NavigationEventPool();
|
||||
|
||||
/**
|
||||
* The NavigationEvent interface represents any event of the navigation.
|
||||
* It contains common properties and methods to any event.
|
||||
*
|
||||
* == Important Properties ==
|
||||
*
|
||||
* - target:
|
||||
* A reference to the navigation context that dispatched the event. It is
|
||||
* different from event.currentTarget when the event handler is called during
|
||||
* the bubbling or capturing phase of the event.
|
||||
*
|
||||
* - currentTarget:
|
||||
* Identifies the current target for the event, as the event traverses the
|
||||
* navigation context tree. It always refers to the navigation context the
|
||||
* event handler has been attached to as opposed to event.target which
|
||||
* identifies the navigation context on which the event occurred.
|
||||
*
|
||||
* - eventPhase:
|
||||
* Returns an integer value which specifies the current evaluation phase of
|
||||
* the event flow; possible values are listed in NavigationEvent phase
|
||||
* constants below.
|
||||
*/
|
||||
class NavigationEvent {
|
||||
static AT_TARGET: number;
|
||||
static BUBBLING_PHASE: number;
|
||||
static CAPTURING_PHASE: number;
|
||||
static NONE: number;
|
||||
|
||||
_currentTarget: ?Object;
|
||||
_data: any;
|
||||
_defaultPrevented: boolean;
|
||||
_propagationStopped: boolean;
|
||||
_disposed: boolean;
|
||||
_target: ?Object;
|
||||
_propagationStopped: boolean;
|
||||
_type: ?string;
|
||||
|
||||
static pool(type: string, target: Object, data: any): NavigationEvent {
|
||||
return _navigationEventPool.get(type, target, data);
|
||||
target: ?Object;
|
||||
|
||||
// Returns an integer value which specifies the current evaluation phase of
|
||||
// the event flow.
|
||||
eventPhase: number;
|
||||
|
||||
static pool(type: string, currentTarget: Object, data: any): NavigationEvent {
|
||||
return _navigationEventPool.get(type, currentTarget, data);
|
||||
}
|
||||
|
||||
constructor(type: string, target: Object, data: any) {
|
||||
constructor(type: string, currentTarget: Object, data: any) {
|
||||
this.target = currentTarget;
|
||||
this.eventPhase = NavigationEvent.NONE;
|
||||
|
||||
this._type = type;
|
||||
this._target = target;
|
||||
this._currentTarget = currentTarget;
|
||||
this._data = data;
|
||||
this._defaultPrevented = false;
|
||||
this._disposed = false;
|
||||
@@ -81,8 +117,8 @@ class NavigationEvent {
|
||||
}
|
||||
|
||||
/* $FlowFixMe - get/set properties not yet supported */
|
||||
get target(): Object {
|
||||
return this._target;
|
||||
get currentTarget(): Object {
|
||||
return this._currentTarget;
|
||||
}
|
||||
|
||||
/* $FlowFixMe - get/set properties not yet supported */
|
||||
@@ -122,8 +158,10 @@ class NavigationEvent {
|
||||
this._disposed = true;
|
||||
|
||||
// Clean up.
|
||||
this.target = null;
|
||||
this.eventPhase = NavigationEvent.NONE;
|
||||
this._type = null;
|
||||
this._target = null;
|
||||
this._currentTarget = null;
|
||||
this._data = null;
|
||||
this._defaultPrevented = false;
|
||||
|
||||
@@ -132,4 +170,26 @@ class NavigationEvent {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event phase constants.
|
||||
* These values describe which phase the event flow is currently being
|
||||
* evaluated.
|
||||
*/
|
||||
|
||||
// No event is being processed at this time.
|
||||
NavigationEvent.NONE = 0;
|
||||
|
||||
// The event is being propagated through the currentTarget's ancestor objects.
|
||||
NavigationEvent.CAPTURING_PHASE = 1;
|
||||
|
||||
// The event has arrived at the event's currentTarget. Event listeners registered for
|
||||
// this phase are called at this time.
|
||||
NavigationEvent.AT_TARGET = 2;
|
||||
|
||||
// The event is propagating back up through the currentTarget's ancestors in reverse
|
||||
// order, starting with the parent. This is known as bubbling, and occurs only
|
||||
// if event propagation isn't prevented. Event listeners registered for this
|
||||
// phase are triggered during this process.
|
||||
NavigationEvent.BUBBLING_PHASE = 3;
|
||||
|
||||
module.exports = NavigationEvent;
|
||||
|
||||
@@ -30,14 +30,15 @@
|
||||
var EventEmitter = require('EventEmitter');
|
||||
var NavigationEvent = require('NavigationEvent');
|
||||
|
||||
type EventParams = {
|
||||
data: any;
|
||||
didEmitCallback: ?Function;
|
||||
eventType: string;
|
||||
type ExtraInfo = {
|
||||
defaultPrevented: ?boolean,
|
||||
eventPhase: ?number,
|
||||
propagationStopped: ?boolean,
|
||||
target: ?Object,
|
||||
};
|
||||
|
||||
class NavigationEventEmitter extends EventEmitter {
|
||||
_emitQueue: Array<EventParams>;
|
||||
_emitQueue: Array<any>;
|
||||
_emitting: boolean;
|
||||
_target: Object;
|
||||
|
||||
@@ -51,18 +52,38 @@ class NavigationEventEmitter extends EventEmitter {
|
||||
emit(
|
||||
eventType: string,
|
||||
data: any,
|
||||
didEmitCallback: ?Function
|
||||
didEmitCallback: ?Function,
|
||||
extraInfo: ?ExtraInfo
|
||||
): void {
|
||||
if (this._emitting) {
|
||||
// An event cycle that was previously created hasn't finished yet.
|
||||
// Put this event cycle into the queue and will finish them later.
|
||||
this._emitQueue.push({eventType, data, didEmitCallback});
|
||||
var args: any = Array.prototype.slice.call(arguments);
|
||||
this._emitQueue.unshift(args);
|
||||
return;
|
||||
}
|
||||
|
||||
this._emitting = true;
|
||||
|
||||
var event = new NavigationEvent(eventType, this._target, data);
|
||||
var event = NavigationEvent.pool(eventType, this._target, data);
|
||||
|
||||
if (extraInfo) {
|
||||
if (extraInfo.target) {
|
||||
event.target = extraInfo.target;
|
||||
}
|
||||
|
||||
if (extraInfo.eventPhase) {
|
||||
event.eventPhase = extraInfo.eventPhase;
|
||||
}
|
||||
|
||||
if (extraInfo.defaultPrevented) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if (extraInfo.propagationStopped) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
// EventEmitter#emit only takes `eventType` as `String`. Casting `eventType`
|
||||
// to `String` to make @flow happy.
|
||||
@@ -76,8 +97,8 @@ class NavigationEventEmitter extends EventEmitter {
|
||||
this._emitting = false;
|
||||
|
||||
while (this._emitQueue.length) {
|
||||
var arg = this._emitQueue.shift();
|
||||
this.emit(arg.eventType, arg.data, arg.didEmitCallback);
|
||||
var args: any = this._emitQueue.shift();
|
||||
this.emit.apply(this, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+212
-10
@@ -25,16 +25,11 @@
|
||||
'use strict';
|
||||
|
||||
jest
|
||||
.dontMock('EmitterSubscription')
|
||||
.dontMock('EventSubscription')
|
||||
.dontMock('EventEmitter')
|
||||
.dontMock('EventSubscriptionVendor')
|
||||
.dontMock('NavigationContext')
|
||||
.dontMock('NavigationEvent')
|
||||
.dontMock('NavigationEventEmitter')
|
||||
.dontMock('invariant');
|
||||
.autoMockOff()
|
||||
.mock('ErrorUtils');
|
||||
|
||||
var NavigationContext = require('NavigationContext');
|
||||
var NavigationEvent = require('NavigationEvent');
|
||||
|
||||
describe('NavigationContext', () => {
|
||||
it('defaults `currentRoute` to null', () => {
|
||||
@@ -47,6 +42,213 @@ describe('NavigationContext', () => {
|
||||
context.emit('didfocus', {route: {name: 'a'}});
|
||||
expect(context.currentRoute.name).toEqual('a');
|
||||
});
|
||||
|
||||
it('has parent', () => {
|
||||
var parent = new NavigationContext();
|
||||
var child = new NavigationContext();
|
||||
parent.appendChild(child);
|
||||
expect(child.parent).toBe(parent);
|
||||
});
|
||||
|
||||
it('captures event', () => {
|
||||
var parent = new NavigationContext();
|
||||
var child = new NavigationContext();
|
||||
parent.appendChild(child);
|
||||
|
||||
var logs = [];
|
||||
|
||||
var listener = (event) => {
|
||||
var {currentTarget, eventPhase, target, type} = event;
|
||||
logs.push({
|
||||
currentTarget,
|
||||
eventPhase,
|
||||
target,
|
||||
type,
|
||||
});
|
||||
};
|
||||
|
||||
parent.addListener('yo', listener, null, true);
|
||||
child.addListener('yo', listener, null, true);
|
||||
|
||||
child.emit('yo');
|
||||
|
||||
expect(logs).toEqual([
|
||||
{
|
||||
currentTarget: parent,
|
||||
eventPhase: NavigationEvent.CAPTURING_PHASE,
|
||||
target: child,
|
||||
type: 'yo',
|
||||
},
|
||||
{
|
||||
currentTarget: child,
|
||||
eventPhase: NavigationEvent.AT_TARGET,
|
||||
target: child,
|
||||
type: 'yo',
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it('bubbles events', () => {
|
||||
var parent = new NavigationContext();
|
||||
var child = new NavigationContext();
|
||||
parent.appendChild(child);
|
||||
|
||||
var logs = [];
|
||||
|
||||
var listener = (event) => {
|
||||
var {currentTarget, eventPhase, target, type} = event;
|
||||
logs.push({
|
||||
currentTarget,
|
||||
eventPhase,
|
||||
target,
|
||||
type,
|
||||
});
|
||||
};
|
||||
|
||||
parent.addListener('yo', listener);
|
||||
child.addListener('yo', listener);
|
||||
|
||||
child.emit('yo');
|
||||
|
||||
expect(logs).toEqual([
|
||||
{
|
||||
currentTarget: child,
|
||||
eventPhase: NavigationEvent.AT_TARGET,
|
||||
target: child,
|
||||
type: 'yo',
|
||||
},
|
||||
{
|
||||
currentTarget: parent,
|
||||
eventPhase: NavigationEvent.BUBBLING_PHASE,
|
||||
target: child,
|
||||
type: 'yo',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('stops event propagation at capture phase', () => {
|
||||
var parent = new NavigationContext();
|
||||
var child = new NavigationContext();
|
||||
parent.appendChild(child);
|
||||
|
||||
var counter = 0;
|
||||
|
||||
parent.addListener('yo', event => event.stopPropagation(), null, true);
|
||||
child.addListener('yo', event => counter++, null, true);
|
||||
|
||||
child.emit('yo');
|
||||
|
||||
expect(counter).toBe(0);
|
||||
});
|
||||
|
||||
it('stops event propagation at bubbling phase', () => {
|
||||
var parent = new NavigationContext();
|
||||
var child = new NavigationContext();
|
||||
parent.appendChild(child);
|
||||
|
||||
var counter = 0;
|
||||
|
||||
parent.addListener('yo', event => counter++);
|
||||
child.addListener('yo', event => event.stopPropagation());
|
||||
|
||||
child.emit('yo');
|
||||
|
||||
expect(counter).toBe(0);
|
||||
});
|
||||
|
||||
it('prevents event at capture phase', () => {
|
||||
var parent = new NavigationContext();
|
||||
var child = new NavigationContext();
|
||||
parent.appendChild(child);
|
||||
|
||||
var val;
|
||||
parent.addListener('yo', event => event.preventDefault(), null, true);
|
||||
child.addListener('yo', event => val = event.defaultPrevented, null, true);
|
||||
|
||||
child.emit('yo');
|
||||
|
||||
expect(val).toBe(true);
|
||||
});
|
||||
|
||||
it('prevents event at bubble phase', () => {
|
||||
var parent = new NavigationContext();
|
||||
var child = new NavigationContext();
|
||||
parent.appendChild(child);
|
||||
|
||||
var val;
|
||||
parent.addListener('yo', event => val = event.defaultPrevented);
|
||||
child.addListener('yo', event => event.preventDefault());
|
||||
|
||||
child.emit('yo');
|
||||
|
||||
expect(val).toBe(true);
|
||||
});
|
||||
|
||||
it('emits nested events in order at capture phase', () => {
|
||||
var parent = new NavigationContext();
|
||||
var child = new NavigationContext();
|
||||
parent.appendChild(child);
|
||||
|
||||
var logs = [];
|
||||
|
||||
var listener = (event) => {
|
||||
var {currentTarget, type} = event;
|
||||
logs.push({
|
||||
currentTarget,
|
||||
type,
|
||||
});
|
||||
};
|
||||
|
||||
child.addListener('yo', event => {
|
||||
// event `didyo` should be fired after the full propagation cycle of the
|
||||
// `yo` event.
|
||||
child.emit('didyo');
|
||||
});
|
||||
|
||||
parent.addListener('yo', listener, null, true);
|
||||
parent.addListener('didyo', listener, null, true);
|
||||
child.addListener('yo', listener, null, true);
|
||||
|
||||
child.emit('yo');
|
||||
|
||||
expect(logs).toEqual([
|
||||
{type: 'yo', currentTarget: parent},
|
||||
{type: 'yo', currentTarget: child},
|
||||
{type: 'didyo', currentTarget: parent},
|
||||
]);
|
||||
});
|
||||
|
||||
it('emits nested events in order at bubbling phase', () => {
|
||||
var parent = new NavigationContext();
|
||||
var child = new NavigationContext();
|
||||
parent.appendChild(child);
|
||||
|
||||
var logs = [];
|
||||
|
||||
var listener = (event) => {
|
||||
var {currentTarget, type} = event;
|
||||
logs.push({
|
||||
currentTarget,
|
||||
type,
|
||||
});
|
||||
};
|
||||
|
||||
child.addListener('yo', event => {
|
||||
// event `didyo` should be fired after the full propagation cycle of the
|
||||
// `yo` event.
|
||||
child.emit('didyo');
|
||||
});
|
||||
|
||||
parent.addListener('yo', listener);
|
||||
child.addListener('yo', listener);
|
||||
parent.addListener('didyo', listener);
|
||||
|
||||
child.emit('yo');
|
||||
|
||||
expect(logs).toEqual([
|
||||
{type: 'yo', currentTarget: child},
|
||||
{type: 'yo', currentTarget: parent},
|
||||
{type: 'didyo', currentTarget: parent},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -591,8 +591,11 @@ var Navigator = React.createClass({
|
||||
|
||||
_handleMoveShouldSetPanResponder: function(e, gestureState) {
|
||||
var sceneConfig = this.state.sceneConfigStack[this.state.presentedIndex];
|
||||
if (!sceneConfig) {
|
||||
return false;
|
||||
}
|
||||
this._expectingGestureGrant = this._matchGestureAction(this._eligibleGestures, sceneConfig.gestures, gestureState);
|
||||
return !! this._expectingGestureGrant;
|
||||
return !!this._expectingGestureGrant;
|
||||
},
|
||||
|
||||
_doesGestureOverswipe: function(gestureName) {
|
||||
|
||||
@@ -284,8 +284,11 @@ RCT_EXPORT_METHOD(getCurrentPosition:(RCTLocationOptions)options
|
||||
[_locationManager stopUpdatingLocation];
|
||||
}
|
||||
|
||||
// Reset location accuracy
|
||||
_locationManager.desiredAccuracy = RCT_DEFAULT_LOCATION_ACCURACY;
|
||||
// Reset location accuracy if desiredAccuracy is changed.
|
||||
// Otherwise update accuracy will force triggering didUpdateLocations, watchPosition would keeping receiving location updates, even there's no location changes.
|
||||
if (ABS(_locationManager.desiredAccuracy - RCT_DEFAULT_LOCATION_ACCURACY) > 0.000001) {
|
||||
_locationManager.desiredAccuracy = RCT_DEFAULT_LOCATION_ACCURACY;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
|
||||
@@ -318,8 +321,11 @@ RCT_EXPORT_METHOD(getCurrentPosition:(RCTLocationOptions)options
|
||||
}
|
||||
[_pendingRequests removeAllObjects];
|
||||
|
||||
// Reset location accuracy
|
||||
_locationManager.desiredAccuracy = RCT_DEFAULT_LOCATION_ACCURACY;
|
||||
// Reset location accuracy if desiredAccuracy is changed.
|
||||
// Otherwise update accuracy will force triggering didUpdateLocations, watchPosition would keeping receiving location updates, even there's no location changes.
|
||||
if (ABS(_locationManager.desiredAccuracy - RCT_DEFAULT_LOCATION_ACCURACY) > 0.000001) {
|
||||
_locationManager.desiredAccuracy = RCT_DEFAULT_LOCATION_ACCURACY;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)checkLocationConfig
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
module.exports = {
|
||||
__packager_asset: true,
|
||||
isStatic: true,
|
||||
path: '/full/path/to/something.png',
|
||||
uri: 'icon',
|
||||
width: 100,
|
||||
|
||||
@@ -126,12 +126,6 @@ var Image = React.createClass({
|
||||
}
|
||||
|
||||
if (source && source.uri) {
|
||||
var isNetwork = source.uri.match(/^https?:/);
|
||||
invariant(
|
||||
!(isNetwork && source.isStatic),
|
||||
'Static image URIs cannot start with "http": "' + source.uri + '"'
|
||||
);
|
||||
|
||||
var {width, height} = source;
|
||||
var style = flattenStyle([{width, height}, styles.base, this.props.style]);
|
||||
|
||||
|
||||
@@ -13,5 +13,4 @@
|
||||
|
||||
type ImageSource = {
|
||||
uri: string;
|
||||
isStatic: boolean;
|
||||
};
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
|
||||
#import "RCTImageLoader.h"
|
||||
|
||||
@interface RCTGIFImageDecoder : NSObject <RCTImageDecoder>
|
||||
@interface RCTGIFImageDecoder : NSObject <RCTImageDataDecoder>
|
||||
|
||||
@end
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879371AAD32A300F088A5 /* RCTImageLoader.m */; };
|
||||
35123E6B1B59C99D00EBAD80 /* RCTImageStoreManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 35123E6A1B59C99D00EBAD80 /* RCTImageStoreManager.m */; };
|
||||
354631681B69857700AA0B86 /* RCTImageEditingManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 354631671B69857700AA0B86 /* RCTImageEditingManager.m */; };
|
||||
58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
@@ -54,8 +53,6 @@
|
||||
354631661B69857700AA0B86 /* RCTImageEditingManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageEditingManager.h; sourceTree = "<group>"; };
|
||||
354631671B69857700AA0B86 /* RCTImageEditingManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageEditingManager.m; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageDownloader.m; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -76,8 +73,6 @@
|
||||
13EF7F7E1BC825B1003F47DD /* RCTXCAssetImageLoader.m */,
|
||||
1304D5B01AA8C50D0002E2BE /* RCTGIFImageDecoder.h */,
|
||||
1304D5B11AA8C50D0002E2BE /* RCTGIFImageDecoder.m */,
|
||||
58B511891A9E6BD600147676 /* RCTImageDownloader.h */,
|
||||
58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */,
|
||||
354631661B69857700AA0B86 /* RCTImageEditingManager.h */,
|
||||
354631671B69857700AA0B86 /* RCTImageEditingManager.m */,
|
||||
143879361AAD32A300F088A5 /* RCTImageLoader.h */,
|
||||
@@ -166,7 +161,6 @@
|
||||
files = (
|
||||
13EF7F0C1BC42D4E003F47DD /* RCTVirtualImageManager.m in Sources */,
|
||||
35123E6B1B59C99D00EBAD80 /* RCTImageStoreManager.m in Sources */,
|
||||
58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */,
|
||||
1304D5AC1AA8C4A30002E2BE /* RCTImageViewManager.m in Sources */,
|
||||
1304D5B21AA8C50D0002E2BE /* RCTGIFImageDecoder.m in Sources */,
|
||||
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */,
|
||||
|
||||
@@ -1,196 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import "RCTImageDownloader.h"
|
||||
|
||||
#import "RCTImageLoader.h"
|
||||
#import "RCTImageUtils.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTNetworking.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
@implementation RCTImageDownloader
|
||||
{
|
||||
NSURLCache *_cache;
|
||||
dispatch_queue_t _processingQueue;
|
||||
}
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_cache = [[NSURLCache alloc] initWithMemoryCapacity:5 * 1024 * 1024 diskCapacity:200 * 1024 * 1024 diskPath:@"React/RCTImageDownloader"];
|
||||
_processingQueue = dispatch_queue_create("com.facebook.React.DownloadProcessingQueue", DISPATCH_QUEUE_SERIAL);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)canLoadImageURL:(NSURL *)requestURL
|
||||
{
|
||||
static NSSet *schemes = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
schemes = [[NSSet alloc] initWithObjects:@"http", @"https", @"file", nil];
|
||||
});
|
||||
return [schemes containsObject:requestURL.scheme.lowercaseString];
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a block of raw data and returns it. Note that the callback block
|
||||
* will not be executed on the same thread you called the method from, nor on
|
||||
* the main thread. Returns a token that can be used to cancel the download.
|
||||
*/
|
||||
- (RCTImageLoaderCancellationBlock)downloadDataForURL:(NSURL *)url
|
||||
progressHandler:(RCTImageLoaderProgressBlock)progressBlock
|
||||
completionHandler:(void (^)(NSError *error, NSData *data))completionBlock
|
||||
{
|
||||
if (![_bridge respondsToSelector:NSSelectorFromString(@"networking")]) {
|
||||
RCTLogError(@"You need to import the RCTNetworking library in order to download remote images.");
|
||||
return ^{};
|
||||
}
|
||||
|
||||
__weak RCTImageDownloader *weakSelf = self;
|
||||
RCTURLRequestCompletionBlock runBlocks = ^(NSURLResponse *response, NSData *data, NSError *error) {
|
||||
|
||||
if (!error && [response isKindOfClass:[NSHTTPURLResponse class]]) {
|
||||
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
|
||||
if (httpResponse.statusCode != 200) {
|
||||
data = nil;
|
||||
error = [[NSError alloc] initWithDomain:NSURLErrorDomain
|
||||
code:httpResponse.statusCode
|
||||
userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
dispatch_async(_processingQueue, ^{
|
||||
completionBlock(error, data);
|
||||
});
|
||||
};
|
||||
|
||||
NSURLRequest *request = [NSURLRequest requestWithURL:url];
|
||||
{
|
||||
NSCachedURLResponse *cachedResponse = [_cache cachedResponseForRequest:request];
|
||||
if (cachedResponse) {
|
||||
runBlocks(cachedResponse.response, cachedResponse.data, nil);
|
||||
return ^{};
|
||||
}
|
||||
}
|
||||
|
||||
RCTDownloadTask *task = [_bridge.networking downloadTaskWithRequest:request completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) {
|
||||
if (response && !error) {
|
||||
RCTImageDownloader *strongSelf = weakSelf;
|
||||
NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:data userInfo:nil storagePolicy:NSURLCacheStorageAllowed];
|
||||
[strongSelf->_cache storeCachedResponse:cachedResponse forRequest:request];
|
||||
}
|
||||
runBlocks(response, data, error);
|
||||
}];
|
||||
if (progressBlock) {
|
||||
task.downloadProgressBlock = progressBlock;
|
||||
}
|
||||
return ^{ [task cancel]; };
|
||||
}
|
||||
|
||||
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
|
||||
size:(CGSize)size
|
||||
scale:(CGFloat)scale
|
||||
resizeMode:(UIViewContentMode)resizeMode
|
||||
progressHandler:(RCTImageLoaderProgressBlock)progressHandler
|
||||
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
|
||||
{
|
||||
if ([imageURL.scheme.lowercaseString hasPrefix:@"http"]) {
|
||||
__block RCTImageLoaderCancellationBlock decodeCancel = nil;
|
||||
|
||||
__weak RCTImageDownloader *weakSelf = self;
|
||||
RCTImageLoaderCancellationBlock downloadCancel = [self downloadDataForURL:imageURL progressHandler:progressHandler completionHandler:^(NSError *error, NSData *imageData) {
|
||||
if (error) {
|
||||
completionHandler(error, nil);
|
||||
} else {
|
||||
decodeCancel = [weakSelf.bridge.imageLoader decodeImageData:imageData size:size scale:scale resizeMode:resizeMode completionBlock:completionHandler];
|
||||
}
|
||||
}];
|
||||
|
||||
return ^{
|
||||
downloadCancel();
|
||||
|
||||
if (decodeCancel) {
|
||||
decodeCancel();
|
||||
}
|
||||
};
|
||||
} else if ([imageURL.scheme caseInsensitiveCompare:@"data"] == NSOrderedSame) {
|
||||
__block BOOL cancelled = NO;
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Normally -dataWithContentsOfURL: would be bad but this is a data URL.
|
||||
NSData *data = [NSData dataWithContentsOfURL:imageURL];
|
||||
|
||||
UIImage *image = [UIImage imageWithData:data scale:scale];
|
||||
if (image) {
|
||||
if (progressHandler) {
|
||||
progressHandler(1, 1);
|
||||
}
|
||||
if (completionHandler) {
|
||||
completionHandler(nil, image);
|
||||
}
|
||||
} else {
|
||||
if (completionHandler) {
|
||||
NSString *message = [NSString stringWithFormat:@"Invalid image data for URL: %@", imageURL];
|
||||
completionHandler(RCTErrorWithMessage(message), nil);
|
||||
}
|
||||
}
|
||||
});
|
||||
return ^{
|
||||
cancelled = YES;
|
||||
};
|
||||
} else if ([imageURL.scheme isEqualToString:@"file"]) {
|
||||
__block BOOL cancelled = NO;
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
UIImage *image = [UIImage imageWithContentsOfFile:imageURL.resourceSpecifier];
|
||||
if (image) {
|
||||
if (progressHandler) {
|
||||
progressHandler(1, 1);
|
||||
}
|
||||
if (completionHandler) {
|
||||
completionHandler(nil, image);
|
||||
}
|
||||
} else {
|
||||
if (completionHandler) {
|
||||
NSString *message = [NSString stringWithFormat:@"Could not find image at path: %@", imageURL.absoluteString];
|
||||
completionHandler(RCTErrorWithMessage(message), nil);
|
||||
}
|
||||
}
|
||||
});
|
||||
return ^{
|
||||
cancelled = YES;
|
||||
};
|
||||
} else {
|
||||
RCTLogError(@"Unexpected image schema %@", imageURL.scheme);
|
||||
return ^{};
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTBridge (RCTImageDownloader)
|
||||
|
||||
- (RCTImageDownloader *)imageDownloader
|
||||
{
|
||||
return self.modules[RCTBridgeModuleNameForClass([RCTImageDownloader class])];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -28,7 +28,7 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
|
||||
|
||||
/**
|
||||
* Loads the specified image at the highest available resolution.
|
||||
* Can be called from any thread, will always call callback on main thread.
|
||||
* Can be called from any thread, will call back on an unspecified thread.
|
||||
*/
|
||||
- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
|
||||
callback:(RCTImageLoaderCompletionBlock)callback;
|
||||
@@ -46,7 +46,8 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
|
||||
|
||||
/**
|
||||
* Finds an appropriate image decoder and passes the target size, scale and
|
||||
* resizeMode for optimal image decoding.
|
||||
* resizeMode for optimal image decoding. Can be called from any thread,
|
||||
* will call callback on an unspecified thread.
|
||||
*/
|
||||
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData
|
||||
size:(CGSize)size
|
||||
@@ -66,7 +67,7 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
|
||||
@end
|
||||
|
||||
/**
|
||||
* Provides the interface needed to register an image data loader. Image data
|
||||
* Provides the interface needed to register an image loader. Image data
|
||||
* loaders are also bridge modules, so should be registered using
|
||||
* RCT_EXPORT_MODULE().
|
||||
*/
|
||||
@@ -85,18 +86,23 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
|
||||
* has finished. The method should also return a cancellation block, if
|
||||
* applicable.
|
||||
*/
|
||||
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler;
|
||||
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
|
||||
size:(CGSize)size
|
||||
scale:(CGFloat)scale
|
||||
resizeMode:(UIViewContentMode)resizeMode
|
||||
progressHandler:(RCTImageLoaderProgressBlock)progressHandler
|
||||
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler;
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* If more than one RCTImageURLLoader responds YES to `-canLoadImageURL:`
|
||||
* then `imageLoaderPriority` is used to determine which one to use. The handler
|
||||
* then `loaderPriority` is used to determine which one to use. The loader
|
||||
* with the highest priority will be selected. Default priority is zero. If
|
||||
* two or more valid handlers have the same priority, the selection order is
|
||||
* two or more valid loaders have the same priority, the selection order is
|
||||
* undefined.
|
||||
*/
|
||||
- (float)imageLoaderPriority;
|
||||
- (float)loaderPriority;
|
||||
|
||||
@end
|
||||
|
||||
@@ -104,7 +110,7 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
|
||||
* Provides the interface needed to register an image decoder. Image decoders
|
||||
* are also bridge modules, so should be registered using RCT_EXPORT_MODULE().
|
||||
*/
|
||||
@protocol RCTImageDecoder <RCTBridgeModule>
|
||||
@protocol RCTImageDataDecoder <RCTBridgeModule>
|
||||
|
||||
/**
|
||||
* Indicates whether this handler is capable of decoding the specified data.
|
||||
@@ -118,17 +124,21 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
|
||||
* completionHandler when the decoding operation has finished. The method
|
||||
* should also return a cancellation block, if applicable.
|
||||
*/
|
||||
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode completionHandler:(RCTImageLoaderCompletionBlock)completionHandler;
|
||||
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData
|
||||
size:(CGSize)size
|
||||
scale:(CGFloat)scale
|
||||
resizeMode:(UIViewContentMode)resizeMode
|
||||
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler;
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* If more than one RCTImageDecoder responds YES to `-canDecodeImageData:`
|
||||
* then `imageDecoderPriority` is used to determine which one to use. The
|
||||
* handler with the highest priority will be selected. Default priority is zero.
|
||||
* If two or more valid handlers have the same priority, the selection order is
|
||||
* If more than one RCTImageDataDecoder responds YES to `-canDecodeImageData:`
|
||||
* then `decoderPriority` is used to determine which one to use. The decoder
|
||||
* with the highest priority will be selected. Default priority is zero.
|
||||
* If two or more valid decoders have the same priority, the selection order is
|
||||
* undefined.
|
||||
*/
|
||||
- (float)imageDecoderPriority;
|
||||
- (float)decoderPriority;
|
||||
|
||||
@end
|
||||
|
||||
+258
-101
@@ -9,26 +9,16 @@
|
||||
|
||||
#import "RCTImageLoader.h"
|
||||
|
||||
#import <libkern/OSAtomic.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTDefines.h"
|
||||
#import "RCTImageDownloader.h"
|
||||
#import "RCTImageUtils.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTNetworking.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
static void RCTDispatchCallbackOnMainQueue(void (^callback)(NSError *, id), NSError *error, UIImage *image)
|
||||
{
|
||||
if ([NSThread isMainThread]) {
|
||||
callback(error, image);
|
||||
} else {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
callback(error, image);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@implementation UIImage (React)
|
||||
|
||||
- (CAKeyframeAnimation *)reactKeyframeAnimation
|
||||
@@ -44,11 +34,131 @@ static void RCTDispatchCallbackOnMainQueue(void (^callback)(NSError *, id), NSEr
|
||||
@end
|
||||
|
||||
@implementation RCTImageLoader
|
||||
{
|
||||
NSArray *_loaders;
|
||||
NSArray *_decoders;
|
||||
NSURLCache *_cache;
|
||||
}
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (void)setBridge:(RCTBridge *)bridge
|
||||
{
|
||||
// Get image loaders and decoders
|
||||
NSMutableArray *loaders = [NSMutableArray array];
|
||||
NSMutableArray *decoders = [NSMutableArray array];
|
||||
for (id<RCTBridgeModule> module in bridge.modules.allValues) {
|
||||
if ([module conformsToProtocol:@protocol(RCTImageURLLoader)]) {
|
||||
[loaders addObject:module];
|
||||
}
|
||||
if ([module conformsToProtocol:@protocol(RCTImageDataDecoder)]) {
|
||||
[decoders addObject:module];
|
||||
}
|
||||
}
|
||||
|
||||
// Sort loaders in reverse priority order (highest priority first)
|
||||
[loaders sortUsingComparator:^NSComparisonResult(id<RCTImageURLLoader> a, id<RCTImageURLLoader> b) {
|
||||
float priorityA = [a respondsToSelector:@selector(loaderPriority)] ? [a loaderPriority] : 0;
|
||||
float priorityB = [b respondsToSelector:@selector(loaderPriority)] ? [b loaderPriority] : 0;
|
||||
if (priorityA > priorityB) {
|
||||
return NSOrderedAscending;
|
||||
} else if (priorityA < priorityB) {
|
||||
return NSOrderedDescending;
|
||||
} else {
|
||||
return NSOrderedSame;
|
||||
}
|
||||
}];
|
||||
|
||||
// Sort decoders in reverse priority order (highest priority first)
|
||||
[decoders sortUsingComparator:^NSComparisonResult(id<RCTImageDataDecoder> a, id<RCTImageDataDecoder> b) {
|
||||
float priorityA = [a respondsToSelector:@selector(decoderPriority)] ? [a decoderPriority] : 0;
|
||||
float priorityB = [b respondsToSelector:@selector(decoderPriority)] ? [b decoderPriority] : 0;
|
||||
if (priorityA > priorityB) {
|
||||
return NSOrderedAscending;
|
||||
} else if (priorityA < priorityB) {
|
||||
return NSOrderedDescending;
|
||||
} else {
|
||||
return NSOrderedSame;
|
||||
}
|
||||
}];
|
||||
|
||||
_bridge = bridge;
|
||||
_loaders = loaders;
|
||||
_decoders = decoders;
|
||||
_cache = [[NSURLCache alloc] initWithMemoryCapacity:5 * 1024 * 1024 // 5MB
|
||||
diskCapacity:200 * 1024 * 1024 // 200MB
|
||||
diskPath:@"React/RCTImageDownloader"];
|
||||
}
|
||||
|
||||
- (id<RCTImageURLLoader>)imageURLLoaderForURL:(NSURL *)URL
|
||||
{
|
||||
if (RCT_DEBUG) {
|
||||
// Check for handler conflicts
|
||||
float previousPriority = 0;
|
||||
id<RCTImageURLLoader> previousLoader = nil;
|
||||
for (id<RCTImageURLLoader> loader in _loaders) {
|
||||
if ([loader canLoadImageURL:URL]) {
|
||||
float priority = [loader respondsToSelector:@selector(loaderPriority)] ? [loader loaderPriority] : 0;
|
||||
if (previousLoader) {
|
||||
if (priority == previousPriority) {
|
||||
RCTLogError(@"The RCTImageURLLoaders %@ and %@ both reported that"
|
||||
" they can load the URL %@, and have equal priority"
|
||||
" (%g). This could result in non-deterministic behavior.",
|
||||
loader, previousLoader, URL, priority);
|
||||
}
|
||||
} else {
|
||||
previousLoader = loader;
|
||||
previousPriority = priority;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Normal code path
|
||||
for (id<RCTImageURLLoader> loader in _loaders) {
|
||||
if ([loader canLoadImageURL:URL]) {
|
||||
return loader;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (id<RCTImageDataDecoder>)imageDataDecoderForData:(NSData *)data
|
||||
{
|
||||
if (RCT_DEBUG) {
|
||||
// Check for handler conflicts
|
||||
float previousPriority = 0;
|
||||
id<RCTImageDataDecoder> previousDecoder = nil;
|
||||
for (id<RCTImageDataDecoder> decoder in _decoders) {
|
||||
if ([decoder canDecodeImageData:data]) {
|
||||
float priority = [decoder respondsToSelector:@selector(decoderPriority)] ? [decoder decoderPriority] : 0;
|
||||
if (previousDecoder) {
|
||||
if (priority == previousPriority) {
|
||||
RCTLogError(@"The RCTImageDataDecoders %@ and %@ both reported that"
|
||||
" they can decode the data <NSData %p; %tu bytes>, and"
|
||||
" have equal priority (%g). This could result in"
|
||||
" non-deterministic behavior.",
|
||||
decoder, previousDecoder, data, data.length, priority);
|
||||
}
|
||||
} else {
|
||||
previousDecoder = decoder;
|
||||
previousPriority = priority;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Normal code path
|
||||
for (id<RCTImageDataDecoder> decoder in _decoders) {
|
||||
if ([decoder canDecodeImageData:data]) {
|
||||
return decoder;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
|
||||
callback:(RCTImageLoaderCompletionBlock)callback
|
||||
{
|
||||
@@ -60,40 +170,11 @@ RCT_EXPORT_MODULE()
|
||||
completionBlock:callback];
|
||||
}
|
||||
|
||||
- (id<RCTImageURLLoader>)imageURLLoaderForRequest:(NSURL *)requestURL
|
||||
{
|
||||
NSMutableArray *handlers = [NSMutableArray array];
|
||||
for (id<RCTBridgeModule> module in _bridge.modules.allValues) {
|
||||
if ([module conformsToProtocol:@protocol(RCTImageURLLoader)]) {
|
||||
if ([(id<RCTImageURLLoader>)module canLoadImageURL:requestURL]) {
|
||||
[handlers addObject:module];
|
||||
}
|
||||
}
|
||||
}
|
||||
[handlers sortUsingComparator:^NSComparisonResult(id<RCTImageURLLoader> a, id<RCTImageURLLoader> b) {
|
||||
float priorityA = [a respondsToSelector:@selector(imageLoaderPriority)] ? [a imageLoaderPriority] : 0;
|
||||
float priorityB = [b respondsToSelector:@selector(imageLoaderPriority)] ? [b imageLoaderPriority] : 0;
|
||||
if (priorityA < priorityB) {
|
||||
return NSOrderedAscending;
|
||||
} else if (priorityA > priorityB) {
|
||||
return NSOrderedDescending;
|
||||
} else {
|
||||
RCTLogError(@"The RCTImageLoader %@ and %@ both reported that they can"
|
||||
" handle the load request %@, and have equal priority (%g)."
|
||||
" This could result in non-deterministic behavior.",
|
||||
a, b, requestURL, priorityA);
|
||||
|
||||
return NSOrderedSame;
|
||||
}
|
||||
}];
|
||||
return [handlers lastObject];
|
||||
}
|
||||
|
||||
- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
|
||||
size:(CGSize)size
|
||||
scale:(CGFloat)scale
|
||||
resizeMode:(UIViewContentMode)resizeMode
|
||||
progressBlock:(RCTImageLoaderProgressBlock)progressBlock
|
||||
progressBlock:(RCTImageLoaderProgressBlock)progressHandler
|
||||
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
|
||||
{
|
||||
if (imageTag.length == 0) {
|
||||
@@ -101,83 +182,157 @@ RCT_EXPORT_MODULE()
|
||||
return ^{};
|
||||
}
|
||||
|
||||
NSURL *requestURL = [RCTConvert NSURL:imageTag];
|
||||
id<RCTImageURLLoader> loadHandler = [self imageURLLoaderForRequest:requestURL];
|
||||
if (!loadHandler) {
|
||||
RCTLogError(@"No suitable image URL loader found for %@", imageTag);
|
||||
__block volatile uint32_t cancelled = 0;
|
||||
RCTImageLoaderCompletionBlock completionHandler = ^(NSError *error, UIImage *image) {
|
||||
if ([NSThread isMainThread]) {
|
||||
|
||||
// Most loaders do not return on the main thread, so caller is probably not
|
||||
// expecting it, and may do expensive post-processing in the callback
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
if (!cancelled) {
|
||||
completionBlock(error, image);
|
||||
}
|
||||
});
|
||||
} else if (!cancelled) {
|
||||
completionBlock(error, image);
|
||||
}
|
||||
};
|
||||
|
||||
// Find suitable image URL loader
|
||||
NSURLRequest *request = [RCTConvert NSURLRequest:imageTag];
|
||||
id<RCTImageURLLoader> loadHandler = [self imageURLLoaderForURL:request.URL];
|
||||
if (loadHandler) {
|
||||
return [loadHandler loadImageForURL:request.URL
|
||||
size:size
|
||||
scale:scale
|
||||
resizeMode:resizeMode
|
||||
progressHandler:progressHandler
|
||||
completionHandler:completionHandler] ?: ^{};
|
||||
}
|
||||
|
||||
// Check if networking module is available
|
||||
if (![_bridge respondsToSelector:@selector(networking)]) {
|
||||
RCTLogError(@"No suitable image URL loader found for %@. You may need to "
|
||||
" import the RCTNetworking library in order to load images.",
|
||||
imageTag);
|
||||
return ^{};
|
||||
}
|
||||
|
||||
return [loadHandler loadImageForURL:requestURL size:size scale:scale resizeMode:resizeMode progressHandler:^(int64_t progress, int64_t total) {
|
||||
if (!progressBlock) {
|
||||
return;
|
||||
}
|
||||
// Use networking module to load image
|
||||
if ([_bridge.networking canHandleRequest:request]) {
|
||||
|
||||
if ([NSThread isMainThread]) {
|
||||
progressBlock(progress, total);
|
||||
} else {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
progressBlock(progress, total);
|
||||
});
|
||||
}
|
||||
} completionHandler:^(NSError *error, UIImage *image) {
|
||||
RCTDispatchCallbackOnMainQueue(completionBlock, error, image);
|
||||
}];
|
||||
}
|
||||
__weak RCTImageLoader *weakSelf = self;
|
||||
__block RCTImageLoaderCancellationBlock decodeCancel = nil;
|
||||
RCTURLRequestCompletionBlock processResponse =
|
||||
^(NSURLResponse *response, NSData *data, __unused NSError *error) {
|
||||
|
||||
- (id<RCTImageDecoder>)imageDecoderForRequest:(NSData *)imageData
|
||||
{
|
||||
NSMutableArray *handlers = [NSMutableArray array];
|
||||
for (id<RCTBridgeModule> module in _bridge.modules.allValues) {
|
||||
if ([module conformsToProtocol:@protocol(RCTImageDecoder)]) {
|
||||
if ([(id<RCTImageDecoder>)module canDecodeImageData:imageData]) {
|
||||
[handlers addObject:module];
|
||||
// Check for http errors
|
||||
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
|
||||
NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode;
|
||||
if (statusCode != 200) {
|
||||
completionHandler([[NSError alloc] initWithDomain:NSURLErrorDomain
|
||||
code:statusCode
|
||||
userInfo:nil], nil);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
[handlers sortUsingComparator:^NSComparisonResult(id<RCTImageDecoder> a, id<RCTImageDecoder> b) {
|
||||
float priorityA = [a respondsToSelector:@selector(imageDecoderPriority)] ? [a imageDecoderPriority] : 0;
|
||||
float priorityB = [b respondsToSelector:@selector(imageDecoderPriority)] ? [b imageDecoderPriority] : 0;
|
||||
if (priorityA < priorityB) {
|
||||
return NSOrderedAscending;
|
||||
} else if (priorityA > priorityB) {
|
||||
return NSOrderedDescending;
|
||||
} else {
|
||||
RCTLogError(@"The RCTImageDecoder %@ and %@ both reported that they can"
|
||||
" handle the decode request <NSData %p; %tu bytes>, and have"
|
||||
" equal priority (%g). This could result in"
|
||||
" non-deterministic behavior.",
|
||||
a, b, imageData, imageData.length, priorityA);
|
||||
|
||||
return NSOrderedSame;
|
||||
// Decode image
|
||||
decodeCancel = [weakSelf decodeImageData:data
|
||||
size:size
|
||||
scale:scale
|
||||
resizeMode:resizeMode
|
||||
completionBlock:completionHandler];
|
||||
};
|
||||
|
||||
// Check for cached response before reloading
|
||||
// TODO: move URL cache out of RCTImageLoader into its own module
|
||||
NSCachedURLResponse *cachedResponse = [_cache cachedResponseForRequest:request];
|
||||
if (cachedResponse) {
|
||||
processResponse(cachedResponse.response, cachedResponse.data, nil);
|
||||
return ^{};
|
||||
}
|
||||
}];
|
||||
return [handlers lastObject];
|
||||
|
||||
// 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"]];
|
||||
request = mutableRequest;
|
||||
}
|
||||
|
||||
// Download image
|
||||
RCTNetworkTask *task = [_bridge.networking networkTaskWithRequest:request completionBlock:
|
||||
^(NSURLResponse *response, NSData *data, NSError *error) {
|
||||
if (error) {
|
||||
completionHandler(error, nil);
|
||||
return;
|
||||
}
|
||||
|
||||
// Cache the response
|
||||
// TODO: move URL cache out of RCTImageLoader into its own module
|
||||
RCTImageLoader *strongSelf = weakSelf;
|
||||
BOOL isHTTPRequest = [request.URL.scheme hasPrefix:@"http"];
|
||||
[strongSelf->_cache storeCachedResponse:
|
||||
[[NSCachedURLResponse alloc] initWithResponse:response
|
||||
data:data
|
||||
userInfo:nil
|
||||
storagePolicy:isHTTPRequest ? NSURLCacheStorageAllowed: NSURLCacheStorageAllowedInMemoryOnly]
|
||||
forRequest:request];
|
||||
|
||||
// Process image data
|
||||
processResponse(response, data, nil);
|
||||
|
||||
}];
|
||||
task.downloadProgressBlock = progressHandler;
|
||||
[task start];
|
||||
|
||||
return ^{
|
||||
[task cancel];
|
||||
if (decodeCancel) {
|
||||
decodeCancel();
|
||||
}
|
||||
OSAtomicOr32Barrier(1, &cancelled);
|
||||
};
|
||||
}
|
||||
|
||||
RCTLogError(@"No suitable image URL loader found for %@", imageTag);
|
||||
return ^{};
|
||||
}
|
||||
|
||||
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data
|
||||
size:(CGSize)size
|
||||
scale:(CGFloat)scale
|
||||
resizeMode:(UIViewContentMode)resizeMode
|
||||
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
|
||||
completionBlock:(RCTImageLoaderCompletionBlock)completionHandler
|
||||
{
|
||||
id<RCTImageDecoder> imageDecoder = [self imageDecoderForRequest:data];
|
||||
id<RCTImageDataDecoder> imageDecoder = [self imageDataDecoderForData:data];
|
||||
if (imageDecoder) {
|
||||
return [imageDecoder decodeImageData:data size:size scale:scale resizeMode:resizeMode completionHandler:^(NSError *error, UIImage *image) {
|
||||
RCTDispatchCallbackOnMainQueue(completionBlock, error, image);
|
||||
}];
|
||||
|
||||
return [imageDecoder decodeImageData:data
|
||||
size:size
|
||||
scale:scale
|
||||
resizeMode:resizeMode
|
||||
completionHandler:completionHandler];
|
||||
} else {
|
||||
|
||||
__block volatile uint32_t cancelled = 0;
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
UIImage *image = [UIImage imageWithData:data scale:scale];
|
||||
if (image) {
|
||||
RCTDispatchCallbackOnMainQueue(completionBlock, nil, image);
|
||||
completionHandler(nil, image);
|
||||
} else {
|
||||
NSString *errorMessage = [NSString stringWithFormat:@"Error decoding image data <NSData %p; %tu bytes>", data, data.length];
|
||||
NSError *finalError = RCTErrorWithMessage(errorMessage);
|
||||
RCTDispatchCallbackOnMainQueue(completionBlock, finalError, nil);
|
||||
completionHandler(finalError, nil);
|
||||
}
|
||||
});
|
||||
return ^{};
|
||||
|
||||
return ^{
|
||||
OSAtomicOr32Barrier(1, &cancelled);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,18 +340,20 @@ RCT_EXPORT_MODULE()
|
||||
|
||||
- (BOOL)canHandleRequest:(NSURLRequest *)request
|
||||
{
|
||||
id<RCTImageURLLoader> handler = [self imageURLLoaderForRequest:request.URL];
|
||||
|
||||
// RCTImageDownloader is an image plugin that uses the networking stack.
|
||||
// We don't want to route any network calls through the image downloader
|
||||
// as that would cause cyclical dependencies.
|
||||
return handler && ![handler isKindOfClass:[RCTImageDownloader class]];
|
||||
NSURL *requestURL = request.URL;
|
||||
for (id<RCTBridgeModule> module in _bridge.modules.allValues) {
|
||||
if ([module conformsToProtocol:@protocol(RCTImageURLLoader)] &&
|
||||
[(id<RCTImageURLLoader>)module canLoadImageURL:requestURL]) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (id)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate>)delegate
|
||||
{
|
||||
__block RCTImageLoaderCancellationBlock requestToken;
|
||||
requestToken = [self.bridge.imageLoader loadImageWithTag:request.URL.absoluteString callback:^(NSError *error, UIImage *image) {
|
||||
requestToken = [self loadImageWithTag:request.URL.absoluteString callback:^(NSError *error, UIImage *image) {
|
||||
if (error) {
|
||||
[delegate URLRequest:requestToken didCompleteWithError:error];
|
||||
return;
|
||||
|
||||
@@ -183,24 +183,26 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||
resizeMode:self.contentMode
|
||||
progressBlock:progressHandler
|
||||
completionBlock:^(NSError *error, UIImage *image) {
|
||||
if (image.reactKeyframeAnimation) {
|
||||
[self.layer addAnimation:image.reactKeyframeAnimation forKey:@"contents"];
|
||||
} else {
|
||||
[self.layer removeAnimationForKey:@"contents"];
|
||||
self.image = image;
|
||||
}
|
||||
if (error) {
|
||||
if (_onError) {
|
||||
_onError(@{ @"error": error.localizedDescription });
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (image.reactKeyframeAnimation) {
|
||||
[self.layer addAnimation:image.reactKeyframeAnimation forKey:@"contents"];
|
||||
} else {
|
||||
[self.layer removeAnimationForKey:@"contents"];
|
||||
self.image = image;
|
||||
}
|
||||
} else {
|
||||
if (_onLoad) {
|
||||
_onLoad(nil);
|
||||
if (error) {
|
||||
if (_onError) {
|
||||
_onError(@{ @"error": error.localizedDescription });
|
||||
}
|
||||
} else {
|
||||
if (_onLoad) {
|
||||
_onLoad(nil);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_onLoadEnd) {
|
||||
_onLoadEnd(nil);
|
||||
}
|
||||
if (_onLoadEnd) {
|
||||
_onLoadEnd(nil);
|
||||
}
|
||||
});
|
||||
}];
|
||||
} else {
|
||||
[self clearImage];
|
||||
@@ -210,7 +212,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||
- (void)reactSetFrame:(CGRect)frame
|
||||
{
|
||||
[super reactSetFrame:frame];
|
||||
if (self.image == nil) {
|
||||
|
||||
if (!self.image || self.image == _defaultImage) {
|
||||
_targetSize = frame.size;
|
||||
[self reloadImage];
|
||||
} else if ([RCTImageView srcNeedsReload:_src]) {
|
||||
@@ -254,7 +257,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||
[strongSelf clearImage];
|
||||
}
|
||||
});
|
||||
} else if (!self.image && self.src) {
|
||||
} else if (!self.image || self.image == _defaultImage) {
|
||||
[self reloadImage];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#import "RCTImageLoader.h"
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTUIManager.h"
|
||||
|
||||
@implementation RCTShadowVirtualImage
|
||||
{
|
||||
@@ -37,10 +38,18 @@ RCT_NOT_IMPLEMENTED(-(instancetype)init)
|
||||
CGFloat scale = [RCTConvert CGFloat:_source[@"scale"]] ?: 1;
|
||||
|
||||
__weak RCTShadowVirtualImage *weakSelf = self;
|
||||
[_bridge.imageLoader loadImageWithTag:imageTag size:CGSizeZero scale:scale resizeMode:UIViewContentModeScaleToFill progressBlock:nil completionBlock:^(NSError *error, UIImage *image) {
|
||||
RCTShadowVirtualImage *strongSelf = weakSelf;
|
||||
strongSelf->_image = image;
|
||||
[strongSelf dirtyText];
|
||||
[_bridge.imageLoader loadImageWithTag:imageTag
|
||||
size:CGSizeZero
|
||||
scale:scale
|
||||
resizeMode:UIViewContentModeScaleToFill
|
||||
progressBlock:nil
|
||||
completionBlock:^(NSError *error, UIImage *image) {
|
||||
|
||||
dispatch_async(_bridge.uiManager.methodQueue, ^{
|
||||
RCTShadowVirtualImage *strongSelf = weakSelf;
|
||||
strongSelf->_image = image;
|
||||
[strongSelf dirtyText];
|
||||
});
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
#import "RCTXCAssetImageLoader.h"
|
||||
|
||||
#import <libkern/OSAtomic.h>
|
||||
|
||||
#import "RCTUtils.h"
|
||||
|
||||
@implementation RCTXCAssetImageLoader
|
||||
@@ -20,39 +22,34 @@ RCT_EXPORT_MODULE()
|
||||
return RCTIsXCAssetURL(requestURL);
|
||||
}
|
||||
|
||||
- (float)imageLoaderPriority
|
||||
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
|
||||
size:(CGSize)size
|
||||
scale:(CGFloat)scale
|
||||
resizeMode:(UIViewContentMode)resizeMode
|
||||
progressHandler:(RCTImageLoaderProgressBlock)progressHandler
|
||||
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
|
||||
{
|
||||
return 100; // higher priority than any ordinary file loader
|
||||
}
|
||||
|
||||
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
|
||||
{
|
||||
__block BOOL cancelled = NO;
|
||||
__block volatile uint32_t cancelled = 0;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *imageName = RCTBundlePathForURL(imageURL);
|
||||
UIImage *image = [UIImage imageNamed:imageName];
|
||||
if (image) {
|
||||
if (progressHandler) {
|
||||
progressHandler(1, 1);
|
||||
}
|
||||
|
||||
if (completionHandler) {
|
||||
completionHandler(nil, image);
|
||||
}
|
||||
completionHandler(nil, image);
|
||||
} else {
|
||||
if (completionHandler) {
|
||||
NSString *message = [NSString stringWithFormat:@"Could not find image named %@", imageName];
|
||||
completionHandler(RCTErrorWithMessage(message), nil);
|
||||
}
|
||||
NSString *message = [NSString stringWithFormat:@"Could not find image named %@", imageName];
|
||||
completionHandler(RCTErrorWithMessage(message), nil);
|
||||
}
|
||||
});
|
||||
|
||||
return ^{
|
||||
cancelled = YES;
|
||||
OSAtomicOr32Barrier(1, &cancelled);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -25,26 +25,23 @@ function expectResolvesAsset(input, expectedSource) {
|
||||
describe('resolveAssetSource', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModuleRegistry();
|
||||
__DEV__ = true;
|
||||
});
|
||||
|
||||
it('returns same source for simple static and network images', () => {
|
||||
var source1 = {uri: 'https://www.facebook.com/logo'};
|
||||
expect(resolveAssetSource(source1)).toBe(source1);
|
||||
|
||||
var source2 = {isStatic: true, uri: 'logo'};
|
||||
var source2 = {uri: 'logo'};
|
||||
expect(resolveAssetSource(source2)).toBe(source2);
|
||||
});
|
||||
|
||||
it('does not change deprecated assets', () => {
|
||||
expect(resolveAssetSource({
|
||||
isStatic: true,
|
||||
deprecated: true,
|
||||
width: 100,
|
||||
height: 200,
|
||||
uri: 'logo',
|
||||
})).toEqual({
|
||||
isStatic: true,
|
||||
deprecated: true,
|
||||
width: 100,
|
||||
height: 200,
|
||||
@@ -78,7 +75,6 @@ describe('resolveAssetSource', () => {
|
||||
type: 'png',
|
||||
}, {
|
||||
__packager_asset: true,
|
||||
isStatic: false,
|
||||
width: 100,
|
||||
height: 200,
|
||||
uri: 'http://10.0.0.1:8081/assets/module/a/logo.png?platform=ios&hash=5b6f00f',
|
||||
@@ -99,7 +95,6 @@ describe('resolveAssetSource', () => {
|
||||
type: 'png',
|
||||
}, {
|
||||
__packager_asset: true,
|
||||
isStatic: false,
|
||||
width: 100,
|
||||
height: 200,
|
||||
uri: 'http://10.0.0.1:8081/assets/module/a/logo@2x.png?platform=ios&hash=5b6f00f',
|
||||
@@ -109,11 +104,10 @@ describe('resolveAssetSource', () => {
|
||||
|
||||
});
|
||||
|
||||
describe('bundle was loaded from file (PROD) on iOS', () => {
|
||||
describe('bundle was loaded from file on iOS', () => {
|
||||
beforeEach(() => {
|
||||
NativeModules.SourceCode.scriptURL =
|
||||
'file:///Path/To/Simulator/main.bundle';
|
||||
__DEV__ = false;
|
||||
Platform.OS = 'ios';
|
||||
});
|
||||
|
||||
@@ -130,19 +124,18 @@ describe('resolveAssetSource', () => {
|
||||
type: 'png',
|
||||
}, {
|
||||
__packager_asset: true,
|
||||
isStatic: true,
|
||||
width: 100,
|
||||
height: 200,
|
||||
uri: 'assets/module/a/logo.png',
|
||||
scale: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('bundle was loaded from file (PROD) on Android', () => {
|
||||
describe('bundle was loaded from file on Android', () => {
|
||||
beforeEach(() => {
|
||||
NativeModules.SourceCode.scriptURL =
|
||||
'file:///Path/To/Simulator/main.bundle';
|
||||
__DEV__ = false;
|
||||
Platform.OS = 'android';
|
||||
});
|
||||
|
||||
@@ -159,10 +152,10 @@ describe('resolveAssetSource', () => {
|
||||
type: 'png',
|
||||
}, {
|
||||
__packager_asset: true,
|
||||
isStatic: true,
|
||||
width: 100,
|
||||
height: 200,
|
||||
uri: 'awesomemodule_subdir_logo1_',
|
||||
scale: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,11 +7,20 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule resolveAssetSource
|
||||
* @flow
|
||||
*
|
||||
* Resolves an asset into a `source` for `Image`.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
export type ResolvedAssetSource = {
|
||||
__packager_asset: boolean,
|
||||
width: number,
|
||||
height: number,
|
||||
uri: string,
|
||||
scale: number,
|
||||
};
|
||||
|
||||
var AssetRegistry = require('AssetRegistry');
|
||||
var PixelRatio = require('PixelRatio');
|
||||
var Platform = require('Platform');
|
||||
@@ -20,10 +29,6 @@ var SourceCode = require('NativeModules').SourceCode;
|
||||
var _serverURL;
|
||||
|
||||
function getDevServerURL() {
|
||||
if (!__DEV__) {
|
||||
// In prod we want assets to be loaded from the archive
|
||||
return null;
|
||||
}
|
||||
if (_serverURL === undefined) {
|
||||
var scriptURL = SourceCode.scriptURL;
|
||||
var match = scriptURL && scriptURL.match(/^https?:\/\/.*?\//);
|
||||
@@ -90,7 +95,7 @@ function getScaledAssetPath(asset) {
|
||||
return assetDir + '/' + asset.name + scaleSuffix + '.' + asset.type;
|
||||
}
|
||||
|
||||
function pickScale(scales, deviceScale) {
|
||||
function pickScale(scales: Array<number>, deviceScale: number): number {
|
||||
// Packager guarantees that `scales` array is sorted
|
||||
for (var i = 0; i < scales.length; i++) {
|
||||
if (scales[i] >= deviceScale) {
|
||||
@@ -104,7 +109,7 @@ function pickScale(scales, deviceScale) {
|
||||
return scales[scales.length - 1] || 1;
|
||||
}
|
||||
|
||||
function resolveAssetSource(source) {
|
||||
function resolveAssetSource(source: any): ?ResolvedAssetSource {
|
||||
if (typeof source === 'object') {
|
||||
return source;
|
||||
}
|
||||
@@ -117,26 +122,15 @@ function resolveAssetSource(source) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function assetToImageSource(asset) {
|
||||
function assetToImageSource(asset): ResolvedAssetSource {
|
||||
var devServerURL = getDevServerURL();
|
||||
if (devServerURL) {
|
||||
return {
|
||||
__packager_asset: true,
|
||||
width: asset.width,
|
||||
height: asset.height,
|
||||
uri: getPathOnDevserver(devServerURL, asset),
|
||||
isStatic: false,
|
||||
scale: pickScale(asset.scales, PixelRatio.get()),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
__packager_asset: true,
|
||||
width: asset.width,
|
||||
height: asset.height,
|
||||
uri: getPathInArchive(asset),
|
||||
isStatic: true,
|
||||
};
|
||||
}
|
||||
return {
|
||||
__packager_asset: true,
|
||||
width: asset.width,
|
||||
height: asset.height,
|
||||
uri: devServerURL ? getPathOnDevserver(devServerURL, asset) : getPathInArchive(asset),
|
||||
scale: pickScale(asset.scales, PixelRatio.get()),
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = resolveAssetSource;
|
||||
|
||||
@@ -114,7 +114,7 @@ function stackFrameToString(stackFrame, maxLength) {
|
||||
var fileName = fileNameParts[fileNameParts.length - 1];
|
||||
|
||||
if (fileName.length > 18) {
|
||||
fileName = fileName.substr(0, 17) + '\u2026' /* ... */;
|
||||
fileName = fileName.substr(0, 17) + '\u2026'; /* ... */
|
||||
}
|
||||
|
||||
var spaces = fillSpaces(maxLength - stackFrame.methodName.length);
|
||||
|
||||
@@ -22,10 +22,6 @@
|
||||
/* eslint strict: 0 */
|
||||
/* globals GLOBAL: true, window: true */
|
||||
|
||||
// Just to make sure the JS gets packaged up.
|
||||
require('RCTDebugComponentOwnership');
|
||||
require('RCTDeviceEventEmitter');
|
||||
require('PerformanceLogger');
|
||||
require('regenerator/runtime');
|
||||
|
||||
if (typeof GLOBAL === 'undefined') {
|
||||
@@ -46,26 +42,33 @@ function handleError(e, isFatal) {
|
||||
|
||||
/**
|
||||
* Assigns a new global property, replacing the existing one if there is one.
|
||||
*
|
||||
*
|
||||
* Existing properties are preserved as `originalPropertyName`. Both properties
|
||||
* will maintain the same enumerability & configurability.
|
||||
*
|
||||
*
|
||||
* This allows you to undo the more aggressive polyfills, should you need to.
|
||||
* For example, if you want to route network requests through DevTools (to trace
|
||||
* them):
|
||||
*
|
||||
* GLOBAL.XMLHTTPRequest = GLOBAL.originalXMLHTTPRequest;
|
||||
*
|
||||
* global.XMLHttpRequest = global.originalXMLHttpRequest;
|
||||
*
|
||||
* For more info on that particular case, see:
|
||||
* https://github.com/facebook/react-native/issues/934
|
||||
*/
|
||||
function polyfillGlobal(name, newValue, scope=GLOBAL) {
|
||||
var descriptor = Object.getOwnPropertyDescriptor(scope, name);
|
||||
var descriptor = Object.getOwnPropertyDescriptor(scope, name) || {
|
||||
// jest for some bad reasons runs the polyfill code multiple times. In jest
|
||||
// environment, XmlHttpRequest doesn't exist so getOwnPropertyDescriptor
|
||||
// returns undefined and defineProperty default for writable is false.
|
||||
// Therefore, the second time it runs, defineProperty will fatal :(
|
||||
writable: true,
|
||||
};
|
||||
|
||||
if (scope[name] !== undefined) {
|
||||
var backupName = `original${name[0].toUpperCase()}${name.substr(1)}`;
|
||||
Object.defineProperty(scope, backupName, {...descriptor, value: scope[name]});
|
||||
}
|
||||
|
||||
Object.defineProperty(scope, name, {...descriptor, value: newValue});
|
||||
}
|
||||
|
||||
@@ -177,6 +180,7 @@ function setUpNumber() {
|
||||
Number.MIN_SAFE_INTEGER = Number.MIN_SAFE_INTEGER || -(Math.pow(2, 53) - 1);
|
||||
}
|
||||
|
||||
setUpProcessEnv();
|
||||
setUpRedBoxErrorHandler();
|
||||
setUpTimers();
|
||||
setUpAlert();
|
||||
@@ -186,6 +190,11 @@ setUpRedBoxConsoleErrorHandler();
|
||||
setUpGeolocation();
|
||||
setUpWebSockets();
|
||||
setUpProfile();
|
||||
setUpProcessEnv();
|
||||
setUpFlowChecker();
|
||||
setUpNumber();
|
||||
|
||||
// Just to make sure the JS gets packaged up. Wait until the JS environment has
|
||||
// been initialized before requiring them.
|
||||
require('RCTDebugComponentOwnership');
|
||||
require('RCTDeviceEventEmitter');
|
||||
require('PerformanceLogger');
|
||||
|
||||
@@ -48,7 +48,7 @@ function fetchSourceMap(): Promise {
|
||||
return Promise.resolve(url);
|
||||
})
|
||||
.then(fetch)
|
||||
.then(response => response.text())
|
||||
.then(response => response.text());
|
||||
}
|
||||
|
||||
function extractSourceMapURL({url, text, fullSourceMappingURL}): ?string {
|
||||
|
||||
@@ -29,7 +29,8 @@ var RCTModalHostView = requireNativeComponent('RCTModalHostView', null);
|
||||
*
|
||||
* In apps written with React Native from the root view down, you should use
|
||||
* Navigator instead of Modal. With a top-level Navigator, you have more control
|
||||
* over how to present the modal scene over the rest of your app.
|
||||
* over how to present the modal scene over the rest of your app by using the
|
||||
* configureScene property.
|
||||
*/
|
||||
class Modal extends React.Component {
|
||||
render(): ?ReactElement {
|
||||
|
||||
@@ -47,26 +47,18 @@ type FormDataPart = {
|
||||
*/
|
||||
class FormData {
|
||||
_parts: Array<FormDataNameValuePair>;
|
||||
_partsByKey: {[key: string]: FormDataNameValuePair};
|
||||
|
||||
constructor() {
|
||||
this._parts = [];
|
||||
this._partsByKey = {};
|
||||
}
|
||||
|
||||
append(key: string, value: FormDataValue) {
|
||||
var parts = this._partsByKey[key];
|
||||
if (parts) {
|
||||
// It's a bit unclear what the behaviour should be in this case.
|
||||
// The XMLHttpRequest spec doesn't specify it, while MDN says that
|
||||
// the any new values should appended to existing values. We're not
|
||||
// doing that for now -- it's tedious and doesn't seem worth the effort.
|
||||
parts[1] = value;
|
||||
return;
|
||||
}
|
||||
parts = [key, value];
|
||||
this._parts.push(parts);
|
||||
this._partsByKey[key] = parts;
|
||||
// The XMLHttpRequest spec doesn't specify if duplicate keys are allowed.
|
||||
// MDN says that any new values should be appended to existing values.
|
||||
// In any case, major browsers allow duplicate keys, so that's what we'll do
|
||||
// too. They'll simply get appended as additional form data parts in the
|
||||
// request body, leaving the server to deal with them.
|
||||
this._parts.push([key, value]);
|
||||
}
|
||||
|
||||
getParts(): Array<FormDataPart> {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import "RCTURLRequestHandler.h"
|
||||
#import "RCTInvalidating.h"
|
||||
|
||||
/**
|
||||
* This is the default RCTURLRequestHandler implementation for data URL requests.
|
||||
*/
|
||||
@interface RCTDataRequestHandler : NSObject <RCTURLRequestHandler, RCTInvalidating>
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import "RCTDataRequestHandler.h"
|
||||
|
||||
@implementation RCTDataRequestHandler
|
||||
{
|
||||
NSOperationQueue *_queue;
|
||||
}
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
[_queue cancelAllOperations];
|
||||
_queue = nil;
|
||||
}
|
||||
|
||||
- (BOOL)canHandleRequest:(NSURLRequest *)request
|
||||
{
|
||||
return [request.URL.scheme caseInsensitiveCompare:@"data"] == NSOrderedSame;
|
||||
}
|
||||
|
||||
- (NSOperation *)sendRequest:(NSURLRequest *)request
|
||||
withDelegate:(id<RCTURLRequestDelegate>)delegate
|
||||
{
|
||||
// Lazy setup
|
||||
if (!_queue) {
|
||||
_queue = [NSOperationQueue new];
|
||||
_queue.maxConcurrentOperationCount = 2;
|
||||
}
|
||||
|
||||
__block NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
|
||||
|
||||
// Get mime type
|
||||
NSRange firstSemicolon = [request.URL.resourceSpecifier rangeOfString:@";"];
|
||||
NSString *mimeType = firstSemicolon.length ? [request.URL.resourceSpecifier substringToIndex:firstSemicolon.location] : nil;
|
||||
|
||||
// Send response
|
||||
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
|
||||
MIMEType:mimeType
|
||||
expectedContentLength:-1
|
||||
textEncodingName:nil];
|
||||
|
||||
[delegate URLRequest:op didReceiveResponse:response];
|
||||
|
||||
// Load data
|
||||
NSError *error;
|
||||
NSData *data = [NSData dataWithContentsOfURL:request.URL
|
||||
options:NSDataReadingMappedIfSafe
|
||||
error:&error];
|
||||
if (data) {
|
||||
[delegate URLRequest:op didReceiveData:data];
|
||||
}
|
||||
[delegate URLRequest:op didCompleteWithError:error];
|
||||
}];
|
||||
|
||||
[_queue addOperation:op];
|
||||
return op;
|
||||
}
|
||||
|
||||
- (void)cancelRequest:(NSOperation *)op
|
||||
{
|
||||
[op cancel];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -7,15 +7,12 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTImageLoader.h"
|
||||
#import "RCTURLRequestHandler.h"
|
||||
#import "RCTInvalidating.h"
|
||||
|
||||
@interface RCTImageDownloader : NSObject <RCTImageURLLoader>
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTBridge (RCTImageDownloader)
|
||||
|
||||
@property (nonatomic, readonly) RCTImageDownloader *imageDownloader;
|
||||
/**
|
||||
* This is the default RCTURLRequestHandler implementation for file requests.
|
||||
*/
|
||||
@interface RCTFileRequestHandler : NSObject <RCTURLRequestHandler, RCTInvalidating>
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import "RCTFileRequestHandler.h"
|
||||
|
||||
#import <MobileCoreServices/MobileCoreServices.h>
|
||||
|
||||
#import "RCTUtils.h"
|
||||
|
||||
@implementation RCTFileRequestHandler
|
||||
{
|
||||
NSOperationQueue *_fileQueue;
|
||||
}
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
[_fileQueue cancelAllOperations];
|
||||
_fileQueue = nil;
|
||||
}
|
||||
|
||||
- (BOOL)canHandleRequest:(NSURLRequest *)request
|
||||
{
|
||||
return
|
||||
[request.URL.scheme caseInsensitiveCompare:@"file"] == NSOrderedSame
|
||||
&& !RCTIsXCAssetURL(request.URL);
|
||||
}
|
||||
|
||||
- (NSOperation *)sendRequest:(NSURLRequest *)request
|
||||
withDelegate:(id<RCTURLRequestDelegate>)delegate
|
||||
{
|
||||
// Lazy setup
|
||||
if (!_fileQueue) {
|
||||
_fileQueue = [NSOperationQueue new];
|
||||
_fileQueue.maxConcurrentOperationCount = 4;
|
||||
}
|
||||
|
||||
__block NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
|
||||
|
||||
// Get content length
|
||||
NSError *error = nil;
|
||||
NSFileManager *fileManager = [NSFileManager new];
|
||||
NSDictionary *fileAttributes = [fileManager attributesOfItemAtPath:request.URL.path error:&error];
|
||||
if (error) {
|
||||
[delegate URLRequest:op didCompleteWithError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
// Get mime type
|
||||
NSString *fileExtension = [request.URL pathExtension];
|
||||
NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(
|
||||
kUTTagClassFilenameExtension, (__bridge CFStringRef)fileExtension, NULL);
|
||||
NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass(
|
||||
(__bridge CFStringRef)UTI, kUTTagClassMIMEType);
|
||||
|
||||
// Send response
|
||||
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
|
||||
MIMEType:contentType
|
||||
expectedContentLength:[fileAttributes[NSFileSize] ?: @-1 integerValue]
|
||||
textEncodingName:nil];
|
||||
|
||||
[delegate URLRequest:op didReceiveResponse:response];
|
||||
|
||||
// Load data
|
||||
NSData *data = [NSData dataWithContentsOfURL:request.URL
|
||||
options:NSDataReadingMappedIfSafe
|
||||
error:&error];
|
||||
if (data) {
|
||||
[delegate URLRequest:op didReceiveData:data];
|
||||
}
|
||||
[delegate URLRequest:op didCompleteWithError:error];
|
||||
}];
|
||||
|
||||
[_fileQueue addOperation:op];
|
||||
return op;
|
||||
}
|
||||
|
||||
- (void)cancelRequest:(NSOperation *)op
|
||||
{
|
||||
[op cancel];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -50,7 +50,9 @@ RCT_EXPORT_MODULE()
|
||||
static NSSet *schemes = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
schemes = [[NSSet alloc] initWithObjects:@"http", @"https", @"file", nil];
|
||||
// technically, RCTHTTPRequestHandler can handle file:// as well,
|
||||
// but it's less efficient than using RCTFileRequestHandler
|
||||
schemes = [[NSSet alloc] initWithObjects:@"http", @"https", nil];
|
||||
});
|
||||
return [schemes containsObject:request.URL.scheme.lowercaseString];
|
||||
}
|
||||
|
||||
@@ -7,8 +7,10 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
134E969A1BCEB7F800AFFDA1 /* RCTDataRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 134E96991BCEB7F800AFFDA1 /* RCTDataRequestHandler.m */; settings = {ASSET_TAGS = (); }; };
|
||||
1372B7371AB03E7B00659ED6 /* RCTNetInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 1372B7361AB03E7B00659ED6 /* RCTNetInfo.m */; };
|
||||
13D6D66A1B5FCF8200883BE9 /* RCTDownloadTask.m in Sources */ = {isa = PBXBuildFile; fileRef = 13D6D6691B5FCF8200883BE9 /* RCTDownloadTask.m */; };
|
||||
13D6D66A1B5FCF8200883BE9 /* RCTNetworkTask.m in Sources */ = {isa = PBXBuildFile; fileRef = 13D6D6691B5FCF8200883BE9 /* RCTNetworkTask.m */; };
|
||||
13EF800E1BCBE015003F47DD /* RCTFileRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 13EF800D1BCBE015003F47DD /* RCTFileRequestHandler.m */; settings = {ASSET_TAGS = (); }; };
|
||||
352DA0BA1B17855800AA15A8 /* RCTHTTPRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */; };
|
||||
58B512081A9E6CE300147676 /* RCTNetworking.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B512071A9E6CE300147676 /* RCTNetworking.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
@@ -26,10 +28,14 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
134E96981BCEB7F800AFFDA1 /* RCTDataRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDataRequestHandler.h; sourceTree = "<group>"; };
|
||||
134E96991BCEB7F800AFFDA1 /* RCTDataRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDataRequestHandler.m; sourceTree = "<group>"; };
|
||||
1372B7351AB03E7B00659ED6 /* RCTNetInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTNetInfo.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
|
||||
1372B7361AB03E7B00659ED6 /* RCTNetInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = RCTNetInfo.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
|
||||
13D6D6681B5FCF8200883BE9 /* RCTDownloadTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDownloadTask.h; sourceTree = "<group>"; };
|
||||
13D6D6691B5FCF8200883BE9 /* RCTDownloadTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDownloadTask.m; sourceTree = "<group>"; };
|
||||
13D6D6681B5FCF8200883BE9 /* RCTNetworkTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTNetworkTask.h; sourceTree = "<group>"; };
|
||||
13D6D6691B5FCF8200883BE9 /* RCTNetworkTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTNetworkTask.m; sourceTree = "<group>"; };
|
||||
13EF800C1BCBE015003F47DD /* RCTFileRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTFileRequestHandler.h; sourceTree = "<group>"; };
|
||||
13EF800D1BCBE015003F47DD /* RCTFileRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTFileRequestHandler.m; sourceTree = "<group>"; };
|
||||
352DA0B71B17855800AA15A8 /* RCTHTTPRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTHTTPRequestHandler.h; sourceTree = "<group>"; };
|
||||
352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTHTTPRequestHandler.m; sourceTree = "<group>"; };
|
||||
58B511DB1A9E6C8500147676 /* libRCTNetwork.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTNetwork.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -51,10 +57,14 @@
|
||||
58B511D21A9E6C8500147676 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
13D6D6681B5FCF8200883BE9 /* RCTDownloadTask.h */,
|
||||
13D6D6691B5FCF8200883BE9 /* RCTDownloadTask.m */,
|
||||
13D6D6681B5FCF8200883BE9 /* RCTNetworkTask.h */,
|
||||
13D6D6691B5FCF8200883BE9 /* RCTNetworkTask.m */,
|
||||
352DA0B71B17855800AA15A8 /* RCTHTTPRequestHandler.h */,
|
||||
352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */,
|
||||
13EF800C1BCBE015003F47DD /* RCTFileRequestHandler.h */,
|
||||
13EF800D1BCBE015003F47DD /* RCTFileRequestHandler.m */,
|
||||
134E96981BCEB7F800AFFDA1 /* RCTDataRequestHandler.h */,
|
||||
134E96991BCEB7F800AFFDA1 /* RCTDataRequestHandler.m */,
|
||||
1372B7351AB03E7B00659ED6 /* RCTNetInfo.h */,
|
||||
1372B7361AB03E7B00659ED6 /* RCTNetInfo.m */,
|
||||
58B512061A9E6CE300147676 /* RCTNetworking.h */,
|
||||
@@ -129,7 +139,9 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
13D6D66A1B5FCF8200883BE9 /* RCTDownloadTask.m in Sources */,
|
||||
13D6D66A1B5FCF8200883BE9 /* RCTNetworkTask.m in Sources */,
|
||||
13EF800E1BCBE015003F47DD /* RCTFileRequestHandler.m in Sources */,
|
||||
134E969A1BCEB7F800AFFDA1 /* RCTDataRequestHandler.m in Sources */,
|
||||
1372B7371AB03E7B00659ED6 /* RCTNetInfo.m in Sources */,
|
||||
58B512081A9E6CE300147676 /* RCTNetworking.m in Sources */,
|
||||
352DA0BA1B17855800AA15A8 /* RCTHTTPRequestHandler.m in Sources */,
|
||||
|
||||
@@ -18,7 +18,7 @@ typedef void (^RCTURLRequestIncrementalDataBlock)(NSData *data);
|
||||
typedef void (^RCTURLRequestProgressBlock)(int64_t progress, int64_t total);
|
||||
typedef void (^RCTURLRequestResponseBlock)(NSURLResponse *response);
|
||||
|
||||
@interface RCTDownloadTask : NSObject <RCTURLRequestDelegate>
|
||||
@interface RCTNetworkTask : NSObject <RCTURLRequestDelegate>
|
||||
|
||||
@property (nonatomic, readonly) NSURLRequest *request;
|
||||
@property (nonatomic, readonly) NSNumber *requestID;
|
||||
@@ -35,6 +35,7 @@ typedef void (^RCTURLRequestResponseBlock)(NSURLResponse *response);
|
||||
handler:(id<RCTURLRequestHandler>)handler
|
||||
completionBlock:(RCTURLRequestCompletionBlock)completionBlock NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (void)start;
|
||||
- (void)cancel;
|
||||
|
||||
@end
|
||||
@@ -7,15 +7,15 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTDownloadTask.h"
|
||||
#import "RCTNetworkTask.h"
|
||||
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTLog.h"
|
||||
|
||||
@implementation RCTDownloadTask
|
||||
@implementation RCTNetworkTask
|
||||
{
|
||||
NSMutableData *_data;
|
||||
id<RCTURLRequestHandler> _handler;
|
||||
RCTDownloadTask *_selfReference;
|
||||
RCTNetworkTask *_selfReference;
|
||||
}
|
||||
|
||||
- (instancetype)initWithRequest:(NSURLRequest *)request
|
||||
@@ -29,18 +29,16 @@
|
||||
static NSUInteger requestID = 0;
|
||||
|
||||
if ((self = [super init])) {
|
||||
if (!(_requestToken = [handler sendRequest:request withDelegate:self])) {
|
||||
return nil;
|
||||
}
|
||||
_requestID = @(requestID++);
|
||||
_request = request;
|
||||
_handler = handler;
|
||||
_completionBlock = completionBlock;
|
||||
_selfReference = self;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
_selfReference = nil;
|
||||
@@ -51,7 +49,15 @@
|
||||
_uploadProgressBlock = nil;
|
||||
}
|
||||
|
||||
RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||
- (void)start
|
||||
{
|
||||
if (_requestToken == nil) {
|
||||
if ([self validateRequestToken:[_handler sendRequest:_request
|
||||
withDelegate:self]]) {
|
||||
_selfReference = self;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)cancel
|
||||
{
|
||||
@@ -63,10 +69,15 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||
|
||||
- (BOOL)validateRequestToken:(id)requestToken
|
||||
{
|
||||
if (_requestToken == nil) {
|
||||
if (requestToken == nil) {
|
||||
return NO;
|
||||
}
|
||||
_requestToken = requestToken;
|
||||
}
|
||||
if (![requestToken isEqual:_requestToken]) {
|
||||
if (RCT_DEBUG) {
|
||||
RCTAssert([requestToken isEqual:_requestToken],
|
||||
@"Unrecognized request token: %@", requestToken);
|
||||
RCTLogError(@"Unrecognized request token: %@ expected: %@", requestToken, _requestToken);
|
||||
}
|
||||
if (_completionBlock) {
|
||||
_completionBlock(_response, _data, [NSError errorWithDomain:RCTErrorDomain code:0
|
||||
@@ -10,12 +10,21 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTDownloadTask.h"
|
||||
#import "RCTNetworkTask.h"
|
||||
|
||||
@interface RCTNetworking : NSObject <RCTBridgeModule>
|
||||
|
||||
- (RCTDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
|
||||
completionBlock:(RCTURLRequestCompletionBlock)completionBlock;
|
||||
/**
|
||||
* Does a handler exist for the specified request?
|
||||
*/
|
||||
- (BOOL)canHandleRequest:(NSURLRequest *)request;
|
||||
|
||||
/**
|
||||
* Return an RCTNetworkTask for the specified request. This is useful for
|
||||
* invoking the React Native networking stack from within native code.
|
||||
*/
|
||||
- (RCTNetworkTask *)networkTaskWithRequest:(NSURLRequest *)request
|
||||
completionBlock:(RCTURLRequestCompletionBlock)completionBlock;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTDownloadTask.h"
|
||||
#import "RCTNetworkTask.h"
|
||||
#import "RCTURLRequestHandler.h"
|
||||
#import "RCTEventDispatcher.h"
|
||||
#import "RCTHTTPRequestHandler.h"
|
||||
@@ -37,10 +37,10 @@ typedef RCTURLRequestCancellationBlock (^RCTHTTPQueryResult)(NSError *error, NSD
|
||||
|
||||
@implementation RCTHTTPFormDataHelper
|
||||
{
|
||||
NSMutableArray *parts;
|
||||
NSMutableData *multipartBody;
|
||||
NSMutableArray *_parts;
|
||||
NSMutableData *_multipartBody;
|
||||
RCTHTTPQueryResult _callback;
|
||||
NSString *boundary;
|
||||
NSString *_boundary;
|
||||
}
|
||||
|
||||
static NSString *RCTGenerateFormBoundary()
|
||||
@@ -56,19 +56,19 @@ static NSString *RCTGenerateFormBoundary()
|
||||
return [[NSString alloc] initWithBytesNoCopy:bytes length:boundaryLength encoding:NSUTF8StringEncoding freeWhenDone:YES];
|
||||
}
|
||||
|
||||
- (RCTURLRequestCancellationBlock)process:(NSArray *)formData
|
||||
- (RCTURLRequestCancellationBlock)process:(NSDictionaryArray *)formData
|
||||
callback:(RCTHTTPQueryResult)callback
|
||||
{
|
||||
if (formData.count == 0) {
|
||||
return callback(nil, nil);
|
||||
}
|
||||
|
||||
parts = [formData mutableCopy];
|
||||
_parts = [formData mutableCopy];
|
||||
_callback = callback;
|
||||
multipartBody = [NSMutableData new];
|
||||
boundary = RCTGenerateFormBoundary();
|
||||
_multipartBody = [NSMutableData new];
|
||||
_boundary = RCTGenerateFormBoundary();
|
||||
|
||||
return [_networker processDataForHTTPQuery:parts[0] callback:^(NSError *error, NSDictionary *result) {
|
||||
return [_networker processDataForHTTPQuery:_parts[0] callback:^(NSError *error, NSDictionary *result) {
|
||||
return [self handleResult:result error:error];
|
||||
}];
|
||||
}
|
||||
@@ -81,37 +81,37 @@ static NSString *RCTGenerateFormBoundary()
|
||||
}
|
||||
|
||||
// Start with boundary.
|
||||
[multipartBody appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary]
|
||||
dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
[_multipartBody appendData:[[NSString stringWithFormat:@"--%@\r\n", _boundary]
|
||||
dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
|
||||
// Print headers.
|
||||
NSMutableDictionary *headers = [parts[0][@"headers"] mutableCopy];
|
||||
NSMutableDictionary *headers = [_parts[0][@"headers"] mutableCopy];
|
||||
NSString *partContentType = result[@"contentType"];
|
||||
if (partContentType != nil) {
|
||||
headers[@"content-type"] = partContentType;
|
||||
}
|
||||
[headers enumerateKeysAndObjectsUsingBlock:^(NSString *parameterKey, NSString *parameterValue, BOOL *stop) {
|
||||
[multipartBody appendData:[[NSString stringWithFormat:@"%@: %@\r\n", parameterKey, parameterValue]
|
||||
dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
[_multipartBody appendData:[[NSString stringWithFormat:@"%@: %@\r\n", parameterKey, parameterValue]
|
||||
dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
}];
|
||||
|
||||
// Add the body.
|
||||
[multipartBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
[multipartBody appendData:result[@"body"]];
|
||||
[multipartBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
[_multipartBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
[_multipartBody appendData:result[@"body"]];
|
||||
[_multipartBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
|
||||
[parts removeObjectAtIndex:0];
|
||||
if (parts.count) {
|
||||
return [_networker processDataForHTTPQuery:parts[0] callback:^(NSError *err, NSDictionary *res) {
|
||||
[_parts removeObjectAtIndex:0];
|
||||
if (_parts.count) {
|
||||
return [_networker processDataForHTTPQuery:_parts[0] callback:^(NSError *err, NSDictionary *res) {
|
||||
return [self handleResult:res error:err];
|
||||
}];
|
||||
}
|
||||
|
||||
// We've processed the last item. Finish and return.
|
||||
[multipartBody appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary]
|
||||
dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=\"%@\"", boundary];
|
||||
return _callback(nil, @{@"body": multipartBody, @"contentType": contentType});
|
||||
[_multipartBody appendData:[[NSString stringWithFormat:@"--%@--\r\n", _boundary]
|
||||
dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=\"%@\"", _boundary];
|
||||
return _callback(nil, @{@"body": _multipartBody, @"contentType": contentType});
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -122,6 +122,7 @@ static NSString *RCTGenerateFormBoundary()
|
||||
@implementation RCTNetworking
|
||||
{
|
||||
NSMutableDictionary *_tasksByRequestID;
|
||||
NSArray *_handlers;
|
||||
}
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
@@ -129,12 +130,65 @@ static NSString *RCTGenerateFormBoundary()
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (instancetype)init
|
||||
- (void)setBridge:(RCTBridge *)bridge
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_tasksByRequestID = [NSMutableDictionary new];
|
||||
// get handlers
|
||||
NSMutableArray *handlers = [NSMutableArray array];
|
||||
for (id<RCTBridgeModule> module in bridge.modules.allValues) {
|
||||
if ([module conformsToProtocol:@protocol(RCTURLRequestHandler)]) {
|
||||
[handlers addObject:module];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
|
||||
// Sort handlers in reverse priority order (highest priority first)
|
||||
[handlers sortUsingComparator:^NSComparisonResult(id<RCTURLRequestHandler> a, id<RCTURLRequestHandler> b) {
|
||||
float priorityA = [a respondsToSelector:@selector(handlerPriority)] ? [a handlerPriority] : 0;
|
||||
float priorityB = [b respondsToSelector:@selector(handlerPriority)] ? [b handlerPriority] : 0;
|
||||
if (priorityA > priorityB) {
|
||||
return NSOrderedAscending;
|
||||
} else if (priorityA < priorityB) {
|
||||
return NSOrderedDescending;
|
||||
} else {
|
||||
return NSOrderedSame;
|
||||
}
|
||||
}];
|
||||
|
||||
_bridge = bridge;
|
||||
_handlers = handlers;
|
||||
_tasksByRequestID = [NSMutableDictionary new];
|
||||
}
|
||||
|
||||
- (id<RCTURLRequestHandler>)handlerForRequest:(NSURLRequest *)request
|
||||
{
|
||||
if (RCT_DEBUG) {
|
||||
// Check for handler conflicts
|
||||
float previousPriority = 0;
|
||||
id<RCTURLRequestHandler> previousHandler = nil;
|
||||
for (id<RCTURLRequestHandler> handler in _handlers) {
|
||||
if ([handler canHandleRequest:request]) {
|
||||
float priority = [handler respondsToSelector:@selector(handlerPriority)] ? [handler handlerPriority] : 0;
|
||||
if (previousHandler) {
|
||||
if (priority == previousPriority) {
|
||||
RCTLogError(@"The RCTURLRequestHandlers %@ and %@ both reported that"
|
||||
" they can handle the request %@, and have equal priority"
|
||||
" (%g). This could result in non-deterministic behavior.",
|
||||
handler, previousHandler, request, priority);
|
||||
}
|
||||
} else {
|
||||
previousHandler = handler;
|
||||
previousPriority = priority;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Normal code path
|
||||
for (id<RCTURLRequestHandler> handler in _handlers) {
|
||||
if ([handler canHandleRequest:request]) {
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (RCTURLRequestCancellationBlock)buildRequest:(NSDictionary *)query
|
||||
@@ -169,37 +223,9 @@ RCT_EXPORT_MODULE()
|
||||
}];
|
||||
}
|
||||
|
||||
- (id<RCTURLRequestHandler>)handlerForRequest:(NSURLRequest *)request
|
||||
- (BOOL)canHandleRequest:(NSURLRequest *)request
|
||||
{
|
||||
NSMutableArray *handlers = [NSMutableArray array];
|
||||
for (id<RCTBridgeModule> module in _bridge.modules.allValues) {
|
||||
if ([module conformsToProtocol:@protocol(RCTURLRequestHandler)]) {
|
||||
if ([(id<RCTURLRequestHandler>)module canHandleRequest:request]) {
|
||||
[handlers addObject:module];
|
||||
}
|
||||
}
|
||||
}
|
||||
[handlers sortUsingComparator:^NSComparisonResult(id<RCTURLRequestHandler> a, id<RCTURLRequestHandler> b) {
|
||||
float priorityA = [a respondsToSelector:@selector(handlerPriority)] ? [a handlerPriority] : 0;
|
||||
float priorityB = [b respondsToSelector:@selector(handlerPriority)] ? [b handlerPriority] : 0;
|
||||
if (priorityA < priorityB) {
|
||||
return NSOrderedAscending;
|
||||
} else if (priorityA > priorityB) {
|
||||
return NSOrderedDescending;
|
||||
} else {
|
||||
RCTLogError(@"The RCTURLRequestHandlers %@ and %@ both reported that"
|
||||
" they can handle the request %@, and have equal priority"
|
||||
" (%g). This could result in non-deterministic behavior.",
|
||||
a, b, request, priorityA);
|
||||
|
||||
return NSOrderedSame;
|
||||
}
|
||||
}];
|
||||
id<RCTURLRequestHandler> handler = handlers.lastObject;
|
||||
if (!handler) {
|
||||
RCTLogError(@"No suitable request handler found for %@", request.URL);
|
||||
}
|
||||
return handler;
|
||||
return [self handlerForRequest:request] != nil;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -234,11 +260,13 @@ RCT_EXPORT_MODULE()
|
||||
if (request) {
|
||||
|
||||
__block RCTURLRequestCancellationBlock cancellationBlock = nil;
|
||||
RCTDownloadTask *task = [self downloadTaskWithRequest:request completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) {
|
||||
RCTNetworkTask *task = [self networkTaskWithRequest:request completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) {
|
||||
cancellationBlock = callback(error, data ? @{@"body": data, @"contentType": RCTNullIfNil(response.MIMEType)} : nil);
|
||||
}];
|
||||
|
||||
__weak RCTDownloadTask *weakTask = task;
|
||||
[task start];
|
||||
|
||||
__weak RCTNetworkTask *weakTask = task;
|
||||
return ^{
|
||||
[weakTask cancel];
|
||||
if (cancellationBlock) {
|
||||
@@ -257,7 +285,7 @@ RCT_EXPORT_MODULE()
|
||||
return callback(nil, nil);
|
||||
}
|
||||
|
||||
- (void)sendData:(NSData *)data forTask:(RCTDownloadTask *)task
|
||||
- (void)sendData:(NSData *)data forTask:(RCTNetworkTask *)task
|
||||
{
|
||||
if (data.length == 0) {
|
||||
return;
|
||||
@@ -276,7 +304,7 @@ RCT_EXPORT_MODULE()
|
||||
if (!responseText && data.length) {
|
||||
|
||||
// We don't have an encoding, or the encoding is incorrect, so now we
|
||||
// try to guess (unfortunately, this feature is available of iOS 8+ only)
|
||||
// try to guess (unfortunately, this feature is available in iOS 8+ only)
|
||||
if ([NSString respondsToSelector:@selector(stringEncodingForData:
|
||||
encodingOptions:
|
||||
convertedString:
|
||||
@@ -303,7 +331,7 @@ RCT_EXPORT_MODULE()
|
||||
incrementalUpdates:(BOOL)incrementalUpdates
|
||||
responseSender:(RCTResponseSenderBlock)responseSender
|
||||
{
|
||||
__block RCTDownloadTask *task;
|
||||
__block RCTNetworkTask *task;
|
||||
|
||||
RCTURLRequestProgressBlock uploadProgressBlock = ^(int64_t progress, int64_t total) {
|
||||
dispatch_async(_methodQueue, ^{
|
||||
@@ -314,16 +342,17 @@ RCT_EXPORT_MODULE()
|
||||
|
||||
void (^responseBlock)(NSURLResponse *) = ^(NSURLResponse *response) {
|
||||
dispatch_async(_methodQueue, ^{
|
||||
NSHTTPURLResponse *httpResponse = nil;
|
||||
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
|
||||
// Might be a local file request
|
||||
httpResponse = (NSHTTPURLResponse *)response;
|
||||
NSDictionary *headers;
|
||||
NSInteger status;
|
||||
if ([response isKindOfClass:[NSHTTPURLResponse class]]) { // Might be a local file request
|
||||
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
|
||||
headers = httpResponse.allHeaderFields ?: @{};
|
||||
status = httpResponse.statusCode;
|
||||
} else {
|
||||
headers = response.MIMEType ? @{@"Content-Type": response.MIMEType} : @{};
|
||||
status = 200;
|
||||
}
|
||||
NSArray *responseJSON = @[task.requestID,
|
||||
@(httpResponse.statusCode ?: 200),
|
||||
httpResponse.allHeaderFields ?: @{},
|
||||
];
|
||||
|
||||
NSArray *responseJSON = @[task.requestID, @(status), headers];
|
||||
[_bridge.eventDispatcher sendDeviceEventWithName:@"didReceiveNetworkResponse"
|
||||
body:responseJSON];
|
||||
});
|
||||
@@ -352,7 +381,7 @@ RCT_EXPORT_MODULE()
|
||||
});
|
||||
};
|
||||
|
||||
task = [self downloadTaskWithRequest:request completionBlock:completionBlock];
|
||||
task = [self networkTaskWithRequest:request completionBlock:completionBlock];
|
||||
task.incrementalDataBlock = incrementalDataBlock;
|
||||
task.responseBlock = responseBlock;
|
||||
task.uploadProgressBlock = uploadProgressBlock;
|
||||
@@ -361,19 +390,22 @@ RCT_EXPORT_MODULE()
|
||||
_tasksByRequestID[task.requestID] = task;
|
||||
responseSender(@[task.requestID]);
|
||||
}
|
||||
|
||||
[task start];
|
||||
}
|
||||
|
||||
#pragma mark - Public API
|
||||
|
||||
- (RCTDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
|
||||
- (RCTNetworkTask *)networkTaskWithRequest:(NSURLRequest *)request
|
||||
completionBlock:(RCTURLRequestCompletionBlock)completionBlock
|
||||
{
|
||||
id<RCTURLRequestHandler> handler = [self handlerForRequest:request];
|
||||
if (!handler) {
|
||||
RCTLogError(@"No suitable URL request handler found for %@", request.URL);
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [[RCTDownloadTask alloc] initWithRequest:request
|
||||
return [[RCTNetworkTask alloc] initWithRequest:request
|
||||
handler:handler
|
||||
completionBlock:completionBlock];
|
||||
}
|
||||
|
||||
@@ -29,6 +29,27 @@ var NOTIF_REGISTER_EVENT = 'remoteNotificationsRegistered';
|
||||
*
|
||||
* To get up and running, [configure your notifications with Apple](https://developer.apple.com/library/ios/documentation/IDEs/Conceptual/AppDistributionGuide/AddingCapabilities/AddingCapabilities.html#//apple_ref/doc/uid/TP40012582-CH26-SW6)
|
||||
* and your server-side system. To get an idea, [this is the Parse guide](https://parse.com/tutorials/ios-push-notifications).
|
||||
*
|
||||
* To enable support for `notification` and `register` events you need to augment your AppDelegate.
|
||||
*
|
||||
* At the top of your `AppDelegate.m`:
|
||||
*
|
||||
* `#import "RCTPushNotificationManager.h"`
|
||||
*
|
||||
* And then in your AppDelegate implementation add the following:
|
||||
*
|
||||
* ```
|
||||
* // Required for the register event.
|
||||
* - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
|
||||
* {
|
||||
* [RCTPushNotificationManager application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
|
||||
* }
|
||||
* // Required for the notification event.
|
||||
* - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification
|
||||
* {
|
||||
* [RCTPushNotificationManager application:application didReceiveRemoteNotification:notification];
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
class PushNotificationIOS {
|
||||
_data: Object;
|
||||
@@ -61,6 +82,13 @@ class PushNotificationIOS {
|
||||
RCTPushNotificationManager.scheduleLocalNotification(details);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels all scheduled localNotifications
|
||||
*/
|
||||
static cancelAllLocalNotifications() {
|
||||
RCTPushNotificationManager.cancelAllLocalNotifications();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the badge number for the app icon on the home screen
|
||||
*/
|
||||
|
||||
@@ -212,10 +212,14 @@ RCT_EXPORT_METHOD(presentLocalNotification:(UILocalNotification *)notification)
|
||||
[RCTSharedApplication() presentLocalNotificationNow:notification];
|
||||
}
|
||||
|
||||
|
||||
RCT_EXPORT_METHOD(scheduleLocalNotification:(UILocalNotification *)notification)
|
||||
{
|
||||
[RCTSharedApplication() scheduleLocalNotification:notification];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(cancelAllLocalNotifications)
|
||||
{
|
||||
[RCTSharedApplication() cancelAllLocalNotifications];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule SnapshotViewIOS
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
module.exports = require('UnimplementedView');
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule SnapshotView
|
||||
* @providesModule SnapshotViewIOS
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
@@ -14,12 +14,12 @@
|
||||
var Platform = require('Platform');
|
||||
var React = require('React');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var { TestModule } = require('NativeModules');
|
||||
var { TestModule, UIManager } = require('NativeModules');
|
||||
var View = require('View');
|
||||
|
||||
var requireNativeComponent = require('requireNativeComponent');
|
||||
|
||||
var SnapshotView = React.createClass({
|
||||
var SnapshotViewIOS = React.createClass({
|
||||
onDefaultAction: function(event: Object) {
|
||||
TestModule.verifySnapshot(TestModule.markTestPassed);
|
||||
},
|
||||
@@ -51,8 +51,11 @@ var style = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
var RCTSnapshot = Platform.OS === 'ios' ?
|
||||
requireNativeComponent('RCTSnapshot', SnapshotView) :
|
||||
// Verify that RCTSnapshot is part of the UIManager since it is only loaded
|
||||
// if you have linked against RCTTest like in tests, otherwise we will have
|
||||
// a warning printed out
|
||||
var RCTSnapshot = UIManager.RCTSnapshot ?
|
||||
requireNativeComponent('RCTSnapshot', SnapshotViewIOS) :
|
||||
View;
|
||||
|
||||
module.exports = SnapshotView;
|
||||
module.exports = SnapshotViewIOS;
|
||||
@@ -135,12 +135,12 @@ var WarningRow = React.createClass({
|
||||
});
|
||||
this.panGesture = PanResponder.create({
|
||||
onStartShouldSetPanResponder: () => {
|
||||
return !! this.dismissalSpring.getCurrentValue();
|
||||
return !!this.dismissalSpring.getCurrentValue();
|
||||
},
|
||||
onMoveShouldSetPanResponder: () => true,
|
||||
onPanResponderGrant: () => {
|
||||
this.isResponderOnlyToBlockTouches =
|
||||
!! this.dismissalSpring.getCurrentValue();
|
||||
!!this.dismissalSpring.getCurrentValue();
|
||||
},
|
||||
onPanResponderMove: (e, gestureState) => {
|
||||
if (this.isResponderOnlyToBlockTouches) {
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
// Require ReactNativeDefaultInjection first for its side effects of setting up
|
||||
// the JS environment
|
||||
var ReactNativeDefaultInjection = require('ReactNativeDefaultInjection');
|
||||
|
||||
var ReactChildren = require('ReactChildren');
|
||||
var ReactClass = require('ReactClass');
|
||||
var ReactComponent = require('ReactComponent');
|
||||
@@ -18,7 +22,6 @@ var ReactCurrentOwner = require('ReactCurrentOwner');
|
||||
var ReactElement = require('ReactElement');
|
||||
var ReactElementValidator = require('ReactElementValidator');
|
||||
var ReactInstanceHandles = require('ReactInstanceHandles');
|
||||
var ReactNativeDefaultInjection = require('ReactNativeDefaultInjection');
|
||||
var ReactNativeMount = require('ReactNativeMount');
|
||||
var ReactPropTypes = require('ReactPropTypes');
|
||||
var ReactUpdates = require('ReactUpdates');
|
||||
|
||||
@@ -47,6 +47,13 @@ var TransformPropTypes = {
|
||||
])
|
||||
),
|
||||
transformMatrix: TransformMatrixPropType,
|
||||
|
||||
/* Deprecated transform props used on Android only */
|
||||
scaleX: ReactPropTypes.number,
|
||||
scaleY: ReactPropTypes.number,
|
||||
rotation: ReactPropTypes.number,
|
||||
translateX: ReactPropTypes.number,
|
||||
translateY: ReactPropTypes.number,
|
||||
};
|
||||
|
||||
module.exports = TransformPropTypes;
|
||||
|
||||
@@ -86,7 +86,10 @@ static css_dim_t RCTMeasure(void *context, float width)
|
||||
parentProperties = [super processUpdatedProperties:applierBlocks
|
||||
parentProperties:parentProperties];
|
||||
|
||||
NSTextStorage *textStorage = [self buildTextStorageForWidth:self.frame.size.width];
|
||||
UIEdgeInsets padding = self.paddingAsInsets;
|
||||
CGFloat width = self.frame.size.width - (padding.left + padding.right);
|
||||
|
||||
NSTextStorage *textStorage = [self buildTextStorageForWidth:width];
|
||||
[applierBlocks addObject:^(RCTSparseArray *viewRegistry) {
|
||||
RCTText *view = viewRegistry[self.reactTag];
|
||||
view.textStorage = textStorage;
|
||||
@@ -105,9 +108,6 @@ static css_dim_t RCTMeasure(void *context, float width)
|
||||
|
||||
- (NSTextStorage *)buildTextStorageForWidth:(CGFloat)width
|
||||
{
|
||||
UIEdgeInsets padding = self.paddingAsInsets;
|
||||
width -= (padding.left + padding.right);
|
||||
|
||||
if (_cachedTextStorage && width == _cachedTextStorageWidth) {
|
||||
return _cachedTextStorage;
|
||||
}
|
||||
|
||||
@@ -113,7 +113,6 @@ var Text = React.createClass({
|
||||
isHighlighted: false,
|
||||
});
|
||||
},
|
||||
|
||||
getDefaultProps: function(): Object {
|
||||
return {
|
||||
allowFontScaling: true,
|
||||
|
||||
@@ -25,6 +25,7 @@ let stringifySafe = require('stringifySafe');
|
||||
let MODULE_IDS = 0;
|
||||
let METHOD_IDS = 1;
|
||||
let PARAMS = 2;
|
||||
let MIN_TIME_BETWEEN_FLUSHES_MS = 5;
|
||||
|
||||
let SPY_MODE = false;
|
||||
|
||||
@@ -53,9 +54,9 @@ class MessageQueue {
|
||||
this._methodTable = {};
|
||||
this._callbacks = [];
|
||||
this._callbackID = 0;
|
||||
this._lastFlush = 0;
|
||||
|
||||
[
|
||||
'processBatch',
|
||||
'invokeCallbackAndReturnFlushedQueue',
|
||||
'callFunctionReturnFlushedQueue',
|
||||
'flushedQueue',
|
||||
@@ -75,42 +76,30 @@ class MessageQueue {
|
||||
/**
|
||||
* Public APIs
|
||||
*/
|
||||
processBatch(batch) {
|
||||
callFunctionReturnFlushedQueue(module, method, args) {
|
||||
guard(() => {
|
||||
ReactUpdates.batchedUpdates(() => {
|
||||
batch.forEach((call) => {
|
||||
let method = call.method === 'callFunctionReturnFlushedQueue' ?
|
||||
'__callFunction' : '__invokeCallback';
|
||||
guard(() => this[method].apply(this, call.args));
|
||||
});
|
||||
|
||||
this.__callImmediates();
|
||||
});
|
||||
|
||||
// batchedUpdates might still trigger setImmediates
|
||||
while (JSTimersExecution.immediates.length) {
|
||||
ReactUpdates.batchedUpdates(() => {
|
||||
this.__callImmediates();
|
||||
});
|
||||
}
|
||||
this.__callFunction(module, method, args);
|
||||
this.__callImmediates();
|
||||
});
|
||||
|
||||
return this.__flushedQueue();
|
||||
}
|
||||
|
||||
callFunctionReturnFlushedQueue(module, method, args) {
|
||||
guard(() => this.__callFunction(module, method, args));
|
||||
return this.flushedQueue();
|
||||
}
|
||||
|
||||
invokeCallbackAndReturnFlushedQueue(cbID, args) {
|
||||
guard(() => this.__invokeCallback(cbID, args));
|
||||
guard(() => {
|
||||
this.__invokeCallback(cbID, args);
|
||||
this.__callImmediates();
|
||||
});
|
||||
|
||||
return this.flushedQueue();
|
||||
}
|
||||
|
||||
flushedQueue() {
|
||||
this.__callImmediates();
|
||||
return this.__flushedQueue();
|
||||
|
||||
let queue = this._queue;
|
||||
this._queue = [[],[],[]];
|
||||
return queue[0].length ? queue : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,11 +112,6 @@ class MessageQueue {
|
||||
BridgeProfiling.profileEnd();
|
||||
}
|
||||
|
||||
__flushedQueue() {
|
||||
let queue = this._queue;
|
||||
this._queue = [[],[],[]];
|
||||
return queue[0].length ? queue : null;
|
||||
}
|
||||
__nativeCall(module, method, params, onFail, onSucc) {
|
||||
if (onFail || onSucc) {
|
||||
// eventually delete old debug info
|
||||
@@ -143,6 +127,14 @@ class MessageQueue {
|
||||
this._queue[MODULE_IDS].push(module);
|
||||
this._queue[METHOD_IDS].push(method);
|
||||
this._queue[PARAMS].push(params);
|
||||
|
||||
var now = new Date().getTime();
|
||||
if (global.nativeFlushQueueImmediate &&
|
||||
now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS) {
|
||||
global.nativeFlushQueueImmediate(this._queue);
|
||||
this._queue = [[],[],[]];
|
||||
this._lastFlush = now;
|
||||
}
|
||||
if (__DEV__ && SPY_MODE && isFinite(module)) {
|
||||
console.log('JS->N : ' + this._remoteModuleTable[module] + '.' +
|
||||
this._remoteMethodTable[module][method] + '(' + JSON.stringify(params) + ')');
|
||||
@@ -151,6 +143,7 @@ class MessageQueue {
|
||||
|
||||
__callFunction(module, method, args) {
|
||||
BridgeProfiling.profile(() => `${module}.${method}(${stringifySafe(args)})`);
|
||||
this._lastFlush = new Date().getTime();
|
||||
if (isFinite(module)) {
|
||||
method = this._methodTable[module][method];
|
||||
module = this._moduleTable[module];
|
||||
@@ -166,6 +159,7 @@ class MessageQueue {
|
||||
__invokeCallback(cbID, args) {
|
||||
BridgeProfiling.profile(
|
||||
() => `MessageQueue.invokeCallback(${cbID}, ${stringifySafe(args)})`);
|
||||
this._lastFlush = new Date().getTime();
|
||||
let callback = this._callbacks[cbID];
|
||||
if (!callback || __DEV__) {
|
||||
let debug = this._debugInfo[cbID >> 1];
|
||||
@@ -192,7 +186,7 @@ class MessageQueue {
|
||||
let moduleNames = Object.keys(localModules);
|
||||
for (var i = 0, l = moduleNames.length; i < l; i++) {
|
||||
let moduleName = moduleNames[i];
|
||||
let methods = localModules[moduleName].methods;
|
||||
let methods = localModules[moduleName].methods || {};
|
||||
let moduleID = localModules[moduleName].moduleID;
|
||||
moduleTable[moduleID] = moduleName;
|
||||
methodTable[moduleID] = {};
|
||||
@@ -216,12 +210,16 @@ class MessageQueue {
|
||||
}
|
||||
|
||||
_genModule(module, moduleConfig) {
|
||||
let methodNames = Object.keys(moduleConfig.methods);
|
||||
let methods = moduleConfig.methods || {};
|
||||
let methodNames = Object.keys(methods);
|
||||
for (var i = 0, l = methodNames.length; i < l; i++) {
|
||||
let methodName = methodNames[i];
|
||||
let methodConfig = moduleConfig.methods[methodName];
|
||||
let methodConfig = methods[methodName];
|
||||
module[methodName] = this._genMethod(
|
||||
moduleConfig.moduleID, methodConfig.methodID, methodConfig.type);
|
||||
moduleConfig.moduleID,
|
||||
methodConfig.methodID,
|
||||
methodConfig.type || MethodTypes.remote
|
||||
);
|
||||
}
|
||||
Object.assign(module, moduleConfig.constants);
|
||||
return module;
|
||||
@@ -266,7 +264,7 @@ class MessageQueue {
|
||||
|
||||
}
|
||||
|
||||
function createErrorFromErrorData(errorData: ErrorData): Error {
|
||||
function createErrorFromErrorData(errorData: {message: string}): Error {
|
||||
var {
|
||||
message,
|
||||
...extraErrorInfo,
|
||||
|
||||
@@ -95,29 +95,6 @@ describe('MessageQueue', () => {
|
||||
queue.__invokeCallback(1);
|
||||
expect(() => queue.__invokeCallback(0)).toThrow();
|
||||
});
|
||||
|
||||
describe('processBatch', () => {
|
||||
|
||||
it('should call __invokeCallback for invokeCallbackAndReturnFlushedQueue', () => {
|
||||
queue.__invokeCallback = jasmine.createSpy();
|
||||
queue.processBatch([{
|
||||
method: 'invokeCallbackAndReturnFlushedQueue',
|
||||
args: [],
|
||||
}]);
|
||||
expect(queue.__invokeCallback.callCount).toEqual(1);
|
||||
});
|
||||
|
||||
it('should call __callFunction for callFunctionReturnFlushedQueue', () => {
|
||||
queue.__callFunction = jasmine.createSpy();
|
||||
queue.processBatch([{
|
||||
method: 'callFunctionReturnFlushedQueue',
|
||||
args: [],
|
||||
}]);
|
||||
expect(queue.__callFunction.callCount).toEqual(1);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
var remoteModulesConfig = {
|
||||
|
||||
@@ -1381,20 +1381,22 @@ static const size_t RCTSRFrameHeaderOverhead = 32;
|
||||
if (aStream.streamError) {
|
||||
[self _failWithError:aStream.streamError];
|
||||
} else {
|
||||
if (self.readyState != RCTSR_CLOSED) {
|
||||
self.readyState = RCTSR_CLOSED;
|
||||
_selfRetain = nil;
|
||||
}
|
||||
dispatch_async(_workQueue, ^{
|
||||
if (self.readyState != RCTSR_CLOSED) {
|
||||
self.readyState = RCTSR_CLOSED;
|
||||
_selfRetain = nil;
|
||||
}
|
||||
|
||||
if (!_sentClose && !_failed) {
|
||||
_sentClose = YES;
|
||||
// If we get closed in this state it's probably not clean because we should be sending this when we send messages
|
||||
[self _performDelegateBlock:^{
|
||||
if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) {
|
||||
[self.delegate webSocket:self didCloseWithCode:RCTSRStatusCodeGoingAway reason:@"Stream end encountered" wasClean:NO];
|
||||
}
|
||||
}];
|
||||
}
|
||||
if (!_sentClose && !_failed) {
|
||||
_sentClose = YES;
|
||||
// If we get closed in this state it's probably not clean because we should be sending this when we send messages
|
||||
[self _performDelegateBlock:^{
|
||||
if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) {
|
||||
[self.delegate webSocket:self didCloseWithCode:RCTSRStatusCodeGoingAway reason:@"Stream end encountered" wasClean:NO];
|
||||
}
|
||||
}];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user