From 48f169d10cdffcba1b36ae5809937ce484a47912 Mon Sep 17 00:00:00 2001 From: asif Date: Thu, 4 Sep 2025 00:14:36 +0530 Subject: [PATCH] applied animations and haptics to TPIN screen --- .../screens/transaction_pin_screen.dart | 113 +++++++++++++----- 1 file changed, 80 insertions(+), 33 deletions(-) diff --git a/lib/features/fund_transfer/screens/transaction_pin_screen.dart b/lib/features/fund_transfer/screens/transaction_pin_screen.dart index c3ba7d9..ecf76dc 100644 --- a/lib/features/fund_transfer/screens/transaction_pin_screen.dart +++ b/lib/features/fund_transfer/screens/transaction_pin_screen.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:kmobile/api/services/auth_service.dart'; import 'package:kmobile/di/injection.dart'; import 'package:kmobile/features/fund_transfer/screens/tpin_prompt_screen.dart'; @@ -14,15 +15,40 @@ class TransactionPinScreen extends StatefulWidget { State createState() => _TransactionPinScreenState(); } -class _TransactionPinScreenState extends State { +class _TransactionPinScreenState extends State + with SingleTickerProviderStateMixin { final List _pin = []; bool _loading = true; bool _isSubmitting = false; + late final AnimationController _bounceController; + late final Animation _bounceAnimation; + @override void initState() { super.initState(); _checkIfTpinIsSet(); + + _bounceController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 150), + ); + _bounceAnimation = Tween(begin: 1.0, end: 1.1).animate( + CurvedAnimation( + parent: _bounceController, + curve: Curves.elasticIn, + reverseCurve: Curves.elasticOut), + )..addStatusListener((status) { + if (status == AnimationStatus.completed) { + _bounceController.reverse(); + } + }); + } + + @override + void dispose() { + _bounceController.dispose(); + super.dispose(); } Future _checkIfTpinIsSet() async { @@ -57,6 +83,7 @@ class _TransactionPinScreenState extends State { if (_pin.isNotEmpty) _pin.removeLast(); } else if (_pin.length < 6) { _pin.add(value); + _bounceController.forward(from: 0); } }); } @@ -83,29 +110,46 @@ class _TransactionPinScreenState extends State { } Widget _buildPinIndicators() { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: List.generate(6, (index) { - return Container( - margin: const EdgeInsets.symmetric(horizontal: 8), - width: 20, - height: 20, - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all(color: Theme.of(context).primaryColor, width: 2), - color: index < _pin.length - ? Theme.of(context).colorScheme.primary - : Colors.transparent, - ), - ); - }), - ); + return AnimatedBuilder( + animation: _bounceAnimation, + builder: (context, child) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate(6, (index) { + final isAnimating = + index == _pin.length - 1 && _bounceController.isAnimating; + return Transform.scale( + scale: isAnimating ? _bounceAnimation.value : 1.0, + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 8), + width: 30, + height: 30, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: index < _pin.length + ? Theme.of(context).colorScheme.onSurfaceVariant + : Theme.of(context) + .colorScheme + .surfaceContainerHighest), + ), + ); + }), + ); + }); } Widget _buildKey(String label, {IconData? icon}) { return Expanded( child: InkWell( - onTap: () => label == 'done' ? _submitPin() : _onKeyPressed(label), + onTap: () { + HapticFeedback.lightImpact(); + if (label == 'done') { + _submitPin(); + } else { + _onKeyPressed(label); + } + }, + borderRadius: BorderRadius.circular(48), child: Padding( padding: const EdgeInsets.symmetric(vertical: 20), child: Center( @@ -147,22 +191,25 @@ class _TransactionPinScreenState extends State { ), body: _loading ? const Center(child: CircularProgressIndicator()) - : Padding( - padding: const EdgeInsets.only(bottom: 20.0), - child: Column( - children: [ - const Spacer(), - Text( - AppLocalizations.of(context).enterTpin, - style: const TextStyle(fontSize: 18), - ), - const SizedBox(height: 20), - _buildPinIndicators(), - const Spacer(), - _buildKeypad(), - ], + : SafeArea( + child: Padding( + padding: const EdgeInsets.only(bottom: 20.0), + child: Column( + children: [ + const Spacer(), + Text( + AppLocalizations.of(context).enterTpin, + style: const TextStyle(fontSize: 18), + ), + const SizedBox(height: 50), + _buildPinIndicators(), + const Spacer(), + _buildKeypad(), + ], + ), ), ), ); } } +