delete account ui screen

This commit is contained in:
jayesh
2024-06-06 18:40:58 +05:30
parent 6bd86897d9
commit 87ebbd288d
24 changed files with 569 additions and 10 deletions

View File

@@ -52,4 +52,7 @@ class RouteName {
//language change
static const String languageChangeScreen = 'languageChangeScreen';
//delete Account
static const String deleteAccountScreen = 'deleteAccountScreen';
}

View File

@@ -10,6 +10,7 @@ import 'package:tanami_app/features/biometric/presentation/pages/biometric_scree
import 'package:tanami_app/features/contactAdmin/presentation/pages/contact_admin_screen.dart';
import 'package:tanami_app/features/countrySelection/presentation/pages/choose_country_screen.dart';
import 'package:tanami_app/features/deleteAccount/presentation/pages/delete_account_screen.dart';
import 'package:tanami_app/features/forgotPassword/presentation/pages/restore_password_screen.dart';
import 'package:tanami_app/features/languageChange/presentation/pages/language_change_screen.dart';
import 'package:tanami_app/features/otpVerification/presentation/pages/otp_screen.dart';
@@ -168,6 +169,13 @@ final goRouter = GoRouter(
return const LanguageChaneScreen();
},
),
GoRoute(
name: RouteName.deleteAccountScreen,
path: RouteName.deleteAccountScreen,
builder: (context, state) {
return const DeleteAccountScreen();
},
),
],
),
// GoRoute(

View File

@@ -80,4 +80,7 @@ class AppColor {
//Language Color
static const Color languageTextColor = Color(0xFF015698);
//Delete Account Color
static const Color descriptionText = Color(0xFFC6C6C6);
}

View File

@@ -162,4 +162,21 @@ class AppText {
static const String chooseTheLanguageText = "Choose the language";
static const String changingTheLanguageWillReloadApp =
"Changing the language will reload the application";
//Delete Screen
static const String weAreSadToSeeYouGo = "We're sad to see you go :(";
static const String enterAdescription = "Enter a description...";
static const String toHelpUsImprovePleaseLetusKnowWhyYouWantToDeleteAccount =
"To help us improve, please let us know why you want to delete your account";
static const String charactersLeft = "characters left";
static const String iUnderstandAndAgreeThatmyDataWillBeDeleted =
"I understand and agree that my data will be deleted";
static const String noIWantToStay = "No, i want to stay";
static const String pleaseEnteraDescription = "Please enter a description";
static const String pleaseCheckThisField = "Please check this field";
static const String weHaveReceivedYourRequestToDeleteAccount =
"We have received your request to delete your account";
static const String theRequestWillBeProcessed =
"The request will be processed within 72 hours";
static const closeText = "Close";
}

View File

