Merge branch 'main' into meet

This commit is contained in:
meet2711
2024-06-06 20:01:36 +05:30
committed by GitHub
46 changed files with 1070 additions and 21 deletions

View File

@@ -0,0 +1,7 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="path-1-inside-1_2028_2122" fill="white">
<path fill-rule="evenodd" clip-rule="evenodd" d="M18 8.08789L13.4757 13.3593C12.6195 14.3307 11.4193 14.3307 10.5631 13.3593L6 8.08789"/>
</mask>
<path d="M19.1383 9.06481C19.6778 8.43617 19.6056 7.48917 18.9769 6.94964C18.3483 6.4101 17.4013 6.48233 16.8617 7.11097L19.1383 9.06481ZM13.4757 13.3593L14.601 14.3511C14.6054 14.3462 14.6097 14.3412 14.614 14.3362L13.4757 13.3593ZM10.5631 13.3593L9.42895 14.3411L9.43781 14.3511L10.5631 13.3593ZM7.13412 7.10617C6.59192 6.47981 5.64463 6.41158 5.01828 6.95378C4.39192 7.49597 4.32369 8.44326 4.86588 9.06961L7.13412 7.10617ZM16.8617 7.11097L12.3375 12.3824L14.614 14.3362L19.1383 9.06481L16.8617 7.11097ZM12.3504 12.3675C12.1561 12.5879 12.0314 12.5879 12.0194 12.5879C12.0075 12.5879 11.8827 12.5879 11.6884 12.3675L9.43781 14.3511C10.0997 15.1021 11.0032 15.5879 12.0194 15.5879C13.0357 15.5879 13.9391 15.1021 14.601 14.3511L12.3504 12.3675ZM11.6972 12.3776L7.13412 7.10617L4.86588 9.06961L9.42899 14.341L11.6972 12.3776Z" fill="#05391B" mask="url(#path-1-inside-1_2028_2122)"/>
<path d="M22.2416 8.60756C22.2406 8.62349 22.2401 8.63944 22.2401 8.6554V15.5204C22.2401 15.5363 22.2406 15.5523 22.2416 15.5682C22.319 16.7783 21.8584 17.977 20.945 18.8831C20.0301 19.7906 18.7409 20.3228 17.3734 20.3379H6.59176C3.68522 20.3379 1.75 18.1955 1.75 15.5204V8.6554C1.75 5.98031 3.68522 3.83789 6.59176 3.83789H17.3734C18.7409 3.85294 20.0301 4.3852 20.945 5.29268C21.8584 6.19874 22.319 7.39744 22.2416 8.60756Z" stroke="#05391B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.99033 4.87254C3.30665 4.34878 5.0495 2.44376 6.29322 2.50127C6.665 2.53208 6.99364 2.75699 7.26067 3.01784C7.87379 3.61656 9.62897 5.88101 9.72859 6.35753C9.97096 7.52621 8.57833 8.1999 9.00454 9.37783C10.0911 12.0366 11.9634 13.9088 14.6233 14.9943C15.8003 15.4205 16.474 14.0279 17.6428 14.2713C18.1183 14.3709 20.3839 16.126 20.9826 16.7391C21.2425 17.0051 21.4684 17.3347 21.4992 17.7065C21.5454 19.0159 19.5222 20.7833 19.1278 21.0092C18.1974 21.6747 16.9834 21.6634 15.5035 20.9753C11.3739 19.2572 4.77426 12.7822 3.02422 8.49669C2.35461 7.02505 2.30839 5.80297 2.99033 4.87254Z" stroke="#05391B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 826 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,11 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g opacity="0.6">
<rect x="7.5" y="4.5" width="7.5" height="13.5" fill="white"/>
<mask id="mask0_2028_5820" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="2" y="2" width="20" height="20">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2ZM12 13C11.45 13 11 12.55 11 12V8C11 7.45 11.45 7 12 7C12.55 7 13 7.45 13 8V12C13 12.55 12.55 13 12 13ZM11 15V17H13V15H11Z" fill="black"/>
</mask>
<g mask="url(#mask0_2028_5820)">
<rect width="24" height="24" fill="#0172CB"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 674 B

View File

@@ -46,8 +46,16 @@ class RouteName {
static const String confirmPinScreen = 'confirmPinScreen';
//Forgot Password
static const String forgotPasswordPhoneVerificationScreen =
'forgotPasswordPhoneVerificationScreen';
static const String forgotPasswordScreen = 'forgotPasswordScreen';
//contact Admin
static const String contactAdminScreen = 'contactAdminScreen';
//language change
static const String languageChangeScreen = 'languageChangeScreen';
//delete Account
static const String deleteAccountScreen = 'deleteAccountScreen';
}

View File

@@ -8,9 +8,12 @@ import 'package:tanami_app/features/MainScreens/Portfolio/presentation/pages/por
import 'package:tanami_app/features/MainScreens/Wallet/presentation/pages/walletDetails.dart';
import 'package:tanami_app/features/biometric/presentation/pages/biometric_screen.dart';
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';
import 'package:tanami_app/features/register/presentation/pages/register_screen.dart';
import 'package:tanami_app/features/register/presentation/pages/register_step_screen.dart';
@@ -154,12 +157,33 @@ final goRouter = GoRouter(
},
),
GoRoute(
name: RouteName.walletDetails,
path: "${RouteName.walletDetails}/:type",
builder: (context, state) {
return WalletDetails(
type: state.pathParameters["type"]!,
);
name: RouteName.contactAdminScreen,
path: RouteName.contactAdminScreen,
builder: (context, state) {
return const ContactAdminScreen();
},
),
GoRoute(
name: RouteName.languageChangeScreen,
path: RouteName.languageChangeScreen,
builder: (context, state) {
return const LanguageChaneScreen();
},
),
GoRoute(
name: RouteName.deleteAccountScreen,
path: RouteName.deleteAccountScreen,
builder: (context, state) {
return const DeleteAccountScreen();
},
),
],

View File

@@ -73,4 +73,14 @@ class AppColor {
//KYC Card Color
static const Color kycCardTextColor = Color(0xFF074A23);
static const Color kycCardBgColor = Color(0x7F074A23);
//Contact Admin Color
static const Color contactAdminIconColor = Color(0xFF05391B);
static const Color contactAdminBgColor = Color(0xFFE2EEE2);
//Language Color
static const Color languageTextColor = Color(0xFF015698);
//Delete Account Color
static const Color descriptionText = Color(0xFFC6C6C6);
}

View File

@@ -92,4 +92,14 @@ class AppImages {
'assets/images/settings_screen/svg/reset_pin_icon.svg';
static const String contactIcon =
'assets/images/settings_screen/svg/contact_icon.svg';
//Contact
static const String byPhoneIcon =
'assets/images/contact_admin_screen/svg/phone_icon.svg';
static const String byMailIcon =
'assets/images/contact_admin_screen/svg/mail_icon.svg';
//Language
static const String infoIcon =
'assets/images/language_screen/png/info_icon.png';
}

View File

