Created
February 10, 2021 12:09
-
-
Save roipeker/4ec27f2f3e2010c41210a41391784bc0 to your computer and use it in GitHub Desktop.
PIN code sample with hidden TextField.
This file contains hidden or 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
/// roipeker 2021. | |
import 'package:cryptericon/utils/common.dart'; | |
class EditPhoneConfirmationView extends GetView<MyTextInputController> { | |
static const id = '/contact-edit/phone/confirmation'; | |
@override | |
Widget build(BuildContext context) { | |
Get.put(MyTextInputController()); | |
return Scaffold( | |
appBar: CommonAppBar( | |
title: 'Phone', | |
actionIcon: null, | |
showBack: true, | |
), | |
body: Padding( | |
padding: const EdgeInsets.symmetric(horizontal: 16.0), | |
child: Center( | |
child: Container( | |
constraints: BoxConstraints(maxWidth: 500), | |
child: Column( | |
crossAxisAlignment: CrossAxisAlignment.stretch, | |
mainAxisAlignment: MainAxisAlignment.start, | |
children: [ | |
kGap18, | |
Text( | |
"Phone Verification", | |
style: TextStyle( | |
fontFamily: 'ProximaNova', | |
color: const Color(0xfff0f0f0), | |
fontSize: 20, | |
fontWeight: FontWeight.w700, | |
fontStyle: FontStyle.normal, | |
// letterSpacing: 0.15000000953674317, | |
letterSpacing: 7.5 / 20, | |
), | |
), | |
Gap(12), | |
Text( | |
"Enter the code that came in the message", | |
style: TextStyle( | |
fontFamily: 'ProximaNova', | |
color: const Color(0xfff0f0f0), | |
fontSize: 14, | |
fontWeight: FontWeight.w400, | |
), | |
), | |
_DigitBuilder(), | |
// kGap18, | |
// kGap32, | |
Obx( | |
() => SolidButton.solid( | |
onTap: controller.isFull ? () {} : null, | |
label: 'SEND', | |
), | |
), | |
], | |
), | |
), | |
), | |
), | |
); | |
} | |
} | |
class _DigitBuilder extends GetView<MyTextInputController> { | |
@override | |
Widget build(BuildContext context) { | |
return GestureDetector( | |
onTap: controller.onTap, | |
child: Container( | |
color: Colors.transparent, | |
padding: EdgeInsets.symmetric(vertical: 33), | |
child: Stack( | |
alignment: Alignment.center, | |
children: [ | |
FittedBox( | |
child: Obx( | |
() => Row( | |
mainAxisAlignment: MainAxisAlignment.start, | |
children: [ | |
...List.generate( | |
controller.numChars, | |
(index) => _DigitInput( | |
value: controller.getSlotValue(index), | |
state: controller.getSlowState(index), | |
hasGap: index < controller.numChars, | |
), | |
), | |
], | |
), | |
), | |
), | |
Offstage( | |
offstage: true, | |
child: TextField( | |
maxLines: 1, | |
maxLength: 6, | |
maxLengthEnforcement: MaxLengthEnforcement.enforced, | |
expands: false, | |
autocorrect: false, | |
keyboardType: TextInputType.number, | |
inputFormatters: [ | |
FilteringTextInputFormatter.digitsOnly, | |
FilteringTextInputFormatter.singleLineFormatter, | |
], | |
focusNode: controller.hiddenFocusNode, | |
controller: controller.hiddenController, | |
), | |
), | |
], | |
), | |
), | |
); | |
} | |
} | |
enum DigitInputState { empty, focused, entered } | |
class _DigitInput extends StatelessWidget { | |
final String value; | |
final bool hasGap; | |
final DigitInputState state; | |
const _DigitInput({ | |
Key key, | |
this.hasGap = false, | |
this.state = DigitInputState.empty, | |
this.value = '1', | |
}) : super(key: key); | |
BoxDecoration _getDecoration() { | |
if (state == DigitInputState.focused) { | |
return BoxDecoration( | |
color: Color(0xff282835), | |
borderRadius: BorderRadius.circular(8), | |
border: Border.all(color: AppColors.macaroniAndCheese, width: 3), | |
); | |
} else if (state == DigitInputState.entered) { | |
return BoxDecoration( | |
color: Color(0xff282835).withOpacity(.8), | |
borderRadius: BorderRadius.circular(8), | |
border: Border.all(color: Color(0xff73CA86).withOpacity(1), width: .8), | |
// border: Border.all(color: Color(0xff73CA86).withOpacity(1), width: 1), | |
); | |
} | |
/// empty | |
return BoxDecoration( | |
color: Color(0xff282835).withOpacity(.2), | |
borderRadius: BorderRadius.circular(8), | |
border: Border.all(color: Color(0xff393A4B), width: 1), | |
); | |
} | |
@override | |
Widget build(BuildContext context) { | |
BoxDecoration deco = _getDecoration(); | |
return AnimatedContainer( | |
duration: .3.seconds, | |
width: 50, | |
height: 60, | |
margin: hasGap ? EdgeInsets.only(right: 16) : null, | |
decoration: deco, | |
alignment: Alignment.center, | |
child: Text( | |
value, | |
style: const TextStyle( | |
fontFamily: 'ProximaNova', | |
color: const Color(0xfff0f0f0), | |
fontSize: 24, | |
fontWeight: FontWeight.w600, | |
), | |
), | |
); | |
} | |
} | |
class MyTextInputController extends GetxController { | |
TextEditingController hiddenController = TextEditingController(); | |
FocusNode hiddenFocusNode = FocusNode(); | |
final _input = ''.obs; | |
final numChars = 6; | |
@override | |
void onReady() { | |
super.onReady(); | |
hiddenController.addListener(() { | |
var txt = hiddenController.text; | |
final maxLen = Math.min(numChars, txt.length); | |
_input.value = txt.substring(0, maxLen); | |
}); | |
} | |
bool get isFull { | |
var len = _input().length; | |
return len >= 6; | |
} | |
String getSlotValue(int index) { | |
var str = _input(); | |
if (str.length <= index) { | |
return ''; | |
} | |
return str[index]; | |
} | |
DigitInputState getSlowState(int index) { | |
var str = _input(); | |
if (index == str.length) { | |
return DigitInputState.focused; | |
} else if (index < str.length) { | |
return DigitInputState.entered; | |
} | |
return DigitInputState.empty; | |
} | |
@override | |
void onClose() { | |
super.onClose(); | |
hiddenFocusNode?.dispose(); | |
hiddenController?.dispose(); | |
_input?.close(); | |
} | |
void onTap() { | |
hiddenFocusNode.requestFocus(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment