diff --git a/Example/android-options.png b/Example/android-options.png new file mode 100644 index 0000000..f736e27 Binary files /dev/null and b/Example/android-options.png differ diff --git a/README.md b/README.md index b4749ab..925cf23 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,48 @@ You can use react-native-cli: react-native link react-native-prompt-android ``` -Or rnpm: -```bash -rnpm link react-native-prompt-android +#### Manual Linking +In case `react-native link` fails you can follow this manual linking. + +1. Include this module in `android/settings.gradle`: + +``` +... +include ':react-native-prompt-android' // Add this +project(':react-native-prompt-android').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-prompt-android/android') // Add this +... +include ':app' +``` + +2. Add a dependency to your app build in `android/app/build.gradle`: + +``` +dependencies { + ... + compile project(':react-native-prompt-android') // Add this +} +``` + +3. Change your main application to "import" and "add" a new package, in `android/app/src/main/.../MainApplication.java`: + +```java +import im.shimo.react.prompt.RNPromptPackage; // Add new import + +public class MainApplication extends Application implements ReactApplication { + ... + + @Override + protected List getPackages() { + return Arrays.asList( + new MainReactPackage(), + new RNPromptPackage() // Add the package here + ); + } +} ``` +4. Re-compile application using `react-native run-android` + ### Usage ``` @@ -42,17 +79,86 @@ prompt( ); ``` -## Props +### API + +#### `prompt` + +###### Android + static showPopupMenu( + title?: string, + message?: string, + positiveCallbackOrButtons: ()=>void | [ButtonPositive] | [ButtonNegative, ButtonPositive] | [ButtonNeutral, ButtonNegative, ButtonPositive], + options: { + disableFullscreenUI?: boolean, + cancelable?: boolean, + type?: 'default' | 'plain-text' | 'secure-text' | 'numeric' | 'email-address' | 'phone-pad', + defaultValue?: string, + style?: 'default' | 'shimo' | 'cust', + placeholder?: string, + placeholderColor?: string, + highlightColor?: string, + color?: string, + buttonColor?: string + } + ): void + +###### iOS + +TODO: - name | description | type | default -:-------------------- |:------------------------------------------- | --------:|:------------------ - type | Text input type: `'numeric', 'secure-text', 'phone-pad', 'email-address'` | String | 'default' - cancelable | | Boolean | - defaultValue | Default input value | String | '' - placeholder | | String | '' +### Options +The third argument is an object. It can have any of these keys: +| Key | Description | Type | Default | +|---------------------|---------------------------------------------------------------------------|----------------------|---------------------------------------------------------------------| +| type | Text input type: `'numeric', 'secure-text', 'phone-pad', 'email-address'` | string | 'default' | +| cancelable | Android only. If tapping outside of the alert box should cause dismiss. | boolean | true | +| defaultValue | Default input value | string | | +| placeholder | String in input that will be rendered when empty. | string | | +| style | `'default', 'shimo', 'cust'` | string | 'default' | +| disableFullscreenUI | When in landscape mode, don't use fullscreen | boolean | false | +| highlightColor | Color of text selection | string | ![Color](#https://facebook.github.io/react-native/docs/colors.html) | +| placeholderColor | Color of the placeholder in input field | string | ![Color](#https://facebook.github.io/react-native/docs/colors.html) | +| color | Color of the text in input field | string | ![Color](#https://facebook.github.io/react-native/docs/colors.html) | +| buttonColor | Color of the buttons | string | ![Color](#https://facebook.github.io/react-native/docs/colors.html) | +| onDismiss | Callback triggered when prompt is dismissed | () => void | | +| onAny | Callback triggered when any action happens | PromptAction => void | | +##### "cust" Style (change underline, cursor, and handle color) +If you set this style, you can adjust the color of the "underline", "cursor", and "handles" of the input field. The default custom color is a reddish color of "#F34336". You can change this by going to `./node_modules/react-native-prompt-android/android/src/main/res/values/colors.xml` and changing the value of the `custUnderlineAndCursorAndHandleColor` field. + +### Screenshots ![Android Screen Shoot](./Example/android.png) ![Android Screen Shoot](./Example/ios.png) + +#### Android with options + + prompt('Edit Memo', undefined, + [ + { text:'Cancel', style:'cancel' }, + { text:'OK', onPress: text => dispatch(patchEvent(id, { memo:text })) }, + ], + { + highlightColor: 'rgba(243, 67, 54, 0.5)', + color: '#212121', + buttonColor: '#000000', + defaultValue: memo, + style: 'cust', + onDismiss: () => console.log('prompt was dismissed') + onAny: action => { + switch(action) { + case prompt.dismissedAction: return console.log('onAny says dismissed'); + case prompt.positiveAction: return console.log('onAny says positive button clicked'); + case prompt.negativeAction: return console.log('onAny says negative button clicked'); + case prompt.neutralAction: return console.log('onAny says neutral button clicked'); + } + } + } + ) + +And to get the red colors, the field `custUnderlineAndCursorAndHandleColor` in `./node_modules/react-native-prompt-android/android/src/main/res/values/colors.xml` was updated to `#F34336` + + +![Android Screen Shoot](./Example/android-options.png) diff --git a/android/src/main/java/im/shimo/react/prompt/RNPromptFragment.java b/android/src/main/java/im/shimo/react/prompt/RNPromptFragment.java index 5428e43..041d776 100644 --- a/android/src/main/java/im/shimo/react/prompt/RNPromptFragment.java +++ b/android/src/main/java/im/shimo/react/prompt/RNPromptFragment.java @@ -1,15 +1,18 @@ package im.shimo.react.prompt; +import android.support.v7.app.AlertDialog; +import android.os.Bundle; +import android.content.Context; import android.app.Dialog; import android.app.DialogFragment; -import android.content.Context; import android.content.DialogInterface; -import android.os.Bundle; -import android.support.v7.app.AlertDialog; +import android.view.inputmethod.EditorInfo; +import android.widget.EditText; +import android.view.inputmethod.InputMethodManager; import android.text.InputType; +import android.view.KeyEvent; import android.view.LayoutInflater; -import android.view.WindowManager; -import android.widget.EditText; +import android.widget.TextView; import javax.annotation.Nullable; @@ -25,9 +28,16 @@ public class RNPromptFragment extends DialogFragment implements DialogInterface. /* package */ static final String ARG_STYLE = "style"; /* package */ static final String ARG_DEFAULT_VALUE = "defaultValue"; /* package */ static final String ARG_PLACEHOLDER = "placeholder"; + /* package */ static final String ARG_PLACEHOLDER_COLOR = "placeholderColor"; + /* package */ static final String ARG_DISABLE_FULL_SCREEN_UI = "disableFullscreenUI"; + /* package */ static final String ARG_HIGHLIGHT_COLOR = "highlightColor"; + /* package */ static final String ARG_COLOR = "color"; + /* package */ static final String ARG_BUTTON_COLOR = "buttonColor"; private EditText mInputText; + private Integer mButtonColor; + public enum PromptTypes { TYPE_DEFAULT("default"), PLAIN_TEXT("plain-text"), @@ -95,7 +105,11 @@ public Dialog createDialog(Context activityContext, Bundle arguments) { builder.setItems(arguments.getCharSequenceArray(ARG_ITEMS), this); } - AlertDialog alertDialog = builder.create(); + if (arguments.containsKey(ARG_BUTTON_COLOR)) { + mButtonColor = arguments.getInt(ARG_BUTTON_COLOR); + } + + final AlertDialog alertDialog = builder.create(); // input style LayoutInflater inflater = LayoutInflater.from(activityContext); @@ -104,10 +118,15 @@ public Dialog createDialog(Context activityContext, Bundle arguments) { case "shimo": input = (EditText) inflater.inflate(R.layout.edit_text, null); break; + case "cust": + input = (EditText) inflater.inflate(R.layout.cust_edit_text, null); + break; default: input = new EditText(activityContext); } + + // input type int type = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS; if (arguments.containsKey(ARG_TYPE)) { @@ -134,6 +153,18 @@ public Dialog createDialog(Context activityContext, Bundle arguments) { } input.setInputType(type); + if (arguments.containsKey(ARG_HIGHLIGHT_COLOR)) { + input.setHighlightColor(arguments.getInt(ARG_HIGHLIGHT_COLOR)); + } + + if (arguments.containsKey(ARG_DISABLE_FULL_SCREEN_UI)) { + boolean disableFullscreenUI = arguments.getBoolean(ARG_DISABLE_FULL_SCREEN_UI); + if (disableFullscreenUI) { + int imeOptions = input.getImeOptions(); + input.setImeOptions(imeOptions | EditorInfo.IME_FLAG_NO_EXTRACT_UI); + } + } + if (arguments.containsKey(ARG_DEFAULT_VALUE)) { String defaultValue = arguments.getString(ARG_DEFAULT_VALUE); if (defaultValue != null) { @@ -143,22 +174,62 @@ public Dialog createDialog(Context activityContext, Bundle arguments) { } } + + if (arguments.containsKey(ARG_COLOR)) { + input.setTextColor(arguments.getInt(ARG_COLOR)); + } + if (arguments.containsKey(ARG_PLACEHOLDER)) { input.setHint(arguments.getString(ARG_PLACEHOLDER)); + if (arguments.containsKey(ARG_PLACEHOLDER_COLOR)) { + input.setHintTextColor(arguments.getInt(ARG_PLACEHOLDER_COLOR)); + } } alertDialog.setView(input, 50, 15, 50, 0); mInputText = input; + + alertDialog.setOnShowListener(new DialogInterface.OnShowListener() + { + @Override + public void onShow(final DialogInterface dialog) + { + input.requestFocus(); + ((InputMethodManager) alertDialog.getContext().getSystemService(Context.INPUT_METHOD_SERVICE)).showSoftInput(input, 0); + } + }); + + + input.setOnEditorActionListener(new TextView.OnEditorActionListener() { + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (actionId == EditorInfo.IME_ACTION_DONE) { + alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).performClick(); + } + return false; + } + }); + return alertDialog; } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { Dialog dialog = this.createDialog(getActivity(), getArguments()); - dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); return dialog; } + @Override + public void onStart() { + super.onStart(); + + if (mButtonColor != null) { + AlertDialog d = (AlertDialog) getDialog(); + d.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(mButtonColor); + d.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(mButtonColor); + d.getButton(AlertDialog.BUTTON_NEUTRAL).setTextColor(mButtonColor); + } + } + @Override public void onClick(DialogInterface dialog, int which) { if (mListener != null) { diff --git a/android/src/main/java/im/shimo/react/prompt/RNPromptModule.java b/android/src/main/java/im/shimo/react/prompt/RNPromptModule.java index 960b8d7..6cea0df 100644 --- a/android/src/main/java/im/shimo/react/prompt/RNPromptModule.java +++ b/android/src/main/java/im/shimo/react/prompt/RNPromptModule.java @@ -42,6 +42,11 @@ public class RNPromptModule extends ReactContextBaseJavaModule implements Lifecy /* package */ static final String KEY_STYLE = "style"; /* package */ static final String KEY_DEFAULT_VALUE = "defaultValue"; /* package */ static final String KEY_PLACEHOLDER = "placeholder"; + /* package */ static final String KEY_PLACEHOLDER_COLOR = "placeholderColor"; + /* package */ static final String KEY_DISABLE_FULL_SCREEN_UI = "disableFullscreenUI"; + /* package */ static final String KEY_HIGHLIGHT_COLOR = "highlightColor"; + /* package */ static final String KEY_COLOR = "color"; + /* package */ static final String KEY_BUTTON_COLOR = "buttonColor"; /* package */ static final Map CONSTANTS = MapBuilder.of( ACTION_BUTTON_CLICKED, ACTION_BUTTON_CLICKED, @@ -127,6 +132,15 @@ public void promptWithArgs(ReadableMap options, final Callback callback) { if (options.hasKey(KEY_CANCELABLE)) { args.putBoolean(KEY_CANCELABLE, options.getBoolean(KEY_CANCELABLE)); } + if (options.hasKey(KEY_DISABLE_FULL_SCREEN_UI)) { + args.putBoolean(KEY_DISABLE_FULL_SCREEN_UI, options.getBoolean(KEY_DISABLE_FULL_SCREEN_UI)); + } + if (options.hasKey(KEY_HIGHLIGHT_COLOR) && !options.isNull(KEY_HIGHLIGHT_COLOR)) { + args.putInt(KEY_HIGHLIGHT_COLOR, options.getInt(KEY_HIGHLIGHT_COLOR)); + } + if (options.hasKey(KEY_COLOR) && !options.isNull(KEY_COLOR)) { + args.putInt(KEY_COLOR, options.getInt(KEY_COLOR)); + } if (options.hasKey(KEY_TYPE)) { args.putString(KEY_TYPE, options.getString(KEY_TYPE)); } @@ -139,6 +153,12 @@ public void promptWithArgs(ReadableMap options, final Callback callback) { if (options.hasKey(KEY_PLACEHOLDER)) { args.putString(KEY_PLACEHOLDER, options.getString(KEY_PLACEHOLDER)); } + if (options.hasKey(KEY_PLACEHOLDER_COLOR) && !options.isNull(KEY_PLACEHOLDER_COLOR)) { + args.putInt(KEY_PLACEHOLDER_COLOR, options.getInt(KEY_PLACEHOLDER_COLOR)); + } + if (options.hasKey(KEY_BUTTON_COLOR) && !options.isNull(KEY_BUTTON_COLOR)) { + args.putInt(KEY_BUTTON_COLOR, options.getInt(KEY_BUTTON_COLOR)); + } fragmentManagerHelper.showNewAlert(mIsInForeground, args, callback); } diff --git a/android/src/main/res/color/prompt_color.xml b/android/src/main/res/color/prompt_color.xml deleted file mode 100644 index dac19ec..0000000 --- a/android/src/main/res/color/prompt_color.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/android/src/main/res/layout/cust_edit_text.xml b/android/src/main/res/layout/cust_edit_text.xml new file mode 100644 index 0000000..dd3e55c --- /dev/null +++ b/android/src/main/res/layout/cust_edit_text.xml @@ -0,0 +1,5 @@ + + diff --git a/android/src/main/res/values/colors.xml b/android/src/main/res/values/colors.xml new file mode 100644 index 0000000..a7aa8df --- /dev/null +++ b/android/src/main/res/values/colors.xml @@ -0,0 +1,5 @@ + + + #F34336 + #41464b + diff --git a/android/src/main/res/values/style.xml b/android/src/main/res/values/style.xml index 14aa009..16f1080 100644 --- a/android/src/main/res/values/style.xml +++ b/android/src/main/res/values/style.xml @@ -9,4 +9,8 @@ @color/prompt_color @color/prompt_color + + diff --git a/index.android.js b/index.android.js index 783340d..18124b7 100644 --- a/index.android.js +++ b/index.android.js @@ -1,6 +1,6 @@ -import { - NativeModules -} from 'react-native'; +import { NativeModules } from 'react-native'; +import processColor from 'react-native/Libraries/StyleSheet/processColor'; + const PromptAndroid = NativeModules.PromptAndroid; export type PromptType = $Enum<{ @@ -39,14 +39,31 @@ export type PromptStyle = $Enum<{ * Shimo alert dialog style */ 'shimo': string, + /** + * Custom input style + */ + 'cust': string }>; +export type PromptAction = + | 'dismissedAction' + | 'positiveAction' + | 'negativeAction' + | 'neutralAction'; + type Options = { - cancelable?: ?boolean; - type?: ?PromptType; - defaultValue?: ?String; - placeholder?: ?String; - style?: ?PromptStyle; + disableFullscreenUI?: boolean; + cancelable?: boolean; + type?: PromptType; + defaultValue?: string; + style?: PromptStyle; + placeholder?: string; + placeholderColor?: string; + highlightColor?: string; + color?: string; + buttonColor?: string; + onAny?: PromptAction => void, + onDismiss: () => void }; /** @@ -59,13 +76,17 @@ type ButtonsArray = Array<{ /** * Button label */ - text?: string, + text?: string, /** * Callback function when button pressed */ - onPress?: ?Function, + onPress?: () => void, }>; +prompt.dismissedAction = 'dismissedAction'; +prompt.positiveAction = 'positiveAction'; +prompt.negativeAction = 'negativeAction'; +prompt.neutralAction = 'neutralAction'; export default function prompt( title: ?string, message?: ?string, @@ -85,7 +106,7 @@ export default function prompt( let buttons = typeof callbackOrButtons === 'function' ? defaultButtons : callbackOrButtons; - + let config = { title: title || '', message: message || '', @@ -94,11 +115,16 @@ export default function prompt( if (options) { config = { ...config, + highlightColor: options.highlightColor ? processColor(options.highlightColor) : options.highlightColor, + placeholderColor: options.placeholderColor ? processColor(options.placeholderColor) : options.placeholderColor, + color: options.color ? processColor(options.color) : options.color, + disableFullscreenUI: options.disableFullscreenUI === true, cancelable: options.cancelable !== false, type: options.type || 'default', style: options.style || 'default', defaultValue: options.defaultValue || '', - placeholder: options.placeholder || '' + placeholder: options.placeholder || null, + buttonColor: options.buttonColor ? processColor(options.buttonColor) : options.buttonColor }; } // At most three buttons (neutral, negative, positive). Ignore rest. @@ -121,19 +147,39 @@ export default function prompt( }; } - PromptAndroid.promptWithArgs( config, (action, buttonKey, input) => { - if (action !== PromptAndroid.buttonClicked) { - return; + if (action === PromptAndroid.dismissed) { + options.onDismiss && options.onDismiss(); + } else if (action === PromptAndroid.buttonClicked) { + switch (buttonKey) { + case PromptAndroid.buttonNeutral: + buttonNeutral.onPress && buttonNeutral.onPress(input); + break; + case PromptAndroid.buttonNegative: + buttonNegative.onPress && buttonNegative.onPress(); + break; + case PromptAndroid.buttonPositive: + buttonPositive.onPress && buttonPositive.onPress(input); + break; + // no default + } } - if (buttonKey === PromptAndroid.buttonNeutral) { - buttonNeutral.onPress && buttonNeutral.onPress(input); - } else if (buttonKey === PromptAndroid.buttonNegative) { - buttonNegative.onPress && buttonNegative.onPress(); - } else if (buttonKey === PromptAndroid.buttonPositive) { - buttonPositive.onPress && buttonPositive.onPress(input); + + if (options.onAny) { + let actionText; + if (action === PromptAndroid.buttonClicked) { + switch (buttonKey) { + case PromptAndroid.buttonNeutral: actionText = prompt.neutralAction; break; + case PromptAndroid.buttonPositive: actionText = prompt.positiveAction; break; + case PromptAndroid.buttonNegative: actionText = prompt.negativeAction; break; + } + } else if (action === PromptAndroid.dismissed) { + actionText = prompt.dismissedAction; + } + + options.onAny(actionText); } } );