react-native-dialogs/DialogAndroid.js

666 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;