Merge pull request #3 from WDI-Ideas/jayeshjain25
login screen, common widgets
7
assets/images/auth_screen/svg/hide_password.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.76069 14.3668C9.18569 13.7928 8.83569 13.0128 8.83569 12.1378C8.83569 10.3848 10.2477 8.9718 11.9997 8.9718C12.8667 8.9718 13.6647 9.3228 14.2297 9.8968" stroke="#363636" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M15.1049 12.6989C14.8729 13.9889 13.8569 15.0069 12.5679 15.2409" stroke="#363636" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.65463 17.4723C5.06763 16.2263 3.72363 14.4063 2.74963 12.1373C3.73363 9.85829 5.08663 8.02829 6.68363 6.77229C8.27063 5.51629 10.1016 4.83429 11.9996 4.83429C13.9086 4.83429 15.7386 5.52629 17.3356 6.79129" stroke="#363636" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M19.4477 8.99081C20.1357 9.90481 20.7407 10.9598 21.2497 12.1368C19.2827 16.6938 15.8067 19.4388 11.9997 19.4388C11.1367 19.4388 10.2857 19.2988 9.46765 19.0258" stroke="#363636" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M19.887 4.2496L4.11304 20.0236" stroke="#363636" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
4
assets/images/auth_screen/svg/show_password.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<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="M15.1615 12.0531C15.1615 13.7991 13.7455 15.2141 11.9995 15.2141C10.2535 15.2141 8.8385 13.7991 8.8385 12.0531C8.8385 10.3061 10.2535 8.89108 11.9995 8.89108C13.7455 8.89108 15.1615 10.3061 15.1615 12.0531Z" stroke="#363636" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.998 19.3549C15.806 19.3549 19.289 16.6169 21.25 12.0529C19.289 7.48888 15.806 4.75089 11.998 4.75089H12.002C8.194 4.75089 4.711 7.48888 2.75 12.0529C4.711 16.6169 8.194 19.3549 12.002 19.3549H11.998Z" stroke="#363636" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 784 B |
9
assets/images/country_flag/svg/bahrain_flag.svg
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
9
assets/images/country_flag/svg/kuwait_flag.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<rect width="24" height="24" rx="12" fill="url(#pattern0_868_22066)"/>
|
||||
<defs>
|
||||
<pattern id="pattern0_868_22066" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||
<use xlink:href="#image0_868_22066" transform="translate(-0.137256) scale(0.00628931)"/>
|
||||
</pattern>
|
||||
<image id="image0_868_22066" width="318" height="159" xlink:href="data:image/png;base64,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"/>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
9
assets/images/country_flag/svg/oman_flag.svg
Normal file
|
After Width: | Height: | Size: 50 KiB |
9
assets/images/country_flag/svg/qatar_flag.svg
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
9
assets/images/country_flag/svg/saudi_arabia_flag.svg
Normal file
|
After Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
@@ -4,8 +4,6 @@ class AppColor {
|
||||
//Primary Color
|
||||
static const Color primaryColor = Color(0xFF002F0F);
|
||||
|
||||
//Secondary Color
|
||||
|
||||
//Welcome Color
|
||||
static const Color indicatorActiveColor = Color(0xFF002F0F);
|
||||
static const Color indicatorInactiveColor = Color(0xFFb8c1bb);
|
||||
@@ -13,4 +11,21 @@ class AppColor {
|
||||
//Common Color
|
||||
static const Color plainWhite = Color(0xFFFFFFFF);
|
||||
static const Color darkGreyColor = Color(0xFF343434);
|
||||
|
||||
//Auth Color
|
||||
static const Color charcoalColor = Color(0xFF272727);
|
||||
static const Color smokeGrayColor = Color(0xFF787878);
|
||||
static const Color textLabelColor = Color(0xFF363636);
|
||||
static const Color forgotPassButtonColor = Color(0xFF888888);
|
||||
|
||||
//Text Form Field Color
|
||||
static const Color txtBorderColor = Color(0xFFE3E3E3);
|
||||
static const Color txtBorderShadowColor = Color(0xFFE9E9E9);
|
||||
static const Color hintTextColor = Color(0xFF8D8D8D);
|
||||
static const Color txtErrorBorderColor = Color(0xFFF1B9B9);
|
||||
static const Color txtErrorColor = Color(0xFFD21C1C);
|
||||
|
||||
//Button Color
|
||||
static const Color inactiveBtnColor = Color(0xFFD8D8D8);
|
||||
static const Color inactiveBtnTxtColor = Color(0xFF8D8D8D);
|
||||
}
|
||||
|
||||
@@ -10,10 +10,29 @@ class AppImages {
|
||||
"assets/images/welcome_screen/svg/Tanami_Capital_Logo.svg";
|
||||
static const String firstWelcome =
|
||||
"assets/images/welcome_screen/png/First_Onboarding.png";
|
||||
|
||||
static const String secondWelcome =
|
||||
"assets/images/welcome_screen/png/Second_Onboarding.png";
|
||||
|
||||
static const String thirdWelcome =
|
||||
"assets/images/welcome_screen/png/Third_Onboarding.png";
|
||||
|
||||
//Auth
|
||||
static const String forwardArrow =
|
||||
"assets/images/auth_screen/svg/right_arrow.svg";
|
||||
static const String hidePassword =
|
||||
"assets/images/auth_screen/svg/hide_password.svg";
|
||||
static const String showPassword =
|
||||
"assets/images/auth_screen/svg/show_password.svg";
|
||||
|
||||
//Country Flag
|
||||
static const String bahrainFlag =
|
||||
"assets/images/country_flag/svg/bahrain_flag.svg";
|
||||
static const String kuwaitFlag =
|
||||
"assets/images/auth_screen/svg/kuwait_flag.svg";
|
||||
static const String omanFlag = "assets/images/auth_screen/svg/oman_flag.svg";
|
||||
static const String qatarFlag =
|
||||
"assets/images/country_flag/svg/qatar_flag.svg";
|
||||
static const String saudiArabiaflag =
|
||||
"assets/images/auth_screen/svg/saudi_arabia_flag.svg";
|
||||
static const String unitedArabEmiratesFlag =
|
||||
"assets/images/auth_screen/svg/united_arab_emirates_flag.svg";
|
||||
}
|
||||
|
||||
@@ -12,6 +12,20 @@ class AppText {
|
||||
static const String weclomeDescription2Text =
|
||||
"experienced investment experts with a long-standing track record";
|
||||
static const String weclomeDescription3Text = "with only SAR 1,000";
|
||||
static const String loginText = "Login In";
|
||||
static const String loginText = "Log In";
|
||||
static const String signUpText = "Sign Up";
|
||||
|
||||
//Login
|
||||
static const String welcomeText = "Welcome back!";
|
||||
static const String pleaseEnterLoginDetails =
|
||||
"Please enter your login details to get started";
|
||||
static const String countryOfResidence = "Country of residence";
|
||||
static const String chooseCountry = "Choose country";
|
||||
static const String phoneNumber = "Phone Number";
|
||||
static const String password = "Password";
|
||||
static const String enterPassword = "Enter password";
|
||||
static const String invalidPhoneNo = "Invalid Phone Number";
|
||||
static const String enterPhoneNo = "Enter phone number";
|
||||
static const String invalidPassword = "Invalid Password";
|
||||
static const String forgorPassword = "Forgot Password";
|
||||
}
|
||||
|
||||
76
lib/features/login/presentation/bloc/login_bloc.dart
Normal file
@@ -0,0 +1,76 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'login_event.dart';
|
||||
import 'login_state.dart';
|
||||
|
||||
class LoginBloc extends Bloc<LoginEvent, LoginState> {
|
||||
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
|
||||
final TextEditingController countrySelectionTextField =
|
||||
TextEditingController();
|
||||
final TextEditingController phoneNumberTextField = TextEditingController();
|
||||
final TextEditingController passwordTextField = TextEditingController();
|
||||
|
||||
GlobalKey<FormState> getFormKey() {
|
||||
return formKey;
|
||||
}
|
||||
|
||||
LoginBloc() : super(LoginInitial()) {
|
||||
phoneNumberTextField.addListener(_onFormFieldChanged);
|
||||
passwordTextField.addListener(_onFormFieldChanged);
|
||||
countrySelectionTextField.addListener(_onFormFieldChanged);
|
||||
on<LoginSubmitted>((event, emit) async {
|
||||
if (!formKey.currentState!.validate()) {
|
||||
return;
|
||||
}
|
||||
emit(LoginLoading());
|
||||
try {
|
||||
// Simulate API call
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
// Replace the next line with actual API call
|
||||
final isSuccess = await _mockLoginApi(
|
||||
event.phoneNumber, event.password, event.countryResidence);
|
||||
if (isSuccess) {
|
||||
emit(LoginSuccess());
|
||||
} else {
|
||||
emit(const LoginFailure(
|
||||
"Login failed. Please check your credentials."));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(LoginFailure(e.toString()));
|
||||
}
|
||||
});
|
||||
}
|
||||
void _onFormFieldChanged() {
|
||||
add(LoginFormChanged());
|
||||
}
|
||||
|
||||
bool areFieldsFilled() {
|
||||
return phoneNumberTextField.text.isNotEmpty &&
|
||||
passwordTextField.text.isNotEmpty &&
|
||||
countrySelectionTextField.text.isNotEmpty;
|
||||
}
|
||||
|
||||
Stream<LoginState> mapEventToState(LoginEvent event) async* {
|
||||
if (event is LoginFormChanged) {
|
||||
yield LoginFieldsState(areFieldsFilled());
|
||||
}
|
||||
}
|
||||
|
||||
// Mock API function, replace with actual API call
|
||||
Future<bool> _mockLoginApi(
|
||||
String email,
|
||||
String password,
|
||||
String countryResidence,
|
||||
) async {
|
||||
return email == "1234567891" && password == "123456";
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
phoneNumberTextField.dispose();
|
||||
passwordTextField.dispose();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
25
lib/features/login/presentation/bloc/login_event.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
abstract class LoginEvent extends Equatable {
|
||||
const LoginEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class LoginSubmitted extends LoginEvent {
|
||||
final String phoneNumber;
|
||||
final String password;
|
||||
final String countryResidence;
|
||||
|
||||
const LoginSubmitted(
|
||||
this.phoneNumber,
|
||||
this.password,
|
||||
this.countryResidence,
|
||||
);
|
||||
|
||||
@override
|
||||
List<Object> get props => [phoneNumber, password, countryResidence];
|
||||
}
|
||||
|
||||
class LoginFormChanged extends LoginEvent {}
|
||||
29
lib/features/login/presentation/bloc/login_state.dart
Normal file
@@ -0,0 +1,29 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
abstract class LoginState extends Equatable {
|
||||
const LoginState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class LoginInitial extends LoginState {}
|
||||
|
||||
class LoginLoading extends LoginState {}
|
||||
|
||||
class LoginSuccess extends LoginState {}
|
||||
|
||||
class LoginFailure extends LoginState {
|
||||
final String error;
|
||||
|
||||
const LoginFailure(this.error);
|
||||
|
||||
@override
|
||||
List<Object> get props => [error];
|
||||
}
|
||||
|
||||
class LoginFieldsState extends LoginState {
|
||||
final bool areFieldsFilled;
|
||||
|
||||
const LoginFieldsState(this.areFieldsFilled);
|
||||
}
|
||||
22
lib/features/login/presentation/pages/login_layout.dart
Normal file
@@ -0,0 +1,22 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tanami_app/features/login/presentation/widgets/login_form.dart';
|
||||
|
||||
import '../widgets/bottom_section.dart';
|
||||
import '../widgets/top_section.dart';
|
||||
|
||||
class LoginLayout extends StatelessWidget {
|
||||
LoginLayout({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: ListView(
|
||||
//
|
||||
children: [
|
||||
const TopSection(),
|
||||
LoginForm(),
|
||||
const BottomSection(),
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../../shared/components/bloc/password_field/password_visibility_bloc.dart';
|
||||
import '../bloc/login_bloc.dart';
|
||||
import 'login_layout.dart';
|
||||
|
||||
class LoginScreen extends StatelessWidget {
|
||||
const LoginScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold();
|
||||
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) => LoginBloc(),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => PasswordVisibilityBloc(),
|
||||
)
|
||||
],
|
||||
child: LoginLayout(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
100
lib/features/login/presentation/widgets/bottom_section.dart
Normal file
@@ -0,0 +1,100 @@
|
||||
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 'package:tanami_app/shared/components/loader.dart';
|
||||
import 'package:tanami_app/shared/components/toast_message.dart';
|
||||
|
||||
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/button_widget.dart';
|
||||
import '../../../../shared/components/text_widget.dart';
|
||||
import '../bloc/login_bloc.dart';
|
||||
import '../bloc/login_event.dart';
|
||||
import '../bloc/login_state.dart';
|
||||
|
||||
class BottomSection extends StatelessWidget {
|
||||
const BottomSection({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
const Gap(12),
|
||||
Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: ButtonWidget().textBtn(
|
||||
function: () {
|
||||
goRouter.goNamed(RouteName.loginScreen);
|
||||
},
|
||||
text: TextWidget().tex15W400(AppText.forgorPassword,
|
||||
clr: AppColor.forgotPassButtonColor,
|
||||
textDecoration: TextDecoration.underline)),
|
||||
),
|
||||
const Gap(20),
|
||||
BlocConsumer<LoginBloc, LoginState>(
|
||||
listener: (context, state) {
|
||||
if (state is LoginLoading) {
|
||||
Loader.loader(context);
|
||||
} else if (state is LoginSuccess) {
|
||||
goRouter.pop();
|
||||
successToastMessage(context, "login successful !");
|
||||
} else if (state is LoginFailure) {
|
||||
goRouter.pop();
|
||||
errorToastMessage(
|
||||
context,
|
||||
state.error,
|
||||
);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
bool isButtonEnabled = false;
|
||||
if (state is LoginFieldsState) {
|
||||
isButtonEnabled = state.areFieldsFilled;
|
||||
}
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
),
|
||||
width: 1.sw,
|
||||
height: 56.h,
|
||||
child: ButtonWidget().elevatedBtn(
|
||||
txtClr: AppColor.inactiveBtnTxtColor,
|
||||
function: () {
|
||||
isButtonEnabled
|
||||
? context.read<LoginBloc>().add(
|
||||
LoginSubmitted(
|
||||
context
|
||||
.read<LoginBloc>()
|
||||
.phoneNumberTextField
|
||||
.text,
|
||||
context
|
||||
.read<LoginBloc>()
|
||||
.passwordTextField
|
||||
.text,
|
||||
""),
|
||||
)
|
||||
: null;
|
||||
},
|
||||
text: AppText.loginText,
|
||||
clr: isButtonEnabled
|
||||
? AppColor.primaryColor
|
||||
: AppColor.inactiveBtnColor,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const Gap(5),
|
||||
ButtonWidget().textBtn(
|
||||
function: () {
|
||||
goRouter.goNamed(RouteName.loginScreen);
|
||||
},
|
||||
text: TextWidget().tex14W700(AppText.signUpText,
|
||||
clr: AppColor.textLabelColor,
|
||||
textDecoration: TextDecoration.underline)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
53
lib/features/login/presentation/widgets/login_form.dart
Normal file
@@ -0,0 +1,53 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:tanami_app/core/styles/app_text.dart';
|
||||
|
||||
import '../../../../shared/components/form_label_textfield.dart';
|
||||
import '../bloc/login_bloc.dart';
|
||||
|
||||
class LoginForm extends StatelessWidget {
|
||||
LoginForm({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loginBloc = context.read<LoginBloc>();
|
||||
return Form(
|
||||
key: loginBloc.formKey,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 14,
|
||||
),
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Gap(50),
|
||||
FormLabelTextField(
|
||||
hintText: AppText.chooseCountry,
|
||||
title: AppText.countryOfResidence,
|
||||
type: "country selection",
|
||||
textEditingController: loginBloc.countrySelectionTextField,
|
||||
),
|
||||
const Gap(20),
|
||||
FormLabelTextField(
|
||||
hintText: "+0 (000) 000 00 00",
|
||||
title: AppText.phoneNumber,
|
||||
type: "phone number",
|
||||
textEditingController: loginBloc.phoneNumberTextField,
|
||||
),
|
||||
const Gap(20),
|
||||
FormLabelTextField(
|
||||
hintText: AppText.enterPassword,
|
||||
title: AppText.password,
|
||||
type: "password",
|
||||
textEditingController: loginBloc.passwordTextField,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
39
lib/features/login/presentation/widgets/top_section.dart
Normal file
@@ -0,0 +1,39 @@
|
||||
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(60),
|
||||
TextWidget().tex20W700(
|
||||
AppText.welcomeText,
|
||||
clr: AppColor.charcoalColor,
|
||||
),
|
||||
const Gap(10),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 75),
|
||||
child: TextWidget().tex14W500(
|
||||
AppText.pleaseEnterLoginDetails,
|
||||
clr: AppColor.smokeGrayColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ class WelcomeScreen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
body: MultiBlocProvider(
|
||||
// Define the providers for the OnboardingBloc and other blocs
|
||||
providers: [
|
||||
|
||||
@@ -13,7 +13,6 @@ class WelcomeLayout extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
body: BlocListener<OnboardingBloc, OnboardingState>(
|
||||
// Define the listener for the BlocListener
|
||||
listener: (context, state) {
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import 'package:flutter/material.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 'package:tanami_app/core/styles/app_color.dart';
|
||||
import 'package:tanami_app/shared/components/button_widget.dart';
|
||||
|
||||
import '../../../../core/styles/app_text.dart';
|
||||
import '../../../../shared/components/text_widget.dart';
|
||||
|
||||
class LoginSignUpButton extends StatelessWidget {
|
||||
const LoginSignUpButton({super.key});
|
||||
@@ -24,11 +27,18 @@ class LoginSignUpButton extends StatelessWidget {
|
||||
child: ButtonWidget().elevatedBtn(
|
||||
function: () {},
|
||||
text: AppText.signUpText,
|
||||
txtClr: AppColor.plainWhite,
|
||||
clr: AppColor.primaryColor,
|
||||
),
|
||||
),
|
||||
const Gap(16),
|
||||
ButtonWidget().textBtn(function: () {}, text: AppText.loginText)
|
||||
ButtonWidget().textBtn(
|
||||
function: () {
|
||||
goRouter.goNamed(RouteName.loginScreen);
|
||||
},
|
||||
text: TextWidget().tex14W700(AppText.loginText,
|
||||
clr: AppColor.darkGreyColor,
|
||||
textDecoration: TextDecoration.underline))
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -28,7 +28,7 @@ class MyApp extends StatefulWidget {
|
||||
|
||||
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
||||
final NetworkConnectivity _networkConnectivity =
|
||||
NetworkConnectivity(onStatusChange: (String) {});
|
||||
NetworkConnectivity(onStatusChange: (_) {});
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'password_visibility_event.dart';
|
||||
import 'password_visibility_state.dart';
|
||||
|
||||
class PasswordVisibilityBloc
|
||||
extends Bloc<PasswordVisibilityEvent, PasswordVisibilityState> {
|
||||
PasswordVisibilityBloc()
|
||||
: super(const PasswordVisibilityState(isPasswordVisible: false)) {
|
||||
on<TogglePasswordVisibility>((event, emit) {
|
||||
emit(
|
||||
PasswordVisibilityState(isPasswordVisible: !state.isPasswordVisible));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
abstract class PasswordVisibilityEvent extends Equatable {
|
||||
const PasswordVisibilityEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class TogglePasswordVisibility extends PasswordVisibilityEvent {}
|
||||
@@ -0,0 +1,10 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class PasswordVisibilityState extends Equatable {
|
||||
final bool isPasswordVisible;
|
||||
|
||||
const PasswordVisibilityState({required this.isPasswordVisible});
|
||||
|
||||
@override
|
||||
List<Object> get props => [isPasswordVisible];
|
||||
}
|
||||
@@ -5,14 +5,12 @@ import 'package:tanami_app/shared/components/text_widget.dart';
|
||||
class ButtonWidget {
|
||||
//Text Button
|
||||
Widget textBtn({
|
||||
required String text,
|
||||
required Widget text,
|
||||
required VoidCallback function,
|
||||
}) {
|
||||
return TextButton(
|
||||
onPressed: function,
|
||||
child: TextWidget().tex14W700(text,
|
||||
clr: AppColor.darkGreyColor,
|
||||
textDecoration: TextDecoration.underline),
|
||||
child: text,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,6 +18,7 @@ class ButtonWidget {
|
||||
Widget elevatedBtn({
|
||||
required String text,
|
||||
required Color clr,
|
||||
Color? txtClr,
|
||||
required VoidCallback function,
|
||||
}) {
|
||||
return ElevatedButton(
|
||||
@@ -29,7 +28,7 @@ class ButtonWidget {
|
||||
),
|
||||
child: TextWidget().tex14W700(
|
||||
text,
|
||||
clr: AppColor.plainWhite,
|
||||
clr: txtClr ?? AppColor.plainWhite,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
68
lib/shared/components/form_label_textfield.dart
Normal file
@@ -0,0 +1,68 @@
|
||||
import 'package:flutter/material.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/password_text_form_field.dart';
|
||||
import 'package:tanami_app/shared/components/text_widget.dart';
|
||||
|
||||
import 'text_from_field_widget.dart';
|
||||
|
||||
class FormLabelTextField extends StatelessWidget {
|
||||
const FormLabelTextField({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.type,
|
||||
required this.textEditingController,
|
||||
required this.hintText,
|
||||
});
|
||||
final String title;
|
||||
final String type;
|
||||
final String hintText;
|
||||
final TextEditingController textEditingController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TextWidget().tex14W500(
|
||||
title,
|
||||
clr: AppColor.textLabelColor,
|
||||
),
|
||||
const Gap(10),
|
||||
type == "password"
|
||||
? PasswordField(
|
||||
controller: textEditingController,
|
||||
)
|
||||
: textFormField(
|
||||
validator: (value) {
|
||||
if (type == "phone number") {
|
||||
if (value != null && value.isEmpty) {
|
||||
return AppText.enterPhoneNo;
|
||||
}
|
||||
return null;
|
||||
} else if (type == "country selection") {
|
||||
if (textEditingController.text.isEmpty) {
|
||||
return AppText.chooseCountry;
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
texttype: type == "phone number"
|
||||
? TextInputType.phone
|
||||
: TextInputType.none,
|
||||
textEditingController: textEditingController,
|
||||
readonly: type == "country selection" ? true : false,
|
||||
hintText: hintText,
|
||||
suffixIcon: type == "country selection"
|
||||
? const Icon(
|
||||
Icons.arrow_forward_ios_rounded,
|
||||
color: Color(0xFFB4B4B4),
|
||||
)
|
||||
: const SizedBox())
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
28
lib/shared/components/loader.dart
Normal file
@@ -0,0 +1,28 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tanami_app/core/styles/app_color.dart';
|
||||
|
||||
class Loader {
|
||||
static loader(BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return Dialog(
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
child: WillPopScope(
|
||||
onWillPop: () async => false,
|
||||
child: const Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
color: AppColor.primaryColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
142
lib/shared/components/password_text_form_field.dart
Normal file
@@ -0,0 +1,142 @@
|
||||
import 'package:control_style/control_style.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:tanami_app/core/styles/app_images.dart';
|
||||
|
||||
import '../../core/styles/app_color.dart';
|
||||
import '../../core/styles/app_text.dart';
|
||||
import 'bloc/password_field/password_visibility_bloc.dart';
|
||||
import 'bloc/password_field/password_visibility_event.dart';
|
||||
import 'bloc/password_field/password_visibility_state.dart';
|
||||
|
||||
class PasswordField extends StatelessWidget {
|
||||
final TextEditingController controller;
|
||||
|
||||
const PasswordField({super.key, required this.controller});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<PasswordVisibilityBloc, PasswordVisibilityState>(
|
||||
builder: (context, state) {
|
||||
return TextFormField(
|
||||
validator: (value) {
|
||||
if (value != null && value.isEmpty) {
|
||||
return AppText.enterPassword;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
controller: controller,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
obscureText: !state.isPasswordVisible,
|
||||
style: GoogleFonts.dmSans(
|
||||
color: AppColor.charcoalColor,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
errorStyle: GoogleFonts.dmSans(
|
||||
color: AppColor.txtErrorColor,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
fillColor: AppColor.plainWhite,
|
||||
filled: true,
|
||||
hintStyle: GoogleFonts.dmSans(
|
||||
color: AppColor.hintTextColor,
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
border: DecoratedInputBorder(
|
||||
child: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
borderSide: const BorderSide(color: AppColor.txtBorderColor),
|
||||
),
|
||||
shadow: const [
|
||||
BoxShadow(
|
||||
color: AppColor.txtBorderShadowColor,
|
||||
blurRadius: 2,
|
||||
offset: Offset(0, 1),
|
||||
spreadRadius: 0.50,
|
||||
)
|
||||
],
|
||||
),
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0),
|
||||
errorBorder: DecoratedInputBorder(
|
||||
child: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
borderSide:
|
||||
const BorderSide(color: AppColor.txtErrorBorderColor),
|
||||
),
|
||||
shadow: const [
|
||||
BoxShadow(
|
||||
color: AppColor.txtBorderShadowColor,
|
||||
blurRadius: 2,
|
||||
offset: Offset(0, 1),
|
||||
spreadRadius: 0.50,
|
||||
)
|
||||
],
|
||||
),
|
||||
disabledBorder: DecoratedInputBorder(
|
||||
child: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
borderSide: const BorderSide(color: AppColor.txtBorderColor),
|
||||
),
|
||||
shadow: const [
|
||||
BoxShadow(
|
||||
color: AppColor.txtBorderShadowColor,
|
||||
blurRadius: 2,
|
||||
offset: Offset(0, 1),
|
||||
spreadRadius: 0.50,
|
||||
)
|
||||
],
|
||||
),
|
||||
enabledBorder: DecoratedInputBorder(
|
||||
child: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
borderSide: const BorderSide(color: AppColor.txtBorderColor),
|
||||
),
|
||||
shadow: const [
|
||||
BoxShadow(
|
||||
color: AppColor.txtBorderShadowColor,
|
||||
blurRadius: 2,
|
||||
offset: Offset(0, 1),
|
||||
spreadRadius: 0.50,
|
||||
)
|
||||
],
|
||||
),
|
||||
focusedBorder: DecoratedInputBorder(
|
||||
child: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
borderSide: const BorderSide(color: AppColor.txtBorderColor),
|
||||
),
|
||||
shadow: const [
|
||||
BoxShadow(
|
||||
color: AppColor.txtBorderShadowColor,
|
||||
blurRadius: 2,
|
||||
offset: Offset(0, 1),
|
||||
spreadRadius: 0.50,
|
||||
)
|
||||
],
|
||||
),
|
||||
hintText: 'Password',
|
||||
suffixIcon: IconButton(
|
||||
icon: SvgPicture.asset(
|
||||
state.isPasswordVisible
|
||||
? AppImages.showPassword
|
||||
: AppImages.hidePassword,
|
||||
),
|
||||
onPressed: () {
|
||||
context
|
||||
.read<PasswordVisibilityBloc>()
|
||||
.add(TogglePasswordVisibility());
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
139
lib/shared/components/text_from_field_widget.dart
Normal file
@@ -0,0 +1,139 @@
|
||||
import 'package:control_style/control_style.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:tanami_app/core/styles/app_color.dart';
|
||||
|
||||
Widget textFormField({
|
||||
final dynamic validator,
|
||||
final TextEditingController? textEditingController,
|
||||
final String? hintText,
|
||||
final Widget? leadingIcon,
|
||||
final Color? prefixIconColor,
|
||||
final String? validatorText,
|
||||
final String? value,
|
||||
final bool? readonly,
|
||||
final bool? enabled,
|
||||
final int? maxlines,
|
||||
final TextInputType? texttype,
|
||||
final dynamic inputFormatters,
|
||||
final Function(String)? onInput,
|
||||
final VoidCallback? onTap,
|
||||
final TextCapitalization? textCapV,
|
||||
final Widget? suffixIcon,
|
||||
}) {
|
||||
return TextFormField(
|
||||
validator: validator,
|
||||
textAlignVertical: TextAlignVertical.center,
|
||||
// cursorColor: AppColor.txtBorderShadowColor,
|
||||
initialValue: value,
|
||||
readOnly: readonly!,
|
||||
onTap: onTap,
|
||||
|
||||
enabled: enabled,
|
||||
enableInteractiveSelection: false,
|
||||
maxLines: maxlines,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
controller: textEditingController,
|
||||
textCapitalization: textCapV ?? TextCapitalization.none,
|
||||
decoration: InputDecoration(
|
||||
fillColor: AppColor.plainWhite,
|
||||
filled: true,
|
||||
hintStyle: GoogleFonts.dmSans(
|
||||
color: AppColor.hintTextColor,
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
hintText: hintText,
|
||||
prefixIconColor: prefixIconColor,
|
||||
prefixIcon: leadingIcon,
|
||||
errorStyle: GoogleFonts.dmSans(
|
||||
color: AppColor.txtErrorColor,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
suffixIcon: suffixIcon,
|
||||
border: DecoratedInputBorder(
|
||||
child: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
borderSide: const BorderSide(color: AppColor.txtBorderColor),
|
||||
),
|
||||
shadow: const [
|
||||
BoxShadow(
|
||||
color: AppColor.txtBorderShadowColor,
|
||||
blurRadius: 2,
|
||||
offset: Offset(0, 1),
|
||||
spreadRadius: 0.50,
|
||||
)
|
||||
],
|
||||
),
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0),
|
||||
errorBorder: DecoratedInputBorder(
|
||||
child: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
borderSide: const BorderSide(color: AppColor.txtErrorBorderColor),
|
||||
),
|
||||
shadow: const [
|
||||
BoxShadow(
|
||||
color: AppColor.txtBorderShadowColor,
|
||||
blurRadius: 2,
|
||||
offset: Offset(0, 1),
|
||||
spreadRadius: 0.50,
|
||||
)
|
||||
],
|
||||
),
|
||||
disabledBorder: DecoratedInputBorder(
|
||||
child: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
borderSide: const BorderSide(color: AppColor.txtBorderColor),
|
||||
),
|
||||
shadow: const [
|
||||
BoxShadow(
|
||||
color: AppColor.txtBorderShadowColor,
|
||||
blurRadius: 2,
|
||||
offset: Offset(0, 1),
|
||||
spreadRadius: 0.50,
|
||||
)
|
||||
],
|
||||
),
|
||||
enabledBorder: DecoratedInputBorder(
|
||||
child: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
borderSide: const BorderSide(color: AppColor.txtBorderColor),
|
||||
),
|
||||
shadow: const [
|
||||
BoxShadow(
|
||||
color: AppColor.txtBorderShadowColor,
|
||||
blurRadius: 2,
|
||||
offset: Offset(0, 1),
|
||||
spreadRadius: 0.50,
|
||||
)
|
||||
],
|
||||
),
|
||||
focusedBorder: DecoratedInputBorder(
|
||||
child: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
borderSide: const BorderSide(color: AppColor.txtBorderColor),
|
||||
),
|
||||
shadow: const [
|
||||
BoxShadow(
|
||||
color: AppColor.txtBorderShadowColor,
|
||||
blurRadius: 2,
|
||||
offset: Offset(0, 1),
|
||||
spreadRadius: 0.50,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
style: GoogleFonts.dmSans(
|
||||
color: AppColor.charcoalColor,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
keyboardType: texttype,
|
||||
inputFormatters: inputFormatters,
|
||||
onChanged: (value) {
|
||||
onInput?.call(value);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -23,8 +23,25 @@ class TextWidget {
|
||||
color: clr ?? AppColor.plainWhite));
|
||||
}
|
||||
|
||||
Widget tex14W500(
|
||||
String text, {
|
||||
Color? clr,
|
||||
TextDecoration? textDecoration,
|
||||
}) {
|
||||
return Text(text,
|
||||
textAlign: TextAlign.center,
|
||||
style: GoogleFonts.dmSans(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
decoration: textDecoration ?? TextDecoration.none,
|
||||
color: clr ?? AppColor.plainWhite));
|
||||
}
|
||||
|
||||
//Text Size 15
|
||||
Widget tex15W500(String text, {Color? clr}) {
|
||||
Widget tex15W500(
|
||||
String text, {
|
||||
Color? clr,
|
||||
}) {
|
||||
return Text(text,
|
||||
textAlign: TextAlign.center,
|
||||
style: GoogleFonts.dmSans(
|
||||
@@ -33,6 +50,22 @@ class TextWidget {
|
||||
color: clr ?? AppColor.plainWhite));
|
||||
}
|
||||
|
||||
Widget tex15W400(
|
||||
String text, {
|
||||
Color? clr,
|
||||
TextDecoration? textDecoration,
|
||||
TextAlign? txtAlign,
|
||||
}) {
|
||||
return Text(text,
|
||||
textAlign: txtAlign ?? TextAlign.center,
|
||||
style: GoogleFonts.dmSans(
|
||||
decoration: textDecoration ?? TextDecoration.none,
|
||||
decorationColor: clr ?? AppColor.plainWhite,
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: clr ?? AppColor.plainWhite));
|
||||
}
|
||||
|
||||
//Text Size 22
|
||||
Widget tex22W700(String text, {Color? clr}) {
|
||||
return Text(text,
|
||||
@@ -41,4 +74,13 @@ class TextWidget {
|
||||
fontWeight: FontWeight.w700,
|
||||
color: clr ?? AppColor.plainWhite));
|
||||
}
|
||||
|
||||
//Text Size 20
|
||||
Widget tex20W700(String text, {Color? clr}) {
|
||||
return Text(text,
|
||||
style: GoogleFonts.dmSans(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: clr ?? AppColor.plainWhite));
|
||||
}
|
||||
}
|
||||
|
||||
43
lib/shared/components/toast_message.dart
Normal file
@@ -0,0 +1,43 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:toastification/toastification.dart';
|
||||
|
||||
import '../../core/styles/app_color.dart';
|
||||
import 'text_widget.dart';
|
||||
|
||||
ToastificationItem successToastMessage(
|
||||
BuildContext context,
|
||||
String textV,
|
||||
) {
|
||||
return toastification.show(
|
||||
showProgressBar: false,
|
||||
style: ToastificationStyle.minimal,
|
||||
icon: const Icon(Icons.done),
|
||||
type: ToastificationType.success,
|
||||
context: context,
|
||||
title: TextWidget().tex15W400(
|
||||
textV,
|
||||
clr: AppColor.darkGreyColor,
|
||||
txtAlign: TextAlign.start,
|
||||
),
|
||||
autoCloseDuration: const Duration(seconds: 5),
|
||||
);
|
||||
}
|
||||
|
||||
ToastificationItem errorToastMessage(
|
||||
BuildContext context,
|
||||
String textV,
|
||||
) {
|
||||
return toastification.show(
|
||||
showProgressBar: false,
|
||||
style: ToastificationStyle.minimal,
|
||||
icon: const Icon(Icons.error),
|
||||
type: ToastificationType.error,
|
||||
context: context,
|
||||
title: TextWidget().tex15W400(
|
||||
textV,
|
||||
clr: AppColor.darkGreyColor,
|
||||
txtAlign: TextAlign.start,
|
||||
),
|
||||
autoCloseDuration: const Duration(seconds: 5),
|
||||
);
|
||||
}
|
||||
@@ -153,6 +153,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
control_style:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: control_style
|
||||
sha256: "4b27dad0c87a7bce81319676b717c8134996450595b1e5cbb0cebdaa8131492a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.0"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -71,6 +71,9 @@ dependencies:
|
||||
# Json Annotation
|
||||
json_serializable: ^6.8.0
|
||||
|
||||
#Style
|
||||
control_style: ^0.1.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
@@ -85,3 +88,8 @@ flutter:
|
||||
- assets/images/welcome_screen/
|
||||
- assets/images/welcome_screen/svg/
|
||||
- assets/images/welcome_screen/png/
|
||||
- assets/images/auth_screen/
|
||||
- assets/images/auth_screen/svg/
|
||||
- assets/images/auth_screen/png/
|
||||
- assets/images/country_flag/
|
||||
- assets/images/country_flag/svg/
|
||||
|
||||