@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:gap/gap.dart';
import 'package:tanami_app/core/routes/route_name.dart';
import 'package:tanami_app/core/routes/routes.dart';
import '../../../../../core/styles/app_color.dart';
import '../../../../../core/styles/app_text.dart';
@@ -24,14 +26,20 @@ class SettingsBottomSection extends StatelessWidget {
height: 56.h,
child: ButtonWidget().elevatedBtn(
txtClr: AppColor.plainWhite,
function: () {},
function: () {
goRouter.goNamed(RouteName.loginScreen, pathParameters: {
"fromScreen": "registerStep",
});
},
text: AppText.logoutText,
clr: AppColor.primaryColor2,
),
),
const Gap(5),
ButtonWidget().textBtn(
function: () {},
function: () {
goRouter.pushNamed(RouteName.deleteAccountScreen);
},
text: TextWidget().text14W700(
AppText.deleteAccountText,
clr: AppColor.hintTextColor,

View File

@@ -0,0 +1,65 @@
import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart';
import 'package:tanami_app/features/deleteAccount/presentation/bloc/delete_account_event.dart';
import 'package:tanami_app/features/deleteAccount/presentation/bloc/delete_account_state.dart';
class DeleteAccountBloc extends Bloc<DeleteAccountEvent, DeleteAccountState> {
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
final TextEditingController descriptionTextField = TextEditingController();
GlobalKey<FormState> getFormKey() {
return formKey;
}
DeleteAccountBloc() : super(DeleteAccountInitial()) {
descriptionTextField.addListener(_onFormFieldChanged);
on<DeleteAccountFormChanged>(_onLoginFormChanged);
on<DeleteAccountSubmitted>((event, emit) async {
if (!formKey.currentState!.validate()) {
return;
}
emit(DeleteAccountLoading());
try {
// Simulate API call
await Future.delayed(const Duration(seconds: 2));
// Replace the next line with actual API call
final isSuccess = _mockApiCheck();
if (isSuccess) {
emit(DeleteAccountSuccess());
} else {
emit(const DeleteAccountFailure("Failed."));
}
} catch (e) {
emit(DeleteAccountFailure(e.toString()));
}
});
}
bool _mockApiCheck() {
return true;
}
void _onFormFieldChanged() {
add(DeleteAccountFormChanged(
descriptionTextField.text,
));
}
void _onLoginFormChanged(
DeleteAccountFormChanged event, Emitter<DeleteAccountState> emit) {
final areFieldsFilled = event.description.isNotEmpty;
emit(DeleteAccountFieldsState(areFieldsFilled));
}
// Method to reset text fields
void resetFields() {
descriptionTextField.clear();
}
@override
Future<void> close() {
descriptionTextField.dispose();
return super.close();
}
}

View File

@@ -0,0 +1,30 @@
import 'package:equatable/equatable.dart';
abstract class DeleteAccountEvent extends Equatable {
const DeleteAccountEvent();
@override
List<Object> get props => [];
}
class DeleteAccountSubmitted extends DeleteAccountEvent {
final String description;
const DeleteAccountSubmitted(
this.description,
);
@override
List<Object> get props => [description];
}
class DeleteAccountFormChanged extends DeleteAccountEvent {
final String description;
const DeleteAccountFormChanged(
this.description,
);
@override
List<Object> get props => [description];
}

View File

@@ -0,0 +1,32 @@
import 'package:equatable/equatable.dart';
abstract class DeleteAccountState extends Equatable {
const DeleteAccountState();
@override
List<Object> get props => [];
}
class DeleteAccountInitial extends DeleteAccountState {}
class DeleteAccountLoading extends DeleteAccountState {}
class DeleteAccountSuccess extends DeleteAccountState {}
class DeleteAccountFailure extends DeleteAccountState {
final String error;
const DeleteAccountFailure(this.error);
@override
List<Object> get props => [error];
}
class DeleteAccountFieldsState extends DeleteAccountState {
final bool areFieldsFilled;
const DeleteAccountFieldsState(this.areFieldsFilled);
@override
List<Object> get props => [areFieldsFilled];
}

View File

@@ -0,0 +1,16 @@
import 'package:bloc/bloc.dart';
import 'text_event.dart';
import 'text_state.dart';
class TextBloc extends Bloc<TextEvent, TextState> {
final int maxCharacters;
TextBloc(this.maxCharacters) : super(TextInitial()) {
on<TextChanged>(_onTextChanged);
}
void _onTextChanged(TextChanged event, Emitter<TextState> emit) {
final charactersLeft = maxCharacters - event.text.length;
emit(TextUpdated(event.text, charactersLeft));
}
}

View File

@@ -0,0 +1,15 @@
import 'package:equatable/equatable.dart';
abstract class TextEvent extends Equatable {
@override
List<Object> get props => [];
}
class TextChanged extends TextEvent {
final String text;
TextChanged(this.text);
@override
List<Object> get props => [text];
}

View File

@@ -0,0 +1,18 @@
import 'package:equatable/equatable.dart';
abstract class TextState extends Equatable {
@override
List<Object> get props => [];
}
class TextInitial extends TextState {}
class TextUpdated extends TextState {
final String text;
final int charactersLeft;
TextUpdated(this.text, this.charactersLeft);
@override
List<Object> get props => [text, charactersLeft];
}

View File

@@ -0,0 +1,26 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:tanami_app/features/deleteAccount/presentation/widgets/top_section.dart';
import '../widgets/bottom_section.dart';
import '../widgets/form_section.dart';
class DeleteAccountLayout extends StatelessWidget {
const DeleteAccountLayout({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: true,
body: SingleChildScrollView(
child: Column(
children: [
const TopSection(),
const FormSection(),
const Gap(30),
bottomSection(context),
],
),
));
}
}

View File

@@ -0,0 +1,40 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:tanami_app/features/deleteAccount/presentation/bloc/text_bloc.dart';
import 'package:tanami_app/features/deleteAccount/presentation/pages/delete_account_layout.dart';
import '../../../../core/styles/app_text.dart';
import '../../../../shared/components/appbar_widget.dart';
import '../../../../shared/components/bloc/checkbox/checkbox_bloc.dart';
import '../bloc/delete_account_bloc.dart';
class DeleteAccountScreen extends StatelessWidget {
const DeleteAccountScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const AppBarWidget(
height: 75,
titleTxt: AppText.deleteAccountText,
),
body: MultiBlocProvider(
providers: [
BlocProvider(
// Create an instance of the OnboardingBloc
create: (context) => DeleteAccountBloc(),
),
BlocProvider(
// Create an instance of the OnboardingBloc
create: (context) => CheckboxBloc(),
),
BlocProvider(
// Create an instance of the OnboardingBloc
create: (context) => TextBloc(350),
),
],
child: const DeleteAccountLayout(),
),
);
}
}

