login screen, common widgets

This commit is contained in:
jayesh
2024-05-28 16:35:33 +05:30
parent 4d4296b107
commit 48444ddf11
36 changed files with 1012 additions and 16 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">
<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

View 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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.0 KiB

View 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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 50 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 110 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@@ -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);
}

View File

@@ -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";
}

View File

@@ -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";
}

View 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();
}
}

View 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 {}

View 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);
}

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

View File

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

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

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

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

View File

@@ -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: [

View File

@@ -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) {

View File

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

View File

@@ -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();

View File

@@ -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));
});
}
}

View File

@@ -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 {}

View File

@@ -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];
}

View File

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

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

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

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

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

View File

@@ -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));
}
}

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

View File

@@ -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:

View File

@@ -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/