react-native-dialogs/DialogAndroid.js

498 lines
18 KiB
JavaScript

// @flow
import { NativeModules } from 'react-native'
import processColor from 'react-native/Libraries/StyleSheet/processColor'
import type { ColorValue } from 'react-native/Libraries/StyleSheet/StyleSheetTypes'
type IdKey = string | 'id';
type LabelKey = string | 'label';
type ListItem = { label:string, id?:any };
type OptionsAlert = {|
...OptionsCommon,
|}
type OptionsCommon = {|
title?: null | string,
titleColor?: ColorValue,
content?: null | string,
contentIsHtml?: boolean,
contentColor?: string,
positiveText?: string, // default "OK"
negativeText?: string,
neutralText?: string,
positiveColor?: ColorValue,
negativeColor?: ColorValue,
neutralColor?: ColorValue,
backgroundColor?: ColorValue,
cancelable?: boolean,
linkColor?: ColorValue, // applies if contentIsHtml is true, and there are <a> elements in content string
forceStacking?: boolean,
checkboxLabel?: string,
checkboxDefaultValue?: boolean
|}
type ListItemJustLabel = { label:string };
type ListItemJustId = { id:string };
type ListItemFull = { label:string, id:any };
type ListItemBare = {};
type OptionsRadioList = {|
maxNumberOfItems?: number,
type: typeof ListType.listRadio,
widgetColor?: ColorValue // radio color
|}
type OptionsCheckboxList = {|
maxNumberOfItems?: number,
type: typeof ListType.listCheckbox,
neutralIsClear?: boolean,
widgetColor?: ColorValue // checkbox color
|}
type OptionsPicker = {|
...OptionsCommon,
type?: typeof ListType.listPlain,
maxNumberOfItems?: number,
items: ListItemJustLabel[],
|} | {|
...OptionsCommon,
type?: typeof ListType.listPlain,
maxNumberOfItems?: number,
items: ListItemBare[],
labelKey: string
|} | {|
// radio - no preselected
...OptionsCommon,
...OptionsRadioList,
items: ListItemJustLabel[],
|} | {|
// radio - no preselected
...OptionsCommon,
...OptionsRadioList,
items: ListItemBare[],
labelKey: string
|} | {|
// radio - preselected - ListItemFull
...OptionsCommon,
...OptionsRadioList,
items: ListItemFull[],
selectedId: any
|} | {|
// radio - preselected - ListItemJustlabel
...OptionsCommon,
...OptionsRadioList,
items: ListItemJustLabel[],
idKey: string,
selectedId: any
|} | {|
// radio - preselected - ListItemJustId
...OptionsCommon,
...OptionsRadioList,
items: ListItemJustId[],
labelKey: string,
selectedId: any
|} | {|
// radio - preselected - ListItemBare
...OptionsCommon,
...OptionsRadioList,
items: ListItemBare[],
idKey: string,
labelKey: string,
selectedId: any
|} | {|
// checklist - no preselected - ListItemJustLabel
...OptionsCommon,
...OptionsCheckboxList,
items: ListItemJustLabel[]
|} | {|
// checklist - no preselected - ListItemBare
...OptionsCommon,
...OptionsCheckboxList,
items: ListItemBare[],
labelKey: string
|} | {|
// checklist - preselected - ListItemFull
...OptionsCommon,
...OptionsCheckboxList,
items: ListItemFull[],
selectedIds: any[]
|} | {|
// checklist - preselected - ListItemJustlabel
...OptionsCommon,
...OptionsCheckboxList,
items: ListItemJustLabel[],
idKey: string,
selectedIds: any
|} | {|
// checklist - preselected - ListItemJustId
...OptionsCommon,
...OptionsCheckboxList,
items: ListItemJustId[],
labelKey: string,
selectedIds: any
|} | {|
// checklist - preselected - ListItemBare
...OptionsCommon,
...OptionsCheckboxList,
items: ListItemBare[],
idKey: string,
labelKey: string,
selectedIds: any
|}
type ListType =
| typeof DialogAndroid.listCheckbox
| typeof DialogAndroid.listPlain
| typeof DialogAndroid.listRadio;
type ActionType =
| typeof DialogAndroid.actionDismiss
| typeof DialogAndroid.actionNegative
| typeof DialogAndroid.actionNeutral
| typeof DialogAndroid.actionPositive
| typeof DialogAndroid.actionSelect;
type Options = OptionsAlert | OptionsPicker | OptionsProgress | OptionsPrompt;
type OptionsProgress = {|
contentColor?: $PropertyType<OptionsCommon, 'contentColor'>,
contentIsHtml?: $PropertyType<OptionsCommon, 'contentIsHtml'>,
linkColor?: $PropertyType<OptionsCommon, 'linkColor'>,
style?: ProgressStyle,
title?: $PropertyType<OptionsCommon, 'title'>,
titleColor?: $PropertyType<OptionsCommon, 'titleColor'>,
widgetColor?: $PropertyType<OptionsCommon, 'widgetColor'>
|}
type ProgressStyle = typeof DialogAndroid.progressHorizontal;
type OptionsPrompt = {|
...OptionsCommon,
keyboardType?: 'numeric' | 'number-pad' | 'decimal-pad' | 'numeric-password' | 'email-address' | 'password' | 'phone-pad' | 'url',
defaultValue?: string,
placeholder?: string,
allowEmptyInput?: boolean,
minLength?: number,
maxLength?: number
|}
type Title = void | null | string;
type Content = void | null | string;
type NativeConfig = {|
...OptionsCommon,
items: string[],
widgetColor?: ColorValue,
selectedIndices?: number[],
selectedIndex?: number[],
progress?: {
indeterminate: true,
style?: 'horizontal'
}
|}
function processColors(nativeConfig: {}) {
for (const prop of Object.keys(nativeConfig)) {
if (prop.endsWith('Color')) {
nativeConfig[prop] = processColor(nativeConfig[prop]);
}
}
}
function pick(source, ...keys) {
const target = {};
for (const key of keys) {
if (source.hasOwnProperty(key)) {
target[key] = source[key];
}
}
return target;
}
class DialogAndroid {
static listPlain = 'listPlain'
static listRadio = 'listRadio'
static listCheckbox = 'listCheckbox'
static actionDismiss = 'actionDismiss'
static actionNegative = 'actionNegative'
static actionNeutral = 'actionNeutral'
static actionPositive = 'actionPositive'
static actionSelect = 'actionSelect'
static progressHorizontal = 'progressHorizontal'
static defaults = {
positiveText: 'OK'
}
static dismiss(): void {
NativeModules.DialogAndroid.dismiss();
}
static assignDefaults(defaults: { title?:Title, content?:Content, ...Options }): void {
Object.assign(DialogAndroid.defaults, defaults);
}
static alert(title: Title, content: Content, options?: OptionsAlert = {}): Promise<
{| action: typeof DialogAndroid.actionPositive | typeof DialogAndroid.actionNegative | typeof DialogAndroid.actionNeutral | typeof DialogAndroid.actionDismiss |} |
{| action: typeof DialogAndroid.actionPositive | typeof DialogAndroid.actionNegative | typeof DialogAndroid.actionNeutral, checked: boolean |}
> {
return new Promise((resolve, reject) => {
const nativeConfig: NativeConfig = {
...DialogAndroid.defaults,
...options,
onAny: true,
dismissListener: true
};
if (title) nativeConfig.title = title;
if (content) nativeConfig.content = content;
processColors(nativeConfig);
NativeModules.DialogAndroid.show(nativeConfig, (kind: string, ...rest) => {
switch (kind) {
case 'error': {
const [ error, nativeConfig ] = rest;
return reject(`DialogAndroid ${error}. nativeConfig: ${nativeConfig}`);
}
case 'dismissListener': {
return resolve({ action:DialogAndroid.actionDismiss });
}
case 'onAny': {
const [ dialogAction, checked ] = rest;
switch (dialogAction) {
case 0: return resolve({ action:DialogAndroid.actionPositive, ...getChecked(nativeConfig, checked) });
case 1: return resolve({ action:DialogAndroid.actionNeutral, ...getChecked(nativeConfig, checked) });
case 2: return resolve({ action:DialogAndroid.actionNegative, ...getChecked(nativeConfig, checked) });
}
}
default: {
return reject(`Unknown callback kind: "${kind}"`);
}
}
});
});
}
static showPicker(title: Title, content: Content, options: OptionsPicker): Promise<
{| action: typeof DialogAndroid.actionNegative | typeof DialogAndroid.actionNeutral | typeof DialogAndroid.actionDismiss |} |
{| action: typeof DialogAndroid.actionNegative | typeof DialogAndroid.actionNeutral, checked: boolean |} |
{| action: typeof DialogAndroid.actionSelect, selectedItem: ListItem |} |
{| action: typeof DialogAndroid.actionSelect, selectedItem: ListItem, checked: boolean |} |
{| action: typeof DialogAndroid.actionSelect, selectedItems: ListItem[] |} |
{| action: typeof DialogAndroid.actionSelect, selectedItems: ListItem[], checked: boolean |}
> {
// options is required, must defined items
return new Promise((resolve, reject) => {
const {
idKey='id',
items,
labelKey='label',
type,
neutralIsClear,
selectedId,
selectedIds,
...filteredOptions
} = options;
const nativeConfig: NativeConfig = {
...DialogAndroid.defaults,
...filteredOptions,
onAny: true,
dismissListener: true
};
if (title) nativeConfig.title = title;
if (content) nativeConfig.content = content;
if (items) {
nativeConfig.items = items.map(item => item[labelKey]);
switch (type) {
case DialogAndroid.listCheckbox: {
nativeConfig.itemsCallbackMultiChoice = true;
if (selectedIds) {
nativeConfig.selectedIndices = selectedIds.map(id => items.findIndex(item => item[idKey] === id));
}
break;
}
case DialogAndroid.listRadio: {
nativeConfig.itemsCallbackSingleChoice = true;
if (selectedId !== undefined) {
nativeConfig.selectedIndex = items.findIndex(item => item[idKey] === selectedId);
}
break;
}
default:
nativeConfig.itemsCallback = true;
}
}
if (neutralIsClear) nativeConfig.multiChoiceClearButton = true;
processColors(nativeConfig);
NativeModules.DialogAndroid.show(nativeConfig, (kind: string, ...rest) => {
switch (kind) {
case 'error': {
const [ error, nativeConfig ] = rest;
return reject(`DialogAndroid ${error}. nativeConfig: ${nativeConfig}`);
}
case 'itemsCallbackMultiChoice': {
const [selectedIndicesString, checked] = rest; // blank string when nothing selected
const selectedItems = selectedIndicesString === '' ? [] : selectedIndicesString.split(',').map(index => items[index]);
return resolve({ action:DialogAndroid.actionPositive, selectedItems, ...getChecked(nativeConfig, checked) });
}
case 'itemsCallback':
case 'itemsCallbackSingleChoice': {
const [ selectedIndex, checked ] = rest;
const selectedItem = items[selectedIndex];
return resolve({ action:DialogAndroid.actionSelect, selectedItem, ...getChecked(nativeConfig, checked) });
}
case 'onAny': {
const [ dialogAction, checked ] = rest;
switch (dialogAction) {
case 0: return resolve({ action:DialogAndroid.actionPositive, ...getChecked(nativeConfig, checked) });
case 1: return resolve({ action:DialogAndroid.actionNeutral, ...getChecked(nativeConfig, checked) });
case 2: return resolve({ action:DialogAndroid.actionNegative, ...getChecked(nativeConfig, checked) });
}
}
case 'dismissListener': {
return resolve({ action:DialogAndroid.actionDismiss });
}
default: {
return reject(`Unknown callback kind: "${kind}"`);
}
}
});
})
}
static showProgress(content: string, options?: OptionsProgress = {}): Promise<
{|
action: typeof DialogAndroid.actionDismiss
|}
> {
return new Promise((resolve, reject) => {
const defaults = pick(DialogAndroid.defaults,
'contentColor',
'contentIsHtml',
'linkColor',
'title',
'widgetColor',
'titleColor'
)
const {
style,
...finalOptions
} = options;
const nativeConfig = {
...defaults,
progress: {
indeterminate: true,
style: style === DialogAndroid.progressHorizontal ? 'horizontal' : undefined
},
cancelable: false,
...finalOptions,
dismissListener: true
}
if (content) nativeConfig.content = content;
if (content && style !== DialogAndroid.progressHorizontal) nativeConfig.content = ' ' + content;
processColors(nativeConfig);
NativeModules.DialogAndroid.show(nativeConfig, (kind: string, ...rest) => {
switch (kind) {
case 'error': {
const [ error, nativeConfig ] = rest;
return reject(`DialogAndroid ${error}. nativeConfig: ${nativeConfig}`);
}
case 'dismissListener': {
return resolve({ action:DialogAndroid.actionDismiss });
}
}
});
})
}
static prompt(title: Title, content: Content, options?: OptionsPrompt = {}): Promise<
{| action: typeof DialogAndroid.actionNegative | typeof DialogAndroid.actionNeutral | typeof DialogAndroid.actionDismiss |} |
{| action: typeof DialogAndroid.actionNegative | typeof DialogAndroid.actionNeutral, checked: boolean |} |
{| action: typeof DialogAndroid.actionPositive, text: string |} |
{| action: typeof DialogAndroid.actionPositive, text: string, checked: boolean |}
> {
return new Promise((resolve, reject) => {
const {
keyboardType,
defaultValue,
placeholder,
allowEmptyInput,
minLength,
maxLength,
...finalOptions
} = options;
const inputConfig = {};
if (defaultValue) inputConfig.prefill = defaultValue;
if (placeholder) inputConfig.hint = placeholder;
if (allowEmptyInput !== undefined) inputConfig.allowEmptyInput = allowEmptyInput;
if (minLength) inputConfig.minLength = minLength;
if (maxLength) inputConfig.maxLength = maxLength;
if (keyboardType) inputConfig.keyboardType = keyboardType;
const nativeConfig = {
...DialogAndroid.defaults,
input: inputConfig,
...finalOptions,
onAny: true,
dismissListener: true
}
if (title) nativeConfig.title = title;
if (content) nativeConfig.content = content;
processColors(nativeConfig);
NativeModules.DialogAndroid.show(nativeConfig, (kind: string, ...rest) => {
switch (kind) {
case 'error': {
const [ error, nativeConfig ] = rest;
return reject(`DialogAndroid ${error}. nativeConfig: ${nativeConfig}`);
}
case 'onAny': {
const [ dialogAction, checked ] = rest;
switch (dialogAction) {
case 1: return resolve({ action:DialogAndroid.actionNeutral, ...getChecked(nativeConfig, checked) });
case 2: return resolve({ action:DialogAndroid.actionNegative, ...getChecked(nativeConfig, checked) });
}
}
case 'input': {
const [ text, checked ] = rest;
return resolve({ action:DialogAndroid.actionPositive, text, ...getChecked(nativeConfig, checked) });
}
case 'dismissListener': {
return resolve({ action:DialogAndroid.actionDismiss });
}
case 'cancelListener': {
// fires when input text field is there and hit back or in back to dismiss
return resolve({ action:DialogAndroid.actionDismiss });
}
default: {
return reject(`Unknown callback kind: "${kind}"`);
}
}
});
})
}
}
function getChecked(nativeConfig, checked) {
return nativeConfig.checkboxLabel ? { checked } : {};
}
export default DialogAndroid