@@ -153,4 +153,32 @@ class AppText {
static const String faqText = "FAQ";
static const String logoutText = "Log Out";
static const String deleteAccountText = "Delete account";
//Contact Admin
static const String byPhoneText = "By phone";
static const String byEmailText = "By-mail";
static const String weAreHereToHelp = "We are here to help!";
//Language Screen
static const String arabicText = "اللغة العربية";
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

@@ -0,0 +1,19 @@
import 'package:url_launcher/url_launcher.dart';
void launchPhone(String phoneNumber) async {
final url = 'tel:$phoneNumber';
if (await canLaunchUrl(Uri.parse(url))) {
await launchUrl(Uri.parse(url));
} else {
throw 'Could not launch $url';
}
}
void launchEmail(String email) async {
final url = 'mailto:$email';
if (await canLaunchUrl(Uri.parse(url))) {
await launchUrl(Uri.parse(url));
} else {
throw 'Could not launch $url';
}
}

View File

@@ -1,6 +1,8 @@
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_images.dart';
import 'package:tanami_app/core/styles/app_text.dart';
@@ -37,7 +39,10 @@ class GeneralSettingsSection extends StatelessWidget {
],
),
const Gap(12),
const SettingsListItem(
SettingsListItem(
onTapFunc: () {
goRouter.pushNamed(RouteName.languageChangeScreen);
},
icon: AppImages.languageIcon,
title: AppText.languageText,
trailing: AppText.englishText,

View File

@@ -37,13 +37,15 @@ class PrivacySettingsSection extends StatelessWidget {
],
),
const Gap(12),
const SettingsListItem(
SettingsListItem(
onTapFunc: () {},
icon: AppImages.resetPasswordIcon,
title: AppText.resetPasswordText,
trailing: "",
),
const Gap(8),
const SettingsListItem(
SettingsListItem(
onTapFunc: () {},
icon: AppImages.resetPinIcon,
title: AppText.resetPinCodeText,
trailing: "",

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

@@ -8,12 +8,14 @@ class SettingsListItem extends StatelessWidget {
final String icon;
final String title;
final String trailing;
final Function() onTapFunc;
const SettingsListItem({
super.key,
required this.icon,
required this.title,
required this.trailing,
required this.onTapFunc,
});
@override
@@ -27,6 +29,7 @@ class SettingsListItem extends StatelessWidget {
color: AppColor.fillColor,
),
child: ListTile(
onTap: onTapFunc,
leading: SvgPicture.asset(icon),
title: Text(title,
style: GoogleFonts.dmSans(

View File

@@ -1,5 +1,7 @@
import 'package:flutter/material.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_images.dart';
import 'package:tanami_app/core/styles/app_text.dart';
@@ -20,25 +22,31 @@ class SupportSettingsSection extends StatelessWidget {
clr: AppColor.hintTextColor,
),
const Gap(8),
const SettingsListItem(
SettingsListItem(
onTapFunc: () {
goRouter.pushNamed(RouteName.contactAdminScreen);
},
icon: AppImages.contactIcon,
title: AppText.contactAdminText,
trailing: "",
),
const Gap(8),
const SettingsListItem(
SettingsListItem(
onTapFunc: () {},
icon: AppImages.privacyIcon,
title: AppText.privacyPolicy,
trailing: "",
),
const Gap(8),
const SettingsListItem(
SettingsListItem(
onTapFunc: () {},
icon: AppImages.privacyIcon,
title: AppText.termsAndCondition,
trailing: "",
),
const Gap(8),
const SettingsListItem(
SettingsListItem(
onTapFunc: () {},
icon: AppImages.faqIcon,
title: AppText.faqText,
trailing: "",

View File

@@ -0,0 +1,26 @@
import 'package:flutter/material.dart';
import 'package:tanami_app/features/contactAdmin/presentation/widgets/bottom_section.dart';
import '../../../../core/styles/app_text.dart';
import '../../../../shared/components/appbar_widget.dart';
import '../widgets/top_section.dart';
class ContactAdminScreen extends StatelessWidget {
const ContactAdminScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const AppBarWidget(
height: 75,
titleTxt: AppText.contactAdminText,
),
body: ListView(
children: [
topSection(),
bottomSection(),
],
),
);
}
}

View File

@@ -0,0 +1,81 @@
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/core/utils/url_launcher/url_launcher.dart';
import '../../../../shared/components/text_widget.dart';
Widget bottomSection() {
return Padding(
padding: const EdgeInsets.all(22.0),
child: Column(
children: [
const Gap(50.0),
contactOption(
icon: AppImages.byPhoneIcon,
title: AppText.byPhoneText,
subtitle: '+973 12345678',
onTap: () {
launchPhone('+973 12345678');
},
),
const Gap(16.0),
contactOption(
icon: AppImages.byMailIcon,
title: AppText.byEmailText,
subtitle: 'info@tanamicapital.com',
onTap: () {
launchEmail('info@tanamicapital.com');
},
),
],
),
);
}
Widget contactOption({
required String icon,
required String title,
required String subtitle,
required VoidCallback onTap,
}) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0),
decoration: BoxDecoration(
color: AppColor.contactAdminBgColor,
borderRadius: BorderRadius.circular(22.0),
),
child: Row(
children: [
SvgPicture.asset(
icon,
),
const Gap(16.0),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextWidget().text14W600(
title,
clr: AppColor.contactAdminIconColor,
),
TextWidget().text14W400(
subtitle,
clr: AppColor.contactAdminIconColor,
),
],
),
const Spacer(),
const Icon(
Icons.arrow_forward_ios,
color: AppColor.contactAdminIconColor,
),
],
),
),
);
}

View File

@@ -0,0 +1,27 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:gap/gap.dart';
import '../../../../core/styles/app_color.dart';
import '../../../../core/styles/app_images.dart';
import '../../../../core/styles/app_text.dart';
import '../../../../shared/components/text_widget.dart';
Widget topSection() {
return Column(
children: [
const Gap(85),
Center(
child: SvgPicture.asset(
AppImages.weclomeLogo,
),
),
const Gap(24),
TextWidget().text17W700(
AppText.weAreHereToHelp,
clr: AppColor.textLabelColor,
),
const Gap(10),
],
);
}

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

@@ -0,0 +1,33 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'choose_language_event.dart';
import 'choose_language_state.dart';
class ChooseLanguageBloc
extends Bloc<ChooseLanguageEvent, ChooseLanguageState> {
ChooseLanguageBloc() : super(ChooseLanguageInitial()) {
on<ChooseLanguageSelected>(_onRadioSelected);
on<ResetRadioSelection>(_onResetRadioSelection);
}
void _onRadioSelected(
ChooseLanguageSelected event, Emitter<ChooseLanguageState> emit) {
emit(ChooseLanguageSelectionChanged(event.selectedIndex));
}
void _onResetRadioSelection(
ResetRadioSelection event, Emitter<ChooseLanguageState> emit) {
emit(ChooseLanguageInitial());
}
void resetSelection() {
add(ResetRadioSelection());
}
int get selectedCountry {
if (state is ChooseLanguageSelectionChanged) {
return (state as ChooseLanguageSelectionChanged).selectedIndex;
}
return 0;
}
}

View File

@@ -0,0 +1,19 @@
import 'package:equatable/equatable.dart';
abstract class ChooseLanguageEvent extends Equatable {
const ChooseLanguageEvent();
@override
List<Object> get props => [];
}
class ChooseLanguageSelected extends ChooseLanguageEvent {
final int selectedIndex;
const ChooseLanguageSelected([this.selectedIndex = 0]);
@override
List<Object> get props => [selectedIndex];
}
class ResetRadioSelection extends ChooseLanguageEvent {}

View File

@@ -0,0 +1,19 @@
import 'package:equatable/equatable.dart';
abstract class ChooseLanguageState extends Equatable {
const ChooseLanguageState();
@override
List<Object> get props => [];
}
class ChooseLanguageInitial extends ChooseLanguageState {}
class ChooseLanguageSelectionChanged extends ChooseLanguageState {
final int selectedIndex;
const ChooseLanguageSelectionChanged(this.selectedIndex);
@override
List<Object> get props => [selectedIndex];
}

View File

@@ -0,0 +1,52 @@
import 'package:flutter/material.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_images.dart';
import 'package:tanami_app/core/styles/app_text.dart';
import 'package:tanami_app/shared/components/appbar_widget.dart';
import 'package:tanami_app/shared/components/text_widget.dart';
import '../widgets/bottom_section.dart';
import '../widgets/language_change_list.dart';
class LanguageChangeLayout extends StatelessWidget {
const LanguageChangeLayout({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: bottomSection(),
appBar: const AppBarWidget(
height: 75,
titleTxt: AppText.languageText,
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
const CountrySelectionList(),
const Gap(24),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.asset(
width: 24,
height: 24,
AppImages.infoIcon,
),
const Gap(8),
SizedBox(
width: 0.8.sw,
child: TextWidget().text12W500(
AppText.changingTheLanguageWillReloadApp,
clr: AppColor.languageTextColor,
),
)
],
)
],
),
),
);
}
}

View File

@@ -0,0 +1,26 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:tanami_app/features/languageChange/presentation/bloc/choose_language_bloc.dart';
import 'language_change_layout.dart';
class LanguageChaneScreen extends StatelessWidget {
const LanguageChaneScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: true,
body: MultiBlocProvider(
// Define the providers for the OnboardingBloc and other blocs
providers: [
BlocProvider(
// Create an instance of the OnboardingBloc
create: (context) => ChooseLanguageBloc(),
),
],
child: const LanguageChangeLayout(),
),
);
}
}

View File

@@ -0,0 +1,41 @@
import 'package:flutter/material.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/button_widget.dart';
import '../../../../shared/components/text_widget.dart';
Widget bottomSection() {
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.submitText,
clr: AppColor.primaryColor2,
),
),
ButtonWidget().textBtn(
function: () {
goRouter.pop();
},
text: TextWidget().text14W700(AppText.backText,
clr: AppColor.textLabelColor,
textDecoration: TextDecoration.underline)),
const Gap(20),
],
);
}

View File

@@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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 '../bloc/choose_language_bloc.dart';
import '../bloc/choose_language_event.dart';
import '../bloc/choose_language_state.dart';
class CountrySelectionList extends StatelessWidget {
const CountrySelectionList({super.key});
@override
Widget build(BuildContext context) {
final radioBloc = context.read<ChooseLanguageBloc>();
return BlocBuilder<ChooseLanguageBloc, ChooseLanguageState>(
builder: (context, state) {
int selectedIndex = 0;
if (state is ChooseLanguageSelectionChanged) {
selectedIndex = state.selectedIndex;
}
return Column(
children: List<Widget>.generate(languageList.length, (int index) {
return Row(
children: [
Radio<int>(
activeColor: AppColor.radioActiveColor,
value: index,
groupValue: selectedIndex,
onChanged: (int? value) {
if (value != null) {
radioBloc.add(ChooseLanguageSelected(value));
}
},
),
TextWidget().text14W500(languageList[index],
clr: AppColor.charcoalColor),
],
);
}),
);
},
);
}
}
List<String> languageList = [
AppText.englishText,
AppText.arabicText,
];

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,

View File

@@ -1222,13 +1222,13 @@ packages:
source: hosted
version: "10.3.0"
url_launcher:
dependency: transitive
dependency: "direct main"
description:
name: url_launcher
sha256: "6ce1e04375be4eed30548f10a315826fd933c1e493206eab82eed01f438c8d2e"
sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3"
url: "https://pub.dev"
source: hosted
version: "6.2.6"
version: "6.3.0"
url_launcher_android:
dependency: transitive
description:

View File

@@ -78,6 +78,9 @@ dependencies:
sms_autofill: ^2.3.1
carousel_slider: ^4.2.1
#Url Launcher
url_launcher: ^6.3.0
dev_dependencies:
flutter_test:
sdk: flutter
@@ -109,5 +112,9 @@ flutter:
- assets/images/biometric_screen/svg/
- assets/images/dialog/
- assets/images/dialog/svg/
- assets/images/contact_admin_screen/
- assets/images/contact_admin_screen/svg/
- assets/images/settings_screen/
- assets/images/settings_screen/svg/
- assets/images/language_screen/
- assets/images/language_screen/png/