View File

@@ -0,0 +1,81 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:gap/gap.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_event.dart';
import '../../../../shared/components/bloc/checkbox/checkbox_state.dart';
import '../../../../shared/components/button_widget.dart';
import '../../../../shared/components/loader.dart';
import '../../../../shared/components/text_widget.dart';
import '../../../../shared/components/toast_message.dart';
import '../bloc/delete_account_bloc.dart';
import '../bloc/delete_account_event.dart';
import '../bloc/delete_account_state.dart';
Widget bottomSection(BuildContext context) {
final deleteAccountBloc = context.read<DeleteAccountBloc>();
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
margin: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 10,
),
width: 1.sw,
height: 56.h,
child: ButtonWidget().elevatedBtn(
txtClr: AppColor.plainWhite,
function: () {
goRouter.pop();
},
text: AppText.noIWantToStay,
clr: AppColor.primaryColor2,
),
),
BlocConsumer<DeleteAccountBloc, DeleteAccountState>(
listener: (context, state) {
if (state is DeleteAccountLoading) {
Loader.loader(context);
} else if (state is DeleteAccountSuccess) {
goRouter.pop();
} else if (state is DeleteAccountFailure) {
goRouter.pop();
errorToastMessage(
context,
state.error,
);
}
},
builder: (context, state) {
return ButtonWidget().textBtn(
function: () {
if (deleteAccountBloc.formKey.currentState!.validate()) {
context.read<CheckboxBloc>().add(ValidateCheckbox());
if (context.read<CheckboxBloc>().state is CheckboxChecked) {
context.read<DeleteAccountBloc>().add(
DeleteAccountSubmitted(
context
.read<DeleteAccountBloc>()
.descriptionTextField
.text,
),
);
}
}
},
text: TextWidget().text14W700(AppText.deleteAccountText,
clr: AppColor.hintTextColor,
textDecoration: TextDecoration.underline));
},
),
const Gap(20),
],
);
}

View File

