pin code screen, forgot pin code screen

This commit is contained in:
jayesh
2024-06-03 19:10:05 +05:30
parent c3d8fce960
commit 4efa4410b9
30 changed files with 882 additions and 102 deletions

View File

@@ -0,0 +1,54 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:tanami_app/core/styles/app_text.dart';
import '../../../../core/utils/secure/secure_storage_service.dart';
part 'pin_event.dart';
part 'pin_state.dart';
class PinBloc extends Bloc<PinEvent, PinState> {
final SecureStorageService secureStorageService;
PinBloc({required this.secureStorageService})
: super(const PinState(
pin: '', pinComplete: false, isVerified: false, error: '')) {
on<NumberPressed>((event, emit) {
final newPin = state.pin + event.number;
if (newPin.length <= 6) {
emit(state.copyWith(
pin: newPin, pinComplete: newPin.length == 6, error: ''));
if (newPin.length == 6) {
add(VerifyPinPressed(newPin));
}
}
});
on<BackspacePressed>((event, emit) {
if (state.pin.isNotEmpty) {
final newPin = state.pin.substring(0, state.pin.length - 1);
emit(state.copyWith(
pin: newPin, pinComplete: newPin.length == 6, error: ''));
}
});
on<SavePinPressed>((event, emit) async {
await secureStorageService.write('pin_code', state.pin);
});
on<VerifyPinPressed>((event, emit) async {
final storedPin = await secureStorageService.read('pin_code');
if (storedPin == event.pin) {
emit(state.copyWith(isVerified: true, error: ''));
} else {
emit(state.copyWith(
isVerified: false,
error: AppText.incorrectPinCode,
));
}
});
}
}

View File

@@ -0,0 +1,30 @@
part of 'pin_bloc.dart';
abstract class PinEvent extends Equatable {
const PinEvent();
@override
List<Object> get props => [];
}
class NumberPressed extends PinEvent {
final String number;
const NumberPressed(this.number);
@override
List<Object> get props => [number];
}
class BackspacePressed extends PinEvent {}
class SavePinPressed extends PinEvent {}
class VerifyPinPressed extends PinEvent {
final String pin;
const VerifyPinPressed(this.pin);
@override
List<Object> get props => [pin];
}

View File

@@ -0,0 +1,32 @@
part of 'pin_bloc.dart';
class PinState extends Equatable {
final String pin;
final bool pinComplete;
final bool isVerified;
final String error;
const PinState({
required this.pin,
required this.pinComplete,
required this.isVerified,
required this.error,
});
PinState copyWith({
String? pin,
bool? pinComplete,
bool? isVerified,
String? error,
}) {
return PinState(
pin: pin ?? this.pin,
pinComplete: pinComplete ?? this.pinComplete,
isVerified: isVerified ?? this.isVerified,
error: error ?? this.error,
);
}
@override
List<Object> get props => [pin, pinComplete, isVerified, error];
}

View File

@@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
import '../widgets/confirm_pin_keypad_section.dart';
import '../widgets/confirm_pin_top_section.dart';
class ConfirmPinLayout extends StatelessWidget {
const ConfirmPinLayout({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: const [
ConfirmPinTopSection(),
ConfirmPinKey(),
],
));
}
}

View File

@@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../core/styles/app_text.dart';
import '../../../../core/utils/secure/secure_storage_service.dart';
import '../../../../shared/components/appbar_widget.dart';
import '../bloc/pin_bloc.dart';
import 'confirm_pin_layout.dart';
class ConfirmPinScreen extends StatelessWidget {
const ConfirmPinScreen({super.key});
@override
Widget build(BuildContext context) {
final secureStorageService = SecureStorageService();
return Scaffold(
appBar: const AppBarWidget(
height: 75,
titleTxt: AppText.createPinCode,
showLeading: true,
),
resizeToAvoidBottomInset: true,
body: MultiBlocProvider(
providers: [
BlocProvider(
// Create an instance of the OnboardingBloc
create: (context) =>
PinBloc(secureStorageService: secureStorageService),
),
],
child: const ConfirmPinLayout(),
),
);
}
}

View File

@@ -0,0 +1,21 @@
import 'package:flutter/material.dart';
import 'package:tanami_app/features/securePin/presentation/widgets/pin_keypad_section.dart';
import 'package:tanami_app/features/securePin/presentation/widgets/pin_top_section.dart';
class PinLayout extends StatelessWidget {
final String fromScreen;
const PinLayout({super.key, required this.fromScreen});
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: [
PinTopSection(fromScreen: fromScreen),
PinKey(
fromScreen: fromScreen,
),
],
));
}
}

View File

@@ -0,0 +1,40 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../core/styles/app_text.dart';
import '../../../../core/utils/secure/secure_storage_service.dart';
import '../../../../shared/components/appbar_widget.dart';
import '../bloc/pin_bloc.dart';
import 'pin_layout.dart';
class PinScreen extends StatelessWidget {
final String fromScreen;
const PinScreen({super.key, required this.fromScreen});
@override
Widget build(BuildContext context) {
final secureStorageService = SecureStorageService();
return Scaffold(
appBar: fromScreen == "register"
? const AppBarWidget(
height: 75,
titleTxt: AppText.createPinCode,
showLeading: false,
)
: null,
resizeToAvoidBottomInset: true,
body: MultiBlocProvider(
providers: [
BlocProvider(
// Create an instance of the OnboardingBloc
create: (context) =>
PinBloc(secureStorageService: secureStorageService),
),
],
child: PinLayout(
fromScreen: fromScreen,
),
),
);
}
}

View File

@@ -0,0 +1,115 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:gap/gap.dart';
import 'package:tanami_app/core/routes/route_name.dart';
import 'package:tanami_app/core/routes/routes.dart';
import 'package:tanami_app/core/styles/app_color.dart';
import 'package:tanami_app/shared/components/text_widget.dart';
import '../bloc/pin_bloc.dart';
class ConfirmPinKey extends StatelessWidget {
const ConfirmPinKey({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
const Gap(20),
BlocConsumer<PinBloc, PinState>(
listener: (context, state) {
if (state.pinComplete && state.isVerified) {
// successToastMessage(context, "Pin verified successfully");
goRouter.goNamed(RouteName.mainScreen);
}
},
builder: (context, state) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(6, (index) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 10),
width: 32,
height: 32,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: state.error.isNotEmpty
? AppColor.txtErrorColor
: index < state.pin.length
? AppColor.pinFillBorderColor
: AppColor.pinInActiveBorderColor),
color: index < state.pin.length
? AppColor.pinFillColor
: Colors.transparent,
),
);
}),
);
},
),
BlocBuilder<PinBloc, PinState>(
builder: (context, state) {
if (state.error.isNotEmpty) {
return Container(
margin: const EdgeInsets.only(top: 5),
child: TextWidget()
.text14W500(state.error, clr: AppColor.txtErrorColor),
);
}
return const SizedBox.shrink();
},
),
const Gap(50),
GridView.builder(
shrinkWrap: true,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 1.3,
),
itemCount: 12,
itemBuilder: (context, index) {
if (index == 9) {
return const SizedBox.shrink();
} else if (index == 11) {
return GestureDetector(
onTap: () {
context.read<PinBloc>().add(BackspacePressed());
},
child: Container(
margin: const EdgeInsets.all(10),
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.black12),
),
child: const Icon(
Icons.arrow_back_rounded,
),
),
);
} else {
final number = index == 10 ? '0' : '${index + 1}';
return GestureDetector(
onTap: () {
context.read<PinBloc>().add(NumberPressed(number));
},
child: Container(
margin: const EdgeInsets.all(10),
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.black12),
),
child: Center(
child: TextWidget().text20W700(
number,
clr: AppColor.plainBlack,
)),
),
);
}
},
),
],
);
}
}

View File

@@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:gap/gap.dart';
import 'package:tanami_app/core/styles/app_color.dart';
import 'package:tanami_app/shared/components/text_widget.dart';
import '../../../../core/styles/app_images.dart';
import '../../../../core/styles/app_text.dart';
class ConfirmPinTopSection extends StatelessWidget {
const ConfirmPinTopSection({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
const Gap(85),
Center(
child: SvgPicture.asset(
AppImages.weclomeLogo,
),
),
const Gap(60),
TextWidget().text14W500(
AppText.confirmPinCode,
clr: AppColor.textLabelColor,
)
],
);
}
}

View File

@@ -0,0 +1,70 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:gap/gap.dart';
import 'package:tanami_app/core/routes/routes.dart';
import 'package:tanami_app/core/styles/app_color.dart';
import 'package:tanami_app/core/styles/app_text.dart';
import 'package:tanami_app/shared/components/button_widget.dart';
import 'package:tanami_app/shared/components/text_widget.dart';
void forgotPinDialog(BuildContext context) {
showDialog(
context: context,
builder: (BuildContext context) {
return Dialog(
backgroundColor: AppColor.plainWhite,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(22),
color: AppColor.plainWhite,
),
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextWidget().text17W700(AppText.notificationText,
clr: AppColor.plainBlack),
const Gap(25),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 25,
),
child: TextWidget().text15W500(
AppText.toRestorePinYouWillBeLoggedOut,
clr: AppColor.hintTextColor,
),
),
const Gap(40),
SizedBox(
width: 0.9.sw,
height: 55.h,
child: ButtonWidget().elevatedBtn(
txtClr: AppColor.plainWhite,
text: AppText.allowText,
clr: AppColor.primaryColor2,
function: () {
goRouter.pop();
},
),
),
const Gap(10),
ButtonWidget().textBtn(
text: TextWidget().text14W700(
AppText.declineText,
textDecoration: TextDecoration.underline,
clr: AppColor.textLabelColor,
),
function: () {
goRouter.pop();
},
),
],
),
),
);
},
);
}

View File

@@ -0,0 +1,131 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:gap/gap.dart';
import 'package:tanami_app/core/routes/route_name.dart';
import 'package:tanami_app/core/routes/routes.dart';
import 'package:tanami_app/core/styles/app_color.dart';
import 'package:tanami_app/core/styles/app_text.dart';
import 'package:tanami_app/features/securePin/presentation/widgets/forgot_pin_dialog.dart';
import 'package:tanami_app/shared/components/text_widget.dart';
import '../bloc/pin_bloc.dart';
class PinKey extends StatelessWidget {
final String fromScreen;
const PinKey({
super.key,
required this.fromScreen,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
const Gap(20),
BlocConsumer<PinBloc, PinState>(
listener: (context, state) {
if (state.pinComplete) {
if (fromScreen == "login") {
goRouter.pushNamed(RouteName.mainScreen);
} else {
context.read<PinBloc>().add(SavePinPressed());
goRouter.pushNamed(RouteName.confirmPinScreen);
}
}
},
builder: (context, state) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(6, (index) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 10),
width: 32,
height: 32,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: index < state.pin.length
? AppColor.pinFillBorderColor
: AppColor.pinInActiveBorderColor,
),
color: index < state.pin.length
? AppColor.pinFillColor
: Colors.transparent,
),
);
}),
);
},
),
fromScreen == "login" ? const Gap(20) : const Gap(0),
fromScreen == "login"
? InkWell(
onTap: () {
forgotPinDialog(context);
},
child: Padding(
padding: const EdgeInsets.only(
right: 50,
),
child: TextWidget().text15W500(
AppText.forgotPinCode,
clr: AppColor.hintTextColor,
textDecoration: TextDecoration.underline,
),
),
)
: const SizedBox(),
const Gap(50),
GridView.builder(
shrinkWrap: true,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 1.3,
),
itemCount: 12,
itemBuilder: (context, index) {
if (index == 9) {
return const SizedBox.shrink();
} else if (index == 11) {
return GestureDetector(
onTap: () {
context.read<PinBloc>().add(BackspacePressed());
},
child: Container(
margin: const EdgeInsets.all(10),
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.black12),
),
child: const Icon(
Icons.arrow_back_rounded,
),
),
);
} else {
final number = index == 10 ? '0' : '${index + 1}';
return GestureDetector(
onTap: () {
context.read<PinBloc>().add(NumberPressed(number));
},
child: Container(
margin: const EdgeInsets.all(10),
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.black12),
),
child: Center(
child: TextWidget().text20W700(
number,
clr: AppColor.plainBlack,
)),
),
);
}
},
),
],
);
}
}

View File

@@ -0,0 +1,59 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:gap/gap.dart';
import 'package:tanami_app/core/styles/app_color.dart';
import 'package:tanami_app/core/styles/app_text.dart';
import 'package:tanami_app/shared/components/text_widget.dart';
import '../../../../core/styles/app_images.dart';
class PinTopSection extends StatelessWidget {
final String fromScreen;
const PinTopSection({
super.key,
required this.fromScreen,
});
@override
Widget build(BuildContext context) {
return Column(
children: [
const Gap(85),
Center(
child: SvgPicture.asset(
AppImages.weclomeLogo,
),
),
const Gap(60),
fromScreen == "login"
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
TextWidget().text14W500(AppText.welcomeBackText,
clr: AppColor.smokeGrayColor),
TextWidget()
.text14W700(", Jayesh", clr: AppColor.plainBlack),
],
),
TextWidget().text14W500(
AppText.userYourAppPinToLoginEnterTanami,
clr: AppColor.smokeGrayColor),
const Gap(25),
TextWidget().text14W400(
AppText.pinCode,
clr: AppColor.textLabelColor,
)
],
)
: TextWidget().text14W500(
AppText.createPinCode,
clr: AppColor.textLabelColor,
)
],
);
}
}