From 96de59e9bed0e933a65821b388de0a4f0c650fe0 Mon Sep 17 00:00:00 2001 From: MAGESHWARAN Date: Wed, 15 Apr 2026 12:32:30 +0530 Subject: [PATCH] 2026-04-15 --- assets/images/revise_sound.mpeg | Bin 0 -> 45184 bytes lib/auth/employee_login_screen.dart | 58 +-- lib/auth/employee_otp_screen.dart | 14 +- lib/auth/login_screen.dart | 48 +- lib/auth/otp_screen.dart | 27 +- lib/auth/register_otp_screen.dart | 27 +- lib/auth/signup_screen.dart | 29 +- lib/consts/app_style.dart | 47 ++ lib/consts/comman_button.dart | 5 +- lib/consts/comman_popup.dart | 21 +- lib/consts/comman_serivce.dart | 5 +- lib/consts/comman_textformfileds.dart | 15 +- lib/consts/download_helper.dart | 32 ++ lib/consts/image_permission.dart | 6 +- lib/consts/local_store.dart | 4 +- lib/consts/notification_webscoket.dart | 10 + lib/controller/api_contoller.dart | 76 ++- lib/controller/api_repository.dart | 49 +- lib/firebase_options.dart | 2 +- lib/main.dart | 5 + lib/model/detail_model.dart | 12 +- lib/model/service_list_history_model.dart | 42 ++ lib/services/notification_service.dart | 94 +++- lib/view/Mahi_chat/chat_profile_screen.dart | 6 +- lib/view/Mahi_chat/comman_input_button.dart | 107 ++-- lib/view/Mahi_chat/downloadchat.dart | 2 +- lib/view/Mahi_chat/file_images_screen.dart | 7 +- lib/view/Mahi_chat/live_chat_screen.dart | 20 +- lib/view/Main_controller/comman_chat_box.dart | 10 +- lib/view/Main_controller/main_controller.dart | 332 +++--------- .../history/completed_live_chat_screen.dart | 20 +- lib/view/screens/history/detail_screen.dart | 316 +++++------- lib/view/screens/history/flitter_popup.dart | 2 +- lib/view/screens/history/pending_screen.dart | 475 +++++++++++------- .../history/serivces_status_screen.dart | 29 +- lib/view/screens/home_screen.dart | 220 ++++---- lib/view/screens/list_service_screen.dart | 10 +- lib/view/screens/notification_screen.dart | 15 +- .../employee_profile_list.dart | 8 +- .../employee_profile_screen.dart | 18 +- .../screens/profile/kyc_details_list.dart | 23 +- lib/view/screens/profile/profile_screen.dart | 43 +- .../screens/profile/staff_list_screen.dart | 4 +- lib/view/screens/serivce_request_screen.dart | 72 +-- pubspec.lock | 52 +- pubspec.yaml | 71 ++- 46 files changed, 1299 insertions(+), 1191 deletions(-) create mode 100644 assets/images/revise_sound.mpeg create mode 100644 lib/consts/app_style.dart diff --git a/assets/images/revise_sound.mpeg b/assets/images/revise_sound.mpeg new file mode 100644 index 0000000000000000000000000000000000000000..b04eec1b9512850768365e47cce0ed295a0ae2b0 GIT binary patch literal 45184 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 Up { final spacingMD = r.spacing(mobile: 20, tablet: 20, desktop: 24); final spacingLG = r.spacing(mobile: 20, tablet: 22, desktop: 28); - return Scaffold( + return PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, result) { + if (didPop) return; + Get.offAllNamed(ConstRouters.login); + }, + child: Scaffold( resizeToAvoidBottomInset: true, body: Container( width: double.infinity, @@ -141,10 +148,8 @@ class _EmployeeLoginScreenState extends ConsumerState { Text( "Login", textAlign: TextAlign.center, - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', + style: AppTextStyles.bold.copyWith( fontSize: titleFontSize, - fontWeight: FontWeight.w600, height: 1.3, letterSpacing: 0.01 * titleFontSize, color: AppColors.authheading, @@ -156,10 +161,8 @@ class _EmployeeLoginScreenState extends ConsumerState { Text( "Enter your registered Mobile Number", textAlign: TextAlign.center, - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', + style: AppTextStyles.bold.copyWith( fontSize: subtitleFontSize, - fontWeight: FontWeight.w600, height: 1.4, letterSpacing: 0.03 * subtitleFontSize, color: AppColors.authleading, @@ -180,9 +183,7 @@ class _EmployeeLoginScreenState extends ConsumerState { Text.rich( TextSpan( text: "By signing up, you agree to the ", - style: TextStyle( - fontFamily: 'Gilroy-Medium', - fontWeight: FontWeight.w500, + style: AppTextStyles.medium.copyWith( fontSize: termsFontSize, height: 1.8, letterSpacing: 0.01, @@ -191,9 +192,7 @@ class _EmployeeLoginScreenState extends ConsumerState { children: [ TextSpan( text: "Terms of Service ", - style: TextStyle( - fontFamily: 'Gilroy-Bold', - fontWeight: FontWeight.w700, + style: AppTextStyles.bold.copyWith( fontSize: termsFontSize, height: 1.8, letterSpacing: 0.01, @@ -202,20 +201,16 @@ class _EmployeeLoginScreenState extends ConsumerState { ), TextSpan( text: "and ", - style: TextStyle( - fontFamily: 'Gilroy-Medium', - fontWeight: FontWeight.w500, - fontSize: termsFontSize, - height: 1.8, - letterSpacing: 0.01, - color: AppColors.authleading, - ), + style: AppTextStyles.medium.copyWith( + fontSize: termsFontSize, + height: 1.8, + letterSpacing: 0.01, + color: AppColors.authleading, + ), ), TextSpan( text: "Data Processing Agreement", - style: TextStyle( - fontFamily: 'Gilroy-Bold', - fontWeight: FontWeight.w700, + style: AppTextStyles.bold.copyWith( fontSize: termsFontSize, height: 1.8, letterSpacing: 0.01, @@ -242,13 +237,11 @@ class _EmployeeLoginScreenState extends ConsumerState { Text( "Other Login", textAlign: TextAlign.center, - style: TextStyle( - fontFamily: 'Inter', - fontWeight: FontWeight.w500, + style: AppTextStyles.semiBold.copyWith( fontSize: otherLoginFontSize, height: 1.8, letterSpacing: 0.13, - color: const Color(0xFF6C7278), + color: AppColors.authleading, ), ), @@ -257,14 +250,16 @@ class _EmployeeLoginScreenState extends ConsumerState { Text.rich( TextSpan( text: "User Login? ", - style: TextStyle(fontSize: linkFontSize), + style: AppTextStyles.bold.copyWith( + fontSize: linkFontSize, + color: AppColors.black, + ), children: [ TextSpan( text: "Click Here", - style: TextStyle( + style: AppTextStyles.bold.copyWith( fontSize: linkFontSize, color: AppColors.authsignup, - fontWeight: FontWeight.bold, ), recognizer: TapGestureRecognizer() ..onTap = () { @@ -282,6 +277,7 @@ class _EmployeeLoginScreenState extends ConsumerState { ), ), ), + ), ); } } diff --git a/lib/auth/employee_otp_screen.dart b/lib/auth/employee_otp_screen.dart index 1acc4cc..f7cd6b5 100644 --- a/lib/auth/employee_otp_screen.dart +++ b/lib/auth/employee_otp_screen.dart @@ -247,7 +247,7 @@ class _EmployeeOtpScreenState extends ConsumerState { "Enter OTP", textAlign: TextAlign.center, style: TextStyle( - fontFamily: 'Gilroy-SemiBold', + fontFamily: "Gilroy", fontSize: titleFontSize, fontWeight: FontWeight.w600, height: 1.3, @@ -262,7 +262,7 @@ class _EmployeeOtpScreenState extends ConsumerState { "OTP has been sent to your registered mobile number", textAlign: TextAlign.center, style: TextStyle( - fontFamily: 'Gilroy-SemiBold', + fontFamily: "Gilroy", fontSize: subtitleFontSize, fontWeight: FontWeight.w600, height: 1.4, @@ -277,7 +277,7 @@ class _EmployeeOtpScreenState extends ConsumerState { TextSpan( text: mobile, style: TextStyle( - fontFamily: 'Gilroy-Medium', + fontFamily: "Gilroy", fontWeight: FontWeight.w500, fontSize: mobileFontSize, height: 1.8, @@ -288,7 +288,7 @@ class _EmployeeOtpScreenState extends ConsumerState { TextSpan( text: " ( Change Number )", style: TextStyle( - fontFamily: 'Gilroy-ExtraBold', + fontFamily: "Gilroy", fontWeight: FontWeight.w800, fontSize: mobileFontSize, height: 1.8, @@ -347,7 +347,7 @@ class _EmployeeOtpScreenState extends ConsumerState { maxLength: 1, enabled: !isLoading, style: TextStyle( - fontFamily: 'Gilroy-Medium', + fontFamily: "Gilroy", fontWeight: FontWeight.w400, fontSize: otpFontSize, letterSpacing: 0.03 * otpFontSize, @@ -375,7 +375,7 @@ class _EmployeeOtpScreenState extends ConsumerState { "Resend", textAlign: TextAlign.center, style: TextStyle( - fontFamily: 'Gilroy-Medium', + fontFamily: "Gilroy", fontWeight: FontWeight.w500, fontSize: resendFontSize, height: 1.4, @@ -393,7 +393,7 @@ class _EmployeeOtpScreenState extends ConsumerState { Text( _formatTime(_remainingSeconds), style: TextStyle( - fontFamily: 'Gilroy-Medium', + fontFamily: "Gilroy", fontWeight: FontWeight.w600, fontSize: timerFontSize, color: _remainingSeconds > 0 diff --git a/lib/auth/login_screen.dart b/lib/auth/login_screen.dart index ce980f6..873f28f 100644 --- a/lib/auth/login_screen.dart +++ b/lib/auth/login_screen.dart @@ -12,6 +12,7 @@ import 'package:taxglide/consts/responsive_helper.dart'; import 'package:taxglide/consts/validation_popup.dart'; import 'package:taxglide/controller/api_contoller.dart'; import 'package:taxglide/router/consts_routers.dart'; +import 'package:taxglide/consts/app_style.dart'; class LoginScreen extends ConsumerStatefulWidget { const LoginScreen({super.key}); @@ -102,7 +103,12 @@ class _LoginScreenState extends ConsumerState { final spacingMD = r.spacing(mobile: 20, tablet: 22, desktop: 26); final spacingLG = r.spacing(mobile: 20, tablet: 22, desktop: 28); - return Scaffold( + return PopScope( + canPop: true, + onPopInvokedWithResult: (didPop, result) { + // Handle custom behavior here if needed in the future + }, + child: Scaffold( resizeToAvoidBottomInset: true, body: Container( width: double.infinity, @@ -141,10 +147,8 @@ class _LoginScreenState extends ConsumerState { // Title Text( "Login", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', + style: AppTextStyles.semiBold.copyWith( fontSize: titleFontSize, - fontWeight: FontWeight.w600, color: AppColors.authheading, ), ), @@ -154,8 +158,7 @@ class _LoginScreenState extends ConsumerState { Text( "Enter your Mobile Number", textAlign: TextAlign.center, - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', + style: AppTextStyles.semiBold.copyWith( fontSize: subtitleFontSize, color: AppColors.authleading, ), @@ -175,15 +178,14 @@ class _LoginScreenState extends ConsumerState { Text.rich( TextSpan( text: "By signing up, you agree to the ", - style: TextStyle( + style: AppTextStyles.medium.copyWith( fontSize: termsFontSize, color: AppColors.authleading, ), children: [ TextSpan( text: "Terms of Service ", - style: TextStyle( - fontWeight: FontWeight.w700, + style: AppTextStyles.bold.copyWith( fontSize: termsFontSize, color: AppColors.authtermsandcondition, ), @@ -259,21 +261,26 @@ class _LoginScreenState extends ConsumerState { ), SizedBox(height: spacingSM), - const Text("Or"), + Text("Or",style: AppTextStyles.medium.copyWith( + fontSize: spacingSM, + color: AppColors.authleading, + ),), SizedBox(height: spacingSM), // Sign up Text.rich( TextSpan( text: "Didn't Have account? ", - style: TextStyle(fontSize: signupFontSize), + style: AppTextStyles.bold.copyWith( + fontSize: signupFontSize, + color: AppColors.black, + ), children: [ TextSpan( text: "Sign Up", - style: TextStyle( + style: AppTextStyles.bold.copyWith( fontSize: signupFontSize, color: AppColors.authsignup, - fontWeight: FontWeight.bold, ), recognizer: TapGestureRecognizer() ..onTap = () { @@ -298,13 +305,11 @@ class _LoginScreenState extends ConsumerState { Text( "Other Login", textAlign: TextAlign.center, - style: TextStyle( - fontFamily: 'Inter', - fontWeight: FontWeight.w500, + style: AppTextStyles.semiBold.copyWith( fontSize: otherLoginFontSize, height: 1.8, letterSpacing: 0.13, - color: const Color(0xFF6C7278), + color: AppColors.authleading, ), ), @@ -313,14 +318,16 @@ class _LoginScreenState extends ConsumerState { Text.rich( TextSpan( text: "Staff Login? ", - style: TextStyle(fontSize: signupFontSize), + style: AppTextStyles.bold.copyWith( + fontSize: signupFontSize, + color: AppColors.black, + ), children: [ TextSpan( text: "Click Here", - style: TextStyle( + style: AppTextStyles.bold.copyWith( fontSize: signupFontSize, color: AppColors.authsignup, - fontWeight: FontWeight.bold, ), recognizer: TapGestureRecognizer() ..onTap = () { @@ -338,6 +345,7 @@ class _LoginScreenState extends ConsumerState { ), ), ), + ), ); } } diff --git a/lib/auth/otp_screen.dart b/lib/auth/otp_screen.dart index 8ec0412..dab58ff 100644 --- a/lib/auth/otp_screen.dart +++ b/lib/auth/otp_screen.dart @@ -13,6 +13,7 @@ import 'package:taxglide/controller/api_contoller.dart'; import 'package:taxglide/router/consts_routers.dart'; import 'package:taxglide/view/Main_controller/main_controller.dart'; +import 'package:taxglide/consts/app_style.dart'; class OtpScreen extends ConsumerStatefulWidget { const OtpScreen({super.key}); @@ -248,10 +249,8 @@ class _OtpScreenState extends ConsumerState { Text( "Enter OTP", textAlign: TextAlign.center, - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', + style: AppTextStyles.semiBold.copyWith( fontSize: titleFontSize, - fontWeight: FontWeight.w600, height: 1.3, letterSpacing: 0.01 * titleFontSize, color: AppColors.authheading, @@ -263,10 +262,8 @@ class _OtpScreenState extends ConsumerState { Text( "OTP has been sent to your registered mobile number", textAlign: TextAlign.center, - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', + style: AppTextStyles.semiBold.copyWith( fontSize: subtitleFontSize, - fontWeight: FontWeight.w600, height: 1.4, letterSpacing: 0.03 * subtitleFontSize, color: AppColors.authleading, @@ -278,9 +275,7 @@ class _OtpScreenState extends ConsumerState { Text.rich( TextSpan( text: mobile, - style: TextStyle( - fontFamily: 'Gilroy-Medium', - fontWeight: FontWeight.w500, + style: AppTextStyles.medium.copyWith( fontSize: mobileFontSize, height: 1.8, letterSpacing: 0.01, @@ -289,9 +284,7 @@ class _OtpScreenState extends ConsumerState { children: [ TextSpan( text: " ( Change Number )", - style: TextStyle( - fontFamily: 'Gilroy-ExtraBold', - fontWeight: FontWeight.w800, + style: AppTextStyles.extraBold.copyWith( fontSize: mobileFontSize, height: 1.8, letterSpacing: 0.04, @@ -349,7 +342,7 @@ class _OtpScreenState extends ConsumerState { maxLength: 1, enabled: !isLoading, style: TextStyle( - fontFamily: 'Gilroy-Medium', + fontFamily: "Gilroy", fontWeight: FontWeight.w400, fontSize: otpFontSize, letterSpacing: 0.03 * otpFontSize, @@ -376,9 +369,7 @@ class _OtpScreenState extends ConsumerState { child: Text( "Resend", textAlign: TextAlign.center, - style: TextStyle( - fontFamily: 'Gilroy-Medium', - fontWeight: FontWeight.w500, + style: AppTextStyles.medium.copyWith( fontSize: resendFontSize, height: 1.4, letterSpacing: 0.02 * resendFontSize, @@ -394,9 +385,7 @@ class _OtpScreenState extends ConsumerState { // Timer Text( _formatTime(_remainingSeconds), - style: TextStyle( - fontFamily: 'Gilroy-Medium', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: timerFontSize, color: _remainingSeconds > 0 ? AppColors.authheading diff --git a/lib/auth/register_otp_screen.dart b/lib/auth/register_otp_screen.dart index 91fb48c..82ca785 100644 --- a/lib/auth/register_otp_screen.dart +++ b/lib/auth/register_otp_screen.dart @@ -11,6 +11,7 @@ import 'package:taxglide/consts/validation_popup.dart'; import 'package:taxglide/controller/api_contoller.dart'; import 'package:taxglide/router/consts_routers.dart'; import 'package:taxglide/view/Main_controller/main_controller.dart'; +import 'package:taxglide/consts/app_style.dart'; class RegisterOtpScreen extends ConsumerStatefulWidget { const RegisterOtpScreen({super.key}); @@ -247,10 +248,8 @@ class _RegisterOtpScreenState extends ConsumerState { Text( "Enter OTP", textAlign: TextAlign.center, - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', + style: AppTextStyles.semiBold.copyWith( fontSize: 32, - fontWeight: FontWeight.w600, height: 1.3, letterSpacing: 0.01 * 32, color: AppColors.authheading, @@ -260,10 +259,8 @@ class _RegisterOtpScreenState extends ConsumerState { Text( "OTP has been sent to your registered mobile number", textAlign: TextAlign.center, - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', + style: AppTextStyles.semiBold.copyWith( fontSize: 14, - fontWeight: FontWeight.w600, height: 1.4, letterSpacing: 0.03 * 14, color: AppColors.authleading, @@ -273,9 +270,7 @@ class _RegisterOtpScreenState extends ConsumerState { Text.rich( TextSpan( text: mobile, - style: const TextStyle( - fontFamily: 'Gilroy-Medium', - fontWeight: FontWeight.w500, + style: AppTextStyles.medium.copyWith( fontSize: 13, height: 1.8, letterSpacing: 0.01, @@ -284,9 +279,7 @@ class _RegisterOtpScreenState extends ConsumerState { children: [ TextSpan( text: " ( Change Number )", - style: const TextStyle( - fontFamily: 'Gilroy-ExtraBold', - fontWeight: FontWeight.w800, + style: AppTextStyles.extraBold.copyWith( fontSize: 13, height: 1.8, letterSpacing: 0.04, @@ -346,7 +339,7 @@ class _RegisterOtpScreenState extends ConsumerState { maxLength: 1, enabled: !isLoading, style: TextStyle( - fontFamily: 'Gilroy-Medium', + fontFamily: "Gilroy", fontWeight: FontWeight.w400, fontSize: 28, letterSpacing: 0.03 * 28, @@ -373,9 +366,7 @@ class _RegisterOtpScreenState extends ConsumerState { child: Text( "Resend", textAlign: TextAlign.center, - style: TextStyle( - fontFamily: 'Gilroy-Medium', - fontWeight: FontWeight.w500, + style: AppTextStyles.medium.copyWith( fontSize: 14, height: 1.4, letterSpacing: 0.02 * 14, @@ -391,9 +382,7 @@ class _RegisterOtpScreenState extends ConsumerState { // Timer Display Text( _formatTime(_remainingSeconds), - style: TextStyle( - fontFamily: 'Gilroy-Medium', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: 16, color: _remainingSeconds > 0 ? AppColors.authheading diff --git a/lib/auth/signup_screen.dart b/lib/auth/signup_screen.dart index ec2b5b8..05eab84 100644 --- a/lib/auth/signup_screen.dart +++ b/lib/auth/signup_screen.dart @@ -9,6 +9,7 @@ import 'package:taxglide/consts/comman_textformfileds.dart'; import 'package:taxglide/consts/validation_popup.dart'; import 'package:taxglide/controller/api_contoller.dart'; import 'package:taxglide/router/consts_routers.dart'; +import 'package:taxglide/consts/app_style.dart'; class SignupScreen extends ConsumerStatefulWidget { const SignupScreen({super.key}); @@ -89,7 +90,13 @@ class _SignupScreenState extends ConsumerState { ); }); - return Scaffold( + return PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, result) { + if (didPop) return; + Get.offAllNamed(ConstRouters.login); + }, + child: Scaffold( body: Stack( children: [ // 🌈 Background gradient @@ -131,10 +138,8 @@ class _SignupScreenState extends ConsumerState { Text( "Sign Up", textAlign: TextAlign.center, - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', + style: AppTextStyles.semiBold.copyWith( fontSize: 32, - fontWeight: FontWeight.w600, height: 1.3, letterSpacing: 0.01 * 32, color: AppColors.authheading, @@ -148,10 +153,9 @@ class _SignupScreenState extends ConsumerState { alignment: Alignment.centerLeft, child: Text( "Name*", - style: TextStyle( - fontFamily: 'Gilroy-Medium', + style: AppTextStyles.semiBold.copyWith( fontSize: 14, - color: AppColors.authleading, + color: AppColors.authheading, ), ), ), @@ -177,10 +181,9 @@ class _SignupScreenState extends ConsumerState { alignment: Alignment.centerLeft, child: Text( "Contact Number *", - style: TextStyle( - fontFamily: 'Gilroy-Medium', + style: AppTextStyles.semiBold.copyWith( fontSize: 14, - color: AppColors.authleading, + color: AppColors.authheading, ), ), ), @@ -206,10 +209,9 @@ class _SignupScreenState extends ConsumerState { alignment: Alignment.centerLeft, child: Text( "Email *", - style: TextStyle( - fontFamily: 'Gilroy-Medium', + style: AppTextStyles.semiBold.copyWith( fontSize: 14, - color: AppColors.authleading, + color: AppColors.authheading, ), ), ), @@ -247,6 +249,7 @@ class _SignupScreenState extends ConsumerState { ), ], ), + ), ); } diff --git a/lib/consts/app_style.dart b/lib/consts/app_style.dart new file mode 100644 index 0000000..8ffe7fa --- /dev/null +++ b/lib/consts/app_style.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; + +class AppTextStyles { + static const TextStyle thin = TextStyle( + fontFamily: 'Gilroy-Thin', + ); + + static const TextStyle ultraLight = TextStyle( + fontFamily: 'Gilroy-UltraLight', + ); + static const TextStyle ultralight = ultraLight; + + static const TextStyle light = TextStyle( + fontFamily: 'Gilroy-Light', + ); + + static const TextStyle regular = TextStyle( + fontFamily: 'Gilroy-Regular', + ); + + static const TextStyle medium = TextStyle( + fontFamily: 'Gilroy-Medium', + ); + + static const TextStyle semiBold = TextStyle( + fontFamily: 'Gilroy-SemiBold', + ); + static const TextStyle semibold = semiBold; + static const TextStyle semi_bold = semiBold; + + static const TextStyle bold = TextStyle( + fontFamily: 'Gilroy-Bold', + ); + + static const TextStyle extraBold = TextStyle( + fontFamily: 'Gilroy-ExtraBold', + ); + static const TextStyle extrabold = extraBold; + + static const TextStyle black = TextStyle( + fontFamily: 'Gilroy-Black', + ); + + static const TextStyle heavy = TextStyle( + fontFamily: 'Gilroy-Heavy', + ); +} diff --git a/lib/consts/comman_button.dart b/lib/consts/comman_button.dart index da96dd8..ad727e2 100644 --- a/lib/consts/comman_button.dart +++ b/lib/consts/comman_button.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:taxglide/consts/app_colors.dart'; +import 'package:taxglide/consts/app_style.dart'; class CommanButton extends StatelessWidget { final String text; @@ -51,9 +52,7 @@ class CommanButton extends StatelessWidget { ], Text( text, - style: const TextStyle( - fontFamily: 'Gilroy-Medium', - fontWeight: FontWeight.w500, + style: AppTextStyles.semiBold.copyWith( fontSize: 16, height: 1.4, letterSpacing: 0.03 * 16, diff --git a/lib/consts/comman_popup.dart b/lib/consts/comman_popup.dart index 5d867fe..75f4359 100644 --- a/lib/consts/comman_popup.dart +++ b/lib/consts/comman_popup.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:taxglide/consts/app_style.dart'; class CommonInfoPopup { /// 🔹 Show a reusable popup dialog @@ -22,10 +23,8 @@ class CommonInfoPopup { contentPadding: const EdgeInsets.fromLTRB(20, 0, 20, 10), title: Text( title, - style: const TextStyle( - fontFamily: 'Gilroy-Bold', + style: AppTextStyles.bold.copyWith( fontSize: 18, - fontWeight: FontWeight.w700, color: Colors.black87, ), ), @@ -48,8 +47,7 @@ class CommonInfoPopup { child: Text( content, textAlign: TextAlign.justify, - style: const TextStyle( - fontFamily: 'Gilroy-Medium', + style: AppTextStyles.regular.copyWith( fontSize: 14, height: 1.6, color: Colors.black54, @@ -63,15 +61,14 @@ class CommonInfoPopup { actions: [ TextButton( onPressed: () => Navigator.of(ctx).pop(), - child: const Text( - "Close", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontSize: 14, - color: Color(0xFF6A4BFC), - ), + child: Text( + "Close", + style: AppTextStyles.regular.copyWith( + fontSize: 14, + color: const Color(0xFF6A4BFC), ), ), + ), ], ); }, diff --git a/lib/consts/comman_serivce.dart b/lib/consts/comman_serivce.dart index ec976f4..8239604 100644 --- a/lib/consts/comman_serivce.dart +++ b/lib/consts/comman_serivce.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:taxglide/consts/app_style.dart'; import 'package:get/get.dart'; import 'package:taxglide/consts/app_colors.dart'; import 'package:taxglide/model/serivce_list_model.dart'; @@ -67,9 +68,7 @@ class CommonServiceItem extends StatelessWidget { textAlign: TextAlign.center, maxLines: 2, overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: 13, height: 1.3, letterSpacing: 0.02, diff --git a/lib/consts/comman_textformfileds.dart b/lib/consts/comman_textformfileds.dart index 03a14b4..ff69030 100644 --- a/lib/consts/comman_textformfileds.dart +++ b/lib/consts/comman_textformfileds.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:taxglide/consts/app_colors.dart'; +import 'package:taxglide/consts/app_style.dart'; import 'package:flutter/services.dart'; class CommanTextFormField extends StatelessWidget { @@ -69,15 +70,13 @@ class CommanTextFormField extends StatelessWidget { prefixIcon: prefixIcon != null ? Icon(prefixIcon, color: AppColors.authleading) : null, - hintStyle: TextStyle( - fontFamily: 'Gilroy-Medium', - fontWeight: FontWeight.w400, + hintStyle: AppTextStyles.medium.copyWith( fontSize: 11.31, height: 1.4, letterSpacing: 0.03, color: hasError ? Colors.red.withOpacity(0.6) - : Colors.grey.shade500, + : AppColors.authleading ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(6), @@ -85,7 +84,7 @@ class CommanTextFormField extends StatelessWidget { color: hasError ? Colors.red : readOnly - ? Colors.grey.shade300 + ? AppColors.authleading : const Color(0xFFDFDFDF), width: 1, ), @@ -113,13 +112,11 @@ class CommanTextFormField extends StatelessWidget { ), ), ), - style: TextStyle( - fontFamily: 'Gilroy-Medium', - fontWeight: FontWeight.w500, + style: AppTextStyles.medium.copyWith( fontSize: 11.31, height: 1.4, letterSpacing: 0.03, - color: readOnly ? Colors.grey.shade600 : AppColors.authleading, + color: readOnly ? AppColors.authleading : AppColors.authleading, ), ), ); diff --git a/lib/consts/download_helper.dart b/lib/consts/download_helper.dart index c9590ce..c515d9d 100644 --- a/lib/consts/download_helper.dart +++ b/lib/consts/download_helper.dart @@ -1,10 +1,13 @@ import 'dart:io'; import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:open_filex/open_filex.dart'; import 'package:gal/gal.dart'; +import 'package:taxglide/services/notification_service.dart'; class DownloadHelper { static const String API_BASE_URL = "https://www.taxglide.amrithaa.net/api/"; @@ -254,6 +257,13 @@ class DownloadHelper { } print("✅ File downloaded to: $savePath"); + + // ⭐ Show Download Notification + try { + Get.find().showDownloadNotification(fileName, savePath); + } catch (e) { + debugPrint('⚠️ Could not show download notification: $e'); + } // Open the downloaded file await openDownloadedFile(savePath); @@ -349,6 +359,28 @@ class DownloadHelper { } } + /// ⭐ NEW: Open folder in system file manager + static Future openFolder(String path) async { + print("📂 Opening folder: $path"); + + try { + final dir = Directory(path); + if (!await dir.exists()) { + print("❌ Folder not found at $path"); + return; + } + + // On Android, most file managers respond well to OpenFilex on a directory + final result = await OpenFilex.open(path); + + if (result.type != ResultType.done) { + print("⚠️ Could not open folder: ${result.message}"); + } + } catch (e) { + print("❌ Error opening folder: $e"); + } + } + /// Get all files from Send folder static Future> getSentFiles() async { try { diff --git a/lib/consts/image_permission.dart b/lib/consts/image_permission.dart index 241909d..80a7b01 100644 --- a/lib/consts/image_permission.dart +++ b/lib/consts/image_permission.dart @@ -318,7 +318,7 @@ class LabeledTextField extends StatelessWidget { Text( label, style: const TextStyle( - fontFamily: 'Gilroy-SemiBold', + fontFamily: "Gilroy", fontWeight: FontWeight.w600, fontSize: 16, height: 1.3, @@ -391,7 +391,7 @@ class SingleImageSectionField extends StatelessWidget { Text( title, style: const TextStyle( - fontFamily: 'Gilroy-SemiBold', + fontFamily: "Gilroy", fontWeight: FontWeight.w600, fontSize: 16, height: 1.3, @@ -527,7 +527,7 @@ class FileUploadSectionField extends StatelessWidget { Text( title, style: const TextStyle( - fontFamily: 'Gilroy-SemiBold', + fontFamily: "Gilroy", fontWeight: FontWeight.w600, fontSize: 16, height: 1.3, diff --git a/lib/consts/local_store.dart b/lib/consts/local_store.dart index 9b3babf..0d1aba6 100644 --- a/lib/consts/local_store.dart +++ b/lib/consts/local_store.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; class LocalStore { @@ -10,7 +11,8 @@ class LocalStore { Future saveLoginData(Map data) async { final token = data['access_token'] ?? ''; final role = data['role'] ?? ''; - +debugPrint('token: $token'); +debugPrint('role: $role'); // Keep existing FCM token (do not overwrite) String? existingFcmToken = await _storage.read(key: 'fcm_token'); diff --git a/lib/consts/notification_webscoket.dart b/lib/consts/notification_webscoket.dart index 23c8002..06ee431 100644 --- a/lib/consts/notification_webscoket.dart +++ b/lib/consts/notification_webscoket.dart @@ -55,6 +55,16 @@ class NotificationWebSocket { _subscribeToChatChannel(chatId); } + /// Manually trigger a refresh for all providers watching `notificationTriggerProvider`. + void triggerRefresh() { + try { + _container?.read(notificationTriggerProvider.notifier).update((state) => state + 1); + debugPrint('🔔 NotificationWS: manual refresh triggered'); + } catch (e) { + debugPrint('⚠️ NotificationWS: triggerRefresh failed: $e'); + } + } + /// Connect (or skip if already connected for the same user). Future connect({ required String userId, diff --git a/lib/controller/api_contoller.dart b/lib/controller/api_contoller.dart index c2c702d..5526cd3 100644 --- a/lib/controller/api_contoller.dart +++ b/lib/controller/api_contoller.dart @@ -185,10 +185,14 @@ class ServiceHistoryNotifier fetchServiceHistory(); } - Future fetchServiceHistory() async { + Future fetchServiceHistory({bool isSilent = false}) async { try { if (!mounted) return; - state = const AsyncValue.loading(); + + // 🔄 Skip loading state for silent refresh + if (!isSilent) { + state = const AsyncValue.loading(); + } final repo = ref.read(apiRepositoryProvider); @@ -238,8 +242,6 @@ final notificationTriggerProvider = StateProvider((ref) => 0); // 🔥 fetch notification count API final notificationCountProvider = FutureProvider.autoDispose((ref) async { - ref.watch(notificationTriggerProvider); // listen for refresh trigger - final repo = ref.read(apiRepositoryProvider); return await repo.fetchNotificationCount(); }); @@ -409,34 +411,55 @@ class ChatMessagesNotifier return dateA.compareTo(dateB); }); - // ⭐ Check if we have any new messages - final lastMessageId = _messages.isNotEmpty ? _messages.last.id : -1; - final latestMessageId = result.last.id; + // ⭐ Merge strategy: + // 1. Update existing messages (read/delivered status) + // 2. Add new messages + bool hasChanges = false; - if (latestMessageId != lastMessageId) { - // ⭐ New messages found - merge them smoothly - print("✅ Found new messages, updating list"); + for (var incomingMsg in result) { + final existingIndex = _messages.indexWhere((m) => m.id == incomingMsg.id); + + if (existingIndex != -1) { + // Check if status changed + final existingMsg = _messages[existingIndex]; + if (existingMsg.isRead != incomingMsg.isRead || + existingMsg.isDelivered != incomingMsg.isDelivered) { + print("♻️ Updating status for message ID ${incomingMsg.id}"); + _messages[existingIndex] = incomingMsg; + hasChanges = true; + } + } else { + // ⭐ NEW: Check if this server message replaces an optimistic one + final optimisticIndex = _messages.indexWhere( + (m) => (m.id > 1000000000000 && + m.message == incomingMsg.message && + m.chatBy == incomingMsg.chatBy) + ); - // Add only new messages that don't exist in current list - for (var newMsg in result) { - final exists = _messages.any((m) => m.id == newMsg.id); - if (!exists) { - _messages.add(newMsg); + if (optimisticIndex != -1) { + print("🔄 refresh: Replacing optimistic message with ID ${incomingMsg.id}"); + _messages[optimisticIndex] = incomingMsg; + hasChanges = true; + } else { + // Truly new message (e.g. incoming from someone else) + print("➕ Adding new message ID ${incomingMsg.id}"); + _messages.add(incomingMsg); + hasChanges = true; } } + } - // Sort the entire list + if (hasChanges) { + // Sort the entire list to be sure _messages.sort((a, b) { - final dateA = - DateTime.tryParse(a.createdAt ?? '') ?? DateTime(1970); - final dateB = - DateTime.tryParse(b.createdAt ?? '') ?? DateTime(1970); + final dateA = DateTime.tryParse(a.createdAt ?? '') ?? DateTime(1970); + final dateB = DateTime.tryParse(b.createdAt ?? '') ?? DateTime(1970); return dateA.compareTo(dateB); }); - + if (mounted) state = AsyncData(List.from(_messages)); } else { - print("✅ No new messages"); + print("✅ No new messages or status changes"); } } } catch (e) { @@ -472,10 +495,8 @@ class ChatMessagesNotifier // 2. Check if this is a server confirmation of an optimistic message // Optimistic messages have large positive IDs from DateTime.now().millisecondsSinceEpoch - // OR content match for very recent messages final optimisticIndex = _messages.indexWhere( (m) => - // Match by content and sender if it's a very recent "local" message (m.id > 1000000000000 && m.message == msg.message && m.chatBy == msg.chatBy), @@ -489,6 +510,13 @@ class ChatMessagesNotifier _messages.add(msg); } + // Sort to ensure order (important if multiple local messages are sent) + _messages.sort((a, b) { + final dateA = DateTime.tryParse(a.createdAt ?? '') ?? DateTime(1970); + final dateB = DateTime.tryParse(b.createdAt ?? '') ?? DateTime(1970); + return dateA.compareTo(dateB); + }); + if (mounted) state = AsyncData(List.from(_messages)); } diff --git a/lib/controller/api_repository.dart b/lib/controller/api_repository.dart index 2ff9546..370c32d 100644 --- a/lib/controller/api_repository.dart +++ b/lib/controller/api_repository.dart @@ -458,7 +458,7 @@ class ApiRepository { headers: await _authorizedHeaders(), ); }); - + debugPrint("response: ${response.body}"); if (response.statusCode == 200) { final data = jsonDecode(response.body); return DetailModel.fromJson(data); @@ -635,17 +635,17 @@ class ApiRepository { File? panFile, File? gstFile, File? incorporationFile, - required int countryId, - required int stateId, - required int cityId, - required String companyPincode, - required String companyAddress, - required String panNumber, - required String gstNumber, - required String tanNumber, - required String cinNumber, - required String yearOfIncorporation, - required String address, + int? countryId, + int? stateId, + int? cityId, + String? companyPincode, + String? companyAddress, + String? panNumber, + String? gstNumber, + String? tanNumber, + String? cinNumber, + String? yearOfIncorporation, + String? address, }) async { try { final token = await _localStore.getToken(); @@ -658,17 +658,17 @@ class ApiRepository { 'Connection': 'keep-alive', }) ..fields.addAll({ - 'country_id': countryId.toString(), - 'state_id': stateId.toString(), - 'district_id': cityId.toString(), - 'company_pincode': companyPincode, - 'company_address': companyAddress, - 'pan_number': panNumber, - 'gst_number': gstNumber, - 'tan_number': tanNumber, - 'cin': cinNumber, - 'year_of_incorporation': yearOfIncorporation, - 'address': address, + if (countryId != null) 'country_id': countryId.toString(), + if (stateId != null) 'state_id': stateId.toString(), + if (cityId != null) 'district_id': cityId.toString(), + if (companyPincode != null) 'company_pincode': companyPincode, + if (companyAddress != null) 'company_address': companyAddress, + if (panNumber != null) 'pan_number': panNumber, + if (gstNumber != null) 'gst_number': gstNumber, + if (tanNumber != null) 'tan_number': tanNumber, + if (cinNumber != null) 'cin': cinNumber, + if (yearOfIncorporation != null) 'year_of_incorporation': yearOfIncorporation, + if (address != null) 'address': address, }); await Future.wait([ @@ -686,7 +686,8 @@ class ApiRepository { ); final response = await http.Response.fromStream(streamedResponse); - +debugPrint('📦 KYC Response Status: ${response.statusCode}'); +debugPrint('📦 KYC Response Body: ${response.body}'); // Handle token expiry if (response.statusCode == 401 || response.statusCode == 403) { await _handleUnauthorized(); diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart index c39f6b8..25de6ca 100644 --- a/lib/firebase_options.dart +++ b/lib/firebase_options.dart @@ -85,4 +85,4 @@ class DefaultFirebaseOptions { storageBucket: 'taxglide-bd535.firebasestorage.app', measurementId: 'G-14E4NG37P2', ); -} \ No newline at end of file +} diff --git a/lib/main.dart b/lib/main.dart index 744ad4a..10a4330 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,6 +6,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:get/get.dart'; import 'package:file_picker/file_picker.dart'; import 'package:permission_handler/permission_handler.dart'; +import 'package:taxglide/consts/notification_webscoket.dart'; import 'firebase_options.dart'; import 'package:taxglide/router/router.dart'; @@ -138,6 +139,9 @@ void _setupFcmListeners() { message, tag: dedupTag, ); + + // 🔄 Trigger live refresh for detail screens/badge counts + NotificationWebSocket().triggerRefresh(); } }); @@ -232,6 +236,7 @@ class _TaxglideAppState extends State { getPages: AppRoutes.routes, theme: ThemeData( useMaterial3: true, + fontFamily: 'Gilroy', colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue), ), ); diff --git a/lib/model/detail_model.dart b/lib/model/detail_model.dart index d394c63..9bd6dd0 100644 --- a/lib/model/detail_model.dart +++ b/lib/model/detail_model.dart @@ -50,12 +50,13 @@ class DetailData { final String? proforma; final int? proformaId; final String? proformaNumber; - final String? proformastatus; + final String? proformaStatus; final String? createdDate; final String? createdTime; final String? createdBy; final String? message; + final String? subject; final String? completedDate; final String? paidBy; @@ -70,7 +71,7 @@ class DetailData { this.firmId, this.dueDate, this.service, - this.proformastatus, + this.proformaStatus, this.serviceStatus, this.paymentStatus, this.paidStatus, @@ -85,6 +86,7 @@ class DetailData { this.createdTime, this.createdBy, this.message, + this.subject, this.completedDate, this.paidBy, required this.userUploadedDocuments, @@ -102,7 +104,7 @@ class DetailData { serviceStatus: json["service_status"], paymentStatus: json["payment_status"], - proformastatus: json["proforma_status"], + proformaStatus: json["proforma_status"], paidStatus: json["paid_status"], paymentDate: json["payment_date"], paymentAmount: json["payment_amount"]?.toString(), @@ -119,6 +121,7 @@ class DetailData { createdBy: json["created_by"], message: json["message"], + subject: json["subject"], completedDate: json["completed_date"], paidBy: json["paid_by"], @@ -153,7 +156,7 @@ class DetailData { "payment_date": paymentDate, "payment_amount": paymentAmount, "invoice": invoice, - "proforma_status": proformastatus, + "proforma_status": proformaStatus, "invoice_number": invoiceNumber, "proforma": proforma, "proforma_id": proformaId, @@ -162,6 +165,7 @@ class DetailData { "created_time": createdTime, "created_by": createdBy, "message": message, + "subject": subject, "completed_date": completedDate, "paid_by": paidBy, "user_uploaded_documents": userUploadedDocuments diff --git a/lib/model/service_list_history_model.dart b/lib/model/service_list_history_model.dart index f6a8451..76231d8 100644 --- a/lib/model/service_list_history_model.dart +++ b/lib/model/service_list_history_model.dart @@ -42,23 +42,41 @@ class ServiceListHistoryModel { class ServiceHistoryData { final int? id; + final String? name; + final String? firmName; final String? paymentStatus; final String? paymentAmount; final String? status; final String? service; final String? message; + final String? subject; + final String? assignee; final String? createdDate; final String? createdTime; + final String? createdAt; + final int? actions; + final int? chatId; + final int? isProformaGenerated; + final String? paymentId; ServiceHistoryData({ this.id, + this.name, + this.firmName, this.paymentStatus, this.paymentAmount, this.status, this.service, this.message, + this.subject, + this.assignee, this.createdDate, this.createdTime, + this.createdAt, + this.actions, + this.chatId, + this.isProformaGenerated, + this.paymentId, }); factory ServiceHistoryData.fromJson(Map json) { @@ -66,26 +84,50 @@ class ServiceHistoryData { id: json['id'] is int ? json['id'] : int.tryParse(json['id']?.toString() ?? ''), + name: json['name']?.toString(), + firmName: json['firm_name']?.toString(), paymentStatus: json['payment_status']?.toString(), paymentAmount: json['payment_amount']?.toString(), status: json['status']?.toString(), service: json['service']?.toString(), message: json['message']?.toString(), + subject: json['subject']?.toString(), + assignee: json['assignee']?.toString(), createdDate: json['created_date']?.toString(), createdTime: json['created_time']?.toString(), + createdAt: json['created_at']?.toString(), + actions: json['actions'] is int + ? json['actions'] + : int.tryParse(json['actions']?.toString() ?? ''), + chatId: json['chat_id'] is int + ? json['chat_id'] + : int.tryParse(json['chat_id']?.toString() ?? ''), + isProformaGenerated: json['is_proforma_generated'] is int + ? json['is_proforma_generated'] + : int.tryParse(json['is_proforma_generated']?.toString() ?? ''), + paymentId: json['payment_id']?.toString(), ); } Map toJson() { return { 'id': id, + 'name': name, + 'firm_name': firmName, 'payment_status': paymentStatus, 'payment_amount': paymentAmount, 'status': status, 'service': service, 'message': message, + 'subject': subject, + 'assignee': assignee, 'created_date': createdDate, 'created_time': createdTime, + 'created_at': createdAt, + 'actions': actions, + 'chat_id': chatId, + 'is_proforma_generated': isProformaGenerated, + 'payment_id': paymentId, }; } } diff --git a/lib/services/notification_service.dart b/lib/services/notification_service.dart index 5ef7a2c..870b1b4 100644 --- a/lib/services/notification_service.dart +++ b/lib/services/notification_service.dart @@ -2,6 +2,7 @@ import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:get/get.dart'; +import 'package:taxglide/consts/download_helper.dart'; import 'package:taxglide/consts/local_store.dart'; import 'package:taxglide/view/Mahi_chat/live_chat_screen.dart'; import 'package:taxglide/view/Main_controller/main_controller.dart'; @@ -26,6 +27,16 @@ const AndroidNotificationChannel _channel = AndroidNotificationChannel( enableVibration: true, ); +/// Android notification channel for download updates. +const AndroidNotificationChannel _downloadChannel = AndroidNotificationChannel( + 'taxglide_downloads', + 'TaxGlide Downloads', + description: 'Notifications for file downloads', + importance: Importance.high, + playSound: true, + enableVibration: true, +); + /// Call once in main() after Firebase.initializeApp(). Future initLocalNotifications() async { const initSettings = InitializationSettings( @@ -37,19 +48,34 @@ Future initLocalNotifications() async { initSettings, onDidReceiveNotificationResponse: (NotificationResponse response) { // Notification tapped while app is open – handled inside NotificationService. - debugPrint('🔔 Local notification tapped: payload=${response.payload}'); + debugPrint('🔔 Local notification action: ${response.actionId} payload=${response.payload}'); + + if (response.actionId == 'location') { + final payload = response.payload; + if (payload != null && payload.startsWith('download:')) { + final filePath = payload.replaceFirst('download:', ''); + final String dirPath = filePath.substring(0, filePath.lastIndexOf('/')); + debugPrint('📂 Opening folder from notification: $dirPath'); + // Open the system file manager at this location + DownloadHelper.openFolder(dirPath); + return; + } + } + Get.find().handleNavigationFromPayload( response.payload, ); }, ); - // Create the Android channel (no-op on iOS). - await flutterLocalNotificationsPlugin + // Create the channels + final androidPlugin = flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin - >() - ?.createNotificationChannel(_channel); + >(); + + await androidPlugin?.createNotificationChannel(_channel); + await androidPlugin?.createNotificationChannel(_downloadChannel); debugPrint('✅ LocalNotifications initialized'); } @@ -123,6 +149,55 @@ class NotificationService extends GetxController { ); } + /// Shows a notification when a file is successfully downloaded. + /// [filename] is the base name of the file. + /// [filePath] is the absolute path to the local file. + void showDownloadNotification(String filename, String filePath) { + debugPrint('📩 NotificationService: showing download success: $filename'); + + flutterLocalNotificationsPlugin.show( + filename.hashCode.abs() % 100000, + 'Download Complete', + 'File saved: $filename', + NotificationDetails( + android: AndroidNotificationDetails( + _downloadChannel.id, + _downloadChannel.name, + channelDescription: _downloadChannel.description, + importance: Importance.high, + priority: Priority.high, + icon: '@mipmap/launcher_icon', + playSound: true, + styleInformation: BigTextStyleInformation( + 'The file **$filename** has been saved to your downloads folder:\n\n$filePath', + contentTitle: 'Download Complete', + summaryText: 'TaxGlide Download', + htmlFormatContent: true, + htmlFormatTitle: true, + ), + actions: [ + const AndroidNotificationAction( + 'view', + 'View File', + showsUserInterface: true, + ), + const AndroidNotificationAction( + 'location', + 'Show in Folder', + showsUserInterface: true, + ), + ], + ), + iOS: const DarwinNotificationDetails( + presentAlert: true, + presentBadge: true, + presentSound: true, + ), + ), + payload: 'download:$filePath', + ); + } + // ── Navigation helpers ────────────────────────────────────────────────────── /// Called when a local notification is tapped (payload = "type:id"). @@ -131,6 +206,15 @@ class NotificationService extends GetxController { _safeNavigate(() => Get.offAll(() => MainController())); return; } + + // ⭐ Handle file download notifications + if (payload.startsWith('download:')) { + final filePath = payload.replaceFirst('download:', ''); + debugPrint('📂 Opening downloaded file from notification: $filePath'); + DownloadHelper.openDownloadedFile(filePath); + return; + } + final parts = payload.split(':'); final type = parts.isNotEmpty ? parts[0] : null; final idStr = parts.length > 1 ? parts[1] : null; diff --git a/lib/view/Mahi_chat/chat_profile_screen.dart b/lib/view/Mahi_chat/chat_profile_screen.dart index 5a86000..3582bf7 100644 --- a/lib/view/Mahi_chat/chat_profile_screen.dart +++ b/lib/view/Mahi_chat/chat_profile_screen.dart @@ -39,7 +39,7 @@ class ChatProfileScreen extends ConsumerWidget { title: const Text( "Profile", style: TextStyle( - fontFamily: "Gilroy-SemiBold", + fontFamily: "Gilroy", fontWeight: FontWeight.w400, fontSize: 20, height: 1.4, @@ -113,7 +113,7 @@ class ChatProfileScreen extends ConsumerWidget { const Text( "Shared By Taxglide", style: TextStyle( - fontFamily: "Gilroy-SemiBold", + fontFamily: "Gilroy", fontWeight: FontWeight.w600, fontSize: 20, height: 1.4, @@ -213,7 +213,7 @@ class ChatProfileScreen extends ConsumerWidget { const Text( "Shared By Client", style: TextStyle( - fontFamily: "Gilroy-SemiBold", + fontFamily: "Gilroy", fontWeight: FontWeight.w600, fontSize: 20, height: 1.4, diff --git a/lib/view/Mahi_chat/comman_input_button.dart b/lib/view/Mahi_chat/comman_input_button.dart index eb7ccf5..5819023 100644 --- a/lib/view/Mahi_chat/comman_input_button.dart +++ b/lib/view/Mahi_chat/comman_input_button.dart @@ -7,6 +7,7 @@ import 'package:permission_handler/permission_handler.dart'; import 'package:taxglide/consts/download_helper.dart'; import 'package:taxglide/controller/api_contoller.dart'; import 'package:taxglide/controller/api_repository.dart'; +import 'package:taxglide/model/chat_model.dart'; import 'package:taxglide/view/Mahi_chat/live_chat_screen.dart'; // ⭐⭐⭐ CHAT INPUT BOX WITH FILE TAG SUPPORT ⭐⭐⭐ @@ -246,49 +247,61 @@ class _ChatInputBoxState extends ConsumerState { setState(() => _isSending = true); - // Show loading dialog - showDialog( - context: context, - barrierDismissible: false, - builder: (context) => WillPopScope( - onWillPop: () async => false, - child: const Center( - child: Card( - child: Padding( - padding: EdgeInsets.all(20), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - CircularProgressIndicator(), - SizedBox(height: 16), - Text("Sending message..."), - ], - ), - ), - ), - ), - ), + // ⭐ OPTIMISTIC UI: Create a temporary message and add it immediately + final tempId = DateTime.now().millisecondsSinceEpoch; + final tempMsg = MessageModel( + id: tempId, + chatBy: "user", // User + message: text, + type: selectedFiles.isNotEmpty ? "file" : "text", + isDelivered: 0, + isRead: 0, + createdAt: DateTime.now().toIso8601String(), + userId: 0, + tagId: tagId > 0 ? tagId : null, + parentTag: taggedMessage != null ? ParentTagModel( + id: taggedMessage.id, + message: taggedMessage.message, + type: taggedMessage.type, + fileName: taggedMessage.fileName, + ) : null, + uploadedDocuments: selectedFiles.map((file) => DocumentModel( + filePath: file.path, + fileName: file.path.split('/').last, + fileType: "image", + )).toList(), + documents: [], ); + // Add to UI immediately + final notifier = ref.read(chatMessagesProvider(widget.chatId).notifier); + notifier.addNewMessage(tempMsg); + + // Clear input immediately for snappy feel + final savedSelectedFiles = List.from(selectedFiles); + messageCtrl.clear(); + selectedFiles.clear(); + ref.read(taggedMessageProvider.notifier).state = null; + + // Notify parent to scroll to bottom instantly + if (widget.onMessageSent != null) { + widget.onMessageSent!(); + } + try { List filesToUpload = []; // ⭐⭐⭐ Save files to Send folder - if (selectedFiles.isNotEmpty) { - print("📤 Saving ${selectedFiles.length} files to Send folder..."); - - for (File file in selectedFiles) { + if (savedSelectedFiles.isNotEmpty) { + for (File file in savedSelectedFiles) { try { // Try to save to Send folder, fallback to original if fails final savedPath = await DownloadHelper.saveToSendFolder(file); filesToUpload.add(File(savedPath)); } catch (e) { - print("⚠️ Could not save file, using original: $e"); filesToUpload.add(file); } } - - print("✅ Files prepared for upload: ${filesToUpload.length}"); } // Send message with files @@ -299,37 +312,22 @@ class _ChatInputBoxState extends ConsumerState { files: filesToUpload, ); - // Refresh chat messages - await ref.read(chatMessagesProvider(widget.chatId).notifier).refresh(); + // Refresh chat messages (this will replace the temp message with the real one) + await notifier.refresh(); ref.invalidate(chatDocumentProvider); - print("✅ Message sent successfully with tagId: $tagId"); - - // Close loading dialog - if (mounted) Navigator.pop(context); - - // Clear input + if (mounted) { - setState(() { - messageCtrl.clear(); - selectedFiles.clear(); - _isSending = false; - }); - } - - // Clear the tagged message - ref.read(taggedMessageProvider.notifier).state = null; - - // Notify parent to scroll to bottom - if (widget.onMessageSent != null) { - widget.onMessageSent!(); + setState(() => _isSending = false); } } catch (e) { print("❌ Error sending message: $e"); + + if (mounted) { + setState(() => _isSending = false); + } - // Close loading dialog - if (mounted) Navigator.pop(context); - - setState(() => _isSending = false); + // If it failed, delete the temporary message + notifier.deleteMessage(tempId); // Show error message if (mounted) { @@ -361,6 +359,7 @@ class _ChatInputBoxState extends ConsumerState { } } + @override Widget build(BuildContext context) { final taggedMessage = ref.watch(taggedMessageProvider); diff --git a/lib/view/Mahi_chat/downloadchat.dart b/lib/view/Mahi_chat/downloadchat.dart index fa05cc3..b96d682 100644 --- a/lib/view/Mahi_chat/downloadchat.dart +++ b/lib/view/Mahi_chat/downloadchat.dart @@ -270,7 +270,7 @@ class _DownloadsScreenState extends State title: const Text( "Downloads", style: TextStyle( - fontFamily: "Gilroy-SemiBold", + fontFamily: "Gilroy", fontWeight: FontWeight.w600, ), ), diff --git a/lib/view/Mahi_chat/file_images_screen.dart b/lib/view/Mahi_chat/file_images_screen.dart index 2c8a24a..7ab9096 100644 --- a/lib/view/Mahi_chat/file_images_screen.dart +++ b/lib/view/Mahi_chat/file_images_screen.dart @@ -9,7 +9,7 @@ class SwipeableMessageBubble extends StatefulWidget { final MessageModel message; final DateTime msgDate; final String Function(DateTime) formatTime; - final Widget Function(int, int) buildTick; + final Widget Function(MessageModel) buildTick; final VoidCallback onSwipe; final Function(int?)? onParentTagTap; final Function(List imageUrls, int initialIndex)? @@ -669,10 +669,7 @@ class SwipeableMessageBubbleState extends State ), ), const SizedBox(width: 4), - widget.buildTick( - widget.message.isDelivered, - widget.message.isRead, - ), + widget.buildTick(widget.message), ], ), ], diff --git a/lib/view/Mahi_chat/live_chat_screen.dart b/lib/view/Mahi_chat/live_chat_screen.dart index 5404749..841fa7c 100644 --- a/lib/view/Mahi_chat/live_chat_screen.dart +++ b/lib/view/Mahi_chat/live_chat_screen.dart @@ -160,8 +160,11 @@ class _LiveChatScreenState extends ConsumerState try { final event = message['event'] as String?; - if (event == 'message.sent' || event == 'message.received') { - debugPrint("🔔 New message detected!"); + if (event == 'message.sent' || + event == 'message.received' || + event == 'message.read' || + event == 'message.delivered') { + debugPrint("🔔 Message status/new message event: $event"); if (WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed) { @@ -409,7 +412,16 @@ class _LiveChatScreenState extends ConsumerState return "$hour:$minute $period"; } - Widget buildTick(int isDelivered, int isRead) { + Widget buildTick(MessageModel message) { + // ⭐ Status 0/1/pending + final isRead = message.isRead; + final isDelivered = message.isDelivered; + + // Check if it's an optimistic pending message (huge ID) + if (message.id > 1000000000000) { + return const Icon(Icons.schedule, size: 14, color: Colors.grey); + } + if (isRead == 1) { return const Icon(Icons.done_all, size: 16, color: Colors.blue); } else if (isDelivered == 1) { @@ -445,7 +457,7 @@ class _LiveChatScreenState extends ConsumerState ? 'File ID : ${widget.fileid.toString()}' : "Live Chat", style: const TextStyle( - fontFamily: "Gilroy-SemiBold", + fontFamily: "Gilroy", fontWeight: FontWeight.w600, ), ), diff --git a/lib/view/Main_controller/comman_chat_box.dart b/lib/view/Main_controller/comman_chat_box.dart index fbe97a1..54a8e76 100644 --- a/lib/view/Main_controller/comman_chat_box.dart +++ b/lib/view/Main_controller/comman_chat_box.dart @@ -1,12 +1,14 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:taxglide/consts/app_asstes.dart'; +import 'package:taxglide/consts/app_style.dart'; import 'package:taxglide/consts/comman_webscoket.dart'; import 'package:taxglide/consts/local_store.dart'; import 'package:taxglide/consts/notification_webscoket.dart'; import 'package:taxglide/controller/api_contoller.dart'; import 'package:taxglide/view/Mahi_chat/live_chat_screen.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:taxglide/consts/responsive_helper.dart'; class CommanChatBox extends ConsumerStatefulWidget { final String? chatId; @@ -86,11 +88,12 @@ class _CommanChatBoxState extends ConsumerState { } final id = snapshot.data ?? "0"; + final r = ResponsiveUtils(context); final countAsync = ref.watch(countProvider(id)); return Positioned( right: 20, - bottom: 120, + bottom: r.spacing(mobile: 35, tablet: 45, desktop: 55), child: Stack( clipBehavior: Clip.none, children: [ @@ -154,10 +157,9 @@ class _CommanChatBoxState extends ConsumerState { ), child: Text( data.count.toString(), - style: const TextStyle( + style: AppTextStyles.bold.copyWith( color: Colors.white, - fontSize: 11, - fontWeight: FontWeight.bold, + fontSize: 10, ), ), ); diff --git a/lib/view/Main_controller/main_controller.dart b/lib/view/Main_controller/main_controller.dart index 606534d..9755d34 100644 --- a/lib/view/Main_controller/main_controller.dart +++ b/lib/view/Main_controller/main_controller.dart @@ -13,6 +13,9 @@ import 'package:taxglide/view/screens/home_screen.dart'; import 'package:taxglide/view/screens/list_service_screen.dart'; import 'package:taxglide/view/screens/profile/employee_profile/employee_profile_screen.dart'; import 'package:taxglide/view/screens/profile/profile_screen.dart'; +import 'package:curved_labeled_navigation_bar/curved_navigation_bar.dart'; +import 'package:curved_labeled_navigation_bar/curved_navigation_bar_item.dart'; +import 'package:taxglide/consts/app_style.dart'; class MainController extends ConsumerStatefulWidget { final Widget? child; // Optional child (like ServiceRequestScreen) @@ -39,7 +42,7 @@ class _MainControllerState extends ConsumerState { final List _labels = [ 'Home', 'Services', - 'Service Details', + 'Task Status', 'Profile', ]; @@ -177,65 +180,6 @@ class _MainControllerState extends ConsumerState { body: Stack( children: [ mainContent, - - /// ✅ Bottom Navigation Bar - Positioned( - bottom: 0, - left: 0, - child: SafeArea( - top: false, - child: Container( - width: size.width, - height: 80, - child: Stack( - clipBehavior: Clip.none, - children: [ - CustomPaint( - size: Size(size.width, 80), - painter: BNBCustomPainter(), - ), - Center( - heightFactor: 0.6, - child: GestureDetector( - onTap: () {}, - child: CustomPaint( - painter: PentagonPainter( - color: const Color(0xFF61277A), - ), - child: Container( - width: 70, - height: 70, - alignment: Alignment.center, - child: Image.asset( - AppAssets.maincontroller, - color: Colors.white, - fit: BoxFit.contain, - height: 40, - width: 40, - ), - ), - ), - ), - ), - SizedBox( - width: size.width, - height: 80, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - _buildNavItem(Icons.home, 0, _labels[0]), - _buildNavItem(Icons.list_alt_sharp, 1, _labels[1]), - SizedBox(width: size.width * 0.20), - _buildNavItem(Icons.history_edu, 2, _labels[2]), - _buildNavItem(Icons.person_2, 3, _labels[3]), - ], - ), - ), - ], - ), - ), - ), - ), dashboardAsync.when( data: (dashboard) { final int? chatIdInt = dashboard.generalChatId; @@ -253,199 +197,89 @@ class _MainControllerState extends ConsumerState { ), ], ), - ), - ); - } - - /// ✅ Navigation Logic with Refresh - Widget _buildNavItem(IconData icon, int index, String label) { - bool isSelected = currentIndex == index; - - return GestureDetector( - onTap: () { - // 🔹 If inside ServiceRequestScreen - if (widget.child != null) { - if (widget.initialIndex == index) return; - - if (widget.sourceTabIndex == 0 && index == 1) { - Get.offAll(() => const MainController(initialIndex: 1)); - return; - } - - if (widget.sourceTabIndex == 1 && index == 1) return; - - Get.offAll(() => MainController(initialIndex: index)); - return; - } - - // 🔹 If same tab tapped again → refresh - if (currentIndex == index) { - _refreshTab(index); - return; - } - - // 🔹 Switch to a different tab - setBottomBarIndex(index); - }, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - AnimatedContainer( - duration: const Duration(milliseconds: 200), - padding: const EdgeInsets.all(6), - decoration: BoxDecoration( - shape: BoxShape.circle, - border: isSelected - ? Border.all(color: const Color(0xFF61277A), width: 2) - : null, - ), - child: Icon( - icon, - color: isSelected - ? const Color(0xFF61277A) - : const Color(0xFF6C7278), - size: isSelected ? 19.5 : 20.8, - ), - ), - if (isSelected) - Padding( - padding: const EdgeInsets.only(top: 4), - child: Text( - label, - style: const TextStyle( - color: Color(0xFF61277A), + bottomNavigationBar: SafeArea( + top: false, + child: CurvedNavigationBar( + backgroundColor: Color(0xFF61277A), + buttonBackgroundColor: Colors.white, + color: Colors.white, + animationDuration: const Duration(milliseconds: 300), + index: currentIndex, + items: [ + CurvedNavigationBarItem( + child: Icon( + Icons.home, + color: currentIndex == 0 ? Colors.black : const Color(0xFF6C7278), + ), + label: _labels[0], + labelStyle: (currentIndex == 0 ? AppTextStyles.bold : AppTextStyles.bold).copyWith( + color: currentIndex == 0 ? const Color(0xFF61277A) : const Color(0xFF6C7278), fontSize: 12, - fontWeight: FontWeight.w600, ), ), - ), - ], + CurvedNavigationBarItem( + child: Icon( + Icons.list_alt_sharp, + color: currentIndex == 1 ? Colors.black : const Color(0xFF6C7278), + ), + label: _labels[1], + labelStyle: (currentIndex == 0 ? AppTextStyles.bold : AppTextStyles.bold).copyWith( + color: currentIndex == 1 ? const Color(0xFF61277A) : const Color(0xFF6C7278), + fontSize: 12, + ), + ), + CurvedNavigationBarItem( + child: Icon( + Icons.history_edu, + color: currentIndex == 2 ? Colors.black : const Color(0xFF6C7278), + ), + label: _labels[2], + labelStyle: (currentIndex == 0 ? AppTextStyles.bold : AppTextStyles.bold).copyWith( + color: currentIndex == 2 ? const Color(0xFF61277A) : const Color(0xFF6C7278), + fontSize: 12, + ), + ), + CurvedNavigationBarItem( + child: Icon( + Icons.person_2, + color: currentIndex == 3 ? Colors.black : const Color(0xFF6C7278), + ), + label: _labels[3], + labelStyle: (currentIndex == 0 ? AppTextStyles.bold : AppTextStyles.bold).copyWith( + color: currentIndex == 3 ? const Color(0xFF61277A) : const Color(0xFF6C7278), + fontSize: 12, + ), + ), + ], + onTap: (index) { + // 🔹 If inside ServiceRequestScreen + if (widget.child != null) { + if (widget.initialIndex == index) return; + + if (widget.sourceTabIndex == 0 && index == 1) { + Get.offAll(() => const MainController(initialIndex: 1)); + return; + } + + if (widget.sourceTabIndex == 1 && index == 1) return; + + Get.offAll(() => MainController(initialIndex: index)); + return; + } + + // 🔹 If same tab tapped again → refresh + if (currentIndex == index) { + _refreshTab(index); + return; + } + + // 🔹 Switch to a different tab + setBottomBarIndex(index); + }, + ), + ), ), ); } -} - -/// ✅ Custom Curved Bottom Navigation Painter -class BNBCustomPainter extends CustomPainter { - @override - void paint(Canvas canvas, Size size) { - Paint paint = Paint() - ..color = Colors.white - ..style = PaintingStyle.fill; - - Path path = Path(); - path.moveTo(0, 20); - path.quadraticBezierTo(size.width * 0.20, 0, size.width * 0.35, 0); - path.quadraticBezierTo(size.width * 0.40, 0, size.width * 0.40, 20); - path.arcToPoint( - Offset(size.width * 0.60, 20), - radius: const Radius.circular(20.0), - clockwise: false, - ); - path.quadraticBezierTo(size.width * 0.60, 0, size.width * 0.65, 0); - path.quadraticBezierTo(size.width * 0.80, 0, size.width, 20); - path.lineTo(size.width, size.height); - path.lineTo(0, size.height); - path.close(); - - canvas.drawShadow(path, Colors.black.withOpacity(0.2), 5, true); - canvas.drawPath(path, paint); - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) => false; -} - -/// ✅ Pentagon Button Painter -/// ✅ Pentagon Button Painter -class PentagonPainter extends CustomPainter { - final Color color; - final double cornerRadius; - - PentagonPainter({ - required this.color, - this.cornerRadius = 8.0, // Adjust this value for more/less rounding - }); - - @override - void paint(Canvas canvas, Size size) { - final paint = Paint() - ..color = color - ..style = PaintingStyle.fill; - - final double w = size.width; - final double cx = w / 2; - final double cy = size.height / 2; - final double r = w / 2; - - // Calculate pentagon vertices - List vertices = []; - for (int i = 0; i < 5; i++) { - double angle = (72 * i - 90) * math.pi / 180; - double x = cx + r * 0.95 * math.cos(angle); - double y = cy + r * 0.95 * math.sin(angle); - vertices.add(Offset(x, y)); - } - - // Create path with rounded corners - final path = Path(); - - for (int i = 0; i < vertices.length; i++) { - final current = vertices[i]; - final next = vertices[(i + 1) % vertices.length]; - final prev = vertices[(i - 1 + vertices.length) % vertices.length]; - - // Calculate direction vectors - final toCurrent = Offset(current.dx - prev.dx, current.dy - prev.dy); - final toNext = Offset(next.dx - current.dx, next.dy - current.dy); - - // Normalize and scale by corner radius - final lengthToCurrent = math.sqrt( - toCurrent.dx * toCurrent.dx + toCurrent.dy * toCurrent.dy, - ); - final lengthToNext = math.sqrt( - toNext.dx * toNext.dx + toNext.dy * toNext.dy, - ); - - final normalizedToCurrent = Offset( - toCurrent.dx / lengthToCurrent, - toCurrent.dy / lengthToCurrent, - ); - final normalizedToNext = Offset( - toNext.dx / lengthToNext, - toNext.dy / lengthToNext, - ); - - // Points before and after the corner - final beforeCorner = Offset( - current.dx - normalizedToCurrent.dx * cornerRadius, - current.dy - normalizedToCurrent.dy * cornerRadius, - ); - final afterCorner = Offset( - current.dx + normalizedToNext.dx * cornerRadius, - current.dy + normalizedToNext.dy * cornerRadius, - ); - - if (i == 0) { - path.moveTo(beforeCorner.dx, beforeCorner.dy); - } else { - path.lineTo(beforeCorner.dx, beforeCorner.dy); - } - - // Draw rounded corner using quadratic bezier - path.quadraticBezierTo( - current.dx, - current.dy, - afterCorner.dx, - afterCorner.dy, - ); - } - path.close(); - - canvas.drawShadow(path, Colors.black.withOpacity(0.3), 4, true); - canvas.drawPath(path, paint); - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) => false; + } diff --git a/lib/view/screens/history/completed_live_chat_screen.dart b/lib/view/screens/history/completed_live_chat_screen.dart index 1507e14..e7addd6 100644 --- a/lib/view/screens/history/completed_live_chat_screen.dart +++ b/lib/view/screens/history/completed_live_chat_screen.dart @@ -4,6 +4,7 @@ import 'package:flutter_riverpod/legacy.dart'; import 'package:get/get.dart'; import 'package:get/get_core/src/get_main.dart'; import 'package:taxglide/consts/app_asstes.dart'; +import 'package:taxglide/consts/app_style.dart'; import 'package:taxglide/controller/api_contoller.dart'; import 'package:taxglide/model/chat_model.dart'; import 'package:taxglide/view/Mahi_chat/chat_profile_screen.dart'; @@ -193,8 +194,11 @@ class _CompletedLiveChatScreenState try { final event = message['event'] as String?; - if (event == 'message.sent' || event == 'message.received') { - debugPrint("🔔 New message detected!"); + if (event == 'message.sent' || + event == 'message.received' || + event == 'message.read' || + event == 'message.delivered') { + debugPrint("🔔 Message status/new message event: $event"); if (WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed) { @@ -332,7 +336,11 @@ class _CompletedLiveChatScreenState return "$hour:$minute $period"; } - Widget buildTick(int isDelivered, int isRead) { + Widget buildTick(MessageModel message) { + // ⭐ Status 0/1 + final isRead = message.isRead; + final isDelivered = message.isDelivered; + if (isRead == 1) { return const Icon(Icons.done_all, size: 16, color: Colors.blue); } else if (isDelivered == 1) { @@ -357,9 +365,9 @@ class _CompletedLiveChatScreenState widget.fileid.toString() != "0") ? 'File ID : ${widget.fileid.toString()}' : "Live Chat", - style: const TextStyle( - fontFamily: "Gilroy-SemiBold", - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( + fontSize: 16, + color: Colors.black, ), ), backgroundColor: Colors.white, diff --git a/lib/view/screens/history/detail_screen.dart b/lib/view/screens/history/detail_screen.dart index 3cefffe..7ae8c2d 100644 --- a/lib/view/screens/history/detail_screen.dart +++ b/lib/view/screens/history/detail_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:taxglide/consts/app_style.dart'; import 'package:get/get.dart'; import 'package:taxglide/consts/app_asstes.dart'; import 'package:taxglide/consts/comman_button.dart'; @@ -183,9 +184,7 @@ class _DetailScreenState extends ConsumerState { label: Text( "Invoice Not Available", textAlign: TextAlign.center, - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w400, + style: AppTextStyles.regular.copyWith( fontSize: width * 0.045, height: 1.3, letterSpacing: 0.04 * 17.64, @@ -239,9 +238,7 @@ class _DetailScreenState extends ConsumerState { ? "View Invoice" : "Download Invoice", textAlign: TextAlign.center, - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w400, + style: AppTextStyles.regular.copyWith( fontSize: width * 0.045, height: 1.3, letterSpacing: 0.04 * 17.64, @@ -312,9 +309,7 @@ class _DetailScreenState extends ConsumerState { : isDownloaded ? "View Proforma" : "Download Proforma", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: 12, height: 1.3, letterSpacing: 0.04 * 14.47, @@ -337,6 +332,22 @@ class _DetailScreenState extends ConsumerState { final detailAsync = ref.watch(serviceDetailProvider(widget.id)); + // 🔄 Silent refresh when notification trigger changes + ref.listen(notificationTriggerProvider, (previous, next) { + if (previous != null && next != previous) { + debugPrint("🔔 Silent refresh triggered for DetailScreen"); + ref.refresh(serviceDetailProvider(widget.id)); + + // Also refresh unread count if available + detailAsync.whenData((model) { + final chatId = model.data?.chatId; + if (chatId != null && chatId.isNotEmpty) { + ref.refresh(countProvider(chatId)); + } + }); + } + }); + return Scaffold( body: Container( height: height, @@ -349,16 +360,16 @@ class _DetailScreenState extends ConsumerState { ), child: SafeArea( child: detailAsync.when( + skipLoadingOnRefresh: true, loading: () => const Center( child: CircularProgressIndicator(color: Colors.deepPurple), ), error: (e, _) => Center( child: Text( "Error: $e", - style: const TextStyle( + style: AppTextStyles.regular.copyWith( color: Colors.red, fontSize: 16, - fontFamily: 'Gilroy-Medium', ), ), ), @@ -391,9 +402,7 @@ class _DetailScreenState extends ConsumerState { Text( "Service Details", textAlign: TextAlign.center, - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: width * 0.055, color: const Color(0xFF111827), ), @@ -474,9 +483,7 @@ class _DetailScreenState extends ConsumerState { children: [ Text( "Detailed Information", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: width * 0.047, height: 1.4, letterSpacing: 0.03 * 20, @@ -508,9 +515,7 @@ class _DetailScreenState extends ConsumerState { child: Center( child: Text( data.serviceStatus.toString(), - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w400, + style: AppTextStyles.regular.copyWith( fontSize: width * 0.029, color: const Color(0xFFFF0F0F), ), @@ -541,9 +546,7 @@ class _DetailScreenState extends ConsumerState { child: Center( child: Text( data.serviceStatus.toString(), - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w400, + style: AppTextStyles.regular.copyWith( fontSize: width * 0.032, letterSpacing: 0.03, color: const Color(0xFF12800C), @@ -575,9 +578,7 @@ class _DetailScreenState extends ConsumerState { child: Center( child: Text( data.serviceStatus.toString(), - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w400, + style: AppTextStyles.regular.copyWith( fontSize: width * 0.029, letterSpacing: 0.03, color: const Color(0xFFFF630F), @@ -616,18 +617,16 @@ class _DetailScreenState extends ConsumerState { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - "Task Final Report", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, - fontSize: width * 0.042, - height: 1.4, - letterSpacing: 0.03 * 16, - color: const Color(0xFF111827), - ), - textAlign: TextAlign.start, + Text( + "This is your final task document", + style: AppTextStyles.semiBold.copyWith( + fontSize: width * 0.042, + height: 1.4, + letterSpacing: 0.03 * 16, + color: const Color(0xFF111827), ), + textAlign: TextAlign.start, + ), const SizedBox(height: 15), Row( mainAxisAlignment: @@ -640,9 +639,7 @@ class _DetailScreenState extends ConsumerState { children: [ TextSpan( text: 'Date: ', - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: width * 0.035, height: 1.3, letterSpacing: 0.04 * 13.97, @@ -651,9 +648,7 @@ class _DetailScreenState extends ConsumerState { ), TextSpan( text: data.createdDate ?? "-", - style: TextStyle( - fontFamily: 'Gilroy-Medium', - fontWeight: FontWeight.w400, + style: AppTextStyles.regular.copyWith( fontSize: width * 0.035, height: 1.3, letterSpacing: 0.04 * 13.97, @@ -673,9 +668,7 @@ class _DetailScreenState extends ConsumerState { children: [ TextSpan( text: 'Payment : ', - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: width * 0.035, height: 1.3, letterSpacing: 0.04 * 13.97, @@ -684,9 +677,7 @@ class _DetailScreenState extends ConsumerState { ), TextSpan( text: data.paymentStatus ?? "-", - style: TextStyle( - fontFamily: 'Gilroy-Medium', - fontWeight: FontWeight.w400, + style: AppTextStyles.regular.copyWith( fontSize: width * 0.035, height: 1.3, letterSpacing: 0.04 * 13.97, @@ -899,7 +890,10 @@ class _DetailScreenState extends ConsumerState { ], Column( children: [ - if (data.paymentStatus == "Un Paid") ...[ + if (data.paymentStatus == "Un Paid" && + !(data.serviceStatus ?? "") + .toLowerCase() + .contains("cancelled")) ...[ Padding( padding: const EdgeInsets.symmetric( vertical: 31, @@ -932,9 +926,7 @@ class _DetailScreenState extends ConsumerState { children: [ Text( "Payment Advice", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: width * 0.042, height: 1.4, letterSpacing: 0.03 * 16, @@ -953,36 +945,22 @@ class _DetailScreenState extends ConsumerState { children: [ TextSpan( text: 'Date: ', - style: TextStyle( - fontFamily: - 'Gilroy-SemiBold', - fontWeight: - FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: width * 0.035, height: 1.3, - letterSpacing: - 0.04 * 13.97, - color: const Color( - 0xFF111827, - ), + letterSpacing: 0.04 * 13.97, + color: const Color(0xFF111827), ), ), TextSpan( text: data.createdDate ?? "-", - style: TextStyle( - fontFamily: - 'Gilroy-Medium', - fontWeight: - FontWeight.w400, + style: AppTextStyles.regular.copyWith( fontSize: width * 0.035, height: 1.3, - letterSpacing: - 0.04 * 13.97, - color: const Color( - 0xFF111827, - ), + letterSpacing: 0.04 * 13.97, + color: const Color(0xFF111827), ), ), ], @@ -998,36 +976,22 @@ class _DetailScreenState extends ConsumerState { children: [ TextSpan( text: 'Payment : ', - style: TextStyle( - fontFamily: - 'Gilroy-SemiBold', - fontWeight: - FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: width * 0.035, height: 1.3, - letterSpacing: - 0.04 * 13.97, - color: const Color( - 0xFF111827, - ), + letterSpacing: 0.04 * 13.97, + color: const Color(0xFF111827), ), ), TextSpan( text: data.paymentStatus ?? "-", - style: TextStyle( - fontFamily: - 'Gilroy-Medium', - fontWeight: - FontWeight.w400, + style: AppTextStyles.regular.copyWith( fontSize: width * 0.035, height: 1.3, - letterSpacing: - 0.04 * 13.97, - color: const Color( - 0xFF111827, - ), + letterSpacing: 0.04 * 13.97, + color: const Color(0xFF111827), ), ), ], @@ -1045,18 +1009,15 @@ class _DetailScreenState extends ConsumerState { children: [ Text( "Total Amount", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: width * 0.04, color: const Color(0xFF111827), ), ), Text( "₹ ${data.paymentAmount ?? '0'}", - style: TextStyle( + style: AppTextStyles.semiBold.copyWith( fontFamily: 'Roboto', - fontWeight: FontWeight.w600, fontSize: width * 0.045, color: const Color(0xFF111827), ), @@ -1105,9 +1066,7 @@ class _DetailScreenState extends ConsumerState { children: [ Text( "Payment Advice", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: width * 0.042, height: 1.4, letterSpacing: 0.03 * 16, @@ -1126,36 +1085,22 @@ class _DetailScreenState extends ConsumerState { children: [ TextSpan( text: 'Date: ', - style: TextStyle( - fontFamily: - 'Gilroy-SemiBold', - fontWeight: - FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: width * 0.035, height: 1.3, - letterSpacing: - 0.04 * 13.97, - color: const Color( - 0xFF111827, - ), + letterSpacing: 0.04 * 13.97, + color: const Color(0xFF111827), ), ), TextSpan( text: data.createdDate ?? "-", - style: TextStyle( - fontFamily: - 'Gilroy-Medium', - fontWeight: - FontWeight.w400, + style: AppTextStyles.regular.copyWith( fontSize: width * 0.035, height: 1.3, - letterSpacing: - 0.04 * 13.97, - color: const Color( - 0xFF111827, - ), + letterSpacing: 0.04 * 13.97, + color: const Color(0xFF111827), ), ), ], @@ -1171,36 +1116,22 @@ class _DetailScreenState extends ConsumerState { children: [ TextSpan( text: 'Payment: ', - style: TextStyle( - fontFamily: - 'Gilroy-SemiBold', - fontWeight: - FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: width * 0.035, height: 1.3, - letterSpacing: - 0.04 * 13.97, - color: const Color( - 0xFF111827, - ), + letterSpacing: 0.04 * 13.97, + color: const Color(0xFF111827), ), ), TextSpan( text: data.paymentStatus ?? "-", - style: TextStyle( - fontFamily: - 'Gilroy-Medium', - fontWeight: - FontWeight.w400, + style: AppTextStyles.regular.copyWith( fontSize: width * 0.035, height: 1.3, - letterSpacing: - 0.04 * 13.97, - color: const Color( - 0xFF111827, - ), + letterSpacing: 0.04 * 13.97, + color: const Color(0xFF111827), ), ), ], @@ -1218,18 +1149,15 @@ class _DetailScreenState extends ConsumerState { children: [ Text( "Total Amount", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: width * 0.04, color: const Color(0xFF111827), ), ), Text( "₹ ${data.paymentAmount ?? '0'}", - style: TextStyle( + style: AppTextStyles.semiBold.copyWith( fontFamily: 'Roboto', - fontWeight: FontWeight.w600, fontSize: width * 0.045, color: const Color(0xFF111827), ), @@ -1248,10 +1176,8 @@ class _DetailScreenState extends ConsumerState { ], ), ), - ] else if (data.paymentStatus == "Waiting") ...[ - const SizedBox.shrink(), ] else ...[ - Text("Unknown Status: ${data.paymentStatus}"), + const SizedBox.shrink(), ], if (data.proforma != null && @@ -1285,10 +1211,8 @@ class _DetailScreenState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Payment Advice", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + "Profoma Advice", + style: AppTextStyles.semiBold.copyWith( fontSize: width * 0.042, height: 1.4, letterSpacing: 0.03 * 16, @@ -1308,10 +1232,7 @@ class _DetailScreenState extends ConsumerState { children: [ TextSpan( text: 'proforma Number : ', - style: TextStyle( - fontFamily: - 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: width * 0.035, height: 1.3, letterSpacing: 0.04 * 13.97, @@ -1321,12 +1242,9 @@ class _DetailScreenState extends ConsumerState { ), ), TextSpan( - text: - data.proformaNumber ?? + text: data.proformaNumber ?? "-", - style: TextStyle( - fontFamily: 'Gilroy-Medium', - fontWeight: FontWeight.w400, + style: AppTextStyles.regular.copyWith( fontSize: width * 0.035, height: 1.3, letterSpacing: 0.04 * 13.97, @@ -1349,9 +1267,7 @@ class _DetailScreenState extends ConsumerState { children: [ TextSpan( text: 'proforma Status : ', - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: width * 0.035, height: 1.3, letterSpacing: 0.04 * 13.97, @@ -1359,10 +1275,35 @@ class _DetailScreenState extends ConsumerState { ), ), TextSpan( - text: data.proformastatus ?? "-", - style: TextStyle( - fontFamily: 'Gilroy-Medium', - fontWeight: FontWeight.w400, + text: data.proformaStatus ?? "-", + style: AppTextStyles.regular.copyWith( + fontSize: width * 0.035, + height: 1.3, + letterSpacing: 0.04 * 13.97, + color: const Color(0xFF111827), + ), + ), + ], + ), + ), + const SizedBox(height: 20), + RichText( + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.end, + text: TextSpan( + children: [ + TextSpan( + text: 'Subject : ', + style: AppTextStyles.semiBold.copyWith( + fontSize: width * 0.035, + height: 1.3, + letterSpacing: 0.04 * 13.97, + color: const Color(0xFF111827), + ), + ), + TextSpan( + text: data.subject ?? "-", + style: AppTextStyles.regular.copyWith( fontSize: width * 0.035, height: 1.3, letterSpacing: 0.04 * 13.97, @@ -1372,15 +1313,12 @@ class _DetailScreenState extends ConsumerState { ], ), ), - const SizedBox(height: 20), - if (data.proformastatus != "Accepted") ...[ + if (data.proformaStatus != "Accepted") ...[ Text( "Note : Once you accept this proforma only you can pay the amount for the service", textAlign: TextAlign.start, - style: const TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: 13, height: 1.78, // 178% line-height letterSpacing: 0.04 * 13, // 4% @@ -1404,7 +1342,7 @@ class _DetailScreenState extends ConsumerState { const SizedBox(width: 8), - if (data.proformastatus != "Accepted") + if (data.proformaStatus != "Accepted") /// ✅ Accept (Less width) Expanded( flex: 2, @@ -1525,9 +1463,7 @@ class _DetailScreenState extends ConsumerState { /// 🔹 File List Section Text( "File Attachments:", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: width * 0.04, color: const Color(0xFF111827), ), @@ -1714,18 +1650,19 @@ class _DetailScreenState extends ConsumerState { ), ) else - const Text( + Text( "No documents uploaded", - style: TextStyle( - fontFamily: 'Gilroy-Medium', - color: Color(0xFF6B7280), + style: AppTextStyles.regular.copyWith( + color: const Color(0xFF6B7280), ), ), ], ), ), ), - if (data.chatId != null && data.chatId!.isNotEmpty) + if (data.chatId != null && + data.chatId!.isNotEmpty && + !(data.serviceStatus ?? "").toLowerCase().contains("cancelled")) Padding( padding: const EdgeInsets.symmetric( horizontal: 20, @@ -1821,7 +1758,7 @@ class _DetailScreenState extends ConsumerState { ), ), - const SizedBox(height: 150), + SizedBox(height: 80.0 + MediaQuery.of(context).padding.bottom), ], ), ); @@ -1839,18 +1776,14 @@ class _DetailScreenState extends ConsumerState { children: [ TextSpan( text: '$label: ', - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: width * 0.04, color: const Color(0xFF111827), ), ), TextSpan( text: value.isEmpty ? '—' : value, - style: TextStyle( - fontFamily: 'Gilroy-Medium', - fontWeight: FontWeight.w500, + style: AppTextStyles.medium.copyWith( fontSize: width * 0.04, height: 1.5, color: const Color(0xFF374151), @@ -1879,10 +1812,9 @@ class _DetailScreenState extends ConsumerState { ), child: Text( count.toString(), - style: const TextStyle( + style: AppTextStyles.bold.copyWith( color: Colors.white, fontSize: 10, - fontWeight: FontWeight.bold, ), ), ), diff --git a/lib/view/screens/history/flitter_popup.dart b/lib/view/screens/history/flitter_popup.dart index 7168bc1..4278466 100644 --- a/lib/view/screens/history/flitter_popup.dart +++ b/lib/view/screens/history/flitter_popup.dart @@ -145,7 +145,7 @@ class _FilterBottomSheetState extends State<_FilterBottomSheet> child: const Text( "Cancel", style: TextStyle( - fontFamily: "Gilroy-SemiBold", // ✅ Font + fontFamily: "Gilroy", // ✅ Font fontWeight: FontWeight.w400, fontSize: 16, height: 1.3, // ✅ line-height 130% diff --git a/lib/view/screens/history/pending_screen.dart b/lib/view/screens/history/pending_screen.dart index 005ee72..b883185 100644 --- a/lib/view/screens/history/pending_screen.dart +++ b/lib/view/screens/history/pending_screen.dart @@ -1,9 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:taxglide/consts/app_style.dart'; import 'package:get/get.dart'; import 'package:taxglide/controller/api_contoller.dart'; import 'package:taxglide/view/Main_controller/main_controller.dart'; import 'package:taxglide/view/screens/history/detail_screen.dart'; +import 'package:taxglide/view/Mahi_chat/live_chat_screen.dart'; +import 'package:taxglide/view/screens/history/completed_live_chat_screen.dart'; class PendingScreen extends ConsumerWidget { final String status; @@ -17,6 +20,17 @@ class PendingScreen extends ConsumerWidget { final width = size.width; final height = size.height; + // 🔄 Silent refresh when notification trigger changes + ref.listen(notificationTriggerProvider, (previous, next) { + if (previous != null && next != previous) { + debugPrint("🔔 Silent refresh triggered for PendingScreen ($status)"); + ref + .read(serviceHistoryNotifierProvider(status).notifier) + .fetchServiceHistory(isSilent: true); + ref.invalidate(countProvider); + } + }); + return pendingAsync.when( data: (data) { final list = data.data ?? []; @@ -31,7 +45,7 @@ class PendingScreen extends ConsumerWidget { width * 0.04, height * 0.01, width * 0.04, - height * 0.2, + 80.0 + MediaQuery.of(context).padding.bottom, ), itemCount: list.length, separatorBuilder: (_, __) => SizedBox(height: height * 0.01), @@ -58,137 +72,236 @@ class PendingScreen extends ConsumerWidget { children: [ // 🔹 File ID Row Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - RichText( - text: TextSpan( - children: [ - TextSpan( - text: 'File ID : ', - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, - fontSize: width * 0.04, - color: const Color(0xFF111827), - ), - ), - TextSpan( - text: '${item.id}', - style: TextStyle( - fontFamily: 'Gilroy-Medium', - fontWeight: FontWeight.w500, - fontSize: width * 0.04, - color: const Color(0xFF111827), - ), - ), - ], + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + /// 🔹 File ID + RichText( + text: TextSpan( + children: [ + TextSpan( + text: 'File ID : ', + style: AppTextStyles.semiBold.copyWith( + fontSize: width * 0.04, + color: const Color(0xFF111827), + ), + ), + TextSpan( + text: '${item.id}', + style: AppTextStyles.medium.copyWith( + fontSize: width * 0.04, + color: const Color(0xFF111827), + ), + ), + ], + ), + ), + + /// 🔥 Status + Chat Icon + Row( + children: [ + /// 🔴 Waiting / Pending / Cancelled + if (item.status == 'Waiting for Admin' || + item.status == 'Payment Pending' || + item.status == 'Cancelled By Admin') + Container( + width: width * 0.4, + height: height * 0.04, + decoration: BoxDecoration( + color: const Color(0xFFFFE8E8), + borderRadius: BorderRadius.circular(5.52), + border: Border.all( + color: const Color(0xFFFFD7D7), + width: 1.84, + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.25), + blurRadius: 7.17, + offset: const Offset(0, 3.68), + ), + ], + ), + child: Center( + child: Text( + item.status.toString(), + style: AppTextStyles.regular.copyWith( + fontSize: 11.03, + color: const Color(0xFFFF0F0F), + ), + ), + ), + ), + + /// 🟢 In Progress + if (item.status == 'In Progress') + Container( + width: 98, + height: 34.9, + decoration: BoxDecoration( + color: const Color(0xFFEAFAE6), + borderRadius: BorderRadius.circular(6.21), + border: Border.all( + color: const Color(0xFFDDFFDD), + width: 2.07, + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.25), + blurRadius: 8.08, + offset: const Offset(0, 4.14), + ), + ], + ), + child: Center( + child: Text( + 'In Progress', + style: AppTextStyles.regular.copyWith( + fontSize: 12.43, + color: const Color(0xFF12800C), + ), + ), + ), + ), + + /// 🟡 Completed + if (item.status == 'Completed') + Container( + width: 87, + height: 31, + decoration: BoxDecoration( + color: const Color(0xFFFAF7E6), + borderRadius: BorderRadius.circular(5.52), + border: Border.all( + color: const Color(0xFFFFE9DD), + width: 1.84, + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.25), + blurRadius: 7.17, + offset: const Offset(0, 3.68), + ), + ], + ), + child: Center( + child: Text( + 'Completed', + style: AppTextStyles.regular.copyWith( + fontSize: 11.03, + color: const Color(0xFFFF630F), + ), + ), + ), + ), + + /// 🔥 Space between status & icon + const SizedBox(width: 8), + if ((item.status == 'Completed' || + item.status == 'Cancelled By Admin' || + item.status == 'In Progress') && + item.chatId != null && + item.chatId.toString().isNotEmpty && + item.chatId.toString() != "0") ...[ + + /// 💬 Chat Icon with Badge + Builder(builder: (context) { + final chatIdStr = item.chatId.toString(); + final countAsync = ref.watch(countProvider(chatIdStr)); + + return GestureDetector( + onTap: () { + final chatid = int.tryParse(chatIdStr) ?? 0; + if (chatid == 0) return; + + if (item.status == 'In Progress') { + Get.to(() => LiveChatScreen( + fileid: item.id.toString(), + chatid: chatid, + ))?.then((_) { + ref.read(notificationTriggerProvider.notifier).state++; + ref.invalidate(chatMessagesProvider(chatid)); + ref.invalidate(countProvider(chatIdStr)); + }); + } else { + Get.to(() => CompletedLiveChatScreen( + fileid: item.id.toString(), + chatid: chatid, + ))?.then((_) { + ref.read(notificationTriggerProvider.notifier).state++; + ref.invalidate(chatMessagesProvider(chatid)); + ref.invalidate(countProvider(chatIdStr)); + }); + } + }, + + child: Stack( + clipBehavior: Clip.none, + children: [ + /// 🔵 Chat Icon + Container( + width: 39, + height: 39, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: const Color(0xFF61277A), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.25), + blurRadius: 8.08, + offset: const Offset(0, 4.14), + ), + ], + ), + child: const Center( + child: Icon( + Icons.message, + color: Colors.white, + size: 20, + ), + ), + ), + + /// 🔴 Badge + countAsync.when( + data: (countData) => countData.count > 0 + ? Positioned( + top: -4, + right: -4, + child: Container( + padding: const EdgeInsets.all(4), + decoration: const BoxDecoration( + color: Colors.red, + shape: BoxShape.circle, + ), + constraints: const BoxConstraints( + minWidth: 16, + minHeight: 16, + ), + child: Text( + '${countData.count}', + textAlign: TextAlign.center, + style: const TextStyle( + color: Colors.white, + fontSize: 10, + fontWeight: FontWeight.bold, ), ), + ), + ) + : const SizedBox.shrink(), + loading: () => const SizedBox.shrink(), + error: (_, __) => const SizedBox.shrink(), + ), + ], + ), + ); + }), - // 🔹 Status Badges - if (item.status == 'Waiting for Admin' || - item.status == 'Payment Pending' || - item.status == 'Cancelled By Admin') - Container( - width: width * 0.3, - height: height * 0.04, - decoration: BoxDecoration( - color: const Color(0xFFFFE8E8), - borderRadius: BorderRadius.circular(5.52), - border: Border.all( - color: const Color(0xFFFFD7D7), - width: 1.84, - ), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.25), - blurRadius: 7.17, - offset: const Offset(0, 3.68), - ), - ], - ), - child: Center( - child: Text( - item.status.toString(), - style: const TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w400, - fontSize: 11.03, - color: Color(0xFFFF0F0F), - ), - ), - ), - ), - - // 🔹 In Progress Badge - if (item.status == 'In Progress') - Container( - width: 98, - height: 34.9, - decoration: BoxDecoration( - color: const Color(0xFFEAFAE6), - borderRadius: BorderRadius.circular(6.21), - border: Border.all( - color: const Color(0xFFDDFFDD), - width: 2.07, - ), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.25), - blurRadius: 8.08, - offset: const Offset(0, 4.14), - ), - ], - ), - child: const Center( - child: Text( - 'In Progress', - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w400, - fontSize: 12.43, - letterSpacing: 0.03, - color: Color(0xFF12800C), - ), - ), - ), - ), - - // 🔹 Completed Badge - if (item.status == 'Completed') - Container( - width: 87, - height: 31, - decoration: BoxDecoration( - color: const Color(0xFFFAF7E6), - borderRadius: BorderRadius.circular(5.52), - border: Border.all( - color: const Color(0xFFFFE9DD), - width: 1.84, - ), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.25), - blurRadius: 7.17, - offset: const Offset(0, 3.68), - ), - ], - ), - child: const Center( - child: Text( - 'Completed', - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w400, - fontSize: 11.03, - letterSpacing: 0.03, - color: Color(0xFFFF630F), - ), - ), - ), - ), - ], - ), + ], + ] + ), + ], +), SizedBox(height: height * 0.015), @@ -198,18 +311,14 @@ class PendingScreen extends ConsumerWidget { children: [ TextSpan( text: 'Request Type : ', - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, - fontSize: width * 0.035, - color: const Color(0xFF111827), - ), + style: AppTextStyles.semiBold.copyWith( + fontSize: width * 0.035, + color: const Color(0xFF111827), + ), ), TextSpan( text: item.service, - style: TextStyle( - fontFamily: 'Gilroy-Medium', - fontWeight: FontWeight.w500, + style: AppTextStyles.medium.copyWith( fontSize: width * 0.035, color: const Color(0xFF111827), ), @@ -228,21 +337,17 @@ class PendingScreen extends ConsumerWidget { children: [ TextSpan( text: 'Date : ', - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, - fontSize: width * 0.035, - color: const Color(0xFF111827), - ), + style: AppTextStyles.semiBold.copyWith( + fontSize: width * 0.035, + color: const Color(0xFF111827), + ), ), TextSpan( text: item.createdDate, - style: TextStyle( - fontFamily: 'Gilroy-Medium', - fontWeight: FontWeight.w500, - fontSize: width * 0.035, - color: const Color(0xFF111827), - ), + style: AppTextStyles.medium.copyWith( + fontSize: width * 0.035, + color: const Color(0xFF111827), + ), ), ], ), @@ -253,21 +358,17 @@ class PendingScreen extends ConsumerWidget { children: [ TextSpan( text: 'Time : ', - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, - fontSize: width * 0.035, - color: const Color(0xFF111827), - ), + style: AppTextStyles.semiBold.copyWith( + fontSize: width * 0.035, + color: const Color(0xFF111827), + ), ), TextSpan( text: item.createdTime, - style: TextStyle( - fontFamily: 'Gilroy-Medium', - fontWeight: FontWeight.w500, - fontSize: width * 0.035, - color: const Color(0xFF111827), - ), + style: AppTextStyles.medium.copyWith( + fontSize: width * 0.035, + color: const Color(0xFF111827), + ), ), ], ), @@ -283,18 +384,14 @@ class PendingScreen extends ConsumerWidget { children: [ TextSpan( text: 'Message : ', - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, - fontSize: width * 0.035, - color: const Color(0xFF111827), - ), + style: AppTextStyles.semiBold.copyWith( + fontSize: width * 0.035, + color: const Color(0xFF111827), + ), ), TextSpan( text: item.message, - style: TextStyle( - fontFamily: 'Gilroy-Medium', - fontWeight: FontWeight.w500, + style: AppTextStyles.medium.copyWith( fontSize: width * 0.035, color: const Color(0xFF111827), ), @@ -314,9 +411,7 @@ class PendingScreen extends ConsumerWidget { children: [ Text( "Total Amount", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: width * 0.038, color: const Color(0xFF111827), ), @@ -357,14 +452,12 @@ class PendingScreen extends ConsumerWidget { ), ], ), - child: const Center( + child: Center( child: Text( "Pay Now", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: 16, - color: Color(0xFF61277A), + color: const Color(0xFF61277A), ), ), ), @@ -399,12 +492,10 @@ class PendingScreen extends ConsumerWidget { ), ], ), - child: const Center( + child: Center( child: Text( "View Details", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: 16, color: Colors.white, ), @@ -434,12 +525,10 @@ class PendingScreen extends ConsumerWidget { ], ), alignment: Alignment.center, - child: const Text( + child: Text( "Payment Status: Paid", textAlign: TextAlign.center, - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w400, + style: AppTextStyles.regular.copyWith( fontSize: 12.08, letterSpacing: 0.04, height: 1.3, @@ -474,12 +563,10 @@ class PendingScreen extends ConsumerWidget { ), ], ), - child: const Center( + child: Center( child: Text( "View Details", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: 16, color: Colors.white, ), @@ -519,12 +606,10 @@ class PendingScreen extends ConsumerWidget { ), ], ), - child: const Center( + child: Center( child: Text( "View Details", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: 16, color: Colors.white, ), diff --git a/lib/view/screens/history/serivces_status_screen.dart b/lib/view/screens/history/serivces_status_screen.dart index a22e4c5..caf2359 100644 --- a/lib/view/screens/history/serivces_status_screen.dart +++ b/lib/view/screens/history/serivces_status_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:taxglide/consts/app_style.dart'; import 'package:taxglide/view/screens/history/flitter_popup.dart'; import 'package:taxglide/view/screens/history/pending_screen.dart'; @@ -57,15 +58,13 @@ class _ServicesStatusScreenState extends ConsumerState children: [ const SizedBox(width: 40), // ✅ Left side space balance // ✅ CENTER TITLE - const Text( - "Service Status", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, - fontSize: 24, - color: Color(0xFF111827), - ), - ), + Text( + "My Task Status", + style: AppTextStyles.bold.copyWith( + fontSize: 24, + color: Color(0xFF111827), + ), + ), // ✅ RIGHT SIDE FILTER ICON GestureDetector( @@ -126,21 +125,17 @@ class _ServicesStatusScreenState extends ConsumerState ), labelColor: const Color(0xFF5F297B), unselectedLabelColor: const Color(0xFF6C7278), - labelStyle: const TextStyle( - fontFamily: "Gilroy-SemiBold", - fontWeight: FontWeight.w600, + labelStyle: AppTextStyles.semiBold.copyWith( fontSize: 16, height: 1.4, letterSpacing: 0.03, - color: Color(0xFF5F297B), + color: const Color(0xFF5F297B), ), - unselectedLabelStyle: const TextStyle( - fontFamily: "Gilroy-Regular", - fontWeight: FontWeight.w400, + unselectedLabelStyle: AppTextStyles.regular.copyWith( fontSize: 16, height: 1.4, letterSpacing: 0.03, - color: Color(0xFF6C7278), + color: const Color(0xFF6C7278), ), tabs: _tabs.map((tab) => Tab(text: tab)).toList(), ), diff --git a/lib/view/screens/home_screen.dart b/lib/view/screens/home_screen.dart index 515969a..0d71e77 100644 --- a/lib/view/screens/home_screen.dart +++ b/lib/view/screens/home_screen.dart @@ -1,9 +1,12 @@ +import 'dart:ui'; + import 'package:carousel_slider/carousel_options.dart'; import 'package:carousel_slider/carousel_slider.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:get/get.dart'; import 'package:taxglide/consts/app_asstes.dart'; +import 'package:taxglide/consts/app_style.dart'; import 'package:taxglide/consts/app_colors.dart'; import 'package:taxglide/consts/comman_serivce.dart'; import 'package:taxglide/consts/home_page_headers.dart'; @@ -128,6 +131,29 @@ class _HomeScreenState extends ConsumerState { final kycTitleSize = r.fontSize(mobile: 16, tablet: 18, desktop: 20); final kycBodySize = r.fontSize(mobile: 10, tablet: 10, desktop: 11); + // My Task Status Button responsive values + final taskStatusGlowWidth = r.getValue( + mobile: 200, + tablet: 220, + desktop: 240, + ); + final taskStatusGlowHeight = r.getValue( + mobile: 45, + tablet: 50, + desktop: 55, + ); + final taskStatusButtonWidth = r.getValue( + mobile: 197.45, + tablet: 215, + desktop: 235, + ); + final taskStatusButtonHeight = r.getValue( + mobile: 41.38, + tablet: 46, + desktop: 50, + ); + final taskStatusFontSize = r.fontSize(mobile: 18, tablet: 20, desktop: 22); + return SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -141,8 +167,8 @@ class _HomeScreenState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ // KYC Banner - if (dashboard.isKycCompleted == false) - Container( + if (dashboard.isKycCompleted == true)...[ + Container( width: double.infinity, padding: EdgeInsets.all(cardPadding), decoration: const BoxDecoration( @@ -162,8 +188,7 @@ class _HomeScreenState extends ConsumerState { children: [ Text( "KYC Details", - style: TextStyle( - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: kycTitleSize, color: Colors.white, ), @@ -171,7 +196,7 @@ class _HomeScreenState extends ConsumerState { const SizedBox(height: 8), Text( "Your KYC verification couldn't be completed. Please complete your KYC details to continue exploring more features.", - style: TextStyle( + style: AppTextStyles.regular.copyWith( fontSize: kycBodySize, color: const Color(0xFFFFFFCC), ), @@ -213,15 +238,85 @@ class _HomeScreenState extends ConsumerState { ), ), - SizedBox( + SizedBox( + height: r.spacing(mobile: 26, tablet: 28, desktop: 32), + ),], + + + +Center( + child: Stack( + alignment: Alignment.center, + children: [ + /// 🌈 Gradient + Blur Glow + ClipRRect( + borderRadius: BorderRadius.circular(40), + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: 16.54, + sigmaY: 16.54, + ), + child: Container( + width: taskStatusGlowWidth, + height: taskStatusGlowHeight, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(40), + gradient: const LinearGradient( + colors: [ + Color(0xFF7BFAFA), + Color(0xFF8BF0FD), + Color(0xFF8EFFD5), + Color(0xFFC8F895), + Color(0xFFE7F392), + Color(0xFFFBE28D), + Color(0xFFFCDAB1), + Color(0xFFFEDCAA), + ], + ), + ), + ), + ), + ), + + /// 🔲 Black Button + GestureDetector( + onTap: () { + Get.offAll( + () => const MainController( + initialIndex: 2, + sourceTabIndex: 0, + ), + ); + }, + child: Container( + width: taskStatusButtonWidth, + height: taskStatusButtonHeight, + alignment: Alignment.center, + decoration: BoxDecoration( + color: const Color(0xFF000000), + borderRadius: BorderRadius.circular(39.02), + ), + child: Text( + "My Task Status", + textAlign: TextAlign.center, + style: AppTextStyles.semiBold.copyWith( + fontSize: taskStatusFontSize, + height: 1.4, + letterSpacing: 0.03 * taskStatusFontSize, + color: Colors.white, + ), + ), + ), + ), + ], + ), +), + SizedBox( height: r.spacing(mobile: 26, tablet: 28, desktop: 32), ), - Text( "List of Services Offered", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.bold.copyWith( fontSize: sectionTitleSize, height: 1.3, letterSpacing: 0.72, @@ -295,9 +390,7 @@ class _HomeScreenState extends ConsumerState { children: [ Text( "Booking List", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.bold.copyWith( fontSize: bookingTitleSize, height: 1.4, letterSpacing: 0.6, @@ -314,9 +407,7 @@ class _HomeScreenState extends ConsumerState { }, child: Text( "See All", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: seeAllSize, height: 1.4, letterSpacing: 0.45, @@ -369,18 +460,14 @@ class _HomeScreenState extends ConsumerState { children: [ TextSpan( text: 'File ID : ', - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: fileIdSize, color: const Color(0xFF111827), ), ), TextSpan( text: '${item.id}', - style: TextStyle( - fontFamily: 'Gilroy-Medium', - fontWeight: FontWeight.w500, + style: AppTextStyles.medium.copyWith( fontSize: fileIdSize, color: const Color(0xFF111827), ), @@ -426,9 +513,7 @@ class _HomeScreenState extends ConsumerState { child: Center( child: Text( item.status.toString(), - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w400, + style: AppTextStyles.regular.copyWith( fontSize: r.fontSize( mobile: 10, tablet: 11, @@ -474,9 +559,7 @@ class _HomeScreenState extends ConsumerState { child: Center( child: Text( 'In Progress', - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w400, + style: AppTextStyles.regular.copyWith( fontSize: r.fontSize( mobile: 11, tablet: 12.43, @@ -523,9 +606,7 @@ class _HomeScreenState extends ConsumerState { child: Center( child: Text( 'Completed', - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w400, + style: AppTextStyles.regular.copyWith( fontSize: r.fontSize( mobile: 10, tablet: 11.03, @@ -548,18 +629,14 @@ class _HomeScreenState extends ConsumerState { children: [ TextSpan( text: 'Request Type : ', - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: labelSize, color: const Color(0xFF111827), ), ), TextSpan( text: item.service, - style: TextStyle( - fontFamily: 'Gilroy-Medium', - fontWeight: FontWeight.w500, + style: AppTextStyles.medium.copyWith( fontSize: valueSize, color: const Color(0xFF111827), ), @@ -578,18 +655,14 @@ class _HomeScreenState extends ConsumerState { children: [ TextSpan( text: 'Date : ', - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: labelSize, color: const Color(0xFF111827), ), ), TextSpan( text: item.createdDate, - style: TextStyle( - fontFamily: 'Gilroy-Medium', - fontWeight: FontWeight.w500, + style: AppTextStyles.medium.copyWith( fontSize: valueSize, color: const Color(0xFF111827), ), @@ -609,18 +682,14 @@ class _HomeScreenState extends ConsumerState { children: [ TextSpan( text: 'Time : ', - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: labelSize, color: const Color(0xFF111827), ), ), TextSpan( text: item.createdTime, - style: TextStyle( - fontFamily: 'Gilroy-Medium', - fontWeight: FontWeight.w500, + style: AppTextStyles.medium.copyWith( fontSize: valueSize, color: const Color(0xFF111827), ), @@ -639,18 +708,14 @@ class _HomeScreenState extends ConsumerState { children: [ TextSpan( text: 'Message : ', - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: labelSize, color: const Color(0xFF111827), ), ), TextSpan( text: item.message, - style: TextStyle( - fontFamily: 'Gilroy-Medium', - fontWeight: FontWeight.w500, + style: AppTextStyles.medium.copyWith( fontSize: valueSize, color: const Color(0xFF111827), ), @@ -671,18 +736,15 @@ class _HomeScreenState extends ConsumerState { children: [ Text( "Total Amount", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: labelSize, color: const Color(0xFF111827), ), ), Text( '₹ ${item.paymentAmount.toString()}', - style: TextStyle( + style: AppTextStyles.semiBold.copyWith( fontFamily: 'Roboto', - fontWeight: FontWeight.w600, fontSize: amountSize, color: const Color(0xFF111827), ), @@ -719,9 +781,7 @@ class _HomeScreenState extends ConsumerState { child: Center( child: Text( "Pay Now", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: buttonTextSize, color: const Color(0xFF61277A), ), @@ -773,9 +833,7 @@ class _HomeScreenState extends ConsumerState { child: Center( child: Text( "View Details", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: buttonTextSize, color: Colors.white, ), @@ -821,9 +879,7 @@ class _HomeScreenState extends ConsumerState { child: Text( "Payment Status: Paid", textAlign: TextAlign.center, - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w400, + style: AppTextStyles.regular.copyWith( fontSize: r.fontSize( mobile: 11, tablet: 12.08, @@ -872,9 +928,7 @@ class _HomeScreenState extends ConsumerState { child: Center( child: Text( "View Details", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: buttonTextSize, color: Colors.white, ), @@ -922,9 +976,7 @@ class _HomeScreenState extends ConsumerState { child: Center( child: Text( "View Details", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: buttonTextSize, color: Colors.white, ), @@ -1038,11 +1090,9 @@ class _HomeScreenState extends ConsumerState { children: [ Text( "What Our Customers Say", - style: TextStyle( - fontFamily: "Gilroy-SemiBold", + style: AppTextStyles.semiBold.copyWith( fontSize: reviewTitleSize, color: Colors.white, - fontWeight: FontWeight.w600, ), ), ], @@ -1084,7 +1134,7 @@ class _HomeScreenState extends ConsumerState { ], ), - const SizedBox(height: 200), + SizedBox(height: r.spacing(mobile: 120, tablet: 140, desktop: 160)), ], ), ); @@ -1129,15 +1179,14 @@ class _HomeScreenState extends ConsumerState { children: [ Text( name, - style: TextStyle( + style: AppTextStyles.bold.copyWith( fontSize: r.fontSize(mobile: 13, tablet: 15, desktop: 16), - fontWeight: FontWeight.w700, color: Colors.black87, ), ), Text( position, - style: TextStyle( + style: AppTextStyles.regular.copyWith( fontSize: r.fontSize(mobile: 10, tablet: 11, desktop: 12), color: Colors.grey, ), @@ -1164,7 +1213,7 @@ class _HomeScreenState extends ConsumerState { maxLines: 4, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, - style: TextStyle( + style: AppTextStyles.regular.copyWith( fontSize: r.fontSize(mobile: 11, tablet: 12, desktop: 13), height: 1.3, color: Colors.black87, @@ -1180,13 +1229,12 @@ class _HomeScreenState extends ConsumerState { width: 40, height: 40, decoration: BoxDecoration(color: iconColor, shape: BoxShape.circle), - child: const Center( + child: Center( child: Text( "❝", - style: TextStyle( + style: AppTextStyles.bold.copyWith( color: Colors.white, fontSize: 22, - fontWeight: FontWeight.bold, ), ), ), diff --git a/lib/view/screens/list_service_screen.dart b/lib/view/screens/list_service_screen.dart index 8c770cf..5d9df96 100644 --- a/lib/view/screens/list_service_screen.dart +++ b/lib/view/screens/list_service_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:taxglide/consts/app_style.dart'; import 'package:taxglide/consts/app_asstes.dart'; import 'package:taxglide/consts/comman_serivce.dart'; import 'package:taxglide/controller/api_contoller.dart'; @@ -38,9 +39,7 @@ class _ListServiceScreenState extends ConsumerState { children: [ Text( "List of Services Offered", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontStyle: FontStyle.normal, fontSize: 18, height: 1.3, @@ -54,14 +53,13 @@ class _ListServiceScreenState extends ConsumerState { serviceAsync.when( data: (services) { if (services.isEmpty) { - return const Padding( + return Padding( padding: EdgeInsets.only(top: 40), child: Center( child: Text( "No Services Available", - style: TextStyle( + style: AppTextStyles.regular.copyWith( fontSize: 16, - fontFamily: 'Gilroy-SemiBold', color: Colors.black54, ), ), diff --git a/lib/view/screens/notification_screen.dart b/lib/view/screens/notification_screen.dart index a13181a..f14ccc5 100644 --- a/lib/view/screens/notification_screen.dart +++ b/lib/view/screens/notification_screen.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:taxglide/consts/app_style.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:get/get.dart'; import 'package:get/get_core/src/get_main.dart'; @@ -55,11 +56,9 @@ class _NotificationScreenState extends ConsumerState { child: Stack( alignment: Alignment.center, children: [ - const Text( + Text( "Notification", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: 24, color: Color(0xFF111827), ), @@ -126,9 +125,7 @@ class _NotificationScreenState extends ConsumerState { children: [ Text( item.title, - style: const TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w700, + style: AppTextStyles.bold.copyWith( fontSize: 16, color: Colors.black, ), @@ -136,12 +133,12 @@ class _NotificationScreenState extends ConsumerState { const SizedBox(height: 4), Text( item.description, - style: const TextStyle(fontSize: 14, color: Colors.grey), + style: AppTextStyles.regular.copyWith(fontSize: 14, color: Colors.grey), ), const SizedBox(height: 8), Text( item.createdAt, - style: const TextStyle(fontSize: 12, color: Colors.grey), + style: AppTextStyles.regular.copyWith(fontSize: 12, color: Colors.grey), ), ], ), diff --git a/lib/view/screens/profile/employee_profile/employee_profile_list.dart b/lib/view/screens/profile/employee_profile/employee_profile_list.dart index b019906..bbd4f5a 100644 --- a/lib/view/screens/profile/employee_profile/employee_profile_list.dart +++ b/lib/view/screens/profile/employee_profile/employee_profile_list.dart @@ -71,7 +71,7 @@ class _EmployeeProfileListState extends ConsumerState { "KYC Details", textAlign: TextAlign.center, style: TextStyle( - fontFamily: 'Gilroy-SemiBold', + fontFamily: "Gilroy", fontWeight: FontWeight .w600, // equivalent to 400 (Normal) fontStyle: FontStyle.normal, @@ -117,7 +117,7 @@ class _EmployeeProfileListState extends ConsumerState { const Text( "Personal Details", style: TextStyle( - fontFamily: 'Gilroy-SemiBold', + fontFamily: "Gilroy", fontSize: 20, color: Color(0xFF111827), ), @@ -202,7 +202,7 @@ class _EmployeeProfileListState extends ConsumerState { Text( title, style: const TextStyle( - fontFamily: 'Gilroy-SemiBold', + fontFamily: "Gilroy", fontWeight: FontWeight.w600, fontSize: 14, height: 1.3, @@ -216,7 +216,7 @@ class _EmployeeProfileListState extends ConsumerState { softWrap: true, overflow: TextOverflow.visible, style: const TextStyle( - fontFamily: 'Gilroy-Regular', + fontFamily: "Gilroy", fontWeight: FontWeight.w400, fontSize: 14, height: 1.3, diff --git a/lib/view/screens/profile/employee_profile/employee_profile_screen.dart b/lib/view/screens/profile/employee_profile/employee_profile_screen.dart index 1976092..3747358 100644 --- a/lib/view/screens/profile/employee_profile/employee_profile_screen.dart +++ b/lib/view/screens/profile/employee_profile/employee_profile_screen.dart @@ -22,6 +22,12 @@ class _EmployeeProfileScreenState extends ConsumerState { String? selectedItem; bool _isLoggingOut = false; + @override + void initState() { + super.initState(); + selectedItem = 'profile'; // Default selection + } + @override Widget build(BuildContext context) { final profileAsync = ref.watch(employeeProfileProvider); @@ -110,7 +116,7 @@ class _EmployeeProfileScreenState extends ConsumerState { Text( data?.name ?? 'No Company Name', style: const TextStyle( - fontFamily: 'Gilroy-SemiBold', + fontFamily: "Gilroy", fontWeight: FontWeight.w600, fontSize: 20, color: Colors.black, @@ -244,7 +250,7 @@ class _EmployeeProfileScreenState extends ConsumerState { const Text( "Note", style: TextStyle( - fontFamily: 'Gilroy-SemiBold', + fontFamily: "Gilroy", fontWeight: FontWeight.w600, fontSize: 20, color: Colors.black, @@ -254,7 +260,7 @@ class _EmployeeProfileScreenState extends ConsumerState { "Are you sure you want to log out?", textAlign: TextAlign.center, style: TextStyle( - fontFamily: 'Gilroy-SemiBold', + fontFamily: "Gilroy", fontWeight: FontWeight.w500, fontSize: 15, color: Colors.black87, @@ -270,7 +276,7 @@ class _EmployeeProfileScreenState extends ConsumerState { style: TextStyle( color: Color(0xFF535A5B), fontSize: 16, - fontFamily: 'Gilroy-SemiBold', + fontFamily: "Gilroy", fontWeight: FontWeight.w600, ), ), @@ -321,7 +327,7 @@ class _EmployeeProfileScreenState extends ConsumerState { style: TextStyle( color: Color(0xFF5F297B), fontSize: 16, - fontFamily: 'Gilroy-SemiBold', + fontFamily: "Gilroy", fontWeight: FontWeight.w600, ), ), @@ -384,7 +390,7 @@ class _EmployeeProfileScreenState extends ConsumerState { child: Text( title, style: const TextStyle( - fontFamily: 'Gilroy-Medium', + fontFamily: "Gilroy", fontSize: 16, fontWeight: FontWeight.w500, color: Colors.black87, diff --git a/lib/view/screens/profile/kyc_details_list.dart b/lib/view/screens/profile/kyc_details_list.dart index b4ea404..ba368e1 100644 --- a/lib/view/screens/profile/kyc_details_list.dart +++ b/lib/view/screens/profile/kyc_details_list.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:get/get.dart'; import 'package:taxglide/consts/app_asstes.dart'; +import 'package:taxglide/consts/app_style.dart'; import 'package:taxglide/consts/download_helper.dart'; import 'package:taxglide/controller/api_consts.dart'; import 'package:taxglide/controller/api_contoller.dart'; @@ -65,7 +66,7 @@ class _KycDetailsListState extends ConsumerState { "KYC Details", textAlign: TextAlign.center, style: TextStyle( - fontFamily: 'Gilroy-SemiBold', + fontFamily: "Gilroy", fontWeight: FontWeight .w600, // equivalent to 400 (Normal) fontStyle: FontStyle.normal, @@ -115,7 +116,7 @@ class _KycDetailsListState extends ConsumerState { "Personal Details", textAlign: TextAlign.center, style: TextStyle( - fontFamily: 'Gilroy-SemiBold', + fontFamily: "Gilroy", fontWeight: FontWeight .w400, // equivalent to 400 (Normal) fontStyle: FontStyle.normal, @@ -181,7 +182,7 @@ class _KycDetailsListState extends ConsumerState { 'Edit', textAlign: TextAlign.center, style: TextStyle( - fontFamily: 'Gilroy-SemiBold', + fontFamily: "Gilroy", fontWeight: FontWeight.w600, fontStyle: FontStyle.normal, fontSize: 16, @@ -277,9 +278,8 @@ class _KycDetailsListState extends ConsumerState { children: [ Text( title, - style: const TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( + fontSize: 14, height: 1.3, letterSpacing: 0.64, @@ -291,9 +291,8 @@ class _KycDetailsListState extends ConsumerState { value, softWrap: true, overflow: TextOverflow.visible, - style: const TextStyle( - fontFamily: 'Gilroy-Regular', - fontWeight: FontWeight.w400, + style: AppTextStyles.regular.copyWith( + fontSize: 14, height: 1.3, letterSpacing: 0.64, @@ -342,9 +341,9 @@ class _KycDetailsListState extends ConsumerState { maxLines: 1, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, - style: const TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( + + fontSize: 13, ), ), diff --git a/lib/view/screens/profile/profile_screen.dart b/lib/view/screens/profile/profile_screen.dart index 10dc23c..2335a60 100644 --- a/lib/view/screens/profile/profile_screen.dart +++ b/lib/view/screens/profile/profile_screen.dart @@ -1,6 +1,7 @@ import 'dart:math' as math show sqrt; import 'package:flutter/material.dart'; +import 'package:taxglide/consts/app_style.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:get/get.dart'; import 'package:taxglide/consts/app_asstes.dart'; @@ -28,6 +29,7 @@ class _ProfileScreenState extends ConsumerState { @override void initState() { super.initState(); + selectedItem = 'profile'; // Set default selection _loadRole(); // <-- load role on start } @@ -104,10 +106,9 @@ class _ProfileScreenState extends ConsumerState { data!.companyName!.isNotEmpty) ? data.companyName![0].toUpperCase() : "M", - style: const TextStyle( + style: AppTextStyles.bold.copyWith( color: Colors.white, fontSize: 40, - fontWeight: FontWeight.bold, ), ), ), @@ -120,9 +121,7 @@ class _ProfileScreenState extends ConsumerState { const SizedBox(height: 60), Text( data?.companyName ?? 'No Company Name', - style: const TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: 20, color: Colors.black, ), @@ -271,21 +270,17 @@ class _ProfileScreenState extends ConsumerState { child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text( + Text( "Note", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: 20, color: Colors.black, ), ), - const Text( + Text( "Are you sure you want to log out?", textAlign: TextAlign.center, - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w500, + style: AppTextStyles.medium.copyWith( fontSize: 15, color: Colors.black87, ), @@ -295,13 +290,11 @@ class _ProfileScreenState extends ConsumerState { children: [ TextButton( onPressed: () => Navigator.pop(context), - child: const Text( + child: Text( "Cancel", - style: TextStyle( - color: Color(0xFF535A5B), + style: AppTextStyles.semiBold.copyWith( + color: const Color(0xFF535A5B), fontSize: 16, - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, ), ), ), @@ -344,13 +337,11 @@ class _ProfileScreenState extends ConsumerState { color: Color(0xFF5F297B), ), ) - : const Text( + : Text( "Log Out", - style: TextStyle( - color: Color(0xFF5F297B), + style: AppTextStyles.semiBold.copyWith( + color: const Color(0xFF5F297B), fontSize: 16, - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, ), ), ), @@ -411,10 +402,8 @@ class _ProfileScreenState extends ConsumerState { Expanded( child: Text( title, - style: const TextStyle( - fontFamily: 'Gilroy-Medium', + style: AppTextStyles.medium.copyWith( fontSize: 16, - fontWeight: FontWeight.w500, color: Colors.black87, ), ), @@ -445,7 +434,7 @@ class _ProfileScreenState extends ConsumerState { Expanded( child: Text( msg, - style: const TextStyle(color: Colors.white, fontSize: 12), + style: AppTextStyles.regular.copyWith(color: Colors.white, fontSize: 12), maxLines: 2, overflow: TextOverflow.ellipsis, ), diff --git a/lib/view/screens/profile/staff_list_screen.dart b/lib/view/screens/profile/staff_list_screen.dart index 01a0401..8e25272 100644 --- a/lib/view/screens/profile/staff_list_screen.dart +++ b/lib/view/screens/profile/staff_list_screen.dart @@ -16,7 +16,7 @@ class _StaffListScreenState extends ConsumerState { final staffAsync = ref.watch(staffListProvider); const commonTextStyle = TextStyle( - fontFamily: "Gilroy-SemiBold", + fontFamily: "Gilroy", fontWeight: FontWeight.w600, fontSize: 14.7, height: 1.30, // 130% @@ -24,7 +24,7 @@ class _StaffListScreenState extends ConsumerState { color: Color(0xFF111827), ); const commonTextStylevalue = TextStyle( - fontFamily: "Gilroy-SemiBold", + fontFamily: "Gilroy", fontWeight: FontWeight.w400, fontSize: 14.7, height: 1.30, // 130% diff --git a/lib/view/screens/serivce_request_screen.dart b/lib/view/screens/serivce_request_screen.dart index ec2379e..218730f 100644 --- a/lib/view/screens/serivce_request_screen.dart +++ b/lib/view/screens/serivce_request_screen.dart @@ -7,6 +7,7 @@ import 'package:permission_handler/permission_handler.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:file_picker/file_picker.dart'; import 'package:path/path.dart' as path; +import 'package:taxglide/consts/app_style.dart'; import 'package:taxglide/consts/comman_button.dart'; import 'package:taxglide/consts/responsive_helper.dart'; import 'package:taxglide/controller/api_contoller.dart'; @@ -71,7 +72,7 @@ class _ServiceRequestScreenState extends ConsumerState { Expanded( child: Text( msg, - style: const TextStyle(color: Colors.white, fontSize: 12), + style: AppTextStyles.regular.copyWith(color: Colors.white, fontSize: 12), overflow: TextOverflow.ellipsis, maxLines: 2, ), @@ -89,11 +90,11 @@ class _ServiceRequestScreenState extends ConsumerState { }); bool isValid = true; - if (_selectedFiles.isEmpty) { - setState(() => _isFileError = true); - _showSnackBar("Please upload at least one file", isError: true); - isValid = false; - } + // if (_selectedFiles.isEmpty) { + // setState(() => _isFileError = true); + // _showSnackBar("Please upload at least one file", isError: true); + // isValid = false; + // } if (!_isTermsAccepted) { setState(() => _isCheckboxError = true); _showSnackBar("Please accept terms and conditions", isError: true); @@ -374,9 +375,7 @@ class _ServiceRequestScreenState extends ConsumerState { children: [ Text( "New Service Request", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: headerFontSize, color: const Color(0xFF111827), ), @@ -424,9 +423,7 @@ class _ServiceRequestScreenState extends ConsumerState { SizedBox(height: spacingMD), Text( widget.service, - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: serviceNameFontSize, color: const Color(0xFF3F3F3F), ), @@ -454,10 +451,8 @@ class _ServiceRequestScreenState extends ConsumerState { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - "File Upload *", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + "File Upload", + style: AppTextStyles.semiBold.copyWith( fontSize: labelFontSize, color: const Color(0xFF111827), ), @@ -527,9 +522,7 @@ class _ServiceRequestScreenState extends ConsumerState { Expanded( child: Text( "I accept the terms and conditions *", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontStyle: FontStyle.normal, fontSize: checkboxFontSize, height: 25.56 / 12.78, @@ -556,7 +549,7 @@ class _ServiceRequestScreenState extends ConsumerState { ), ), - const SizedBox(height: 250), + SizedBox(height: r.spacing(mobile: 40, tablet: 60, desktop: 80)), ], ), ), @@ -568,9 +561,7 @@ class _ServiceRequestScreenState extends ConsumerState { padding: EdgeInsets.symmetric(horizontal: hPadding), child: Text( title, - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: fontSize, color: const Color(0xFF111827), ), @@ -597,9 +588,7 @@ class _ServiceRequestScreenState extends ConsumerState { padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 21), child: Text( text, - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: fontSize, color: const Color(0xFF3F3F3F), ), @@ -619,8 +608,8 @@ class _ServiceRequestScreenState extends ConsumerState { color: Colors.white, borderRadius: BorderRadius.circular(6), border: Border.all( - color: _isFileError ? Colors.red : const Color(0xFFDFDFDF), - width: _isFileError ? 2 : 1, + color: const Color(0xFFDFDFDF), + width: 1, ), ), padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 21), @@ -628,34 +617,22 @@ class _ServiceRequestScreenState extends ConsumerState { children: [ Text( "Upload PDF, IMG, JPG, ZIP", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: fontSize, height: 25.56 / 12.78, letterSpacing: 0.8, - color: _isFileError ? Colors.red : const Color(0xFF4F4C4C), + color: const Color(0xFF4F4C4C), ), ), Text( "(40 MB only allowed File)", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: subFontSize, height: 25.56 / 12.78, letterSpacing: 0.8, - color: _isFileError ? Colors.red : const Color(0xFF4F4C4C), + color: const Color(0xFF4F4C4C), ), ), - if (_isFileError) - const Padding( - padding: EdgeInsets.only(top: 8), - child: Text( - "Please upload at least one file", - style: TextStyle(color: Colors.red, fontSize: 11), - ), - ), ], ), ), @@ -705,9 +682,7 @@ class _ServiceRequestScreenState extends ConsumerState { const SizedBox(width: 8), Text( "Uploaded Files (${_selectedFiles.length})", - style: TextStyle( - fontFamily: 'Gilroy-SemiBold', - fontWeight: FontWeight.w600, + style: AppTextStyles.semiBold.copyWith( fontSize: countFontSize, color: const Color(0xFF111827), ), @@ -894,8 +869,7 @@ class _ServiceRequestScreenState extends ConsumerState { controller: _messageController, maxLines: null, expands: true, - style: TextStyle( - fontFamily: 'Gilroy-Medium', + style: AppTextStyles.regular.copyWith( fontSize: fontSize, color: const Color(0xFF6C7278), ), diff --git a/pubspec.lock b/pubspec.lock index fdde2f4..b9af59f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d" + sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f url: "https://pub.dev" source: hosted - version: "93.0.0" + version: "85.0.0" _flutterfire_internals: dependency: transitive description: @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: analyzer - sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b + sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d" url: "https://pub.dev" source: hosted - version: "10.0.1" + version: "7.7.1" animated_notch_bottom_bar: dependency: "direct main" description: @@ -101,10 +101,10 @@ packages: dependency: transitive description: name: characters - sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.4.0" checked_yaml: dependency: transitive description: @@ -185,6 +185,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + curved_labeled_navigation_bar: + dependency: "direct main" + description: + name: curved_labeled_navigation_bar + sha256: "936d8a34128478498e330588dbed3fdd7a6cc55ebada6e67340c080387e1a37e" + url: "https://pub.dev" + source: hosted + version: "2.0.6" dbus: dependency: transitive description: @@ -708,26 +716,26 @@ packages: dependency: transitive description: name: matcher - sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.18" + version: "0.12.17" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.13.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.16.0" mime: dependency: transitive description: @@ -1113,26 +1121,26 @@ packages: dependency: transitive description: name: test - sha256: "54c516bbb7cee2754d327ad4fca637f78abfc3cbcc5ace83b3eda117e42cd71a" + sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb" url: "https://pub.dev" source: hosted - version: "1.29.0" + version: "1.26.2" test_api: dependency: transitive description: name: test_api - sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.9" + version: "0.7.6" test_core: dependency: transitive description: name: test_core - sha256: "394f07d21f0f2255ec9e3989f21e54d3c7dc0e6e9dbce160e5a9c1a6be0e2943" + sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a" url: "https://pub.dev" source: hosted - version: "0.6.15" + version: "0.6.11" timezone: dependency: transitive description: @@ -1149,6 +1157,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + universal_io: + dependency: transitive + description: + name: universal_io + sha256: f63cbc48103236abf48e345e07a03ce5757ea86285ed313a6a032596ed9301e2 + url: "https://pub.dev" + source: hosted + version: "2.3.1" url_launcher: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index c4b878a..677c539 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,6 +39,7 @@ dependencies: gal: ^2.3.2 web_socket_channel: ^3.0.2 flutter_local_notifications: ^18.0.1 + curved_labeled_navigation_bar: ^2.0.6 @@ -69,55 +70,39 @@ flutter: fonts: + # ========================= + # Gilroy Font Family + # ========================= - family: Gilroy fonts: - - asset: assets/fonts/Gilroy-Thin.ttf - weight: 100 - - asset: assets/fonts/Gilroy-ThinItalic.ttf - weight: 100 - style: italic - - asset: assets/fonts/Gilroy-UltraLight.ttf - weight: 200 - - asset: assets/fonts/Gilroy-UltraLightItalic.ttf - weight: 200 - style: italic - - asset: assets/fonts/Gilroy-Light.ttf - weight: 300 - - asset: assets/fonts/Gilroy-LightItalic.ttf - weight: 300 - style: italic - asset: assets/fonts/Gilroy-Regular.ttf - weight: 400 - - asset: assets/fonts/Gilroy-RegularItalic.ttf - weight: 400 - style: italic + - family: Gilroy-Thin + fonts: + - asset: assets/fonts/Gilroy-Thin.ttf + - family: Gilroy-UltraLight + fonts: + - asset: assets/fonts/Gilroy-UltraLight.ttf + - family: Gilroy-Light + fonts: + - asset: assets/fonts/Gilroy-Light.ttf + - family: Gilroy-Regular + fonts: + - asset: assets/fonts/Gilroy-Regular.ttf + - family: Gilroy-Medium + fonts: - asset: assets/fonts/Gilroy-Medium.ttf - weight: 500 - - asset: assets/fonts/Gilroy-MediumItalic.ttf - weight: 500 - style: italic + - family: Gilroy-SemiBold + fonts: - asset: assets/fonts/Gilroy-SemiBold.ttf - weight: 600 - - asset: assets/fonts/Gilroy-SemiBoldItalic.ttf - weight: 600 - style: italic + - family: Gilroy-Bold + fonts: - asset: assets/fonts/Gilroy-Bold.ttf - weight: 700 - - asset: assets/fonts/Gilroy-BoldItalic.ttf - weight: 700 - style: italic + - family: Gilroy-ExtraBold + fonts: - asset: assets/fonts/Gilroy-ExtraBold.ttf - weight: 800 - - asset: assets/fonts/Gilroy-ExtraBoldItalic.ttf - weight: 800 - style: italic + - family: Gilroy-Black + fonts: - asset: assets/fonts/Gilroy-Black.ttf - weight: 900 - - asset: assets/fonts/Gilroy-BlackItalic.ttf - weight: 900 - style: italic + - family: Gilroy-Heavy + fonts: - asset: assets/fonts/Gilroy-Heavy.ttf - weight: 900 - - asset: assets/fonts/Gilroy-HeavyItalic.ttf - weight: 900 - style: italic