Merge branch 'main' into meet

This commit is contained in:
meet2711
2024-06-04 15:19:11 +05:30
committed by GitHub
30 changed files with 882 additions and 94 deletions

View File

@@ -37,4 +37,8 @@ class RouteName {
//Biometric
static const String otpScreen = 'otpScreen';
//Pin Screen
static const String pinScreen = 'pinScreen';
static const String confirmPinScreen = 'confirmPinScreen';
}

View File

@@ -13,10 +13,12 @@ import 'package:tanami_app/features/countrySelection/presentation/pages/choose_c
import 'package:tanami_app/features/otpVerification/presentation/pages/otp_screen.dart';
import 'package:tanami_app/features/register/presentation/pages/register_screen.dart';
import 'package:tanami_app/features/register/presentation/pages/register_step_screen.dart';
import 'package:tanami_app/features/securePin/presentation/pages/pin_screen.dart';
import 'package:tanami_app/features/welcome/presentation/pages/weclome_screen.dart';
import '../../features/login/presentation/pages/login_screen.dart';
import '../../features/register/presentation/pages/register_user_details_screen.dart';
import '../../features/securePin/presentation/pages/confirm_pin_screen.dart';
import '../../features/splash/presentation/pages/splash_screen.dart';
/* CREATED BY - JAYESH JAIN
@@ -88,6 +90,13 @@ final goRouter = GoRouter(
return const RegisterScreen();
},
),
GoRoute(
name: RouteName.registerUserDetailsScreen,
path: RouteName.registerUserDetailsScreen,
builder: (context, state) {
return const RegisterUserDetailsScreen();
},
),
GoRoute(
name: RouteName.otpScreen,
path: RouteName.otpScreen,
@@ -109,6 +118,22 @@ final goRouter = GoRouter(
return const AcademyDetailsScreen();
},
),
GoRoute(
name: RouteName.pinScreen,
path: "${RouteName.pinScreen}/:fromScreen",
builder: (context, state) {
return PinScreen(
fromScreen: state.pathParameters["fromScreen"]!,
);
},
),
GoRoute(
name: RouteName.confirmPinScreen,
path: RouteName.confirmPinScreen,
builder: (context, state) {
return const ConfirmPinScreen();
},
),
],
),
// GoRoute(

View File

@@ -44,4 +44,12 @@ class AppColor {
//CheckBox Color
static const Color checkBoxActiveColor = Color(0xFF09622E);
//Selectable Text Color
static const Color selectableTextColor = Color(0xFF015BA2);
//Pin Code Color
static const Color pinFillColor = Color(0xFFC9D9CB);
static const Color pinFillBorderColor = Color(0xFF648774);
static const Color pinInActiveBorderColor = Color(0xFFDFDFE3);
}

View File

@@ -61,6 +61,10 @@ class AppText {
static const String enterEmail = "Enter Email";
static const String cantBeEmptyText = "Can't Be Empty";
static const String passwordMismatch = "Password Mismatch";
static const String iAgreeToThe = "I agree to the ";
static const String termsAndCondition = "Terms & Conditions";
static const String andThe = " and the";
static const String privacyPolicy = "Privacy Policy";
//Country Name
static const String bahrainCountryText = "Bahrain";
@@ -98,4 +102,20 @@ class AppText {
static const String resendSms = "Resend SMS";
static const String otpVerifiedSucessfully = "OTP Verified Successfully!";
static const String otpVerifiedFailed = "OTP Verification Failed:";
//Pin Code
static const String pinCode = "Pin Code";
static const String createPinCode = "Create Pin Code";
static const String confirmPinCode = "Confirm Pin Code";
static const String incorrectPinCode = "Incorrect PIN. Please try again.";
static const String welcomeBackText = "Welcome back";
static const String userYourAppPinToLoginEnterTanami =
"Use your app PIN to login to enter Tanami";
static const String forgotPinCode = "Forgot Pin Code";
static const String notificationText = "Notification";
static const String toRestorePinYouWillBeLoggedOut =
"To restore PIN you will be Logged out";
static const String allowText = "Allow";
static const String declineText = "Decline";
}

View File

@@ -0,0 +1,21 @@
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class SecureStorageService {
final FlutterSecureStorage _storage = const FlutterSecureStorage(
aOptions: AndroidOptions(
encryptedSharedPreferences: true,
),
);
Future<void> write(String key, String value) async {
await _storage.write(key: key, value: value);
}
Future<String?> read(String key) async {
return await _storage.read(key: key);
}
Future<void> delete(String key) async {
await _storage.delete(key: key);
}
}

View File

@@ -1,7 +1,4 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:tanami_app/core/routes/route_name.dart';
@@ -93,7 +90,7 @@ class _AcademyScreenState extends State<AcademyScreen> {
title: Text(
'Academy',
style: GoogleFonts.dmSans(
color: Color(0xFF272727),
color: const Color(0xFF272727),
fontSize: 22.sp,
fontWeight: FontWeight.w700,
),

View File

@@ -10,6 +10,8 @@ class InvestScreen extends StatefulWidget {
class _InvestScreenState extends State<InvestScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(body: Text('Invest'),);
return const Scaffold(
body: Text('Invest'),
);
}
}
}

View File

@@ -6,6 +6,8 @@ import 'package:tanami_app/features/MainScreens/Settings/presentation/pages/sett
import 'package:tanami_app/features/MainScreens/Wallet/presentation/pages/walletScreen.dart';
import 'package:tanami_app/shared/components/common_bottom_navigation.dart';
import '../../shared/components/exit_app_dialog.dart';
var currentTab = [
const WalletScreen(),
const PortfolioScreen(),
@@ -24,10 +26,16 @@ class MainScreen extends StatelessWidget {
const MainScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: currentTab[selectedIndex],
bottomNavigationBar: bottomnavigationbar(selectedIndex),
return WillPopScope(
onWillPop: () async {
exitAppDialog(context);
return false;
},
child: Scaffold(
backgroundColor: Colors.white,
body: currentTab[selectedIndex],
bottomNavigationBar: bottomnavigationbar(selectedIndex),
),
);
}
}

View File

@@ -37,13 +37,13 @@ class _DetailsScreenState extends State<DetailsScreen> {
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(20.0)),
borderRadius: const BorderRadius.all(Radius.circular(20.0)),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.15),
spreadRadius: 2,
blurRadius: 10,
offset: Offset(0, 3), // changes position of shadow
offset: const Offset(0, 3), // changes position of shadow
),
],
),
@@ -230,7 +230,7 @@ class _DetailsScreenState extends State<DetailsScreen> {
child: Text(
AppText.investmentamount,
style: GoogleFonts.dmSans(
color: Color(0xFF535353),
color: const Color(0xFF535353),
fontSize: 14.sp,
fontWeight: FontWeight.w500,
),
@@ -271,7 +271,7 @@ class _DetailsScreenState extends State<DetailsScreen> {
child: Text(
AppText.currentval,
style: GoogleFonts.dmSans(
color: Color(0xFF535353),
color: const Color(0xFF535353),
fontSize: 14.sp,
fontWeight: FontWeight.w500,
),
@@ -312,7 +312,7 @@ class _DetailsScreenState extends State<DetailsScreen> {
child: Text(
AppText.disttodate,
style: GoogleFonts.dmSans(
color: Color(0xFF535353),
color: const Color(0xFF535353),
fontSize: 14.sp,
fontWeight: FontWeight.w500,
),
@@ -338,7 +338,7 @@ class _DetailsScreenState extends State<DetailsScreen> {
Text(
AppText.totalreturn,
style: GoogleFonts.dmSans(
color: Color(0xFF535353),
color: const Color(0xFF535353),
fontSize: 14.sp,
fontWeight: FontWeight.w500,
),
@@ -346,7 +346,7 @@ class _DetailsScreenState extends State<DetailsScreen> {
Text(
'+ 20.0%',
style: GoogleFonts.dmSans(
color: Color(0xFF066123),
color: const Color(0xFF066123),
fontSize: 14.sp,
fontWeight: FontWeight.w700,
),
@@ -373,7 +373,7 @@ class _DetailsScreenState extends State<DetailsScreen> {
color: Colors.black.withOpacity(0.15),
spreadRadius: 2,
blurRadius: 10,
offset: Offset(0, 3), // changes position of shadow
offset: const Offset(0, 3), // changes position of shadow
),
],
),
@@ -404,12 +404,12 @@ class _DetailsScreenState extends State<DetailsScreen> {
(index) {
return Center(
child: Container(
decoration: BoxDecoration(
decoration: const BoxDecoration(
color: Color(0xFFF4F4F4),
borderRadius:
BorderRadius.all(Radius.circular(10.0)),
),
padding: EdgeInsets.all(20.0),
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
Row(
@@ -443,7 +443,7 @@ class _DetailsScreenState extends State<DetailsScreen> {
Text(
"512 Mb",
style: GoogleFonts.dmSans(
color: Color(0xFF535353),
color: const Color(0xFF535353),
fontSize: 12.sp,
fontWeight: FontWeight.w700,
),

View File

@@ -36,7 +36,8 @@ class _PortfolioScreenState extends State<PortfolioScreen> {
var opacity = (1 - percentage).clamp(0.0, 1.0);
return FlexibleSpaceBar(
// centerTitle: true,
titlePadding: EdgeInsets.only(left: 30, top: 0, bottom: 15),
titlePadding:
const EdgeInsets.only(left: 30, top: 0, bottom: 15),
title: Opacity(
opacity: opacity,
child: Row(
@@ -44,7 +45,7 @@ class _PortfolioScreenState extends State<PortfolioScreen> {
Text(
AppText.portfolio,
style: GoogleFonts.dmSans(
color: Color(0xFF888888),
color: const Color(0xFF888888),
fontSize: 12.sp,
fontWeight: FontWeight.w700,
),
@@ -78,7 +79,7 @@ class _PortfolioScreenState extends State<PortfolioScreen> {
Text(
AppText.portfolio,
style: GoogleFonts.dmSans(
color: Color(0xFFC9D9CB),
color: const Color(0xFFC9D9CB),
fontSize: 14.sp,
fontWeight: FontWeight.w700,
),
@@ -115,13 +116,16 @@ class _PortfolioScreenState extends State<PortfolioScreen> {
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(20.0)),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.15),
spreadRadius: 2,
blurRadius: 10,
offset: Offset(0, 3), // changes position of shadow
offset:
const Offset(0, 3), // changes position of shadow
),
],
),
@@ -152,7 +156,7 @@ class _PortfolioScreenState extends State<PortfolioScreen> {
Text(
'Mar 01 2024',
style: GoogleFonts.dmSans(
color: Color(0xFF004717),
color: const Color(0xFF004717),
fontSize: 12.sp,
fontWeight: FontWeight.w500,
),
@@ -163,8 +167,8 @@ class _PortfolioScreenState extends State<PortfolioScreen> {
(index == 2) ? 'Exited' : 'Pending',
style: GoogleFonts.dmSans(
color: (index == 2)
? Color(0xFF8D8D8D)
: Color(0xFF0FA4A4),
? const Color(0xFF8D8D8D)
: const Color(0xFF0FA4A4),
fontSize: 14.sp,
fontWeight: FontWeight.w700,
),
@@ -209,8 +213,8 @@ class _PortfolioScreenState extends State<PortfolioScreen> {
AppText.investmentamount,
style: GoogleFonts.dmSans(
color: (index == 2)
? Color(0xFF8D8D8D)
: Color(0xFF535353),
? const Color(0xFF8D8D8D)
: const Color(0xFF535353),
fontSize: 14.sp,
fontWeight: FontWeight.w500,
),
@@ -223,7 +227,7 @@ class _PortfolioScreenState extends State<PortfolioScreen> {
'SAR 100,000',
style: GoogleFonts.dmSans(
color: (index == 2)
? Color(0xFF8D8D8D)
? const Color(0xFF8D8D8D)
: Colors.black,
fontSize: 14.sp,
fontWeight: FontWeight.w700,
@@ -233,7 +237,7 @@ class _PortfolioScreenState extends State<PortfolioScreen> {
' \$ 26,700',
style: GoogleFonts.dmSans(
color: (index == 2)
? Color(0xFF8D8D8D)
? const Color(0xFF8D8D8D)
: Colors.black,
fontSize: 11.sp,
fontWeight: FontWeight.w400,
@@ -255,8 +259,8 @@ class _PortfolioScreenState extends State<PortfolioScreen> {
AppText.currentval,
style: GoogleFonts.dmSans(
color: (index == 2)
? Color(0xFF8D8D8D)
: Color(0xFF535353),
? const Color(0xFF8D8D8D)
: const Color(0xFF535353),
fontSize: 14.sp,
fontWeight: FontWeight.w500,
),
@@ -269,7 +273,7 @@ class _PortfolioScreenState extends State<PortfolioScreen> {
'SAR 100,000',
style: GoogleFonts.dmSans(
color: (index == 2)
? Color(0xFF8D8D8D)
? const Color(0xFF8D8D8D)
: Colors.black,
fontSize: 14.sp,
fontWeight: FontWeight.w700,
@@ -279,7 +283,7 @@ class _PortfolioScreenState extends State<PortfolioScreen> {
' \$ 26,700',
style: GoogleFonts.dmSans(
color: (index == 2)
? Color(0xFF8D8D8D)
? const Color(0xFF8D8D8D)
: Colors.black,
fontSize: 11.sp,
fontWeight: FontWeight.w400,
@@ -301,8 +305,8 @@ class _PortfolioScreenState extends State<PortfolioScreen> {
AppText.totalreturn,
style: GoogleFonts.dmSans(
color: (index == 2)
? Color(0xFF8D8D8D)
: Color(0xFF535353),
? const Color(0xFF8D8D8D)
: const Color(0xFF535353),
fontSize: 14.sp,
fontWeight: FontWeight.w500,
),
@@ -311,8 +315,8 @@ class _PortfolioScreenState extends State<PortfolioScreen> {
(index == 2) ? '- 20.0%' : '+ 20.0%',
style: GoogleFonts.dmSans(
color: (index == 2)
? Color(0xFFde9595)
: Color(0xFF066123),
? const Color(0xFFde9595)
: const Color(0xFF066123),
fontSize: 14.sp,
fontWeight: FontWeight.w700,
),

View File

@@ -10,7 +10,7 @@ class SettingsScreen extends StatefulWidget {
class _SettingsScreenState extends State<SettingsScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
return const Scaffold(
body: Text('Settings'),
);
}

View File

@@ -14,6 +14,7 @@ class WalletScreen extends StatefulWidget {
class _WalletScreenState extends State<WalletScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: CustomScrollView(
@@ -156,6 +157,7 @@ class _WalletScreenState extends State<WalletScreen> {
),
],
),
);
}
}

View File

@@ -45,8 +45,9 @@ class BottomSection extends StatelessWidget {
successToastMessage(context, "login successful !");
goRouter.pop();
// goRouter.goNamed(RouteName.biometricScreen);
goRouter.goNamed(RouteName.mainScreen);
goRouter.goNamed(RouteName.pinScreen, pathParameters: {
"fromScreen": "login",
});
} else if (state is LoginFailure) {
goRouter.pop();
errorToastMessage(

View File

@@ -20,7 +20,7 @@ class TimerInitial extends TimerState {
}
class TimerRunInProgress extends TimerState {
const TimerRunInProgress(int duration) : super(duration);
const TimerRunInProgress(super.duration);
}
class TimerRunComplete extends TimerState {

View File

@@ -1,8 +1,13 @@
import 'dart:developer';
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:gap/gap.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:tanami_app/core/styles/app_images.dart';
import 'package:tanami_app/shared/components/checkbox_widget.dart';
import 'package:tanami_app/shared/components/loader.dart';
@@ -12,6 +17,8 @@ import '../../../../core/routes/route_name.dart';
import '../../../../core/routes/routes.dart';
import '../../../../core/styles/app_color.dart';
import '../../../../core/styles/app_text.dart';
import '../../../../shared/components/bloc/checkbox/checkbox_bloc.dart';
import '../../../../shared/components/bloc/checkbox/checkbox_state.dart';
import '../../../../shared/components/button_widget.dart';
import '../../../../shared/components/text_widget.dart';
import '../bloc/register_user_bloc.dart';
@@ -27,41 +34,52 @@ class RegisterUserBottomSection extends StatelessWidget {
children: [
const Gap(40),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
const CheckBoxWidget(),
Container(
width: 0.8.sw,
child: Row(
children: [
const Text("I agree to the "),
InkWell(
onTap: () {
// Handle Terms & Conditions tap
},
child: const Text(
"Terms & Conditions",
style: TextStyle(
color: Colors.blue,
decoration: TextDecoration.underline,
),
Expanded(
child: RichText(
text: TextSpan(
text: AppText.iAgreeToThe,
style: GoogleFonts.dmSans(
color: AppColor.charcoalColor,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
const Text(" and the "),
InkWell(
onTap: () {
// Handle Privacy Policy tap
},
child: const Text(
"Privacy Policy",
style: TextStyle(
color: Colors.blue,
decoration: TextDecoration.underline,
children: [
TextSpan(
style: GoogleFonts.dmSans(
color: AppColor.selectableTextColor,
fontSize: 14,
fontWeight: FontWeight.w500,
decoration: TextDecoration.underline,
),
text: AppText.termsAndCondition,
recognizer: TapGestureRecognizer()
..onTap = () => log('Tap Here onTap'),
),
),
),
],
const TextSpan(
text: AppText.andThe,
style: TextStyle(
color: AppColor.charcoalColor,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
TextSpan(
text: AppText.privacyPolicy,
recognizer: TapGestureRecognizer()
..onTap = () => log('Tap Here onTap'),
style: const TextStyle(
color: AppColor.selectableTextColor,
fontSize: 14,
fontWeight: FontWeight.w500,
decoration: TextDecoration.underline,
),
),
]),
),
)
),
],
),
const Gap(10),
@@ -79,7 +97,9 @@ class RegisterUserBottomSection extends StatelessWidget {
successToastMessage(context, "successful !");
goRouter.pop();
goRouter.pushNamed(RouteName.otpScreen);
goRouter.goNamed(RouteName.pinScreen, pathParameters: {
"fromScreen": "register",
});
} else if (state is RegisterUserFailure) {
goRouter.pop();
errorToastMessage(
@@ -90,10 +110,13 @@ class RegisterUserBottomSection extends StatelessWidget {
},
builder: (context, state) {
bool isButtonEnabled = false;
if (state is RegisterUserFieldsState) {
final checkboxState = context.watch<CheckboxBloc>().state;
if (state is RegisterUserFieldsState &&
checkboxState is CheckboxChecked) {
isButtonEnabled = state.areFieldsFilled;
} else if (state is RegisterUserSuccess ||
state is RegisterUserFailure) {
} else if ((state is RegisterUserSuccess ||
state is RegisterUserFailure) &&
checkboxState is CheckboxChecked) {
isButtonEnabled = true;
}
return Container(

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,
)
],
);
}
}

View File

@@ -41,21 +41,23 @@ class AppBarWidget extends StatelessWidget implements PreferredSizeWidget {
padding: EdgeInsets.only(
left: 16.w,
),
child: GestureDetector(
onTap: () {
customBack ?? false
? goRouter.goNamed(backPageName!)
: goRouter.pop();
},
child: Padding(
padding: EdgeInsets.only(left: 8.w),
child: Icon(
Icons.arrow_back_rounded,
color: AppColor.appBarIconColor,
size: 25.r,
),
),
),
child: !showLeading!
? null
: GestureDetector(
onTap: () {
customBack ?? false
? goRouter.goNamed(backPageName!)
: goRouter.pop();
},
child: Padding(
padding: EdgeInsets.only(left: 8.w),
child: Icon(
Icons.arrow_back_rounded,
color: AppColor.appBarIconColor,
size: 25.r,
),
),
),
),
actions: [
if (customActionWidget != null)

View File

@@ -29,7 +29,7 @@ class CheckBoxWidget extends StatelessWidget {
},
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6), // Custom radius
borderRadius: BorderRadius.circular(6),
),
value: state is CheckboxChecked,
onChanged: (value) {

View File

@@ -14,6 +14,21 @@ class TextWidget {
//Text Size 14
Widget text14W400(
String text, {
Color? clr,
TextDecoration? textDecoration,
TextAlign? txtAlign,
}) {
return Text(text,
textAlign: txtAlign ?? TextAlign.center,
style: GoogleFonts.dmSans(
fontSize: 14,
fontWeight: FontWeight.w400,
decoration: textDecoration ?? TextDecoration.none,
color: clr ?? AppColor.plainWhite));
}
Widget text14W500(
String text, {
Color? clr,
@@ -44,11 +59,14 @@ class TextWidget {
Widget text15W500(
String text, {
Color? clr,
TextDecoration? textDecoration,
}) {
return Text(text,
textAlign: TextAlign.center,
style: GoogleFonts.dmSans(
fontSize: 15,
decorationColor: AppColor.hintTextColor,
decoration: textDecoration ?? TextDecoration.none,
fontWeight: FontWeight.w500,
color: clr ?? AppColor.plainWhite));
}
@@ -69,6 +87,22 @@ class TextWidget {
color: clr ?? AppColor.plainWhite));
}
//Text Size 17
Widget text17W700(
String text, {
Color? clr,
TextDecoration? textDecoration,
}) {
return Text(text,
textAlign: TextAlign.center,
style: GoogleFonts.dmSans(
fontSize: 17,
decorationColor: AppColor.hintTextColor,
decoration: textDecoration ?? TextDecoration.none,
fontWeight: FontWeight.w700,
color: clr ?? AppColor.plainWhite));
}
//Text Size 22
Widget text22W700(String text, {Color? clr}) {
return Text(text,