Skip to content

Instantly share code, notes, and snippets.

@cmaas
Last active January 21, 2020 11:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cmaas/84796aa351f211e50f855200c3a298dc to your computer and use it in GitHub Desktop.
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
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>
);
}
}
@cmaas
Copy link
Author

cmaas commented Jan 21, 2020

This is what it looks like:

stencil-code-entry-component

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment