added pinput package for better ux of enter otp and added back button in otp,set password and set otp screen

This commit is contained in:
Raj.Ghag
2026-04-24 19:23:18 +05:30
parent 7e2a743227
commit 79816d3066
10 changed files with 289 additions and 165 deletions

View File

@@ -20,7 +20,5 @@
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.0</string> <string>1.0</string>
<key>MinimumOSVersion</key>
<string>13.0</string>
</dict> </dict>
</plist> </plist>

View File

@@ -36,6 +36,8 @@ PODS:
- Flutter (1.0.0) - Flutter (1.0.0)
- flutter_native_splash (2.4.3): - flutter_native_splash (2.4.3):
- Flutter - Flutter
- image_picker_ios (0.0.1):
- Flutter
- mobile_scanner (7.0.0): - mobile_scanner (7.0.0):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
@@ -54,6 +56,7 @@ DEPENDENCIES:
- file_picker (from `.symlinks/plugins/file_picker/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`) - mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
@@ -72,6 +75,8 @@ EXTERNAL SOURCES:
:path: Flutter :path: Flutter
flutter_native_splash: flutter_native_splash:
:path: ".symlinks/plugins/flutter_native_splash/ios" :path: ".symlinks/plugins/flutter_native_splash/ios"
image_picker_ios:
:path: ".symlinks/plugins/image_picker_ios/ios"
mobile_scanner: mobile_scanner:
:path: ".symlinks/plugins/mobile_scanner/darwin" :path: ".symlinks/plugins/mobile_scanner/darwin"
path_provider_foundation: path_provider_foundation:
@@ -82,13 +87,14 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS: SPEC CHECKSUMS:
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29
mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93 image_picker_ios: 4f2f91b01abdb52842a8e277617df877e40f905b
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e
path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba
SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
PODFILE CHECKSUM: 251cb053df7158f337c0712f2ab29f4e0fa474ce PODFILE CHECKSUM: 251cb053df7158f337c0712f2ab29f4e0fa474ce

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1610" LastUpgradeVersion = "1510"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"

View File

@@ -2,12 +2,15 @@ import Flutter
import UIKit import UIKit
@main @main
@objc class AppDelegate: FlutterAppDelegate { @objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
override func application( override func application(
_ application: UIApplication, _ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool { ) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions) return super.application(application, didFinishLaunchingWithOptions: launchOptions)
} }
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
}
} }

View File

@@ -1,48 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Citycards Partner Flutter</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>citycards_partner_flutter</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>This app needs camera access to scan QR codes</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs photos access to get QR code from photo library</string>
<key>UIApplicationSceneManifest</key>
<dict> <dict>
<key>CADisableMinimumFrameDurationOnPhone</key> <key>UIApplicationSupportsMultipleScenes</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Citycards Partner Flutter</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>citycards_partner_flutter</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>This app needs camera access to scan QR codes</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs photos access to get QR code from photo library</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen.storyboard</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIStatusBarHidden</key>
<false/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/> <false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneConfigurationName</key>
<string>flutter</string>
<key>UISceneDelegateClassName</key>
<string>FlutterSceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict> </dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen.storyboard</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIStatusBarHidden</key>
<false/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist> </plist>

View File

@@ -62,6 +62,29 @@ class _ForgotPasswordPageState extends State<ForgotPasswordPage> {
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
child: Scaffold( child: Scaffold(
backgroundColor: AppColors.backgroundWhite, backgroundColor: AppColors.backgroundWhite,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
leadingWidth: 70.w,
leading: Padding(
padding: EdgeInsets.only(left: 24.w, top: 8.h, bottom: 8.h),
child: InkWell(
onTap: () => Navigator.pop(context),
borderRadius: BorderRadius.circular(50),
child: Container(
decoration: const BoxDecoration(
color: AppColors.primaryRed,
shape: BoxShape.circle,
),
child: Icon(
Icons.arrow_back_ios_new,
color: Colors.white,
size: 18.sp,
),
),
),
),
),
body: BlocConsumer<ForgotPasswordBloc, ForgotPasswordState>( body: BlocConsumer<ForgotPasswordBloc, ForgotPasswordState>(
listener: (context, state) { listener: (context, state) {
if (state.status == ForgotPasswordStatus.success) { if (state.status == ForgotPasswordStatus.success) {
@@ -97,7 +120,7 @@ class _ForgotPasswordPageState extends State<ForgotPasswordPage> {
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
SizedBox(height: 40.h), SizedBox(height: 10.h),
// ===== LOGO SECTION ===== // ===== LOGO SECTION =====
Center( Center(
child: Column( child: Column(

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_otp_text_field/flutter_otp_text_field.dart'; import 'package:pinput/pinput.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import '../../constants/app_assets.dart'; import '../../constants/app_assets.dart';
@@ -21,7 +21,30 @@ class OtpVerificationPage extends StatelessWidget {
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
child: Scaffold( child: Scaffold(
backgroundColor: AppColors.backgroundWhite, backgroundColor: AppColors.backgroundWhite,
body: BlocConsumer<VerifyOtpBloc, VerifyOtpState>( appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
leadingWidth: 70.w,
leading: Padding(
padding: EdgeInsets.only(left: 24.w, top: 8.h, bottom: 8.h),
child: InkWell(
onTap: () => Navigator.pop(context),
borderRadius: BorderRadius.circular(50),
child: Container(
decoration: const BoxDecoration(
color: AppColors.primaryRed,
shape: BoxShape.circle,
),
child: Icon(
Icons.arrow_back_ios_new,
color: Colors.white,
size: 18.sp,
),
),
),
),
),
body: BlocListener<VerifyOtpBloc, VerifyOtpState>(
listener: (context, state) { listener: (context, state) {
if (state.status == VerifyOtpStatus.success) { if (state.status == VerifyOtpStatus.success) {
Navigator.pushReplacementNamed( Navigator.pushReplacementNamed(
@@ -39,122 +62,149 @@ class OtpVerificationPage extends StatelessWidget {
); );
} }
}, },
builder: (context, state) { child: SafeArea(
final isLoading = state.status == VerifyOtpStatus.loading; child: Padding(
padding: EdgeInsets.symmetric(horizontal: 24.w),
return SafeArea( child: Column(
child: Padding( children: [
padding: EdgeInsets.symmetric(horizontal: 24.w), Expanded(
child: Column( child: SingleChildScrollView(
children: [ child: Column(
Expanded( children: [
child: SingleChildScrollView( SizedBox(height: 10.h),
child: Column( // ===== LOGO SECTION =====
children: [ Center(
SizedBox(height: 40.h), child: Column(
// ===== LOGO SECTION ===== children: [
Center( Image.asset(
child: Column( AppAssets.appIcon,
children: [ height: 60.h,
Image.asset( ),
AppAssets.appIcon, SizedBox(height: 8.h),
height: 60.h, Text(
"Partner's App",
style: GoogleFonts.poppins(
color: AppColors.primaryRed,
fontSize: 20.sp,
fontWeight: FontWeight.w500,
), ),
SizedBox(height: 8.h), ),
Text( ],
"Partner's App",
style: GoogleFonts.poppins(
color: AppColors.primaryRed,
fontSize: 20.sp,
fontWeight: FontWeight.w500,
),
),
],
),
), ),
SizedBox(height: 60.h), ),
SizedBox(height: 60.h),
// ===== HEADER TEXT ===== // ===== HEADER TEXT =====
Text( Text(
"Verify OTP", "Verify OTP",
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
color: AppColors.black, color: AppColors.black,
fontSize: 24.sp, fontSize: 24.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
),
), ),
SizedBox(height: 12.h), ),
Text( SizedBox(height: 12.h),
"We've sent an OTP to your registered email. Please enter it below.", Text(
textAlign: TextAlign.center, "We've sent an OTP to your registered email. Please enter it below.",
style: GoogleFonts.poppins( textAlign: TextAlign.center,
color: AppColors.textGrey, style: GoogleFonts.poppins(
fontSize: 16.sp, color: AppColors.textGrey,
height: 1.4, fontSize: 16.sp,
), height: 1.4,
), ),
SizedBox(height: 48.h), ),
SizedBox(height: 48.h),
// ===== OTP INPUT FIELDS ===== // ===== OTP INPUT FIELDS =====
OtpTextField( Pinput(
numberOfFields: 6, length: 6,
borderColor: AppColors.borderGrey, defaultPinTheme: PinTheme(
focusedBorderColor: AppColors.primaryRed, width: 48.w,
showFieldAsBox: true, height: 52.h,
fieldWidth: 45.w,
borderRadius: BorderRadius.circular(12.r),
enabledBorderColor: AppColors.borderGrey,
cursorColor: AppColors.primaryRed,
textStyle: GoogleFonts.poppins( textStyle: GoogleFonts.poppins(
fontSize: 18.sp, fontSize: 18.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.black, color: AppColors.black,
), ),
onCodeChanged: (String code) { decoration: BoxDecoration(
context.read<VerifyOtpBloc>().add( borderRadius: BorderRadius.circular(12.r),
OtpChanged(otp: code), border: Border.all(color: AppColors.borderGrey),
); ),
},
onSubmit: (String verificationCode) {
if (isLoading) return;
context.read<VerifyOtpBloc>().add(
OtpChanged(otp: verificationCode),
);
context.read<VerifyOtpBloc>().add(
VerifyOtpSubmitted(
emailAddress: email,
otp: verificationCode,
),
);
},
), ),
], focusedPinTheme: PinTheme(
), width: 48.w,
), height: 52.h,
), textStyle: GoogleFonts.poppins(
fontSize: 18.sp,
// ===== VERIFY BUTTON ===== fontWeight: FontWeight.w600,
CustomButton( color: AppColors.black,
text: "Verify", ),
isLoading: isLoading, decoration: BoxDecoration(
onPressed: state.otp.length == 6 && !isLoading borderRadius: BorderRadius.circular(12.r),
? () { border: Border.all(color: AppColors.primaryRed),
),
),
submittedPinTheme: PinTheme(
width: 48.w,
height: 52.h,
textStyle: GoogleFonts.poppins(
fontSize: 18.sp,
fontWeight: FontWeight.w600,
color: AppColors.black,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.r),
border: Border.all(color: AppColors.borderGrey),
),
),
onChanged: (String code) {
context.read<VerifyOtpBloc>().add(
OtpChanged(otp: code),
);
},
onCompleted: (String verificationCode) {
context.read<VerifyOtpBloc>().add( context.read<VerifyOtpBloc>().add(
VerifyOtpSubmitted( VerifyOtpSubmitted(
emailAddress: email, emailAddress: email,
otp: state.otp, otp: verificationCode,
), ),
); );
} },
: null, ),
],
),
), ),
SizedBox(height: 24.h), ),
],
), // ===== VERIFY BUTTON =====
BlocBuilder<VerifyOtpBloc, VerifyOtpState>(
buildWhen: (previous, current) =>
previous.otp.length != current.otp.length ||
previous.status != current.status,
builder: (context, state) {
final isLoading = state.status == VerifyOtpStatus.loading;
return CustomButton(
text: "Verify",
isLoading: isLoading,
onPressed: state.otp.length == 6 && !isLoading
? () {
context.read<VerifyOtpBloc>().add(
VerifyOtpSubmitted(
emailAddress: email,
otp: state.otp,
),
);
}
: null,
);
},
),
SizedBox(height: 24.h),
],
), ),
); ),
}, ),
), ),
), ),
); );

View File

@@ -70,6 +70,29 @@ class _ResetPasswordPageState extends State<ResetPasswordPage> {
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
child: Scaffold( child: Scaffold(
backgroundColor: AppColors.backgroundWhite, backgroundColor: AppColors.backgroundWhite,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
leadingWidth: 70.w,
leading: Padding(
padding: EdgeInsets.only(left: 24.w, top: 8.h, bottom: 8.h),
child: InkWell(
onTap: () => Navigator.pop(context),
borderRadius: BorderRadius.circular(50),
child: Container(
decoration: const BoxDecoration(
color: AppColors.primaryRed,
shape: BoxShape.circle,
),
child: Icon(
Icons.arrow_back_ios_new,
color: Colors.white,
size: 18.sp,
),
),
),
),
),
body: BlocConsumer<ResetPasswordBloc, ResetPasswordState>( body: BlocConsumer<ResetPasswordBloc, ResetPasswordState>(
listener: (context, state) { listener: (context, state) {
if (state.status == ResetPasswordStatus.success) { if (state.status == ResetPasswordStatus.success) {
@@ -105,7 +128,7 @@ class _ResetPasswordPageState extends State<ResetPasswordPage> {
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
SizedBox(height: 40.h), SizedBox(height: 10.h),
// ===== LOGO SECTION ===== // ===== LOGO SECTION =====
Center( Center(
child: Column( child: Column(

View File

@@ -270,14 +270,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.7" version: "2.4.7"
flutter_otp_text_field:
dependency: "direct main"
description:
name: flutter_otp_text_field
sha256: e7e589dc51cde120d63da6db55f3cef618f5d013d12adba76137ca1a51ce1390
url: "https://pub.dev"
source: hosted
version: "1.5.1+1"
flutter_plugin_android_lifecycle: flutter_plugin_android_lifecycle:
dependency: transitive dependency: transitive
description: description:
@@ -460,10 +452,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.19" version: "0.12.18"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
@@ -568,6 +560,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.1" version: "7.0.1"
pinput:
dependency: "direct main"
description:
name: pinput
sha256: "4c3f1b84768b47a56a1abdaca551bd7cef4ac673b882209039ecdf803a5d6e68"
url: "https://pub.dev"
source: hosted
version: "6.0.2"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@@ -737,10 +737,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.10" version: "0.7.9"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:

View File

@@ -37,7 +37,6 @@ dependencies:
cupertino_icons: ^1.0.8 cupertino_icons: ^1.0.8
google_fonts: ^6.3.2 google_fonts: ^6.3.2
flutter_bloc: ^9.1.1 flutter_bloc: ^9.1.1
flutter_otp_text_field: ^1.5.1+1
intl: ^0.20.2 intl: ^0.20.2
table_calendar: ^3.2.0 table_calendar: ^3.2.0
file_picker: ^10.3.3 file_picker: ^10.3.3
@@ -52,6 +51,7 @@ dependencies:
dio_cookie_manager: ^3.4.0 dio_cookie_manager: ^3.4.0
cookie_jar: ^4.0.9 cookie_jar: ^4.0.9
image_picker: ^1.2.1 image_picker: ^1.2.1
pinput: ^6.0.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: