526 lines
16 KiB
Dart
526 lines
16 KiB
Dart
library flutter_lock_screen;
|
|
|
|
import 'dart:async';
|
|
import 'dart:io';
|
|
import 'package:flutter/material.dart';
|
|
|
|
typedef void DeleteCode();
|
|
typedef Future<bool> PassCodeVerify(List<int> passcode);
|
|
|
|
class LockScreen extends StatefulWidget {
|
|
/// Password on success method
|
|
final VoidCallback onSuccess;
|
|
|
|
/// Password finger function for auth
|
|
final VoidCallback? fingerFunction;
|
|
|
|
/// Password finger verify for auth
|
|
final bool? fingerVerify;
|
|
|
|
/// screen title
|
|
final String title;
|
|
|
|
/// Pass length
|
|
final int passLength;
|
|
|
|
/// Wrong password dialog
|
|
final bool? showWrongPassDialog;
|
|
|
|
/// Showing finger print area
|
|
final bool? showFingerPass;
|
|
|
|
/// Wrong password dialog title
|
|
final String? wrongPassTitle;
|
|
|
|
/// Wrong password dialog content
|
|
final String? wrongPassContent;
|
|
|
|
/// Wrong password dialog button text
|
|
final String? wrongPassCancelButtonText;
|
|
|
|
/// Background image
|
|
final String? bgImage;
|
|
|
|
/// Color for numbers
|
|
final Color? numColor;
|
|
|
|
/// Finger print image
|
|
final Widget? fingerPrintImage;
|
|
|
|
/// border color
|
|
final Color? borderColor;
|
|
|
|
/// foreground color
|
|
final Color? foregroundColor;
|
|
|
|
/// Password verify
|
|
final PassCodeVerify passCodeVerify;
|
|
|
|
/// Lock Screen constructer
|
|
LockScreen({
|
|
required this.onSuccess,
|
|
required this.title,
|
|
this.borderColor,
|
|
this.foregroundColor = Colors.transparent,
|
|
required this.passLength,
|
|
required this.passCodeVerify,
|
|
this.fingerFunction,
|
|
this.fingerVerify = false,
|
|
this.showFingerPass = false,
|
|
this.bgImage,
|
|
this.numColor = Colors.black,
|
|
this.fingerPrintImage,
|
|
this.showWrongPassDialog = false,
|
|
this.wrongPassTitle,
|
|
this.wrongPassContent,
|
|
this.wrongPassCancelButtonText,
|
|
}) : assert(passLength <= 8),
|
|
assert(bgImage != null),
|
|
assert(borderColor != null),
|
|
assert(foregroundColor != null);
|
|
|
|
@override
|
|
_LockScreenState createState() => _LockScreenState();
|
|
}
|
|
|
|
class _LockScreenState extends State<LockScreen> {
|
|
var _currentCodeLength = 0;
|
|
var _inputCodes = <int>[];
|
|
var _currentState = 0;
|
|
Color circleColor = Colors.white;
|
|
|
|
_onCodeClick(int code) {
|
|
if (_currentCodeLength < widget.passLength) {
|
|
setState(() {
|
|
_currentCodeLength++;
|
|
_inputCodes.add(code);
|
|
});
|
|
|
|
if (_currentCodeLength == widget.passLength) {
|
|
widget.passCodeVerify(_inputCodes).then((onValue) {
|
|
if (onValue) {
|
|
setState(() {
|
|
_currentState = 1;
|
|
});
|
|
widget.onSuccess();
|
|
} else {
|
|
_currentState = 2;
|
|
new Timer(new Duration(milliseconds: 1000), () {
|
|
setState(() {
|
|
_currentState = 0;
|
|
_currentCodeLength = 0;
|
|
_inputCodes.clear();
|
|
});
|
|
});
|
|
if (widget.showWrongPassDialog!) {
|
|
showDialog(
|
|
barrierDismissible: false,
|
|
context: context,
|
|
builder: (BuildContext context) {
|
|
return Center(
|
|
child: AlertDialog(
|
|
title: Text(
|
|
widget.wrongPassTitle!,
|
|
style: TextStyle(fontFamily: "Open Sans"),
|
|
),
|
|
content: Text(
|
|
widget.wrongPassContent!,
|
|
style: TextStyle(fontFamily: "Open Sans"),
|
|
),
|
|
actions: <Widget>[
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: Text(
|
|
widget.wrongPassCancelButtonText!,
|
|
style: TextStyle(color: Colors.blue),
|
|
),
|
|
)
|
|
],
|
|
),
|
|
);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
_fingerPrint() {
|
|
if (widget.fingerVerify!) {
|
|
widget.onSuccess();
|
|
}
|
|
}
|
|
|
|
_deleteCode() {
|
|
setState(() {
|
|
if (_currentCodeLength > 0) {
|
|
_currentState = 0;
|
|
_currentCodeLength--;
|
|
_inputCodes.removeAt(_currentCodeLength);
|
|
}
|
|
});
|
|
}
|
|
|
|
_deleteAllCode() {
|
|
setState(() {
|
|
if (_currentCodeLength > 0) {
|
|
_currentState = 0;
|
|
_currentCodeLength = 0;
|
|
_inputCodes.clear();
|
|
}
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
Future.delayed(Duration(milliseconds: 200), () {
|
|
_fingerPrint();
|
|
});
|
|
return Scaffold(
|
|
backgroundColor: Colors.white,
|
|
body: Stack(
|
|
children: <Widget>[
|
|
Container(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: <Widget>[
|
|
Expanded(
|
|
flex: 3,
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
),
|
|
child: Stack(
|
|
children: <Widget>[
|
|
ClipPath(
|
|
clipper: BgClipper(),
|
|
child: Container(
|
|
height: MediaQuery.of(context).size.height,
|
|
width: MediaQuery.of(context).size.width,
|
|
decoration: BoxDecoration(
|
|
image: DecorationImage(
|
|
image: AssetImage(widget.bgImage!),
|
|
fit: BoxFit.cover,
|
|
colorFilter: ColorFilter.mode(
|
|
Colors.grey.shade800,
|
|
BlendMode.hardLight,
|
|
),
|
|
),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
children: <Widget>[
|
|
SizedBox(
|
|
height: Platform.isIOS ? 60 : 40,
|
|
),
|
|
Text(
|
|
widget.title,
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
fontFamily: "Open Sans"),
|
|
),
|
|
SizedBox(
|
|
height: Platform.isIOS ? 50 : 30,
|
|
),
|
|
CodePanel(
|
|
codeLength: widget.passLength,
|
|
currentLength: _currentCodeLength,
|
|
borderColor: widget.borderColor,
|
|
foregroundColor: widget.foregroundColor,
|
|
deleteCode: _deleteCode,
|
|
fingerVerify: widget.fingerVerify!,
|
|
status: _currentState,
|
|
),
|
|
SizedBox(
|
|
height: Platform.isIOS ? 30 : 15,
|
|
),
|
|
Text(
|
|
"TYPE PASSCODE",
|
|
style: TextStyle(
|
|
color: Colors.white70.withOpacity(0.3),
|
|
fontSize: 18,
|
|
fontFamily: "Open Sans"),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
widget.showFingerPass!
|
|
? Positioned(
|
|
top: MediaQuery.of(context).size.height /
|
|
(Platform.isIOS ? 4 : 5),
|
|
left: 20,
|
|
bottom: 10,
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
widget.fingerFunction!();
|
|
},
|
|
child: widget.fingerPrintImage!,
|
|
),
|
|
)
|
|
: Container(),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
Expanded(
|
|
flex: Platform.isIOS ? 5 : 6,
|
|
child: Container(
|
|
padding: EdgeInsets.only(left: 0, top: 0),
|
|
child:
|
|
NotificationListener<OverscrollIndicatorNotification>(
|
|
onNotification: (overscroll) {
|
|
overscroll.disallowIndicator();
|
|
return true;
|
|
},
|
|
child: GridView.count(
|
|
crossAxisCount: 3,
|
|
childAspectRatio: 1.6,
|
|
mainAxisSpacing: 35,
|
|
padding: EdgeInsets.all(8),
|
|
children: <Widget>[
|
|
buildContainerCircle(1),
|
|
buildContainerCircle(2),
|
|
buildContainerCircle(3),
|
|
buildContainerCircle(4),
|
|
buildContainerCircle(5),
|
|
buildContainerCircle(6),
|
|
buildContainerCircle(7),
|
|
buildContainerCircle(8),
|
|
buildContainerCircle(9),
|
|
buildRemoveIcon(Icons.close),
|
|
buildContainerCircle(0),
|
|
buildContainerIcon(Icons.arrow_back),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
)
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget buildContainerCircle(int number) {
|
|
return InkResponse(
|
|
highlightColor: Colors.red,
|
|
onTap: () {
|
|
_onCodeClick(number);
|
|
},
|
|
child: Container(
|
|
height: 50,
|
|
width: 50,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
shape: BoxShape.circle,
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black12,
|
|
blurRadius: 10,
|
|
spreadRadius: 0,
|
|
)
|
|
]),
|
|
child: Center(
|
|
child: Text(
|
|
number.toString(),
|
|
style: TextStyle(
|
|
fontSize: 28,
|
|
fontWeight: FontWeight.normal,
|
|
color: widget.numColor),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget buildRemoveIcon(IconData icon) {
|
|
return InkResponse(
|
|
onTap: () {
|
|
if (0 < _currentCodeLength) {
|
|
_deleteAllCode();
|
|
}
|
|
},
|
|
child: Container(
|
|
height: 50,
|
|
width: 50,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
shape: BoxShape.circle,
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black12,
|
|
blurRadius: 10,
|
|
spreadRadius: 0,
|
|
)
|
|
]),
|
|
child: Center(
|
|
child: Icon(
|
|
icon,
|
|
size: 30,
|
|
color: widget.numColor,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget buildContainerIcon(IconData icon) {
|
|
return InkResponse(
|
|
onTap: () {
|
|
if (0 < _currentCodeLength) {
|
|
setState(() {
|
|
circleColor = Colors.grey.shade300;
|
|
});
|
|
Future.delayed(Duration(milliseconds: 200)).then((func) {
|
|
setState(() {
|
|
circleColor = Colors.white;
|
|
});
|
|
});
|
|
}
|
|
_deleteCode();
|
|
},
|
|
child: Container(
|
|
height: 50,
|
|
width: 50,
|
|
decoration: BoxDecoration(
|
|
color: circleColor,
|
|
shape: BoxShape.circle,
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black12,
|
|
blurRadius: 10,
|
|
spreadRadius: 0,
|
|
)
|
|
]),
|
|
child: Center(
|
|
child: Icon(
|
|
icon,
|
|
size: 30,
|
|
color: widget.numColor,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class CodePanel extends StatelessWidget {
|
|
final codeLength;
|
|
final currentLength;
|
|
final borderColor;
|
|
final bool? fingerVerify;
|
|
final foregroundColor;
|
|
final H = 30.0;
|
|
final W = 30.0;
|
|
final DeleteCode? deleteCode;
|
|
final int? status;
|
|
CodePanel(
|
|
{this.codeLength,
|
|
this.currentLength,
|
|
this.borderColor,
|
|
this.foregroundColor,
|
|
this.deleteCode,
|
|
this.fingerVerify,
|
|
this.status})
|
|
: assert(codeLength > 0),
|
|
assert(currentLength >= 0),
|
|
assert(currentLength <= codeLength),
|
|
assert(deleteCode != null),
|
|
assert(status == 0 || status == 1 || status == 2);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
var circles = <Widget>[];
|
|
var color = borderColor;
|
|
int circlePice = 1;
|
|
|
|
if (fingerVerify == true) {
|
|
do {
|
|
circles.add(
|
|
SizedBox(
|
|
width: W,
|
|
height: H,
|
|
child: new Container(
|
|
decoration: new BoxDecoration(
|
|
shape: BoxShape.circle,
|
|
border: new Border.all(color: color, width: 1.0),
|
|
color: Colors.green.shade500,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
circlePice++;
|
|
} while (circlePice <= codeLength);
|
|
} else {
|
|
if (status == 1) {
|
|
color = Colors.green.shade500;
|
|
}
|
|
if (status == 2) {
|
|
color = Colors.red.shade500;
|
|
}
|
|
for (int i = 1; i <= codeLength; i++) {
|
|
if (i > currentLength) {
|
|
circles.add(SizedBox(
|
|
width: W,
|
|
height: H,
|
|
child: Container(
|
|
decoration: new BoxDecoration(
|
|
shape: BoxShape.circle,
|
|
border: new Border.all(color: color, width: 2.0),
|
|
color: foregroundColor),
|
|
)));
|
|
} else {
|
|
circles.add(new SizedBox(
|
|
width: W,
|
|
height: H,
|
|
child: new Container(
|
|
decoration: new BoxDecoration(
|
|
shape: BoxShape.circle,
|
|
border: new Border.all(color: color, width: 1.0),
|
|
color: color,
|
|
),
|
|
)));
|
|
}
|
|
}
|
|
}
|
|
|
|
return new SizedBox.fromSize(
|
|
size: new Size(MediaQuery.of(context).size.width, 30.0),
|
|
child: new Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: <Widget>[
|
|
SizedBox.fromSize(
|
|
size: new Size(40.0 * codeLength, H),
|
|
child: new Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: circles,
|
|
)),
|
|
]),
|
|
);
|
|
}
|
|
}
|
|
|
|
class BgClipper extends CustomClipper<Path> {
|
|
@override
|
|
Path getClip(Size size) {
|
|
Path path = Path();
|
|
path.lineTo(0, size.height);
|
|
path.lineTo(size.width, size.height / 1.5);
|
|
path.lineTo(size.width, 0);
|
|
return path;
|
|
}
|
|
|
|
@override
|
|
bool shouldReclip(CustomClipper<Path> oldClipper) {
|
|
return false;
|
|
}
|
|
}
|