@@ -0,0 +1,114 @@
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: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 '../../../../shared/components/bloc/checkbox/checkbox_bloc.dart';
import '../../../../shared/components/bloc/checkbox/checkbox_state.dart';
import '../../../../shared/components/checkbox_widget.dart';
import '../../../../shared/components/form_label_textfield.dart';
import '../bloc/delete_account_bloc.dart';
import '../bloc/text_bloc.dart';
import '../bloc/text_event.dart';
import '../bloc/text_state.dart';
class FormSection extends StatelessWidget {
const FormSection({super.key});
@override
Widget build(BuildContext context) {
final deleteAccountBloc = context.read<DeleteAccountBloc>();
return Form(
key: deleteAccountBloc.formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(
left: 16.0,
right: 16,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Gap(40),
BlocBuilder<TextBloc, TextState>(
builder: (context, state) {
return FormLabelTextField(
onChangeFun: (p0) {
context.read<TextBloc>().add(TextChanged(
deleteAccountBloc.descriptionTextField.text,
));
},
hintText: AppText.enterAdescription,
textEditingController:
deleteAccountBloc.descriptionTextField,
title: AppText
.toHelpUsImprovePleaseLetusKnowWhyYouWantToDeleteAccount,
type: "description",
);
},
),
const Gap(8),
BlocBuilder<TextBloc, TextState>(
builder: (context, state) {
if (state is TextUpdated) {
return TextWidget().text14W400(
"${state.charactersLeft} ${AppText.charactersLeft}",
clr: AppColor.descriptionText);
}
return const Text('350 characters left');
},
),
],
),
),
Stack(
children: [
SizedBox(
width: 1.sw,
height: 70.h,
),
const CheckBoxWidget(),
Positioned(
left: 45,
top: 14,
child: SizedBox(
width: 0.75.sw,
child: TextWidget().text14W500(
AppText.iUnderstandAndAgreeThatmyDataWillBeDeleted,
clr: AppColor.charcoalColor,
txtAlign: TextAlign.start,
),
),
),
BlocBuilder<CheckboxBloc, CheckBoxState>(
builder: (context, state) {
if (state is CheckboxError) {
return Positioned(
left: 45,
top: 55,
child: SizedBox(
width: 0.75.sw,
child: TextWidget().text14W500(
AppText.pleaseCheckThisField,
clr: AppColor.txtErrorColor,
txtAlign: TextAlign.start,
),
),
);
}
return const SizedBox.shrink();
},
),
],
),
],
),
);
}
}

View File

@@ -0,0 +1,33 @@
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_images.dart';
import 'package:tanami_app/core/styles/app_text.dart';
import 'package:tanami_app/shared/components/text_widget.dart';
class TopSection extends StatelessWidget {
const TopSection({
super.key,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Gap(85),
Center(
child: SvgPicture.asset(
AppImages.weclomeLogo,
),
),
const Gap(30),
TextWidget().text20W700(
AppText.weAreSadToSeeYouGo,
clr: AppColor.charcoalColor,
),
],
);
}
}

View File

