// @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 = {| ...OptionsCheckbox, ...OptionsCommon |} type OptionsCheckbox = {| checkboxLabel?: string, checkboxDefaultValue?: boolean |} 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, cancelable?: boolean, linkColor?: ColorValue, // applies if contentIsHtml is true, and there are elements in content string forceStacking?: boolean |} type ListItemJustLabel = { label:string }; type ListItemJustId = { id:string }; type ListItemFull = { label:string, id:any }; type ListItemBare = {}; type OptionsRadio = {| type: typeof ListType.listRadio, widgetColor?: ColorValue // radio color |} type OptionsCheckbox = {| type: typeof ListType.listCheckbox, neutralIsClear?: boolean, widgetColor?: ColorValue // checkbox color |} type OptionsPicker = {| ...OptionsCommon, type?: typeof ListType.listPlain, items: ListItemJustLabel[], |} | {| ...OptionsCommon, type?: typeof ListType.listPlain, items: ListItemBare[], labelKey: string |} | {| // radio - no preselected ...OptionsCommon, ...OptionsRadio, items: ListItemJustLabel[], |} | {| // radio - no preselected ...OptionsCommon, ...OptionsRadio, items: ListItemBare[], labelKey: string |} | {| // radio - preselected - ListItemFull ...OptionsCommon, ...OptionsRadio, items: ListItemFull[], selectedId: any |} | {| // radio - preselected - ListItemJustlabel ...OptionsCommon, ...OptionsRadio, items: ListItemJustLabel[], idKey: string, selectedId: any |} | {| // radio - preselected - ListItemJustId ...OptionsCommon, ...OptionsRadio, items: ListItemJustId[], labelKey: string, selectedId: any |} | {| // radio - preselected - ListItemBare ...OptionsCommon, ...OptionsRadio, items: ListItemBare[], idKey: string, labelKey: string, selectedId: any |} | {| // checklist - no preselected - ListItemJustLabel ...OptionsCommon, ...OptionsCheckbox, items: ListItemJustLabel[] |} | {| // checklist - no preselected - ListItemBare ...OptionsCommon, ...OptionsCheckbox, items: ListItemBare[], labelKey: string |} | {| // checklist - preselected - ListItemFull ...OptionsCommon, ...OptionsCheckbox, items: ListItemFull[], selectedIds: any[] |} | {| // checklist - preselected - ListItemJustlabel ...OptionsCommon, ...OptionsCheckbox, items: ListItemJustLabel[], idKey: string, selectedIds: any |} | {| // checklist - preselected - ListItemJustId ...OptionsCommon, ...OptionsCheckbox, items: ListItemJustId[], labelKey: string, selectedIds: any |} | {| // checklist - preselected - ListItemBare ...OptionsCommon, ...OptionsCheckbox, 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 = OptionsCommon | OptionsPicker | OptionsProgress | OptionsPrompt; type OptionsProgress = {| contentColor?: $PropertyType, contentIsHtml?: $PropertyType, linkColor?: $PropertyType, style?: ProgressStyle, title?: $PropertyType, titleColor?: $PropertyType, widgetColor?: $PropertyType |} type ProgressStyle = typeof DialogAndroid.progressHorizontal; type OptionsPrompt = {| ...OptionsCommon, keyboardType?: 'numeric' | 'numbers-and-punctuation' | 'numeric-password' | 'email-address' | 'password' | 'phone-pad' | 'decimal-pad', 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; // TODO: support this on native side - https://github.com/aakashns/react-native-dialogs/pull/55 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