import 'dart:async'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; import 'package:pin_code_fields/pin_code_fields.dart'; import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart'; import 'package:syncrow_app/features/auth/view/create_new_password.dart'; import 'package:syncrow_app/features/shared_widgets/default_button.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/title_medium.dart'; import 'package:syncrow_app/generated/assets.dart'; import 'package:syncrow_app/navigation/routing_constants.dart'; import 'package:syncrow_app/utils/context_extension.dart'; import 'package:syncrow_app/utils/helpers/life_cycle_event_handler.dart'; import 'package:syncrow_app/utils/helpers/shared_preferences_helper.dart'; import 'package:syncrow_app/utils/resource_manager/constants.dart'; import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; class OtpView extends StatefulWidget { final bool isForgetPage; const OtpView({super.key, this.isForgetPage = false}); @override State createState() => _OtpViewState(); } class _OtpViewState extends State { bool timerCanceled = false; Timer? countdownTimer; Duration myDuration = const Duration(); late LifecycleEventHandler _lifecycleEventHandler; String otpCode = ''; int? remainingSec = 30; @override void initState() { super.initState(); bool timerStarted = false; _lifecycleEventHandler = LifecycleEventHandler( resumeCallBack: () async { SharedPreferencesHelper.saveBoolToSP('timeStampSaved', false); String timeStampInBackground = await SharedPreferencesHelper.readStringFromSP('timeStamp'); int savedCounter = await SharedPreferencesHelper.readIntFromSP('savedCounter'); DateTime currentTime = DateTime.now(); int differenceInSeconds = timeStampInBackground.isNotEmpty ? currentTime.difference(DateTime.parse(timeStampInBackground)).inSeconds : 0; remainingSec = differenceInSeconds > savedCounter ? 0 : savedCounter - differenceInSeconds; timerStarted = true; startTimer(remainingSec ?? 0); return; }, suspendingCallBack: () async { handleTimerOnBackground(); }, onPauseCallBack: () async { handleTimerOnBackground(); }, inactiveCallBack: () async { handleTimerOnBackground(); }, ); WidgetsBinding.instance.addObserver(_lifecycleEventHandler); if (!timerStarted) { timerStarted = false; startTimer(remainingSec ?? 0); } } @override void dispose() { WidgetsBinding.instance.removeObserver(_lifecycleEventHandler); super.dispose(); } handleTimerOnBackground() async { bool timeStampSaved = await SharedPreferencesHelper.readBoolFromSP('timeStampSaved') ?? false; if (!timeStampSaved) { final dateInString = DateTime.now().toString(); SharedPreferencesHelper.saveIntToSP('savedCounter', remainingSec ?? 0); SharedPreferencesHelper.saveStringToSP('timeStamp', dateInString); SharedPreferencesHelper.saveBoolToSP('timeStampSaved', true); } } void startTimer(int sec) { timerCanceled = false; if (countdownTimer != null) { countdownTimer!.cancel(); countdownTimer = null; } myDuration = Duration(seconds: sec); int seconds = sec; countdownTimer = Timer.periodic(const Duration(seconds: 1), (values) { seconds = seconds - 1; remainingSec = seconds; if (mounted) { if (seconds < 0) { setState(() { countdownTimer!.cancel(); timerCanceled = true; WidgetsBinding.instance.removeObserver(_lifecycleEventHandler); }); } else { setState(() { myDuration = Duration(seconds: remainingSec ?? seconds); }); } } }); } @override Widget build(BuildContext context) { String maskedEmail = AuthCubit.get(context).maskEmail(AuthCubit.get(context).email); return BlocConsumer( listener: (context, state) { if (state is AuthOtpSuccess) { Navigator.of(context).pop(); Navigator.of(context).pop(); widget.isForgetPage ? Navigator.push( context, MaterialPageRoute( builder: (context) => const CreateNewPasswordPage(), )) : Navigator.popAndPushNamed(context, Routes.homeRoute); } if (state is ResendOtpSuccess) { startTimer(30); } }, builder: (context, state) { return Scaffold( body: Stack( children: [ Container( width: MediaQuery.sizeOf(context).width, height: MediaQuery.sizeOf(context).height, decoration: const BoxDecoration( image: DecorationImage( image: AssetImage( Assets.assetsImagesBackground, ), fit: BoxFit.cover, ), ), ), Container( width: MediaQuery.sizeOf(context).width, height: MediaQuery.sizeOf(context).height, decoration: const BoxDecoration( image: DecorationImage( image: AssetImage(Assets.assetsImagesVector), fit: BoxFit.cover, opacity: 0.9, ), ), ), SafeArea( child: Padding( padding: const EdgeInsets.only( right: Constants.defaultPadding, left: Constants.defaultPadding, top: Constants.defaultPadding, ), child: SingleChildScrollView( child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ Center( child: SvgPicture.asset( Assets.assetsImagesLogo, width: 160, ), ), const SizedBox( height: 40, ), TitleMedium( text: 'Verification Code', style: context.titleMedium.copyWith( fontWeight: FontsManager.extraBold, color: Colors.white, ), ), const SizedBox( height: 20, ), GestureDetector( onTap: () { Navigator.of(context).pop(); }, child: RichText( text: TextSpan( text: 'We have sent the verification code to', style: Theme.of(context).textTheme.titleSmall!.copyWith( color: Colors.white, fontWeight: FontsManager.regular, fontSize: 14, ), children: [ TextSpan( text: ' $maskedEmail', style: Theme.of(context).textTheme.titleSmall!.copyWith( color: Colors.black, fontWeight: FontsManager.bold, fontSize: 14, ), ), TextSpan( text: ' change email?', style: Theme.of(context).textTheme.titleSmall!.copyWith( color: const Color(0xFF87C7FF), fontWeight: FontsManager.regular, fontSize: 14, ), ), ]), ), ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox( height: 40, ), PinCodeTextField( key: const Key('pin_code_text_field'), appContext: context, length: 6, obscuringCharacter: '-', cursorHeight: 25, keyboardType: TextInputType.number, autoFocus: true, backgroundColor: Colors.transparent, animationDuration: const Duration(milliseconds: 30), beforeTextPaste: (text) { // Allow pasting only if all characters are numeric return int.tryParse(text!) != null; }, textStyle: Theme.of(context) .textTheme .headlineMedium! .copyWith(color: Colors.white), hintStyle: Theme.of(context) .textTheme .headlineMedium! .copyWith(color: Colors.white), enablePinAutofill: true, pinTheme: PinTheme( borderRadius: BorderRadius.circular(8), inactiveBorderWidth: 1, disabledBorderWidth: 1, selectedBorderWidth: 1, activeBorderWidth: 1, errorBorderWidth: 1, borderWidth: 1, errorBorderColor: Colors.red, activeColor: state is AuthLoginError ? Colors.red : Colors.white, inactiveColor: state is AuthLoginError ? Colors.red : Colors.white, activeFillColor: state is AuthLoginError ? Colors.red : Colors.white, inactiveFillColor: state is AuthLoginError ? Colors.red : Colors.white, selectedFillColor: state is AuthLoginError ? Colors.red : Colors.white, disabledColor: Colors.white, fieldHeight: 56, fieldWidth: MediaQuery.sizeOf(context).width > 340 ? 40 : 20, // fieldWidth: 40, selectedColor: Colors.white, shape: PinCodeFieldShape.box, ), onChanged: (value) { AuthCubit.get(context).setOtpCode(value); }, onCompleted: (value) {}, onSubmitted: (value) { // AuthCubit.get(context).setOtpCode(value); }, ), const SizedBox(height: 40), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( child: DefaultButton( isDone: state is AuthLoginSuccess, isLoading: state is AuthLoading, customButtonStyle: ButtonStyle( backgroundColor: MaterialStateProperty.all( Colors.black.withOpacity(.25), ), foregroundColor: MaterialStateProperty.all( Colors.white, ), ), child: const Text( 'Verify', ), onPressed: () { if ((state is! AuthLoading)) { AuthCubit.get(context).verifyOtp(widget.isForgetPage); FocusScope.of(context).unfocus(); } }, ), ), const SizedBox( width: 4, ), Expanded( child: DefaultButton( isDone: state is AuthLoginSuccess, isLoading: state is AuthLoading, customButtonStyle: ButtonStyle( backgroundColor: MaterialStateProperty.all( Colors.black.withOpacity(.25), ), foregroundColor: MaterialStateProperty.all( Colors.white, ), ), child: Text( timerCanceled ? 'Resend' : myDuration.inSeconds .remainder(60) .toString() .padLeft(2, '0'), ), onPressed: () async { if (!timerCanceled) { return; } if ((state is! AuthLoading)) { await AuthCubit.get(context).reSendOtp(); FocusScope.of(context).unfocus(); } }, ), ), ], ) ], ), ], ), ), ), ), ) ], ), ); }, ); } }