@@ -21,6 +21,8 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> {
on<LoginFormChanged>(_onLoginFormChanged);
on<LoginSubmitted>((event, emit) async {
if (!formKey.currentState!.validate()) {
emit(
const LoginFailure("Login failed. Please check your credentials."));
return;
}
emit(LoginLoading());

View File

@@ -15,7 +15,7 @@ class LoginSignUpButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 40.0),
padding: const EdgeInsets.symmetric(vertical: 20.0),
child: Column(
children: [
Container(

View File

@@ -1,4 +1,5 @@
import 'package:bloc/bloc.dart';
import 'package:tanami_app/core/styles/app_text.dart';
import 'checkbox_event.dart';
import 'checkbox_state.dart';
@@ -6,6 +7,7 @@ import 'checkbox_state.dart';
class CheckboxBloc extends Bloc<CheckboxEvent, CheckBoxState> {
CheckboxBloc() : super(CheckboxUnchecked()) {
on<ToggleCheckbox>(_onToggleCheckbox);
on<ValidateCheckbox>(_onValidateCheckbox);
}
void _onToggleCheckbox(ToggleCheckbox event, Emitter<CheckBoxState> emit) {
@@ -15,4 +17,11 @@ class CheckboxBloc extends Bloc<CheckboxEvent, CheckBoxState> {
emit(CheckboxUnchecked());
}
}
void _onValidateCheckbox(
ValidateCheckbox event, Emitter<CheckBoxState> emit) {
if (state is! CheckboxChecked) {
emit(const CheckboxError(AppText.pleaseCheckThisField));
}
}
}

View File

@@ -8,3 +8,5 @@ abstract class CheckboxEvent extends Equatable {
}
class ToggleCheckbox extends CheckboxEvent {}
class ValidateCheckbox extends CheckboxEvent {}

View File

@@ -10,3 +10,12 @@ abstract class CheckBoxState extends Equatable {
class CheckboxUnchecked extends CheckBoxState {}
class CheckboxChecked extends CheckBoxState {}
class CheckboxError extends CheckBoxState {
final String message;
const CheckboxError(this.message);
@override
List<Object> get props => [message];
}

View File

@@ -1,16 +1,32 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:tanami_app/core/styles/app_color.dart';
import 'package:tanami_app/shared/components/text_widget.dart';
class ButtonWidget {
//Text Button
Widget textBtn({
required Widget text,
Color? clr,
required VoidCallback function,
}) {
return TextButton(
onPressed: function,
child: text,
return InkWell(
onTap: function,
child: Container(
clipBehavior: Clip.antiAlias,
decoration: ShapeDecoration(
shape: RoundedRectangleBorder(
side: const BorderSide(width: 1, color: AppColor.txtBorderColor),
borderRadius: BorderRadius.circular(30),
),
),
margin: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 10,
),
width: 1.sw,
height: 56.h,
child: Center(child: text),
),
);
}

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:gap/gap.dart';
import 'package:tanami_app/core/styles/app_color.dart';
import 'package:tanami_app/core/styles/app_text.dart';
@@ -17,12 +18,14 @@ class FormLabelTextField extends StatelessWidget {
required this.textEditingController,
required this.hintText,
this.prefixWidget,
this.onChangeFun,
});
final String title;
final String type;
final String hintText;
final TextEditingController textEditingController;
final Widget? prefixWidget;
final Function(String)? onChangeFun;
@override
Widget build(BuildContext context) {
@@ -32,6 +35,7 @@ class FormLabelTextField extends StatelessWidget {
TextWidget().text14W500(
title,
clr: AppColor.textLabelColor,
txtAlign: type == "description" ? TextAlign.start : TextAlign.center,
),
const Gap(10),
type == "password"
@@ -39,6 +43,7 @@ class FormLabelTextField extends StatelessWidget {
controller: textEditingController,
)
: textFormField(
onInput: onChangeFun,
validator: (value) {
if (type == "phone number") {
if (value != null && value.isEmpty) {
@@ -50,10 +55,19 @@ class FormLabelTextField extends StatelessWidget {
return AppText.chooseCountry;
}
return null;
} else if (type == "description") {
if (textEditingController.text.isEmpty) {
return AppText.pleaseEnteraDescription;
}
return null;
} else {
return null;
}
},
inputFormatters: [
LengthLimitingTextInputFormatter(350),
],
maxlines: type == "description" ? 6 : 1,
texttype: type == "phone number"
? TextInputType.phone
: TextInputType.name,

View File

@@ -24,13 +24,11 @@ Widget textFormField({
return TextFormField(
validator: validator,
textAlignVertical: TextAlignVertical.center,
// cursorColor: AppColor.txtBorderShadowColor,
cursorColor: AppColor.primaryColor2,
initialValue: value,
readOnly: readonly!,
onTap: onTap,
enabled: enabled,
enableInteractiveSelection: false,
maxLines: maxlines,
autovalidateMode: AutovalidateMode.onUserInteraction,
controller: textEditingController,