Open source Android date and time pickers
Reviewed By: bestander Differential Revision: D2856486 fb-gh-sync-id: 0bb81136289e2f121387649765ba682103e4701b
This commit is contained in:
parent
5f0ef12cb5
commit
9a0539d2c4
|
@ -0,0 +1,117 @@
|
|||
/**
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react-native');
|
||||
var {
|
||||
DatePickerAndroid,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableWithoutFeedback,
|
||||
} = React;
|
||||
|
||||
var UIExplorerBlock = require('./UIExplorerBlock');
|
||||
var UIExplorerPage = require('./UIExplorerPage');
|
||||
|
||||
var DatePickerAndroidExample = React.createClass({
|
||||
|
||||
statics: {
|
||||
title: 'DatePickerAndroid',
|
||||
description: 'Standard Android date picker dialog',
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
presetDate: new Date(2020, 4, 5),
|
||||
allDate: new Date(2020, 4, 5),
|
||||
simpleText: 'pick a date',
|
||||
minText: 'pick a date, no earlier than today',
|
||||
maxText: 'pick a date, no later than today',
|
||||
presetText: 'pick a date, preset to 2020/5/5',
|
||||
allText: 'pick a date between 2020/5/1 and 2020/5/10',
|
||||
};
|
||||
},
|
||||
|
||||
async showPicker(stateKey, options) {
|
||||
try {
|
||||
var newState = {};
|
||||
const {action, year, month, day} = await DatePickerAndroid.open(options);
|
||||
if (action === DatePickerAndroid.dismissedAction) {
|
||||
newState[stateKey + 'Text'] = 'dismissed';
|
||||
} else {
|
||||
var date = new Date(year, month, day);
|
||||
newState[stateKey + 'Text'] = date.toLocaleDateString();
|
||||
newState[stateKey + 'Date'] = date;
|
||||
}
|
||||
this.setState(newState);
|
||||
} catch ({code, message}) {
|
||||
console.warn(`Error in example '${stateKey}': `, message);
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<UIExplorerPage title="DatePickerAndroid">
|
||||
<UIExplorerBlock title="Simple date picker">
|
||||
<TouchableWithoutFeedback
|
||||
onPress={this.showPicker.bind(this, 'simple', {date: this.state.simpleDate})}>
|
||||
<Text style={styles.text}>{this.state.simpleText}</Text>
|
||||
</TouchableWithoutFeedback>
|
||||
</UIExplorerBlock>
|
||||
<UIExplorerBlock title="Date picker with pre-set date">
|
||||
<TouchableWithoutFeedback
|
||||
onPress={this.showPicker.bind(this, 'preset', {date: this.state.presetDate})}>
|
||||
<Text style={styles.text}>{this.state.presetText}</Text>
|
||||
</TouchableWithoutFeedback>
|
||||
</UIExplorerBlock>
|
||||
<UIExplorerBlock title="Date picker with minDate">
|
||||
<TouchableWithoutFeedback
|
||||
onPress={this.showPicker.bind(this, 'min', {
|
||||
date: this.state.minDate,
|
||||
minDate: new Date(),
|
||||
})}>
|
||||
<Text style={styles.text}>{this.state.minText}</Text>
|
||||
</TouchableWithoutFeedback>
|
||||
</UIExplorerBlock>
|
||||
<UIExplorerBlock title="Date picker with maxDate">
|
||||
<TouchableWithoutFeedback
|
||||
onPress={this.showPicker.bind(this, 'max', {
|
||||
date: this.state.maxDate,
|
||||
maxDate: new Date(),
|
||||
})}>
|
||||
<Text style={styles.text}>{this.state.maxText}</Text>
|
||||
</TouchableWithoutFeedback>
|
||||
</UIExplorerBlock>
|
||||
<UIExplorerBlock title="Date picker with all options">
|
||||
<TouchableWithoutFeedback
|
||||
onPress={this.showPicker.bind(this, 'all', {
|
||||
date: this.state.allDate,
|
||||
minDate: new Date(2020, 4, 1),
|
||||
maxDate: new Date(2020, 4, 10),
|
||||
})}>
|
||||
<Text style={styles.text}>{this.state.allText}</Text>
|
||||
</TouchableWithoutFeedback>
|
||||
</UIExplorerBlock>
|
||||
</UIExplorerPage>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
text: {
|
||||
color: 'black',
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = DatePickerAndroidExample;
|
|
@ -30,7 +30,7 @@ var UIExplorerPage = require('./UIExplorerPage');
|
|||
|
||||
var ListViewSimpleExample = React.createClass({
|
||||
statics: {
|
||||
title: '<ListView> - Simple',
|
||||
title: '<ListView>',
|
||||
description: 'Performant, scrollable list of data.'
|
||||
},
|
||||
|
||||
|
@ -50,7 +50,7 @@ var ListViewSimpleExample = React.createClass({
|
|||
render: function() {
|
||||
return (
|
||||
<UIExplorerPage
|
||||
title={this.props.navigator ? null : '<ListView> - Simple'}
|
||||
title={this.props.navigator ? null : '<ListView>'}
|
||||
noSpacer={true}
|
||||
noScroll={true}>
|
||||
<ListView
|
||||
|
|
|
@ -27,6 +27,12 @@ const {
|
|||
const Item = PickerAndroid.Item;
|
||||
|
||||
const PickerAndroidExample = React.createClass({
|
||||
|
||||
statics: {
|
||||
title: '<PickerAndroid>',
|
||||
description: 'Provides multiple options to choose from, using either a dropdown menu or a dialog.',
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
selected1: 'key1',
|
||||
|
@ -124,19 +130,11 @@ const PickerAndroidExample = React.createClass({
|
|||
this.setState({mode: newMode});
|
||||
},
|
||||
|
||||
onSelect: function(key, value) {
|
||||
onSelect: function(key: string, value: string) {
|
||||
const newState = {};
|
||||
newState[key] = value;
|
||||
this.setState(newState);
|
||||
},
|
||||
});
|
||||
|
||||
exports.title = '<PickerAndroid>';
|
||||
exports.displayName = 'PickerAndroidExample';
|
||||
exports.description = 'The Android Picker component provides multiple options to choose from';
|
||||
exports.examples = [
|
||||
{
|
||||
title: 'PickerAndroidExample',
|
||||
render(): ReactElement { return <PickerAndroidExample />; }
|
||||
},
|
||||
];
|
||||
module.exports = PickerAndroidExample;
|
||||
|
|
|
@ -69,7 +69,7 @@ const RefreshControlExample = React.createClass({
|
|||
isRefreshing: false,
|
||||
loaded: 0,
|
||||
rowData: Array.from(new Array(20)).map(
|
||||
(val, i) => ({text: 'Initial row' + i, clicks: 0})),
|
||||
(val, i) => ({text: 'Initial row ' + i, clicks: 0})),
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -108,7 +108,7 @@ const RefreshControlExample = React.createClass({
|
|||
// prepend 10 items
|
||||
const rowData = Array.from(new Array(10))
|
||||
.map((val, i) => ({
|
||||
text: 'Loaded row' + (+this.state.loaded + i),
|
||||
text: 'Loaded row ' + (+this.state.loaded + i),
|
||||
clicks: 0,
|
||||
}))
|
||||
.concat(this.state.rowData);
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
/**
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react-native');
|
||||
var {
|
||||
TimePickerAndroid,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableWithoutFeedback,
|
||||
} = React;
|
||||
|
||||
var UIExplorerBlock = require('./UIExplorerBlock');
|
||||
var UIExplorerPage = require('./UIExplorerPage');
|
||||
|
||||
var TimePickerAndroidExample = React.createClass({
|
||||
|
||||
statics: {
|
||||
title: 'TimePickerAndroid',
|
||||
description: 'Standard Android time picker dialog',
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
// *Text, *Hour and *Minute are set by successCallback -- this updates the text with the time
|
||||
// picked by the user and makes it so the next time they open it the hour and minute they picked
|
||||
// before is displayed.
|
||||
return {
|
||||
isoFormatText: 'pick a time (24-hour format)',
|
||||
presetHour: 4,
|
||||
presetMinute: 4,
|
||||
presetText: 'pick a time, default: 4:04AM',
|
||||
simpleText: 'pick a time',
|
||||
};
|
||||
},
|
||||
|
||||
async showPicker(stateKey, options) {
|
||||
try {
|
||||
const {action, minute, hour} = await TimePickerAndroid.open(options);
|
||||
var newState = {};
|
||||
if (action === TimePickerAndroid.timeSetAction) {
|
||||
newState[stateKey + 'Text'] = _formatTime(hour, minute);
|
||||
newState[stateKey + 'Hour'] = hour;
|
||||
newState[stateKey + 'Minute'] = minute;
|
||||
} else if (action === TimePickerAndroid.dismissedAction) {
|
||||
newState[stateKey + 'Text'] = 'dismissed';
|
||||
}
|
||||
this.setState(newState);
|
||||
} catch ({code, message}) {
|
||||
console.warn(`Error in example '${stateKey}': `, message);
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<UIExplorerPage title="TimePickerAndroid">
|
||||
<UIExplorerBlock title="Simple time picker">
|
||||
<TouchableWithoutFeedback
|
||||
onPress={this.showPicker.bind(this, 'simple')}>
|
||||
<Text style={styles.text}>{this.state.simpleText}</Text>
|
||||
</TouchableWithoutFeedback>
|
||||
</UIExplorerBlock>
|
||||
<UIExplorerBlock title="Time picker with pre-set time">
|
||||
<TouchableWithoutFeedback
|
||||
onPress={this.showPicker.bind(this, 'preset', {
|
||||
hour: this.state.presetHour,
|
||||
minute: this.state.presetMinute,
|
||||
})}>
|
||||
<Text style={styles.text}>{this.state.presetText}</Text>
|
||||
</TouchableWithoutFeedback>
|
||||
</UIExplorerBlock>
|
||||
|
||||
<UIExplorerBlock title="Time picker with 24-hour time format">
|
||||
<TouchableWithoutFeedback
|
||||
onPress={this.showPicker.bind(this, 'isoFormat', {
|
||||
hour: this.state.isoFormatHour,
|
||||
minute: this.state.isoFormatMinute,
|
||||
is24Hour: true,
|
||||
})}>
|
||||
<Text style={styles.text}>{this.state.isoFormatText}</Text>
|
||||
</TouchableWithoutFeedback>
|
||||
</UIExplorerBlock>
|
||||
</UIExplorerPage>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns e.g. '3:05'.
|
||||
*/
|
||||
function _formatTime(hour, minute) {
|
||||
return hour + ':' + (minute < 10 ? '0' + minute : minute);
|
||||
}
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
text: {
|
||||
color: 'black',
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = TimePickerAndroidExample;
|
||||
|
|
@ -47,6 +47,7 @@ var APIS = [
|
|||
require('./BorderExample'),
|
||||
require('./CameraRollExample'),
|
||||
require('./ClipboardExample'),
|
||||
require('./DatePickerAndroidExample'),
|
||||
require('./GeolocationExample'),
|
||||
require('./IntentAndroidExample.android'),
|
||||
require('./LayoutEventsExample'),
|
||||
|
@ -54,6 +55,7 @@ var APIS = [
|
|||
require('./NetInfoExample'),
|
||||
require('./PanResponderExample'),
|
||||
require('./PointerEventsExample'),
|
||||
require('./TimePickerAndroidExample'),
|
||||
require('./TimerExample'),
|
||||
require('./ToastAndroidExample.android'),
|
||||
require('./XHRExample'),
|
||||
|
|
|
@ -26,10 +26,10 @@ module.exports = {
|
|||
*/
|
||||
getString() {
|
||||
if (arguments.length > 0) {
|
||||
let callback = arguments[0];
|
||||
console.warn('Clipboard.getString(callback) is deprecated. Use the returned Promise instead');
|
||||
Clipboard.getString().then(callback);
|
||||
return;
|
||||
let callback = arguments[0];
|
||||
console.warn('Clipboard.getString(callback) is deprecated. Use the returned Promise instead');
|
||||
Clipboard.getString().then(callback);
|
||||
return;
|
||||
}
|
||||
return Clipboard.getString();
|
||||
},
|
||||
|
@ -40,7 +40,7 @@ module.exports = {
|
|||
* Clipboard.setString('hello world');
|
||||
* }
|
||||
* ```
|
||||
* @param this parameter is content that will be set into clipboard.
|
||||
* @param the content to be stored in the clipboard.
|
||||
*/
|
||||
setString(content) {
|
||||
Clipboard.setString(content);
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
/**
|
||||
* 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 DatePickerAndroid
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const DatePickerModule = require('NativeModules').DatePickerAndroid;
|
||||
|
||||
/**
|
||||
* Convert a Date to a timestamp.
|
||||
*/
|
||||
function _toMillis(options: Object, key: string) {
|
||||
const dateVal = options[key];
|
||||
// Is it a Date object?
|
||||
if (typeof dateVal === 'object' && typeof dateVal.getMonth === 'function') {
|
||||
options[key] = dateVal.getTime();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the standard Android date picker dialog.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```
|
||||
* try {
|
||||
* const {action, year, month, day} = await DatePickerAndroid.open({
|
||||
* // Use `new Date()` for current date.
|
||||
* // May 25 2020. Month 0 is January.
|
||||
* date: new Date(2020, 4, 25)
|
||||
* });
|
||||
* if (action !== DatePickerAndroid.dismissedAction) {
|
||||
* // Selected year, month (0-11), day
|
||||
* }
|
||||
* } catch ({code, message}) {
|
||||
* console.warn('Cannot open date picker', message);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
class DatePickerAndroid {
|
||||
/**
|
||||
* Opens the standard Android date picker dialog.
|
||||
*
|
||||
* The available keys for the `options` object are:
|
||||
* * `date` (`Date` object or timestamp in milliseconds) - date to show by default
|
||||
* * `minDate` (`Date` or timestamp in milliseconds) - minimum date that can be selected
|
||||
* * `maxDate` (`Date` object or timestamp in milliseconds) - minimum date that can be selected
|
||||
*
|
||||
* Returns a Promise which will be invoked an object containing `action`, `year`, `month` (0-11),
|
||||
* `day` if the user picked a date. If the user dismissed the dialog, the Promise will
|
||||
* still be resolved with action being `DatePickerAndroid.dismissedAction` and all the other keys
|
||||
* being undefined. **Always** check whether the `action` before reading the values.
|
||||
*
|
||||
* Note the native date picker dialog has some UI glitches on Android 4 and lower
|
||||
* when using the `minDate` and `maxDate` options.
|
||||
*/
|
||||
static async open(options: Object): Promise<Object> {
|
||||
let optionsMs = options;
|
||||
if (optionsMs) {
|
||||
_toMillis(options, 'date');
|
||||
_toMillis(options, 'minDate');
|
||||
_toMillis(options, 'maxDate');
|
||||
}
|
||||
return DatePickerModule.open(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* A date has been selected.
|
||||
*/
|
||||
static get dateSetAction() { return 'dateSetAction'; }
|
||||
/**
|
||||
* The dialog has been dismissed.
|
||||
*/
|
||||
static get dismissedAction() { return 'dismissedAction'; }
|
||||
}
|
||||
|
||||
module.exports = DatePickerAndroid;
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* 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 DatePickerAndroid
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var warning = require('warning');
|
||||
|
||||
const DatePickerAndroid = {
|
||||
async open(options: Object): Promise<Object> {
|
||||
return Promise.reject({
|
||||
message: 'DatePickerAndroid is not supported on this platform.'
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = DatePickerAndroid;
|
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* 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 TimePickerAndroid
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const TimePickerModule = require('NativeModules').TimePickerAndroid;
|
||||
|
||||
/**
|
||||
* Opens the standard Android time picker dialog.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```
|
||||
* try {
|
||||
* const {action, hour, minute} = await TimePickerAndroid.open({
|
||||
* hour: 14,
|
||||
* minute: 0,
|
||||
* is24Hour: false, // Will display '2 PM'
|
||||
* });
|
||||
* if (action !== DatePickerAndroid.dismissedAction) {
|
||||
* // Selected hour (0-23), minute (0-59)
|
||||
* }
|
||||
* } catch ({code, message}) {
|
||||
* console.warn('Cannot open time picker', message);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
class TimePickerAndroid {
|
||||
|
||||
/**
|
||||
* Opens the standard Android time picker dialog.
|
||||
*
|
||||
* The available keys for the `options` object are:
|
||||
* * `hour` (0-23) - the hour to show, defaults to the current time
|
||||
* * `minute` (0-59) - the minute to show, defaults to the current time
|
||||
* * `is24Hour` (boolean) - If `true`, the picker uses the 24-hour format. If `false`,
|
||||
* the picker shows an AM/PM chooser. If undefined, the default for the current locale
|
||||
* is used.
|
||||
*
|
||||
* Returns a Promise which will be invoked an object containing `action`, `hour` (0-23),
|
||||
* `minute` (0-59) if the user picked a time. If the user dismissed the dialog, the Promise will
|
||||
* still be resolved with action being `TimePickerAndroid.dismissedAction` and all the other keys
|
||||
* being undefined. **Always** check whether the `action` before reading the values.
|
||||
*/
|
||||
static async open(options: Object): Promise<Object> {
|
||||
return TimePickerModule.open(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* A time has been selected.
|
||||
*/
|
||||
static get timeSetAction() { return 'timeSetAction'; }
|
||||
/**
|
||||
* The dialog has been dismissed.
|
||||
*/
|
||||
static get dismissedAction() { return 'dismissedAction'; }
|
||||
};
|
||||
|
||||
module.exports = TimePickerAndroid;
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* 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 TimePickerAndroid
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var warning = require('warning');
|
||||
|
||||
const TimePickerAndroid = {
|
||||
async open(options: Object): Promise<Object> {
|
||||
return Promise.reject({
|
||||
message: 'TimePickerAndroid is not supported on this platform.'
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = TimePickerAndroid;
|
|
@ -19,7 +19,7 @@ var ToastAndroid = {
|
|||
message: string,
|
||||
duration: number
|
||||
): void {
|
||||
warning(false, 'Cannot use ToastAndroid on iOS.');
|
||||
warning(false, 'ToastAndroid is not supported on this platform.');
|
||||
},
|
||||
|
||||
};
|
||||
|
|
|
@ -66,6 +66,7 @@ var ReactNative = {
|
|||
get BackAndroid() { return require('BackAndroid'); },
|
||||
get CameraRoll() { return require('CameraRoll'); },
|
||||
get Clipboard() { return require('Clipboard'); },
|
||||
get DatePickerAndroid() { return require('DatePickerAndroid'); },
|
||||
get Dimensions() { return require('Dimensions'); },
|
||||
get Easing() { return require('Easing'); },
|
||||
get ImagePickerIOS() { return require('ImagePickerIOS'); },
|
||||
|
@ -80,6 +81,7 @@ var ReactNative = {
|
|||
get Settings() { return require('Settings'); },
|
||||
get StatusBarIOS() { return require('StatusBarIOS'); },
|
||||
get StyleSheet() { return require('StyleSheet'); },
|
||||
get TimePickerAndroid() { return require('TimePickerAndroid'); },
|
||||
get UIManager() { return require('UIManager'); },
|
||||
get VibrationIOS() { return require('VibrationIOS'); },
|
||||
|
||||
|
|
|
@ -78,6 +78,7 @@ var ReactNative = Object.assign(Object.create(require('React')), {
|
|||
BackAndroid: require('BackAndroid'),
|
||||
CameraRoll: require('CameraRoll'),
|
||||
Clipboard: require('Clipboard'),
|
||||
DatePickerAndroid: require('DatePickerAndroid'),
|
||||
Dimensions: require('Dimensions'),
|
||||
Easing: require('Easing'),
|
||||
ImagePickerIOS: require('ImagePickerIOS'),
|
||||
|
@ -92,6 +93,7 @@ var ReactNative = Object.assign(Object.create(require('React')), {
|
|||
Settings: require('Settings'),
|
||||
StatusBarIOS: require('StatusBarIOS'),
|
||||
StyleSheet: require('StyleSheet'),
|
||||
TimePickerAndroid: require('TimePickerAndroid'),
|
||||
UIManager: require('UIManager'),
|
||||
VibrationIOS: require('VibrationIOS'),
|
||||
|
||||
|
|
|
@ -9,8 +9,6 @@
|
|||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Interface that represents a JavaScript Promise which can be passed to the native module as a
|
||||
* method parameter.
|
||||
|
@ -19,10 +17,38 @@ import javax.annotation.Nullable;
|
|||
* will be marked as "remoteAsync" and will return a promise when invoked from JavaScript.
|
||||
*/
|
||||
public interface Promise {
|
||||
|
||||
/**
|
||||
* Successfully resolve the Promise.
|
||||
*/
|
||||
void resolve(Object value);
|
||||
void reject(Throwable reason);
|
||||
|
||||
/**
|
||||
* Report an error which wasn't caused by an exception.
|
||||
*/
|
||||
void reject(String code, String message);
|
||||
|
||||
/**
|
||||
* Report an exception.
|
||||
*/
|
||||
void reject(String code, Throwable e);
|
||||
|
||||
/**
|
||||
* Report an exception with a custom error message.
|
||||
*/
|
||||
void reject(String code, String message, Throwable e);
|
||||
|
||||
/**
|
||||
* Report an error which wasn't caused by an exception.
|
||||
* @deprecated Prefer passing a module-specific error code to JS.
|
||||
* Using this method will pass the error code "EUNSPECIFIED".
|
||||
*/
|
||||
@Deprecated
|
||||
void reject(String reason);
|
||||
void reject(String code, Throwable extra);
|
||||
void reject(String code, String reason, @Nullable Throwable extra);
|
||||
void reject(String message);
|
||||
|
||||
/**
|
||||
* Report an exception, with default error code.
|
||||
* Useful in catch-all scenarios where it's unclear why the error occurred.
|
||||
*/
|
||||
void reject(Throwable reason);
|
||||
}
|
||||
|
|
|
@ -34,23 +34,28 @@ public class PromiseImpl implements Promise {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void reject(Throwable reason) {
|
||||
reject(DEFAULT_ERROR, reason.getMessage(), reason);
|
||||
public void reject(String code, String message) {
|
||||
reject(code, message, /*Throwable*/null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void reject(String reason) {
|
||||
reject(DEFAULT_ERROR, reason, null);
|
||||
public void reject(String message) {
|
||||
reject(DEFAULT_ERROR, message, /*Throwable*/null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reject(String code, Throwable extra) {
|
||||
reject(code, extra.getMessage(), extra);
|
||||
public void reject(String code, Throwable e) {
|
||||
reject(code, e.getMessage(), e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reject(String code, String reason, @Nullable Throwable extra) {
|
||||
public void reject(Throwable e) {
|
||||
reject(DEFAULT_ERROR, e.getMessage(), e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reject(String code, String message, @Nullable Throwable e) {
|
||||
if (mReject != null) {
|
||||
if (code == null) {
|
||||
code = DEFAULT_ERROR;
|
||||
|
@ -60,7 +65,7 @@ public class PromiseImpl implements Promise {
|
|||
// error instance.
|
||||
WritableNativeMap errorInfo = new WritableNativeMap();
|
||||
errorInfo.putString("code", code);
|
||||
errorInfo.putString("message", reason);
|
||||
errorInfo.putString("message", message);
|
||||
// TODO(8850038): add the stack trace info in, need to figure out way to serialize that
|
||||
mReject.invoke(errorInfo);
|
||||
}
|
||||
|
|
|
@ -59,7 +59,6 @@ import com.facebook.react.common.ReactConstants;
|
|||
*/
|
||||
public class CameraRollManager extends ReactContextBaseJavaModule {
|
||||
|
||||
private static final String TAG = "Catalyst/CameraRollManager";
|
||||
private static final String ERROR_UNABLE_TO_LOAD = "E_UNABLE_TO_LOAD";
|
||||
private static final String ERROR_UNABLE_TO_LOAD_PERMISSION = "E_UNABLE_TO_LOAD_PERMISSION";
|
||||
private static final String ERROR_UNABLE_TO_SAVE = "E_UNABLE_TO_SAVE";
|
||||
|
@ -145,7 +144,7 @@ public class CameraRollManager extends ReactContextBaseJavaModule {
|
|||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
|
||||
pictures.mkdirs();
|
||||
if (!pictures.isDirectory()) {
|
||||
mPromise.reject(ERROR_UNABLE_TO_LOAD, "External storage pictures directory not available", null);
|
||||
mPromise.reject(ERROR_UNABLE_TO_LOAD, "External storage pictures directory not available");
|
||||
return;
|
||||
}
|
||||
File dest = new File(pictures, source.getName());
|
||||
|
@ -178,7 +177,7 @@ public class CameraRollManager extends ReactContextBaseJavaModule {
|
|||
if (uri != null) {
|
||||
mPromise.resolve(uri.toString());
|
||||
} else {
|
||||
mPromise.reject(ERROR_UNABLE_TO_SAVE, "Could not add image to gallery", null);
|
||||
mPromise.reject(ERROR_UNABLE_TO_SAVE, "Could not add image to gallery");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -302,7 +301,7 @@ public class CameraRollManager extends ReactContextBaseJavaModule {
|
|||
Images.Media.DATE_TAKEN + " DESC, " + Images.Media.DATE_MODIFIED + " DESC LIMIT " +
|
||||
(mFirst + 1)); // set LIMIT to first + 1 so that we know how to populate page_info
|
||||
if (photos == null) {
|
||||
mPromise.reject(ERROR_UNABLE_TO_LOAD, "Could not get photos", null);
|
||||
mPromise.reject(ERROR_UNABLE_TO_LOAD, "Could not get photos");
|
||||
} else {
|
||||
try {
|
||||
putEdges(resolver, photos, response, mFirst);
|
||||
|
@ -412,7 +411,7 @@ public class CameraRollManager extends ReactContextBaseJavaModule {
|
|||
width = options.outWidth;
|
||||
height = options.outHeight;
|
||||
} catch (IOException e) {
|
||||
FLog.e(TAG, "Could not get width/height for " + photoUri.toString(), e);
|
||||
FLog.e(ReactConstants.TAG, "Could not get width/height for " + photoUri.toString(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
include_defs('//ReactAndroid/DEFS')
|
||||
|
||||
android_library(
|
||||
name = 'datepicker',
|
||||
srcs = glob(['**/*.java']),
|
||||
deps = [
|
||||
react_native_target('java/com/facebook/react/bridge:bridge'),
|
||||
react_native_target('java/com/facebook/react/common:common'),
|
||||
react_native_dep('third-party/android/support/v4:lib-support-v4'),
|
||||
react_native_dep('third-party/java/infer-annotations:infer-annotations'),
|
||||
react_native_dep('third-party/java/jsr-305:jsr-305'),
|
||||
],
|
||||
visibility = [
|
||||
'PUBLIC',
|
||||
],
|
||||
)
|
||||
|
||||
project_config(
|
||||
src_target = ':datepicker',
|
||||
)
|
|
@ -0,0 +1,103 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.datepicker;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.DatePickerDialog;
|
||||
import android.app.DatePickerDialog.OnDateSetListener;
|
||||
import android.app.Dialog;
|
||||
import android.app.DialogFragment;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnDismissListener;
|
||||
import android.os.Bundle;
|
||||
import android.widget.DatePicker;
|
||||
|
||||
@SuppressLint("ValidFragment")
|
||||
public class DatePickerDialogFragment extends DialogFragment {
|
||||
|
||||
/**
|
||||
* Minimum date supported by {@link DatePicker}, 01 Jan 1900
|
||||
*/
|
||||
private static final long DEFAULT_MIN_DATE = -2208988800001l;
|
||||
|
||||
@Nullable
|
||||
private OnDateSetListener mOnDateSetListener;
|
||||
@Nullable
|
||||
private OnDismissListener mOnDismissListener;
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
Bundle args = getArguments();
|
||||
return createDialog(args, getActivity(), mOnDateSetListener);
|
||||
}
|
||||
|
||||
/*package*/ static Dialog createDialog(
|
||||
Bundle args, Context activityContext, @Nullable OnDateSetListener onDateSetListener) {
|
||||
final Calendar c = Calendar.getInstance();
|
||||
if (args != null && args.containsKey(DatePickerDialogModule.ARG_DATE)) {
|
||||
c.setTimeInMillis(args.getLong(DatePickerDialogModule.ARG_DATE));
|
||||
}
|
||||
final int year = c.get(Calendar.YEAR);
|
||||
final int month = c.get(Calendar.MONTH);
|
||||
final int day = c.get(Calendar.DAY_OF_MONTH);
|
||||
|
||||
final DatePickerDialog dialog =
|
||||
new DismissableDatePickerDialog(activityContext, onDateSetListener, year, month, day);
|
||||
final DatePicker datePicker = dialog.getDatePicker();
|
||||
|
||||
if (args != null && args.containsKey(DatePickerDialogModule.ARG_MINDATE)) {
|
||||
// Set minDate to the beginning of the day. We need this because of clowniness in datepicker
|
||||
// that causes it to throw an exception if minDate is greater than the internal timestamp
|
||||
// that it generates from the y/m/d passed in the constructor.
|
||||
c.setTimeInMillis(args.getLong(DatePickerDialogModule.ARG_MINDATE));
|
||||
c.set(Calendar.HOUR_OF_DAY, 0);
|
||||
c.set(Calendar.MINUTE, 0);
|
||||
c.set(Calendar.SECOND, 0);
|
||||
c.set(Calendar.MILLISECOND, 0);
|
||||
datePicker.setMinDate(c.getTimeInMillis());
|
||||
} else {
|
||||
// This is to work around a bug in DatePickerDialog where it doesn't display a title showing
|
||||
// the date under certain conditions.
|
||||
datePicker.setMinDate(DEFAULT_MIN_DATE);
|
||||
}
|
||||
if (args != null && args.containsKey(DatePickerDialogModule.ARG_MAXDATE)) {
|
||||
// Set maxDate to the end of the day, same reason as for minDate.
|
||||
c.setTimeInMillis(args.getLong(DatePickerDialogModule.ARG_MAXDATE));
|
||||
c.set(Calendar.HOUR_OF_DAY, 23);
|
||||
c.set(Calendar.MINUTE, 59);
|
||||
c.set(Calendar.SECOND, 59);
|
||||
c.set(Calendar.MILLISECOND, 999);
|
||||
datePicker.setMaxDate(c.getTimeInMillis());
|
||||
}
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
super.onDismiss(dialog);
|
||||
if (mOnDismissListener != null) {
|
||||
mOnDismissListener.onDismiss(dialog);
|
||||
}
|
||||
}
|
||||
|
||||
/*package*/ void setOnDateSetListener(@Nullable OnDateSetListener onDateSetListener) {
|
||||
mOnDateSetListener = onDateSetListener;
|
||||
}
|
||||
|
||||
/*package*/ void setOnDismissListener(@Nullable OnDismissListener onDismissListener) {
|
||||
mOnDismissListener = onDismissListener;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,176 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.datepicker;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.DatePickerDialog.OnDateSetListener;
|
||||
import android.app.DialogFragment;
|
||||
import android.app.FragmentManager;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnDismissListener;
|
||||
import android.os.Bundle;
|
||||
import android.widget.DatePicker;
|
||||
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.bridge.WritableNativeMap;
|
||||
import com.facebook.react.common.annotations.VisibleForTesting;
|
||||
|
||||
/**
|
||||
* {@link NativeModule} that allows JS to show a native date picker dialog and get called back when
|
||||
* the user selects a date.
|
||||
*/
|
||||
public class DatePickerDialogModule extends ReactContextBaseJavaModule {
|
||||
|
||||
@VisibleForTesting
|
||||
public static final String FRAGMENT_TAG = "DatePickerAndroid";
|
||||
|
||||
private static final String ERROR_NO_ACTIVITY = "E_NO_ACTIVITY";
|
||||
|
||||
/* package */ static final String ARG_DATE = "date";
|
||||
/* package */ static final String ARG_MINDATE = "minDate";
|
||||
/* package */ static final String ARG_MAXDATE = "maxDate";
|
||||
|
||||
/* package */ static final String ACTION_DATE_SET = "dateSetAction";
|
||||
/* package */ static final String ACTION_DISMISSED = "dismissedAction";
|
||||
|
||||
public DatePickerDialogModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "DatePickerAndroid";
|
||||
}
|
||||
|
||||
private class DatePickerDialogListener implements OnDateSetListener, OnDismissListener {
|
||||
|
||||
private final Promise mPromise;
|
||||
private boolean mPromiseResolved = false;
|
||||
|
||||
public DatePickerDialogListener(final Promise promise) {
|
||||
mPromise = promise;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDateSet(DatePicker view, int year, int month, int day) {
|
||||
if (!mPromiseResolved && getReactApplicationContext().hasActiveCatalystInstance()) {
|
||||
WritableMap result = new WritableNativeMap();
|
||||
result.putString("action", ACTION_DATE_SET);
|
||||
result.putInt("year", year);
|
||||
result.putInt("month", month);
|
||||
result.putInt("day", day);
|
||||
mPromise.resolve(result);
|
||||
mPromiseResolved = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
if (!mPromiseResolved && getReactApplicationContext().hasActiveCatalystInstance()) {
|
||||
WritableMap result = new WritableNativeMap();
|
||||
result.putString("action", ACTION_DISMISSED);
|
||||
mPromise.resolve(result);
|
||||
mPromiseResolved = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a date picker dialog.
|
||||
*
|
||||
* @param options a map containing options. Available keys are:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@code date} (timestamp in milliseconds) the date to show by default</li>
|
||||
* <li>
|
||||
* {@code minDate} (timestamp in milliseconds) the minimum date the user should be allowed
|
||||
* to select
|
||||
* </li>
|
||||
* <li>
|
||||
* {@code maxDate} (timestamp in milliseconds) the maximum date the user should be allowed
|
||||
* to select
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* @param promise This will be invoked with parameters action, year,
|
||||
* month (0-11), day, where action is {@code dateSetAction} or
|
||||
* {@code dismissedAction}, depending on what the user did. If the action is
|
||||
* dismiss, year, month and date are undefined.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void open(@Nullable final ReadableMap options, Promise promise) {
|
||||
Activity activity = getCurrentActivity();
|
||||
if (activity == null) {
|
||||
promise.reject(
|
||||
ERROR_NO_ACTIVITY,
|
||||
"Tried to open a DatePicker dialog while not attached to an Activity");
|
||||
return;
|
||||
}
|
||||
// We want to support both android.app.Activity and the pre-Honeycomb FragmentActivity
|
||||
// (for apps that use it for legacy reasons). This unfortunately leads to some code duplication.
|
||||
if (activity instanceof android.support.v4.app.FragmentActivity) {
|
||||
android.support.v4.app.FragmentManager fragmentManager =
|
||||
((android.support.v4.app.FragmentActivity) activity).getSupportFragmentManager();
|
||||
android.support.v4.app.DialogFragment oldFragment =
|
||||
(android.support.v4.app.DialogFragment)fragmentManager.findFragmentByTag(FRAGMENT_TAG);
|
||||
if (oldFragment != null) {
|
||||
oldFragment.dismiss();
|
||||
}
|
||||
SupportDatePickerDialogFragment fragment = new SupportDatePickerDialogFragment();
|
||||
if (options != null) {
|
||||
final Bundle args = createFragmentArguments(options);
|
||||
fragment.setArguments(args);
|
||||
}
|
||||
final DatePickerDialogListener listener = new DatePickerDialogListener(promise);
|
||||
fragment.setOnDismissListener(listener);
|
||||
fragment.setOnDateSetListener(listener);
|
||||
fragment.show(fragmentManager, FRAGMENT_TAG);
|
||||
} else {
|
||||
FragmentManager fragmentManager = activity.getFragmentManager();
|
||||
DialogFragment oldFragment = (DialogFragment)fragmentManager.findFragmentByTag(FRAGMENT_TAG);
|
||||
if (oldFragment != null) {
|
||||
oldFragment.dismiss();
|
||||
}
|
||||
DatePickerDialogFragment fragment = new DatePickerDialogFragment();
|
||||
if (options != null) {
|
||||
final Bundle args = createFragmentArguments(options);
|
||||
fragment.setArguments(args);
|
||||
}
|
||||
final DatePickerDialogListener listener = new DatePickerDialogListener(promise);
|
||||
fragment.setOnDismissListener(listener);
|
||||
fragment.setOnDateSetListener(listener);
|
||||
fragment.show(fragmentManager, FRAGMENT_TAG);
|
||||
}
|
||||
}
|
||||
|
||||
private Bundle createFragmentArguments(ReadableMap options) {
|
||||
final Bundle args = new Bundle();
|
||||
if (options.hasKey(ARG_DATE) && !options.isNull(ARG_DATE)) {
|
||||
args.putLong(ARG_DATE, (long) options.getDouble(ARG_DATE));
|
||||
}
|
||||
if (options.hasKey(ARG_MINDATE) && !options.isNull(ARG_MINDATE)) {
|
||||
args.putLong(ARG_MINDATE, (long) options.getDouble(ARG_MINDATE));
|
||||
}
|
||||
if (options.hasKey(ARG_MAXDATE) && !options.isNull(ARG_MAXDATE)) {
|
||||
args.putLong(ARG_MAXDATE, (long) options.getDouble(ARG_MAXDATE));
|
||||
}
|
||||
return args;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.datepicker;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.app.DatePickerDialog;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Certain versions of Android (Jellybean-KitKat) have a bug where when dismissed, the
|
||||
* {@link DatePickerDialog} still calls the OnDateSetListener. This class works around that issue.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* See: <a href="https://code.google.com/p/android/issues/detail?id=34833">Issue 34833</a>
|
||||
* </p>
|
||||
*/
|
||||
public class DismissableDatePickerDialog extends DatePickerDialog {
|
||||
|
||||
public DismissableDatePickerDialog(
|
||||
Context context,
|
||||
@Nullable OnDateSetListener callback,
|
||||
int year,
|
||||
int monthOfYear,
|
||||
int dayOfMonth) {
|
||||
super(context, callback, year, monthOfYear, dayOfMonth);
|
||||
}
|
||||
|
||||
public DismissableDatePickerDialog(
|
||||
Context context,
|
||||
int theme,
|
||||
@Nullable OnDateSetListener callback,
|
||||
int year,
|
||||
int monthOfYear,
|
||||
int dayOfMonth) {
|
||||
super(context, theme, callback, year, monthOfYear, dayOfMonth);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
// do *not* call super.onStop() on KitKat on lower, as that would erroneously call the
|
||||
// OnDateSetListener when the dialog is dismissed, or call it twice when "OK" is pressed.
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
|
||||
super.onStop();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.datepicker;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.DatePickerDialog.OnDateSetListener;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnDismissListener;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
|
||||
@SuppressLint("ValidFragment")
|
||||
public class SupportDatePickerDialogFragment extends DialogFragment {
|
||||
|
||||
@Nullable
|
||||
private OnDateSetListener mOnDateSetListener;
|
||||
@Nullable
|
||||
private OnDismissListener mOnDismissListener;
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final Bundle args = getArguments();
|
||||
return DatePickerDialogFragment.createDialog(args, getActivity(), mOnDateSetListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
super.onDismiss(dialog);
|
||||
if (mOnDismissListener != null) {
|
||||
mOnDismissListener.onDismiss(dialog);
|
||||
}
|
||||
}
|
||||
|
||||
/*package*/ void setOnDateSetListener(@Nullable OnDateSetListener onDateSetListener) {
|
||||
mOnDateSetListener = onDateSetListener;
|
||||
}
|
||||
|
||||
/*package*/ void setOnDismissListener(@Nullable OnDismissListener onDismissListener) {
|
||||
mOnDismissListener = onDismissListener;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
include_defs('//ReactAndroid/DEFS')
|
||||
|
||||
android_library(
|
||||
name = 'timepicker',
|
||||
srcs = glob(['**/*.java']),
|
||||
deps = [
|
||||
react_native_target('java/com/facebook/react/bridge:bridge'),
|
||||
react_native_target('java/com/facebook/react/common:common'),
|
||||
react_native_dep('third-party/android/support/v4:lib-support-v4'),
|
||||
react_native_dep('third-party/java/infer-annotations:infer-annotations'),
|
||||
react_native_dep('third-party/java/jsr-305:jsr-305'),
|
||||
],
|
||||
visibility = [
|
||||
'PUBLIC',
|
||||
],
|
||||
)
|
||||
|
||||
project_config(
|
||||
src_target = ':timepicker',
|
||||
)
|
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.timepicker;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.app.TimePickerDialog;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Certain versions of Android (Jellybean-KitKat) have a bug where when dismissed, the
|
||||
* {@link TimePickerDialog} still calls the OnTimeSetListener. This class works around that issue
|
||||
* by *not* calling super.onStop on KitKat on lower, as that would erroneously call the
|
||||
* OnTimeSetListener when the dialog is dismissed, or call it twice when "OK" is pressed.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* See: <a href="https://code.google.com/p/android/issues/detail?id=34833">Issue 34833</a>
|
||||
* </p>
|
||||
*/
|
||||
public class DismissableTimePickerDialog extends TimePickerDialog {
|
||||
|
||||
public DismissableTimePickerDialog(
|
||||
Context context,
|
||||
@Nullable OnTimeSetListener callback,
|
||||
int hourOfDay,
|
||||
int minute,
|
||||
boolean is24HourView) {
|
||||
super(context, callback, hourOfDay, minute, is24HourView);
|
||||
}
|
||||
|
||||
public DismissableTimePickerDialog(
|
||||
Context context,
|
||||
int theme,
|
||||
@Nullable OnTimeSetListener callback,
|
||||
int hourOfDay,
|
||||
int minute,
|
||||
boolean is24HourView) {
|
||||
super(context, theme, callback, hourOfDay, minute, is24HourView);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
|
||||
super.onStop();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.timepicker;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.app.TimePickerDialog.OnTimeSetListener;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnDismissListener;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
|
||||
@SuppressWarnings("ValidFragment")
|
||||
public class SupportTimePickerDialogFragment extends DialogFragment {
|
||||
|
||||
@Nullable
|
||||
private OnTimeSetListener mOnTimeSetListener;
|
||||
@Nullable
|
||||
private OnDismissListener mOnDismissListener;
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final Bundle args = getArguments();
|
||||
return TimePickerDialogFragment.createDialog(args, getActivity(), mOnTimeSetListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
super.onDismiss(dialog);
|
||||
if (mOnDismissListener != null) {
|
||||
mOnDismissListener.onDismiss(dialog);
|
||||
}
|
||||
}
|
||||
|
||||
public void setOnDismissListener(@Nullable OnDismissListener onDismissListener) {
|
||||
mOnDismissListener = onDismissListener;
|
||||
}
|
||||
|
||||
public void setOnTimeSetListener(@Nullable OnTimeSetListener onTimeSetListener) {
|
||||
mOnTimeSetListener = onTimeSetListener;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.timepicker;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.app.DialogFragment;
|
||||
import android.app.TimePickerDialog.OnTimeSetListener;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnDismissListener;
|
||||
import android.os.Bundle;
|
||||
import android.text.format.DateFormat;
|
||||
|
||||
@SuppressWarnings("ValidFragment")
|
||||
public class TimePickerDialogFragment extends DialogFragment {
|
||||
|
||||
@Nullable
|
||||
private OnTimeSetListener mOnTimeSetListener;
|
||||
@Nullable
|
||||
private OnDismissListener mOnDismissListener;
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final Bundle args = getArguments();
|
||||
return createDialog(args, getActivity(), mOnTimeSetListener);
|
||||
}
|
||||
|
||||
/*package*/ static Dialog createDialog(
|
||||
Bundle args, Context activityContext, @Nullable OnTimeSetListener onTimeSetListener
|
||||
) {
|
||||
final Calendar now = Calendar.getInstance();
|
||||
int hour = now.get(Calendar.HOUR_OF_DAY);
|
||||
int minute = now.get(Calendar.MINUTE);
|
||||
boolean is24hour = DateFormat.is24HourFormat(activityContext);
|
||||
|
||||
if (args != null) {
|
||||
hour = args.getInt(TimePickerDialogModule.ARG_HOUR, now.get(Calendar.HOUR_OF_DAY));
|
||||
minute = args.getInt(TimePickerDialogModule.ARG_MINUTE, now.get(Calendar.MINUTE));
|
||||
is24hour = args.getBoolean(
|
||||
TimePickerDialogModule.ARG_IS24HOUR,
|
||||
DateFormat.is24HourFormat(activityContext));
|
||||
}
|
||||
|
||||
return new DismissableTimePickerDialog(
|
||||
activityContext,
|
||||
onTimeSetListener,
|
||||
hour,
|
||||
minute,
|
||||
is24hour);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
super.onDismiss(dialog);
|
||||
if (mOnDismissListener != null) {
|
||||
mOnDismissListener.onDismiss(dialog);
|
||||
}
|
||||
}
|
||||
|
||||
public void setOnDismissListener(@Nullable OnDismissListener onDismissListener) {
|
||||
mOnDismissListener = onDismissListener;
|
||||
}
|
||||
|
||||
public void setOnTimeSetListener(@Nullable OnTimeSetListener onTimeSetListener) {
|
||||
mOnTimeSetListener = onTimeSetListener;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.facebook.react.modules.timepicker;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.DialogFragment;
|
||||
import android.app.FragmentManager;
|
||||
import android.app.TimePickerDialog.OnTimeSetListener;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnDismissListener;
|
||||
import android.os.Bundle;
|
||||
import android.widget.TimePicker;
|
||||
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.bridge.WritableNativeMap;
|
||||
import com.facebook.react.common.annotations.VisibleForTesting;
|
||||
|
||||
/**
|
||||
* {@link NativeModule} that allows JS to show a native time picker dialog and get called back when
|
||||
* the user selects a time.
|
||||
*/
|
||||
public class TimePickerDialogModule extends ReactContextBaseJavaModule {
|
||||
|
||||
@VisibleForTesting
|
||||
public static final String FRAGMENT_TAG = "TimePickerAndroid";
|
||||
|
||||
private static final String ERROR_NO_ACTIVITY = "E_NO_ACTIVITY";
|
||||
|
||||
/* package */ static final String ARG_HOUR = "hour";
|
||||
/* package */ static final String ARG_MINUTE = "minute";
|
||||
/* package */ static final String ARG_IS24HOUR = "is24Hour";
|
||||
/* package */ static final String ACTION_TIME_SET = "timeSetAction";
|
||||
/* package */ static final String ACTION_DISMISSED = "dismissedAction";
|
||||
|
||||
public TimePickerDialogModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "TimePickerAndroid";
|
||||
}
|
||||
|
||||
private class TimePickerDialogListener implements OnTimeSetListener, OnDismissListener {
|
||||
|
||||
private final Promise mPromise;
|
||||
private boolean mPromiseResolved = false;
|
||||
|
||||
public TimePickerDialogListener(Promise promise) {
|
||||
mPromise = promise;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimeSet(TimePicker view, int hour, int minute) {
|
||||
if (!mPromiseResolved && getReactApplicationContext().hasActiveCatalystInstance()) {
|
||||
WritableMap result = new WritableNativeMap();
|
||||
result.putString("action", ACTION_TIME_SET);
|
||||
result.putInt("hour", hour);
|
||||
result.putInt("minute", minute);
|
||||
mPromise.resolve(result);
|
||||
mPromiseResolved = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
if (!mPromiseResolved && getReactApplicationContext().hasActiveCatalystInstance()) {
|
||||
WritableMap result = new WritableNativeMap();
|
||||
result.putString("action", ACTION_DISMISSED);
|
||||
mPromise.resolve(result);
|
||||
mPromiseResolved = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void open(@Nullable final ReadableMap options, Promise promise) {
|
||||
|
||||
Activity activity = getCurrentActivity();
|
||||
if (activity == null) {
|
||||
promise.reject(
|
||||
ERROR_NO_ACTIVITY,
|
||||
"Tried to open a TimePicker dialog while not attached to an Activity");
|
||||
return;
|
||||
}
|
||||
// We want to support both android.app.Activity and the pre-Honeycomb FragmentActivity
|
||||
// (for apps that use it for legacy reasons). This unfortunately leads to some code duplication.
|
||||
if (activity instanceof android.support.v4.app.FragmentActivity) {
|
||||
android.support.v4.app.FragmentManager fragmentManager =
|
||||
((android.support.v4.app.FragmentActivity) activity).getSupportFragmentManager();
|
||||
android.support.v4.app.DialogFragment oldFragment =
|
||||
(android.support.v4.app.DialogFragment)fragmentManager.findFragmentByTag(FRAGMENT_TAG);
|
||||
if (oldFragment != null) {
|
||||
oldFragment.dismiss();
|
||||
}
|
||||
SupportTimePickerDialogFragment fragment = new SupportTimePickerDialogFragment();
|
||||
if (options != null) {
|
||||
Bundle args = createFragmentArguments(options);
|
||||
fragment.setArguments(args);
|
||||
}
|
||||
TimePickerDialogListener listener = new TimePickerDialogListener(promise);
|
||||
fragment.setOnDismissListener(listener);
|
||||
fragment.setOnTimeSetListener(listener);
|
||||
fragment.show(fragmentManager, FRAGMENT_TAG);
|
||||
} else {
|
||||
FragmentManager fragmentManager = activity.getFragmentManager();
|
||||
DialogFragment oldFragment = (DialogFragment) fragmentManager.findFragmentByTag(FRAGMENT_TAG);
|
||||
if (oldFragment != null) {
|
||||
oldFragment.dismiss();
|
||||
}
|
||||
TimePickerDialogFragment fragment = new TimePickerDialogFragment();
|
||||
if (options != null) {
|
||||
final Bundle args = createFragmentArguments(options);
|
||||
fragment.setArguments(args);
|
||||
}
|
||||
TimePickerDialogListener listener = new TimePickerDialogListener(promise);
|
||||
fragment.setOnDismissListener(listener);
|
||||
fragment.setOnTimeSetListener(listener);
|
||||
fragment.show(fragmentManager, FRAGMENT_TAG);
|
||||
}
|
||||
}
|
||||
|
||||
private Bundle createFragmentArguments(ReadableMap options) {
|
||||
final Bundle args = new Bundle();
|
||||
if (options.hasKey(ARG_HOUR) && !options.isNull(ARG_HOUR)) {
|
||||
args.putInt(ARG_HOUR, options.getInt(ARG_HOUR));
|
||||
}
|
||||
if (options.hasKey(ARG_MINUTE) && !options.isNull(ARG_MINUTE)) {
|
||||
args.putInt(ARG_MINUTE, options.getInt(ARG_MINUTE));
|
||||
}
|
||||
if (options.hasKey(ARG_IS24HOUR) && !options.isNull(ARG_IS24HOUR)) {
|
||||
args.putBoolean(ARG_IS24HOUR, options.getBoolean(ARG_IS24HOUR));
|
||||
}
|
||||
return args;
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@ android_library(
|
|||
react_native_target('java/com/facebook/react/modules/camera:camera'),
|
||||
react_native_target('java/com/facebook/react/modules/clipboard:clipboard'),
|
||||
react_native_target('java/com/facebook/react/modules/core:core'),
|
||||
react_native_target('java/com/facebook/react/modules/datepicker:datepicker'),
|
||||
react_native_target('java/com/facebook/react/modules/debug:debug'),
|
||||
react_native_target('java/com/facebook/react/modules/dialog:dialog'),
|
||||
react_native_target('java/com/facebook/react/modules/fresco:fresco'),
|
||||
|
@ -36,6 +37,7 @@ android_library(
|
|||
react_native_target('java/com/facebook/react/modules/netinfo:netinfo'),
|
||||
react_native_target('java/com/facebook/react/modules/network:network'),
|
||||
react_native_target('java/com/facebook/react/modules/storage:storage'),
|
||||
react_native_target('java/com/facebook/react/modules/timepicker:timepicker'),
|
||||
react_native_target('java/com/facebook/react/modules/toast:toast'),
|
||||
react_native_target('java/com/facebook/react/uimanager:uimanager'),
|
||||
react_native_target('java/com/facebook/react/modules/websocket:websocket'),
|
||||
|
|
|
@ -17,16 +17,19 @@ import com.facebook.react.ReactPackage;
|
|||
import com.facebook.react.bridge.JavaScriptModule;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.modules.appstate.AppStateModule;
|
||||
import com.facebook.react.modules.camera.CameraRollManager;
|
||||
import com.facebook.react.modules.clipboard.ClipboardModule;
|
||||
import com.facebook.react.modules.dialog.DialogModule;
|
||||
import com.facebook.react.modules.datepicker.DatePickerDialogModule;
|
||||
import com.facebook.react.modules.fresco.FrescoModule;
|
||||
import com.facebook.react.modules.intent.IntentModule;
|
||||
import com.facebook.react.modules.location.LocationModule;
|
||||
import com.facebook.react.modules.netinfo.NetInfoModule;
|
||||
import com.facebook.react.modules.network.NetworkingModule;
|
||||
import com.facebook.react.modules.storage.AsyncStorageModule;
|
||||
import com.facebook.react.modules.timepicker.TimePickerDialogModule;
|
||||
import com.facebook.react.modules.toast.ToastModule;
|
||||
import com.facebook.react.modules.appstate.AppStateModule;
|
||||
import com.facebook.react.modules.websocket.WebSocketModule;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
import com.facebook.react.views.art.ARTRenderableViewManager;
|
||||
|
@ -50,7 +53,6 @@ import com.facebook.react.views.view.ReactViewManager;
|
|||
import com.facebook.react.views.viewpager.ReactViewPagerManager;
|
||||
import com.facebook.react.views.swiperefresh.SwipeRefreshLayoutManager;
|
||||
import com.facebook.react.views.webview.ReactWebViewManager;
|
||||
import com.facebook.react.modules.clipboard.ClipboardModule;
|
||||
|
||||
/**
|
||||
* Package defining basic modules and view managers.
|
||||
|
@ -64,12 +66,14 @@ public class MainReactPackage implements ReactPackage {
|
|||
new AsyncStorageModule(reactContext),
|
||||
new CameraRollManager(reactContext),
|
||||
new ClipboardModule(reactContext),
|
||||
new DatePickerDialogModule(reactContext),
|
||||
new DialogModule(reactContext),
|
||||
new FrescoModule(reactContext),
|
||||
new IntentModule(reactContext),
|
||||
new LocationModule(reactContext),
|
||||
new NetworkingModule(reactContext),
|
||||
new NetInfoModule(reactContext),
|
||||
new TimePickerDialogModule(reactContext),
|
||||
new ToastModule(reactContext),
|
||||
new WebSocketModule(reactContext));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue