Last active
January 21, 2020 11:33
-
-
Save cmaas/84796aa351f211e50f855200c3a298dc to your computer and use it in GitHub Desktop.
Simple StencilJS component to show a code entry for an app lock screen. Requires @ionic/core for UI
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Component, Event, EventEmitter, Prop, State, h } from '@stencil/core'; | |
/* | |
Requires @ionic/core for the UI. | |
CSS shake animation by Sarah Drashner to place whereever you want, | |
taken from https://css-tricks.com/snippets/css/shake-css-keyframe-animation/ | |
.shake-animation { | |
animation: shake 0.82s cubic-bezier(.36,.07,.19,.97) both; | |
transform: translate3d(0, 0, 0); | |
backface-visibility: hidden; | |
perspective: 1000px; | |
} | |
@keyframes shake { | |
10%, 90% { | |
transform: translate3d(-1px, 0, 0); | |
} | |
20%, 80% { | |
transform: translate3d(2px, 0, 0); | |
} | |
30%, 50%, 70% { | |
transform: translate3d(-4px, 0, 0); | |
} | |
40%, 60% { | |
transform: translate3d(4px, 0, 0); | |
} | |
} | |
*/ | |
/** | |
* If a code is required, check if it's correct. Also check if it matches a master code, | |
* because users WILL forget their code. This component is not for security, but as a simple | |
* method for protecting the app from kids or snoopy friends. | |
*/ | |
function hasError(enteredCode: string, requiredCode: string): boolean { | |
// no error, if no code is required | |
if (! requiredCode || requiredCode.length < 4) { | |
return false; | |
} | |
// no error, if code is correct | |
if (enteredCode === requiredCode) { | |
return false; | |
} | |
// no error, if code is master code | |
if (enteredCode === '0203') { | |
return false; | |
} | |
return true; | |
} | |
/** | |
* Simple code entry component. Emits an event, if 4 digits were entered. | |
* Optionally checks if the provided code matches the correct code and shows | |
* a shaking animation when the code is wrong. | |
* | |
* This way, this component can be used as a lock screen (by providing a `correctCode`) | |
* or when setting up the code for the lockscreen itself. Then, you probably want to have | |
* a two-step process: 1. show component without requiring a correct code, 2. show component | |
* to match the previously entered code as `correctCode`. | |
*/ | |
@Component({ | |
tag: 'code-entry' | |
}) | |
export class CodeEntry { | |
/** | |
* Optional: If a correct code is provided, show an animation if the entered code is wrong | |
*/ | |
@Prop() correctCode = ''; | |
/** | |
* Keep track of what was entered | |
*/ | |
@State() enteredCode = ''; | |
/** | |
* Once all 4 digits are entered, emit an event | |
*/ | |
@Event() codeCompleted: EventEmitter; | |
/** | |
* Bind methods | |
*/ | |
constructor() { | |
this.appendDigit.bind(this); | |
this.removeLastDigit.bind(this); | |
} | |
/** | |
* Add a digit to the end | |
*/ | |
appendDigit(digit: string) { | |
if (this.enteredCode.length < 4) { | |
this.enteredCode += digit; | |
} | |
if (this.enteredCode.length >= 4) { | |
// emit event whenever 4 digits were entered | |
this.codeCompleted.emit(this.enteredCode); | |
// if the component requires the correct code, clear the input | |
// after 1 second (roughly after the animation is done) | |
if (hasError(this.enteredCode, this.correctCode)) { | |
setTimeout( () => { | |
this.enteredCode = ''; | |
}, 1000); | |
} | |
} | |
} | |
/** | |
* Remove last entered digit | |
*/ | |
removeLastDigit() { | |
if (this.enteredCode.length > 0) { | |
this.enteredCode = this.enteredCode.substr(0, this.enteredCode.length - 1); | |
} | |
} | |
render() { | |
const error = this.enteredCode.length >= 4 && hasError(this.enteredCode, this.correctCode); | |
return ( | |
<section> | |
<div class={'ion-text-center' + (error ? ' shake-animation' : '')} style={{'margin-bottom': '32px'}}> | |
<ion-icon color={error ? 'danger' : 'dark'} size="large" name={this.enteredCode.length >= 1 ? 'radio-button-on' : 'radio-button-off'}></ion-icon> | |
<ion-icon color={error ? 'danger' : 'dark'} size="large" name={this.enteredCode.length >= 2 ? 'radio-button-on' : 'radio-button-off'}></ion-icon> | |
<ion-icon color={error ? 'danger' : 'dark'} size="large" name={this.enteredCode.length >= 3 ? 'radio-button-on' : 'radio-button-off'}></ion-icon> | |
<ion-icon color={error ? 'danger' : 'dark'} size="large" name={this.enteredCode.length >= 4 ? 'radio-button-on' : 'radio-button-off'}></ion-icon> | |
</div> | |
<ion-grid style={{'max-width': '280px'}}> | |
<ion-row class="ion-justify-content-center"> | |
<ion-col class="ion-text-right"><ion-button color="dark" fill="outline" shape="round" size="large" style={{'width': '60px', 'height': '60px', 'padding': '0'}} onClick={() => this.appendDigit('1')}>1</ion-button></ion-col> | |
<ion-col class="ion-text-center"><ion-button color="dark" fill="outline" shape="round" size="large" style={{'width': '60px', 'height': '60px', 'padding': '0'}} onClick={() => this.appendDigit('2')}>2</ion-button></ion-col> | |
<ion-col class="ion-text-left"><ion-button color="dark" fill="outline" shape="round" size="large" style={{'width': '60px', 'height': '60px', 'padding': '0'}} onClick={() => this.appendDigit('3')}>3</ion-button></ion-col> | |
</ion-row> | |
<ion-row class="ion-justify-content-center"> | |
<ion-col class="ion-text-right"><ion-button color="dark" fill="outline" shape="round" size="large" style={{'width': '60px', 'height': '60px', 'padding': '0'}} onClick={() => this.appendDigit('4')}>4</ion-button></ion-col> | |
<ion-col class="ion-text-center"><ion-button color="dark" fill="outline" shape="round" size="large" style={{'width': '60px', 'height': '60px', 'padding': '0'}} onClick={() => this.appendDigit('5')}>5</ion-button></ion-col> | |
<ion-col class="ion-text-left"><ion-button color="dark" fill="outline" shape="round" size="large" style={{'width': '60px', 'height': '60px', 'padding': '0'}} onClick={() => this.appendDigit('6')}>6</ion-button></ion-col> | |
</ion-row> | |
<ion-row class="ion-justify-content-center"> | |
<ion-col class="ion-text-right"><ion-button color="dark" fill="outline" shape="round" size="large" style={{'width': '60px', 'height': '60px', 'padding': '0'}} onClick={() => this.appendDigit('7')}>7</ion-button></ion-col> | |
<ion-col class="ion-text-center"><ion-button color="dark" fill="outline" shape="round" size="large" style={{'width': '60px', 'height': '60px', 'padding': '0'}} onClick={() => this.appendDigit('8')}>8</ion-button></ion-col> | |
<ion-col class="ion-text-left"><ion-button color="dark" fill="outline" shape="round" size="large" style={{'width': '60px', 'height': '60px', 'padding': '0'}} onClick={() => this.appendDigit('9')}>9</ion-button></ion-col> | |
</ion-row> | |
<ion-row class="ion-justify-content-center"> | |
<ion-col offset="4" class="ion-text-center"><ion-button color="dark" fill="outline" shape="round" size="large" style={{'width': '60px', 'height': '60px', 'padding': '0'}} onClick={() => this.appendDigit('0')}>0</ion-button></ion-col> | |
<ion-col class="ion-text-left"><ion-button color="dark" fill="clear" size="large" onClick={() => this.removeLastDigit()}><ion-icon name="backspace"></ion-icon></ion-button></ion-col> | |
</ion-row> | |
</ion-grid> | |
</section> | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is what it looks like: