├── .watchmanconfig ├── .gitattributes ├── .babelrc ├── app.json ├── .buckconfig ├── package.json ├── .gitignore ├── .flowconfig ├── README.md └── src └── index.js /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["module:metro-react-native-babel-preset"] 3 | } 4 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RNSwipeVerify", 3 | "displayName": "RNSwipeVerify" 4 | } -------------------------------------------------------------------------------- /.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-swipe-verify", 3 | "version": "0.1.6", 4 | "license": "MIT", 5 | "author": { 6 | "name": "Darwin Morocho", 7 | "email": "dsmr.apps@gmail.com", 8 | "url": "https://dina.ec" 9 | }, 10 | "keywords": [ 11 | "react-native-swipe-verify", 12 | "react-native-confirm", 13 | "react-native-verify", 14 | "react-native-slide-confirm", 15 | "react-native-slide-verify" 16 | ], 17 | "homepage": "https://github.com/MorochoRochaDarwin/RNSwipeVerify", 18 | "private": false, 19 | "main": "src/index.js", 20 | "scripts": {}, 21 | "dependencies": { 22 | "prop-types": "^15.6.2" 23 | }, 24 | "devDependencies": { 25 | "babel-jest": "23.6.0", 26 | "metro-react-native-babel-preset": "0.49.2", 27 | "react": "16.6.1", 28 | "react-native": "0.57.5", 29 | "react-test-renderer": "16.6.1" 30 | }, 31 | "engines": { 32 | "node": ">=8.12.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | android/app/assets/index.android.bundle 33 | android/app/build/ 34 | 35 | # node.js 36 | # 37 | node_modules/ 38 | npm-debug.log 39 | yarn-error.log 40 | 41 | # BUCK 42 | buck-out/ 43 | \.buckd/ 44 | *.keystore 45 | 46 | # fastlane 47 | # 48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 49 | # screenshots whenever they are needed. 50 | # For more information about the recommended setup visit: 51 | # https://docs.fastlane.tools/best-practices/source-control/ 52 | 53 | */fastlane/report.xml 54 | */fastlane/Preview.html 55 | */fastlane/screenshots 56 | 57 | # Bundle artifact 58 | *.jsbundle 59 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | ; We fork some components by platform 3 | .*/*[.]android.js 4 | 5 | ; Ignore "BUCK" generated dirs 6 | /\.buckd/ 7 | 8 | ; Ignore unexpected extra "@providesModule" 9 | .*/node_modules/.*/node_modules/fbjs/.* 10 | 11 | ; Ignore duplicate module providers 12 | ; For RN Apps installed via npm, "Libraries" folder is inside 13 | ; "node_modules/react-native" but in the source repo it is in the root 14 | .*/Libraries/react-native/React.js 15 | 16 | ; Ignore polyfills 17 | .*/Libraries/polyfills/.* 18 | 19 | ; Ignore metro 20 | .*/node_modules/metro/.* 21 | 22 | [include] 23 | 24 | [libs] 25 | node_modules/react-native/Libraries/react-native/react-native-interface.js 26 | node_modules/react-native/flow/ 27 | node_modules/react-native/flow-github/ 28 | 29 | [options] 30 | emoji=true 31 | 32 | esproposal.optional_chaining=enable 33 | esproposal.nullish_coalescing=enable 34 | 35 | module.system=haste 36 | module.system.haste.use_name_reducers=true 37 | # get basename 38 | module.system.haste.name_reducers='^.*/\([a-zA-Z0-9$_.-]+\.js\(\.flow\)?\)$' -> '\1' 39 | # strip .js or .js.flow suffix 40 | module.system.haste.name_reducers='^\(.*\)\.js\(\.flow\)?$' -> '\1' 41 | # strip .ios suffix 42 | module.system.haste.name_reducers='^\(.*\)\.ios$' -> '\1' 43 | module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1' 44 | module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1' 45 | module.system.haste.paths.blacklist=.*/__tests__/.* 46 | module.system.haste.paths.blacklist=.*/__mocks__/.* 47 | module.system.haste.paths.blacklist=/node_modules/react-native/Libraries/Animated/src/polyfills/.* 48 | module.system.haste.paths.whitelist=/node_modules/react-native/Libraries/.* 49 | 50 | munge_underscores=true 51 | 52 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' 53 | 54 | module.file_ext=.js 55 | module.file_ext=.jsx 56 | module.file_ext=.json 57 | module.file_ext=.native.js 58 | 59 | suppress_type=$FlowIssue 60 | suppress_type=$FlowFixMe 61 | suppress_type=$FlowFixMeProps 62 | suppress_type=$FlowFixMeState 63 | 64 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) 65 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ 66 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 67 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError 68 | 69 | [version] 70 | ^0.78.0 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RNSwipeVerify 2 | ========= 3 | 4 | A small componenent to Swipe Verify 5 | 6 | ## Installation 7 | 8 | `npm install --save react-native-swipe-verify` or 9 | `yarn add react-native-swipe-verify` 10 | 11 | ## Preview 12 | 13 | | | | 14 | | --- | --- | 15 | | lo | lo | 16 | 17 | 18 | 19 | ## Important 20 | If the **RNSwipeVerify** is inside another component with PanResponder the RNSwipeVerify will be cancelled. 21 | 22 | 23 | ## Usage 24 | 25 | 26 | ```JSX 27 | import React, { Component } from 'react'; 28 | 29 | import { 30 | View, 31 | TouchableOpacity, Text, 32 | Dimensions 33 | } from 'react-native'; 34 | 35 | 36 | const { width } = Dimensions.get('window') 37 | 38 | import RNSwipeVerify from 'react-native-swipe-verify' 39 | 40 | import LottieView from 'lottie-react-native'; 41 | 42 | export default class App extends Component { 43 | 44 | constructor(props) { 45 | super(props) 46 | 47 | this.state = { isUnlocked: false, isUploaded: false } 48 | 49 | } 50 | 51 | render() { 52 | 53 | 54 | const { isUnlocked, isUploaded } = this.state 55 | 56 | const lottieSizeIcon = isUploaded ? 60 : 40; 57 | 58 | return ( 59 | 60 | 61 | 62 | {/** Lottie example */} 63 | 64 | this.swipeVerify2 = ref} 65 | width={width - 50} 66 | buttonSize={60} 67 | buttonColor="#2962FF" 68 | borderColor="#2962FF" 69 | backgroundColor="#fff" 70 | textColor="#37474F" 71 | borderRadius={30} 72 | okButton={{ visible: true, duration: 400 }} 73 | onVerified={() => { 74 | this.setState({ isUploaded: true }) 75 | }} 76 | icon={ 77 | 78 | 85 | 86 | } 87 | > 88 | 89 | {isUploaded ? 'UPLOADED' : 'slide to upload'} 90 | 91 | 92 | 93 | {/** end Lottie example */} 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | this.swipeVerify3 = ref} 102 | width={width - 50} 103 | buttonSize={60} 104 | borderColor="#fff" 105 | buttonColor="#37474F" 106 | backgroundColor="#ececec" 107 | textColor="#37474F" 108 | okButton={{ visible: false, duration: 400 }} 109 | onVerified={() => { 110 | this.setState({ isUnlocked: true }) 111 | }} 112 | icon={} 113 | > 114 | 115 | {isUnlocked ? 'UNLOCKED' : 'slide to unlock'} 116 | 117 | 118 | 119 | 120 | 121 | { 122 | 123 | this.swipeVerify2.reset() 124 | this.swipeVerify3.reset() 125 | this.setState({ isUnlocked: false, isUploaded: false }) 126 | }} style={{ marginTop: 30 }}> 127 | RESET 128 | 129 | 130 | 131 | 132 | 133 | ); 134 | } 135 | } 136 | ``` 137 | 138 | 139 | ## Props 140 | | name | type | default | description | 141 | | --- | --- | --- | --- | 142 | | **width** (required) | number | required | the width of swipe-verify | 143 | | **buttonSize** (required) | number | required | the button (Icon) size of swipe-verify | 144 | | **backgroundColor** (optional) | string | #F50057 | background color | 145 | | **buttonColor** (optional) | string | #D50000 | button background color | 146 | | **icon** (optional) | component | | see the example to more information | 147 | | **borderColor** (optional) | string | #D50000 | border color | 148 | | **borderRadius** (optional) | number | 0 | border radius to the button and container | 149 | | **okButton** (optional) | object | { visible: true, duration: 300 } | if visible is false the icon button will be hidden, duration (number) is the animation duration in miliseconds | 150 | 151 | 152 | 153 | ## Callbacks 154 | 155 | | name | arguments | notes | 156 | | --- | --- | --- | 157 | | **onVerified** (required) | bool | listener to check if the swipe is verified (user has completed swipe) | 158 | 159 | 160 | 161 | ## Methods 162 | 163 | | name | arguments | notes | 164 | | --- | --- | --- | 165 | | **reset()** | none | reset the swipe-verify to default values | 166 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * RNSwipeVerify 3 | */ 4 | 5 | import React, { Component } from "react"; 6 | import PropTypes from "prop-types"; 7 | import { View, PanResponder, Animated, UIManager } from "react-native"; 8 | 9 | // Enable LayoutAnimation on Android 10 | if (UIManager.setLayoutAnimationEnabledExperimental) { 11 | UIManager.setLayoutAnimationEnabledExperimental(true); 12 | } 13 | 14 | const propTypes = { 15 | buttonSize: PropTypes.number.isRequired, 16 | backgroundColor: PropTypes.string, 17 | buttonColor: PropTypes.string, 18 | text: PropTypes.string, 19 | onVerified: PropTypes.func.isRequired, 20 | textColor: PropTypes.string, 21 | borderColor: PropTypes.string, 22 | icon: PropTypes.node, 23 | okIcon: PropTypes.any, 24 | okButton: PropTypes.object, 25 | borderRadius: PropTypes.number 26 | }; 27 | 28 | //default props value 29 | const defaultProps = { 30 | backgroundColor: "#fff", 31 | buttonColor: "#D50000", 32 | textColor: "#000", 33 | borderColor: "rgba(0,0,0,0)", 34 | okButton: { visible: true, duration: 300 }, 35 | borderRadius: 0 36 | }; 37 | 38 | export default class RNSwipeVerify extends Component { 39 | constructor(props) { 40 | super(props); 41 | 42 | this.state = { 43 | drag: new Animated.ValueXY(), 44 | buttonOpacity: new Animated.Value(1), 45 | moving: false, 46 | verify: false, 47 | percent: 0, 48 | position: { x: 0, y: 0 }, 49 | dimensions: { width: 0, height: 0 } 50 | }; 51 | 52 | this._panResponder = PanResponder.create({ 53 | onStartShouldSetPanResponder: () => true, 54 | onPanResponderGrant: () => { 55 | const positionXY = this.state.drag.__getValue(); 56 | this.state.drag.setOffset(positionXY); 57 | this.state.drag.setValue({ x: 0, y: 0 }); 58 | }, 59 | onPanResponderMove: Animated.event([null, { dx: this.state.drag.x }], { 60 | // limit sliding out of box 61 | listener: (event, gestureState) => { 62 | const { buttonSize } = this.props; 63 | 64 | const { 65 | drag, 66 | verify, 67 | dimensions: { width } 68 | } = this.state; 69 | const maxMoving = width - buttonSize; 70 | 71 | var toX = gestureState.dx; 72 | 73 | if (toX < 0) toX = 0; 74 | if (toX > maxMoving) toX = maxMoving; 75 | const percent = ((toX * 100) / maxMoving).toFixed(); 76 | this.setState({ percent }); 77 | 78 | if (verify) { 79 | drag.setValue({ x: 0, y: 0 }); 80 | return; 81 | } 82 | drag.setValue({ x: toX, y: 0 }); 83 | } 84 | }), 85 | onPanResponderRelease: () => { 86 | if (this.state.verify) return; 87 | if (this.state.percent >= 100) { 88 | this.setState({ moving: false, verify: true }); 89 | this.props.onVerified(); //communicate that the verification was successful 90 | 91 | const { visible, duration } = this.props.okButton; 92 | if (!visible) { 93 | this.toggleShowAnimation(false, duration); 94 | } 95 | } else if (!this.state.verify) { 96 | this.reset(); 97 | } 98 | }, 99 | onPanResponderTerminate: () => { 100 | // Another component has become the responder, so this gesture 101 | // should be cancelled 102 | // console.log("onPanResponderTerminate", gestureState); 103 | } 104 | }); 105 | } 106 | 107 | reset() { 108 | this.state.drag.setOffset({ x: 0, y: 0 }); 109 | Animated.timing(this.state.drag, { 110 | toValue: { x: 0, y: 0 }, 111 | duration: 300 112 | }).start(); 113 | this.toggleShowAnimation(true, this.props.okButton.duration); 114 | this.setState({ moving: false, verify: false, percent: 0 }); 115 | } 116 | 117 | toggleShowAnimation(visible, duration) { 118 | Animated.timing( 119 | // Animate over time 120 | this.state.buttonOpacity, // The animated value to drive 121 | { 122 | toValue: visible ? 1 : 0, // Animate to opacity: 1 (opaque) 123 | duration: duration // Make it take a while 124 | } 125 | ).start(); 126 | } 127 | 128 | render() { 129 | const { 130 | buttonColor, 131 | buttonSize, 132 | borderColor, 133 | backgroundColor, 134 | icon, 135 | borderRadius 136 | } = this.props; 137 | const { buttonOpacity } = this.state; 138 | 139 | const position = { transform: this.state.drag.getTranslateTransform() }; 140 | 141 | return ( 142 | 152 | { 154 | var { x, y, width, height } = event.nativeEvent.layout; 155 | this.setState({ 156 | dimensions: { width, height }, 157 | position: { x, y } 158 | }); 159 | }} 160 | style={{ 161 | backgroundColor, 162 | height: buttonSize, 163 | borderRadius, 164 | justifyContent: "center" 165 | }} 166 | > 167 | {this.props.children && ( 168 | 174 | {this.props.children} 175 | 176 | )} 177 | 178 | 193 | {icon} 194 | 195 | 196 | 197 | ); 198 | } 199 | } 200 | 201 | RNSwipeVerify.propTypes = propTypes; 202 | RNSwipeVerify.defaultProps = defaultProps; 203 | --------------------------------------------------------------------------------