From ba4cbd180af5cad7f1e451ef5e800c43ae8f858d Mon Sep 17 00:00:00 2001 From: vergiLgood1 Date: Mon, 19 May 2025 15:09:05 +0700 Subject: [PATCH] Refactor step indicator implementation and enhance registration form - Removed the old StepIndicator widget and replaced it with a new implementation that supports multiple styles (standard, rounded, numbered). - Added new styles for the StepIndicator: NumberedStepIndicator, RoundedStepIndicator, and StandardStepIndicator. - Updated the registration form screen to utilize the new StepIndicator with improved styling and functionality. - Enhanced the StateScreen widget to adapt to dark mode and utilize constants for spacing and sizes. - Refactored CustomTextField to support dark mode and improved styling. - Introduced a new FormRegistrationScreen for handling user registration with multiple steps and validation. --- .../src/cores/bindings/service_bindings.dart | 5 +- .../lib/src/cores/routes/app_pages.dart | 2 +- .../registraion_form_screen.dart} | 135 ++++++--- .../presentasion/widgets/auth_button.dart | 22 +- .../presentasion/widgets/auth_divider.dart | 27 +- .../presentasion/widgets/auth_header.dart | 8 +- .../presentasion/widgets/otp_input_field.dart | 19 +- .../presentasion/widgets/password_field.dart | 2 + .../presentasion/widgets/social_button.dart | 13 +- .../lib/src/shared/widgets/appbar/tabbar.dart | 48 +++- .../widgets/indicators/step_indicator.dart | 108 ------- .../indicators/step_indicator/index.dart | 4 + .../step_indicator/step_indicator.dart | 66 +++++ .../styles/numbered_step_indicator.dart | 221 +++++++++++++++ .../styles/rounded_step_indicator.dart | 230 +++++++++++++++ .../styles/standard_step_indicator.dart | 263 ++++++++++++++++++ .../state_screeen/secondary_state_screen.dart | 33 ++- .../widgets/text/custom_text_field.dart | 37 ++- 18 files changed, 1027 insertions(+), 216 deletions(-) rename sigap-mobile/lib/src/features/auth/presentasion/pages/{step-form/step_form_screen.dart => registration-form/registraion_form_screen.dart} (74%) delete mode 100644 sigap-mobile/lib/src/shared/widgets/indicators/step_indicator.dart create mode 100644 sigap-mobile/lib/src/shared/widgets/indicators/step_indicator/index.dart create mode 100644 sigap-mobile/lib/src/shared/widgets/indicators/step_indicator/step_indicator.dart create mode 100644 sigap-mobile/lib/src/shared/widgets/indicators/step_indicator/styles/numbered_step_indicator.dart create mode 100644 sigap-mobile/lib/src/shared/widgets/indicators/step_indicator/styles/rounded_step_indicator.dart create mode 100644 sigap-mobile/lib/src/shared/widgets/indicators/step_indicator/styles/standard_step_indicator.dart diff --git a/sigap-mobile/lib/src/cores/bindings/service_bindings.dart b/sigap-mobile/lib/src/cores/bindings/service_bindings.dart index 05d8dfe..1b5abe0 100644 --- a/sigap-mobile/lib/src/cores/bindings/service_bindings.dart +++ b/sigap-mobile/lib/src/cores/bindings/service_bindings.dart @@ -9,15 +9,14 @@ class ServiceBindings extends Bindings { Future dependencies() async { // Initialize background service - final supabaseService = await BackgroundService.instance - .compute((message) => SupabaseService(), null); + final locationService = await BackgroundService.instance .compute((message) => LocationService(), null); final biometricService = await BackgroundService.instance .compute((message) => BiometricService(), null); // Initialize services - await Get.putAsync(() => supabaseService.init(), permanent: true); + await Get.putAsync(() => SupabaseService().init(), permanent: true); await Get.putAsync(() => biometricService.init(), permanent: true); await Get.putAsync(() => locationService.init(), permanent: true); } diff --git a/sigap-mobile/lib/src/cores/routes/app_pages.dart b/sigap-mobile/lib/src/cores/routes/app_pages.dart index af7ed34..cdde2a9 100644 --- a/sigap-mobile/lib/src/cores/routes/app_pages.dart +++ b/sigap-mobile/lib/src/cores/routes/app_pages.dart @@ -2,7 +2,7 @@ import 'package:get/get.dart'; import 'package:sigap/src/features/auth/presentasion/pages/forgot-password/forgot_password.dart'; import 'package:sigap/src/features/auth/presentasion/pages/signin/signin_screen.dart'; import 'package:sigap/src/features/auth/presentasion/pages/signup/signup_screen.dart'; -import 'package:sigap/src/features/auth/presentasion/pages/step-form/step_form_screen.dart'; +import 'package:sigap/src/features/auth/presentasion/pages/registration-form/registraion_form_screen.dart'; import 'package:sigap/src/features/onboarding/presentasion/pages/onboarding/onboarding_screen.dart'; import 'package:sigap/src/features/onboarding/presentasion/pages/role-selection/role_selection_screen.dart'; import 'package:sigap/src/features/onboarding/presentasion/pages/welcome/welcome_screen.dart'; diff --git a/sigap-mobile/lib/src/features/auth/presentasion/pages/step-form/step_form_screen.dart b/sigap-mobile/lib/src/features/auth/presentasion/pages/registration-form/registraion_form_screen.dart similarity index 74% rename from sigap-mobile/lib/src/features/auth/presentasion/pages/step-form/step_form_screen.dart rename to sigap-mobile/lib/src/features/auth/presentasion/pages/registration-form/registraion_form_screen.dart index 2bca25f..8ed0fd7 100644 --- a/sigap-mobile/lib/src/features/auth/presentasion/pages/step-form/step_form_screen.dart +++ b/sigap-mobile/lib/src/features/auth/presentasion/pages/registration-form/registraion_form_screen.dart @@ -5,9 +5,11 @@ import 'package:sigap/src/features/auth/presentasion/controllers/step_form_contr import 'package:sigap/src/features/auth/presentasion/widgets/auth_button.dart'; import 'package:sigap/src/features/personalization/data/models/index.dart'; import 'package:sigap/src/shared/widgets/dropdown/custom_dropdown.dart'; -import 'package:sigap/src/shared/widgets/indicators/step_indicator.dart'; +import 'package:sigap/src/shared/widgets/indicators/step_indicator/step_indicator.dart'; import 'package:sigap/src/shared/widgets/text/custom_text_field.dart'; import 'package:sigap/src/utils/constants/colors.dart'; +import 'package:sigap/src/utils/constants/sizes.dart'; +import 'package:sigap/src/utils/helpers/helper_functions.dart'; import 'package:sigap/src/utils/validators/validation.dart'; class FormRegistrationScreen extends StatelessWidget { @@ -17,6 +19,7 @@ class FormRegistrationScreen extends StatelessWidget { Widget build(BuildContext context) { // Get the controller final controller = Get.find(); + final dark = THelperFunctions.isDarkMode(context); // Set system overlay style SystemChrome.setSystemUIOverlayStyle( @@ -27,22 +30,25 @@ class FormRegistrationScreen extends StatelessWidget { ); return Scaffold( - backgroundColor: TColors.light, + backgroundColor: dark ? TColors.dark : TColors.light, appBar: AppBar( backgroundColor: Colors.transparent, elevation: 0, title: Obx( () => Text( 'Complete Your ${controller.selectedRole.value?.name ?? ""} Profile', - style: TextStyle( - color: TColors.textPrimary, - fontWeight: FontWeight.bold, - ), + style: Theme.of( + context, + ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), ), ), centerTitle: true, leading: IconButton( - icon: Icon(Icons.arrow_back, color: TColors.textPrimary), + icon: Icon( + Icons.arrow_back, + color: dark ? TColors.white : TColors.black, + size: TSizes.iconMd, + ), onPressed: () => Get.back(), ), ), @@ -57,13 +63,14 @@ class FormRegistrationScreen extends StatelessWidget { children: [ // Step indicator Padding( - padding: const EdgeInsets.all(24.0), + padding: const EdgeInsets.all(TSizes.defaultSpace), child: Obx( () => StepIndicator( currentStep: controller.currentStep.value, totalSteps: controller.stepFormKeys.length, stepTitles: _getStepTitles(controller.selectedRole.value!), onStepTapped: controller.goToStep, + style: StepIndicatorStyle.standard, ), ), ), @@ -72,7 +79,7 @@ class FormRegistrationScreen extends StatelessWidget { Expanded( child: SingleChildScrollView( child: Padding( - padding: const EdgeInsets.all(24.0), + padding: const EdgeInsets.all(TSizes.defaultSpace), child: Obx(() { return _buildStepContent(controller); }), @@ -82,7 +89,7 @@ class FormRegistrationScreen extends StatelessWidget { // Navigation buttons Padding( - padding: const EdgeInsets.all(24.0), + padding: const EdgeInsets.all(TSizes.defaultSpace), child: Row( children: [ // Back button @@ -91,7 +98,9 @@ class FormRegistrationScreen extends StatelessWidget { controller.currentStep.value > 0 ? Expanded( child: Padding( - padding: const EdgeInsets.only(right: 8.0), + padding: const EdgeInsets.only( + right: TSizes.sm, + ), child: AuthButton( text: 'Previous', onPressed: controller.previousStep, @@ -106,7 +115,10 @@ class FormRegistrationScreen extends StatelessWidget { Expanded( child: Padding( padding: EdgeInsets.only( - left: controller.currentStep.value > 0 ? 8.0 : 0.0, + left: + controller.currentStep.value > 0 + ? TSizes.sm + : 0.0, ), child: Obx( () => AuthButton( @@ -135,7 +147,7 @@ class FormRegistrationScreen extends StatelessWidget { if (role.isOfficer) { return ['Personal', 'Officer Info', 'Unit Info']; } else { - return ['Personal', 'Emergency']; + return ['Personal', 'Identity']; } } @@ -148,7 +160,7 @@ class FormRegistrationScreen extends StatelessWidget { case 1: return isOfficer ? _buildOfficerInfoStep(controller) - : _buildEmergencyContactStep(controller); + : _buildPrivacyIdentity(controller); case 2: // This step only exists for officers if (isOfficer) { @@ -169,17 +181,20 @@ class FormRegistrationScreen extends StatelessWidget { Text( 'Personal Information', style: TextStyle( - fontSize: 20, + fontSize: TSizes.fontSizeLg, fontWeight: FontWeight.bold, color: TColors.textPrimary, ), ), - const SizedBox(height: 8), + const SizedBox(height: TSizes.sm), Text( 'Please provide your personal details', - style: TextStyle(fontSize: 14, color: TColors.textSecondary), + style: TextStyle( + fontSize: TSizes.fontSizeSm, + color: TColors.textSecondary, + ), ), - const SizedBox(height: 24), + const SizedBox(height: TSizes.spaceBtwItems), // First Name field Obx( @@ -191,6 +206,11 @@ class FormRegistrationScreen extends StatelessWidget { TValidators.validateUserInput('First name', value, 50), errorText: controller.firstNameError.value, textInputAction: TextInputAction.next, + hintText: 'e.g., John', + onChanged: (value) { + controller.firstNameController.text = value; + controller.firstNameError.value = ''; + }, ), ), @@ -208,6 +228,11 @@ class FormRegistrationScreen extends StatelessWidget { ), errorText: controller.lastNameError.value, textInputAction: TextInputAction.next, + hintText: 'e.g., Doe', + onChanged: (value) { + controller.lastNameController.text = value; + controller.lastNameError.value = ''; + }, ), ), @@ -220,6 +245,11 @@ class FormRegistrationScreen extends StatelessWidget { errorText: controller.phoneError.value, keyboardType: TextInputType.phone, textInputAction: TextInputAction.next, + hintText: 'e.g., 081234567890', + onChanged: (value) { + controller.phoneController.text = value; + controller.phoneError.value = ''; + }, ), ), @@ -234,6 +264,11 @@ class FormRegistrationScreen extends StatelessWidget { errorText: controller.addressError.value, textInputAction: TextInputAction.done, maxLines: 3, + hintText: 'e.g., 123 Main St, City, Country', + onChanged: (value) { + controller.addressController.text = value; + controller.addressError.value = ''; + }, ), ), ], @@ -241,7 +276,7 @@ class FormRegistrationScreen extends StatelessWidget { ); } - Widget _buildEmergencyContactStep(FormRegistrationController controller) { + Widget _buildPrivacyIdentity(FormRegistrationController controller) { return Form( key: controller.stepFormKeys[1], child: Column( @@ -250,17 +285,20 @@ class FormRegistrationScreen extends StatelessWidget { Text( 'Additional Information', style: TextStyle( - fontSize: 20, + fontSize: TSizes.fontSizeLg, fontWeight: FontWeight.bold, color: TColors.textPrimary, ), ), - const SizedBox(height: 8), + const SizedBox(height: TSizes.sm), Text( 'Please provide additional personal details', - style: TextStyle(fontSize: 14, color: TColors.textSecondary), + style: TextStyle( + fontSize: TSizes.fontSizeSm, + color: TColors.textSecondary, + ), ), - const SizedBox(height: 24), + const SizedBox(height: TSizes.spaceBtwItems), // NIK field Obx( @@ -272,6 +310,11 @@ class FormRegistrationScreen extends StatelessWidget { errorText: controller.nikError.value, textInputAction: TextInputAction.next, keyboardType: TextInputType.number, + hintText: 'e.g., 1234567890123456', + onChanged: (value) { + controller.nikController.text = value; + controller.nikError.value = ''; + }, ), ), @@ -291,6 +334,10 @@ class FormRegistrationScreen extends StatelessWidget { textInputAction: TextInputAction.next, maxLines: 3, hintText: 'Tell us a little about yourself (optional)', + onChanged: (value) { + controller.bioController.text = value; + controller.bioError.value = ''; + }, ), ), @@ -306,6 +353,10 @@ class FormRegistrationScreen extends StatelessWidget { textInputAction: TextInputAction.done, keyboardType: TextInputType.datetime, hintText: 'e.g., 1990-01-31', + onChanged: (value) { + controller.birthDateController.text = value; + controller.birthDateError.value = ''; + }, ), ), ], @@ -322,17 +373,20 @@ class FormRegistrationScreen extends StatelessWidget { Text( 'Officer Information', style: TextStyle( - fontSize: 20, + fontSize: TSizes.fontSizeLg, fontWeight: FontWeight.bold, color: TColors.textPrimary, ), ), - const SizedBox(height: 8), + const SizedBox(height: TSizes.sm), Text( 'Please provide your officer details', - style: TextStyle(fontSize: 14, color: TColors.textSecondary), + style: TextStyle( + fontSize: TSizes.fontSizeSm, + color: TColors.textSecondary, + ), ), - const SizedBox(height: 24), + const SizedBox(height: TSizes.spaceBtwItems), // NRP field Obx( @@ -342,6 +396,12 @@ class FormRegistrationScreen extends StatelessWidget { validator: TValidators.validateNRP, errorText: controller.nrpError.value, textInputAction: TextInputAction.next, + keyboardType: TextInputType.number, + hintText: 'e.g., 123456789', + onChanged: (value) { + controller.nrpController.text = value; + controller.nrpError.value = ''; + }, ), ), @@ -353,6 +413,11 @@ class FormRegistrationScreen extends StatelessWidget { validator: TValidators.validateRank, errorText: controller.rankError.value, textInputAction: TextInputAction.done, + hintText: 'e.g., Captain', + onChanged: (value) { + controller.rankController.text = value; + controller.rankError.value = ''; + }, ), ), ], @@ -371,17 +436,20 @@ class FormRegistrationScreen extends StatelessWidget { Text( 'Unit Information', style: TextStyle( - fontSize: 20, + fontSize: TSizes.fontSizeLg, fontWeight: FontWeight.bold, color: TColors.textPrimary, ), ), - const SizedBox(height: 8), + const SizedBox(height: TSizes.sm), Text( 'Please provide your unit details', - style: TextStyle(fontSize: 14, color: TColors.textSecondary), + style: TextStyle( + fontSize: TSizes.fontSizeSm, + color: TColors.textSecondary, + ), ), - const SizedBox(height: 24), + const SizedBox(height: TSizes.spaceBtwItems), // Position field Obx( @@ -391,6 +459,11 @@ class FormRegistrationScreen extends StatelessWidget { validator: TValidators.validatePosition, errorText: controller.positionError.value, textInputAction: TextInputAction.next, + hintText: 'e.g., Commander', + onChanged: (value) { + controller.positionController.text = value; + controller.positionError.value = ''; + }, ), ), diff --git a/sigap-mobile/lib/src/features/auth/presentasion/widgets/auth_button.dart b/sigap-mobile/lib/src/features/auth/presentasion/widgets/auth_button.dart index 77f4b70..45d5c41 100644 --- a/sigap-mobile/lib/src/features/auth/presentasion/widgets/auth_button.dart +++ b/sigap-mobile/lib/src/features/auth/presentasion/widgets/auth_button.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:sigap/src/utils/constants/colors.dart'; - +import 'package:sigap/src/utils/constants/sizes.dart'; class AuthButton extends StatelessWidget { final String text; @@ -20,7 +20,7 @@ class AuthButton extends StatelessWidget { Widget build(BuildContext context) { return SizedBox( width: double.infinity, - height: 56, + height: TSizes.buttonHeight * 3, // Using consistent button height child: isPrimary ? ElevatedButton( @@ -29,16 +29,16 @@ class AuthButton extends StatelessWidget { backgroundColor: TColors.primary, foregroundColor: TColors.white, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), + borderRadius: BorderRadius.circular(TSizes.buttonRadius), ), - elevation: 0, + elevation: TSizes.buttonElevation, disabledBackgroundColor: TColors.primary.withOpacity(0.6), ), child: isLoading ? SizedBox( - width: 24, - height: 24, + width: TSizes.iconMd, + height: TSizes.iconMd, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation( @@ -49,7 +49,7 @@ class AuthButton extends StatelessWidget { : Text( text, style: const TextStyle( - fontSize: 16, + fontSize: TSizes.fontSizeMd, fontWeight: FontWeight.bold, ), ), @@ -60,15 +60,15 @@ class AuthButton extends StatelessWidget { foregroundColor: TColors.primary, side: BorderSide(color: TColors.primary), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), + borderRadius: BorderRadius.circular(TSizes.buttonRadius), ), disabledForegroundColor: TColors.primary.withOpacity(0.6), ), child: isLoading ? SizedBox( - width: 24, - height: 24, + width: TSizes.iconMd, + height: TSizes.iconMd, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation( @@ -79,7 +79,7 @@ class AuthButton extends StatelessWidget { : Text( text, style: const TextStyle( - fontSize: 16, + fontSize: TSizes.fontSizeMd, fontWeight: FontWeight.bold, ), ), diff --git a/sigap-mobile/lib/src/features/auth/presentasion/widgets/auth_divider.dart b/sigap-mobile/lib/src/features/auth/presentasion/widgets/auth_divider.dart index 3cdeca8..e0bfbcb 100644 --- a/sigap-mobile/lib/src/features/auth/presentasion/widgets/auth_divider.dart +++ b/sigap-mobile/lib/src/features/auth/presentasion/widgets/auth_divider.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:sigap/src/utils/constants/colors.dart'; +import 'package:sigap/src/utils/constants/sizes.dart'; class AuthDivider extends StatelessWidget { final String text; @@ -10,15 +11,27 @@ class AuthDivider extends StatelessWidget { Widget build(BuildContext context) { return Row( children: [ - Expanded(child: Divider(color: TColors.borderPrimary, thickness: 1)), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Text( - text, - style: TextStyle(color: TColors.textSecondary, fontSize: 14), + Expanded( + child: Divider( + color: TColors.borderPrimary, + thickness: TSizes.dividerHeight, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: TSizes.md), + child: Text( + text, + style: Theme.of( + context, + ).textTheme.bodyMedium?.copyWith(color: TColors.textSecondary), + ), + ), + Expanded( + child: Divider( + color: TColors.borderPrimary, + thickness: TSizes.dividerHeight, ), ), - Expanded(child: Divider(color: TColors.borderPrimary, thickness: 1)), ], ); } diff --git a/sigap-mobile/lib/src/features/auth/presentasion/widgets/auth_header.dart b/sigap-mobile/lib/src/features/auth/presentasion/widgets/auth_header.dart index 6053cca..fc53be0 100644 --- a/sigap-mobile/lib/src/features/auth/presentasion/widgets/auth_header.dart +++ b/sigap-mobile/lib/src/features/auth/presentasion/widgets/auth_header.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:sigap/src/utils/constants/sizes.dart'; +import 'package:sigap/src/utils/helpers/helper_functions.dart'; class AuthHeader extends StatelessWidget { final String title; @@ -8,6 +10,8 @@ class AuthHeader extends StatelessWidget { @override Widget build(BuildContext context) { + final dark = THelperFunctions.isDarkMode(context); + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -15,12 +19,12 @@ class AuthHeader extends StatelessWidget { title, style: Theme.of(context).textTheme.headlineMedium, ), - const SizedBox(height: 8), + const SizedBox(height: TSizes.sm), Text( subtitle, style: Theme.of(context).textTheme.titleMedium ), - const SizedBox(height: 32), + const SizedBox(height: TSizes.spaceBtwSections), ], ); } diff --git a/sigap-mobile/lib/src/features/auth/presentasion/widgets/otp_input_field.dart b/sigap-mobile/lib/src/features/auth/presentasion/widgets/otp_input_field.dart index c402fce..73f1b67 100644 --- a/sigap-mobile/lib/src/features/auth/presentasion/widgets/otp_input_field.dart +++ b/sigap-mobile/lib/src/features/auth/presentasion/widgets/otp_input_field.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:sigap/src/utils/constants/colors.dart'; +import 'package:sigap/src/utils/constants/sizes.dart'; +import 'package:sigap/src/utils/device/device_utility.dart'; class OtpInputField extends StatelessWidget { final TextEditingController controller; @@ -18,9 +20,13 @@ class OtpInputField extends StatelessWidget { @override Widget build(BuildContext context) { + // Use responsive sizing based on screen width + final width = TDeviceUtils.getScreenWidth(context) * 0.12; + final height = width; // Square aspect ratio + return SizedBox( - width: 60, - height: 60, + width: width, + height: height, child: TextFormField( controller: controller, focusNode: focusNode, @@ -32,8 +38,7 @@ class OtpInputField extends StatelessWidget { LengthLimitingTextInputFormatter(1), FilteringTextInputFormatter.digitsOnly, ], - style: TextStyle( - fontSize: 24, + style: Theme.of(context).textTheme.headlineSmall?.copyWith( fontWeight: FontWeight.bold, color: TColors.textPrimary, ), @@ -42,15 +47,15 @@ class OtpInputField extends StatelessWidget { filled: true, fillColor: TColors.lightContainer, border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(TSizes.borderRadiusMd), borderSide: BorderSide(color: TColors.borderPrimary, width: 1), ), enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(TSizes.borderRadiusMd), borderSide: BorderSide(color: TColors.borderPrimary, width: 1), ), focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(TSizes.borderRadiusMd), borderSide: BorderSide(color: TColors.primary, width: 2), ), ), diff --git a/sigap-mobile/lib/src/features/auth/presentasion/widgets/password_field.dart b/sigap-mobile/lib/src/features/auth/presentasion/widgets/password_field.dart index 201fc87..4b52567 100644 --- a/sigap-mobile/lib/src/features/auth/presentasion/widgets/password_field.dart +++ b/sigap-mobile/lib/src/features/auth/presentasion/widgets/password_field.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:sigap/src/utils/constants/colors.dart'; +import 'package:sigap/src/utils/constants/sizes.dart'; import '../../../../shared/widgets/text/custom_text_field.dart'; @@ -38,6 +39,7 @@ class PasswordField extends StatelessWidget { icon: Icon( isVisible.value ? Icons.visibility_off : Icons.visibility, color: TColors.textSecondary, + size: TSizes.iconMd, ), onPressed: onToggleVisibility, ), diff --git a/sigap-mobile/lib/src/features/auth/presentasion/widgets/social_button.dart b/sigap-mobile/lib/src/features/auth/presentasion/widgets/social_button.dart index 09ecccd..6389e67 100644 --- a/sigap-mobile/lib/src/features/auth/presentasion/widgets/social_button.dart +++ b/sigap-mobile/lib/src/features/auth/presentasion/widgets/social_button.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:sigap/src/utils/constants/colors.dart'; +import 'package:sigap/src/utils/constants/sizes.dart'; +import 'package:sigap/src/utils/helpers/helper_functions.dart'; class SocialButton extends StatelessWidget { final String text; @@ -15,17 +17,18 @@ class SocialButton extends StatelessWidget { @override Widget build(BuildContext context) { + final dark = THelperFunctions.isDarkMode(context); + return SizedBox( width: double.infinity, - height: 56, + height: TSizes.buttonHeight * 3, child: OutlinedButton.icon( onPressed: onPressed, - icon: Icon(icon, color: TColors.textPrimary), + icon: Icon(icon, color: TColors.textPrimary, size: TSizes.iconMd), label: Text( text, - style: TextStyle( + style: Theme.of(context).textTheme.bodyLarge?.copyWith( color: TColors.textPrimary, - fontSize: 16, fontWeight: FontWeight.w500, ), ), @@ -33,7 +36,7 @@ class SocialButton extends StatelessWidget { foregroundColor: TColors.textPrimary, side: BorderSide(color: TColors.borderPrimary), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), + borderRadius: BorderRadius.circular(TSizes.buttonRadius), ), ), ), diff --git a/sigap-mobile/lib/src/shared/widgets/appbar/tabbar.dart b/sigap-mobile/lib/src/shared/widgets/appbar/tabbar.dart index 1f11366..70e4f56 100644 --- a/sigap-mobile/lib/src/shared/widgets/appbar/tabbar.dart +++ b/sigap-mobile/lib/src/shared/widgets/appbar/tabbar.dart @@ -1,6 +1,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:sigap/src/utils/constants/colors.dart'; +import 'package:sigap/src/utils/constants/sizes.dart'; import 'package:sigap/src/utils/device/device_utility.dart'; import 'package:sigap/src/utils/helpers/helper_functions.dart'; @@ -11,7 +12,7 @@ class TabBarApp extends StatelessWidget implements PreferredSizeWidget { this.controller, this.isScrollable = false, this.padding, - this.indicatorColor = TColors.primary, + this.indicatorColor, this.automaticIndicatorColorAdjustment = true, this.indicatorWeight = 2.0, this.indicatorPadding = EdgeInsets.zero, @@ -19,10 +20,10 @@ class TabBarApp extends StatelessWidget implements PreferredSizeWidget { this.indicatorSize, this.dividerColor, this.dividerHeight, - this.labelColor = TColors.primary, + this.labelColor, this.labelStyle, this.labelPadding, - this.unselectedLabelColor = TColors.darkGrey, + this.unselectedLabelColor, this.unselectedLabelStyle, this.dragStartBehavior = DragStartBehavior.start, this.overlayColor, @@ -68,27 +69,35 @@ class TabBarApp extends StatelessWidget implements PreferredSizeWidget { @override Widget build(BuildContext context) { - final dark = THelperFunctions.isDarkMode(context); + final isDark = THelperFunctions.isDarkMode(context); + return Material( - color: dark ? TColors.black : TColors.white, + color: isDark ? TColors.dark : TColors.white, child: TabBar( tabs: tabs, controller: controller, isScrollable: isScrollable, padding: padding, - indicatorColor: indicatorColor, + indicatorColor: indicatorColor ?? TColors.primary, automaticIndicatorColorAdjustment: automaticIndicatorColorAdjustment, indicatorWeight: indicatorWeight, indicatorPadding: indicatorPadding, indicator: indicator, indicatorSize: indicatorSize, dividerColor: dividerColor, - dividerHeight: dividerHeight, - labelColor: labelColor, - labelStyle: labelStyle, - labelPadding: labelPadding, - unselectedLabelColor: unselectedLabelColor, - unselectedLabelStyle: unselectedLabelStyle, + dividerHeight: dividerHeight ?? TSizes.dividerHeight, + labelColor: labelColor ?? TColors.primary, + labelStyle: + labelStyle ?? + Theme.of( + context, + ).textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600), + labelPadding: + labelPadding ?? const EdgeInsets.symmetric(horizontal: TSizes.md), + unselectedLabelColor: + unselectedLabelColor ?? (isDark ? TColors.grey : TColors.darkGrey), + unselectedLabelStyle: + unselectedLabelStyle ?? Theme.of(context).textTheme.bodyMedium, dragStartBehavior: dragStartBehavior, mouseCursor: mouseCursor, enableFeedback: enableFeedback, @@ -98,8 +107,19 @@ class TabBarApp extends StatelessWidget implements PreferredSizeWidget { tabAlignment: tabAlignment, textScaler: textScaler, indicatorAnimation: indicatorAnimation, - overlayColor: WidgetStateProperty.all(Colors.blue.shade50), - splashBorderRadius: splashBorderRadius, + overlayColor: + overlayColor ?? + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.hovered)) { + return TColors.primary.withOpacity(0.1); + } + if (states.contains(WidgetState.pressed)) { + return TColors.primary.withOpacity(0.2); + } + return null; + }), + splashBorderRadius: + splashBorderRadius ?? BorderRadius.circular(TSizes.md), ), ); } diff --git a/sigap-mobile/lib/src/shared/widgets/indicators/step_indicator.dart b/sigap-mobile/lib/src/shared/widgets/indicators/step_indicator.dart deleted file mode 100644 index 569b610..0000000 --- a/sigap-mobile/lib/src/shared/widgets/indicators/step_indicator.dart +++ /dev/null @@ -1,108 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:sigap/src/utils/constants/colors.dart'; - -class StepIndicator extends StatelessWidget { - final int currentStep; - final int totalSteps; - final List stepTitles; - final Function(int) onStepTapped; - - const StepIndicator({ - super.key, - required this.currentStep, - required this.totalSteps, - required this.stepTitles, - required this.onStepTapped, - }); - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Row( - children: List.generate(totalSteps, (index) { - final isActive = index <= currentStep; - final isLast = index == totalSteps - 1; - - return Expanded( - child: Row( - children: [ - // Step circle - GestureDetector( - onTap: () => onStepTapped(index), - child: Container( - width: 32, - height: 32, - decoration: BoxDecoration( - color: isActive ? TColors.primary : TColors.secondary, - shape: BoxShape.circle, - ), - child: Center( - child: - isActive - ? Icon( - index < currentStep - ? Icons.check - : Icons.circle, - color: TColors.white, - size: index < currentStep ? 16 : 12, - ) - : Text( - '${index + 1}', - style: TextStyle( - color: TColors.textSecondary, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ), - - // Connector line - if (!isLast) - Expanded( - child: Container( - height: 2, - color: - index < currentStep - ? TColors.primary - : TColors.borderPrimary, - ), - ), - ], - ), - ); - }), - ), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: List.generate(totalSteps, (index) { - return Expanded( - child: Text( - stepTitles[index], - textAlign: - index == 0 - ? TextAlign.start - : index == totalSteps - 1 - ? TextAlign.end - : TextAlign.center, - style: TextStyle( - fontSize: 12, - fontWeight: - index == currentStep - ? FontWeight.bold - : FontWeight.normal, - color: - index == currentStep - ? TColors.primary - : TColors.textSecondary, - ), - ), - ); - }), - ), - ], - ); - } -} diff --git a/sigap-mobile/lib/src/shared/widgets/indicators/step_indicator/index.dart b/sigap-mobile/lib/src/shared/widgets/indicators/step_indicator/index.dart new file mode 100644 index 0000000..e38769d --- /dev/null +++ b/sigap-mobile/lib/src/shared/widgets/indicators/step_indicator/index.dart @@ -0,0 +1,4 @@ +export 'step_indicator.dart'; +export 'styles/numbered_step_indicator.dart'; +export 'styles/rounded_step_indicator.dart'; +export 'styles/standard_step_indicator.dart'; diff --git a/sigap-mobile/lib/src/shared/widgets/indicators/step_indicator/step_indicator.dart b/sigap-mobile/lib/src/shared/widgets/indicators/step_indicator/step_indicator.dart new file mode 100644 index 0000000..b9c75e1 --- /dev/null +++ b/sigap-mobile/lib/src/shared/widgets/indicators/step_indicator/step_indicator.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:sigap/src/shared/widgets/indicators/step_indicator/styles/numbered_step_indicator.dart'; +import 'package:sigap/src/shared/widgets/indicators/step_indicator/styles/rounded_step_indicator.dart'; +import 'package:sigap/src/shared/widgets/indicators/step_indicator/styles/standard_step_indicator.dart'; +import 'package:sigap/src/utils/helpers/helper_functions.dart'; + +enum StepIndicatorStyle { + standard, // Original style with improvements + rounded, // Rounded connector lines + numbered, // Numbered steps with different styling +} + +class StepIndicator extends StatelessWidget { + final int currentStep; + final int totalSteps; + final List stepTitles; + final Function(int) onStepTapped; + final StepIndicatorStyle style; + + const StepIndicator({ + super.key, + required this.currentStep, + required this.totalSteps, + required this.stepTitles, + required this.onStepTapped, + this.style = StepIndicatorStyle.standard, + }); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final isDark = THelperFunctions.isDarkMode(context); + + // Choose the appropriate style + switch (style) { + case StepIndicatorStyle.rounded: + return RoundedStepIndicator( + currentStep: currentStep, + totalSteps: totalSteps, + stepTitles: stepTitles, + onStepTapped: onStepTapped, + theme: theme, + isDark: isDark, + ); + case StepIndicatorStyle.numbered: + return NumberedStepIndicator( + currentStep: currentStep, + totalSteps: totalSteps, + stepTitles: stepTitles, + onStepTapped: onStepTapped, + theme: theme, + isDark: isDark, + ); + case StepIndicatorStyle.standard: + default: + return StandardStepIndicator( + currentStep: currentStep, + totalSteps: totalSteps, + stepTitles: stepTitles, + onStepTapped: onStepTapped, + theme: theme, + isDark: isDark, + ); + } + } +} diff --git a/sigap-mobile/lib/src/shared/widgets/indicators/step_indicator/styles/numbered_step_indicator.dart b/sigap-mobile/lib/src/shared/widgets/indicators/step_indicator/styles/numbered_step_indicator.dart new file mode 100644 index 0000000..8766f84 --- /dev/null +++ b/sigap-mobile/lib/src/shared/widgets/indicators/step_indicator/styles/numbered_step_indicator.dart @@ -0,0 +1,221 @@ +import 'package:flutter/material.dart'; +import 'package:sigap/src/utils/constants/colors.dart'; +import 'package:sigap/src/utils/constants/sizes.dart'; + +class NumberedStepIndicator extends StatelessWidget { + final int currentStep; + final int totalSteps; + final List stepTitles; + final Function(int) onStepTapped; + final ThemeData theme; + final bool isDark; + + const NumberedStepIndicator({ + super.key, + required this.currentStep, + required this.totalSteps, + required this.stepTitles, + required this.onStepTapped, + required this.theme, + required this.isDark, + }); + + @override + Widget build(BuildContext context) { + // Special case for 2 steps + if (totalSteps == 2) { + return _buildTwoStepIndicator(context); + } + + // Normal case (3+ steps) + return _buildMultiStepIndicator(context); + } + + Widget _buildTwoStepIndicator(BuildContext context) { + return Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: TSizes.md / 2), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // First step + _buildStepBox(0), + + // Connector line + Expanded( + child: Stack( + alignment: Alignment.center, + children: [ + Container( + height: TSizes.dividerHeight, + color: + isDark ? TColors.darkerGrey : TColors.borderPrimary, + ), + if (currentStep > 0) + Container( + height: TSizes.dividerHeight, + color: theme.primaryColor, + ), + ], + ), + ), + + // Second step + _buildStepBox(1), + ], + ), + ), + SizedBox(height: TSizes.sm), + + // Step titles + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: List.generate(totalSteps, (index) { + return Text( + stepTitles[index], + style: theme.textTheme.labelMedium?.copyWith( + fontWeight: + index == currentStep ? FontWeight.bold : FontWeight.normal, + color: + index == currentStep + ? theme.primaryColor + : isDark + ? Colors.white70 + : TColors.textSecondary, + ), + ); + }), + ), + ], + ); + } + + Widget _buildMultiStepIndicator(BuildContext context) { + return Column( + children: [ + Row( + children: List.generate(totalSteps, (index) { + final isLast = index == totalSteps - 1; + + return Expanded( + child: Row( + children: [ + // Step number + _buildStepBox(index), + + // Connector line + if (!isLast) + Expanded( + child: Stack( + alignment: Alignment.center, + children: [ + Container( + height: TSizes.dividerHeight, + color: + isDark + ? TColors.darkerGrey + : TColors.borderPrimary, + ), + Container( + height: TSizes.dividerHeight, + width: + index < currentStep + ? MediaQuery.of(context).size.width + : 0, + color: theme.primaryColor, + ), + ], + ), + ), + ], + ), + ); + }), + ), + SizedBox(height: TSizes.sm), + + // Step titles + Row( + children: List.generate(totalSteps, (index) { + return Expanded( + child: Padding( + padding: EdgeInsets.only( + left: index == 0 ? 0 : TSizes.xs, + right: index == totalSteps - 1 ? 0 : TSizes.xs, + ), + child: Text( + stepTitles[index], + textAlign: + index == 0 + ? TextAlign.start + : index == totalSteps - 1 + ? TextAlign.end + : TextAlign.center, + style: theme.textTheme.labelMedium?.copyWith( + fontWeight: + index == currentStep + ? FontWeight.bold + : FontWeight.normal, + color: + index == currentStep + ? theme.primaryColor + : isDark + ? Colors.white70 + : TColors.textSecondary, + ), + ), + ), + ); + }), + ), + ], + ); + } + + Widget _buildStepBox(int index) { + final isActive = index <= currentStep; + final isCompleted = index < currentStep; + + return GestureDetector( + onTap: () => onStepTapped(index), + child: AnimatedContainer( + duration: const Duration(milliseconds: 300), + width: TSizes.xl + TSizes.xs, + height: TSizes.xl + TSizes.xs, + decoration: BoxDecoration( + color: + isCompleted + ? theme.primaryColor + : isActive + ? theme.primaryColor.withOpacity(0.2) + : isDark + ? TColors.darkerGrey + : TColors.secondary.withOpacity(0.3), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: isActive ? theme.primaryColor : Colors.transparent, + width: 2, + ), + ), + child: Center( + child: + isCompleted + ? Icon(Icons.check, color: Colors.white, size: TSizes.iconSm) + : Text( + '${index + 1}', + style: theme.textTheme.bodyMedium?.copyWith( + color: + isActive + ? theme.primaryColor + : isDark + ? Colors.white + : TColors.textSecondary, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ); + } +} diff --git a/sigap-mobile/lib/src/shared/widgets/indicators/step_indicator/styles/rounded_step_indicator.dart b/sigap-mobile/lib/src/shared/widgets/indicators/step_indicator/styles/rounded_step_indicator.dart new file mode 100644 index 0000000..1146857 --- /dev/null +++ b/sigap-mobile/lib/src/shared/widgets/indicators/step_indicator/styles/rounded_step_indicator.dart @@ -0,0 +1,230 @@ +import 'package:flutter/material.dart'; +import 'package:sigap/src/utils/constants/colors.dart'; +import 'package:sigap/src/utils/constants/sizes.dart'; + +class RoundedStepIndicator extends StatelessWidget { + final int currentStep; + final int totalSteps; + final List stepTitles; + final Function(int) onStepTapped; + final ThemeData theme; + final bool isDark; + + const RoundedStepIndicator({ + super.key, + required this.currentStep, + required this.totalSteps, + required this.stepTitles, + required this.onStepTapped, + required this.theme, + required this.isDark, + }); + + @override + Widget build(BuildContext context) { + // Special case for 2 steps + if (totalSteps == 2) { + return _buildTwoStepIndicator(context); + } + + // Normal case (3+ steps) + return _buildMultiStepIndicator(context); + } + + Widget _buildTwoStepIndicator(BuildContext context) { + return Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: TSizes.md / 2), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // First step + _buildStepCircle(0), + + // Connector line + Expanded( + child: Container( + height: TSizes.dividerHeight, + margin: EdgeInsets.symmetric(horizontal: TSizes.xs), + decoration: BoxDecoration( + color: + currentStep > 0 + ? theme.primaryColor + : isDark + ? TColors.darkerGrey + : TColors.borderPrimary, + borderRadius: BorderRadius.circular( + TSizes.dividerHeight / 2, + ), + ), + ), + ), + + // Second step + _buildStepCircle(1), + ], + ), + ), + SizedBox(height: TSizes.sm), + + // Step titles + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: List.generate(totalSteps, (index) { + return SizedBox( + width: TSizes.xl, + child: Text( + stepTitles[index], + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + style: theme.textTheme.labelMedium?.copyWith( + fontWeight: + index == currentStep + ? FontWeight.bold + : FontWeight.normal, + color: + index == currentStep + ? theme.primaryColor + : isDark + ? Colors.white70 + : TColors.textSecondary, + ), + ), + ); + }), + ), + ], + ); + } + + Widget _buildMultiStepIndicator(BuildContext context) { + return Column( + children: [ + SizedBox( + height: TSizes.xl + TSizes.xs, + child: Stack( + children: [ + // Connector lines first (in the background) + Positioned.fill( + top: TSizes.xl / 2 - TSizes.dividerHeight / 2, + child: Row( + children: List.generate(totalSteps - 1, (index) { + final isActive = index < currentStep; + return Expanded( + child: Container( + height: TSizes.dividerHeight, + margin: EdgeInsets.symmetric(horizontal: TSizes.xs), + decoration: BoxDecoration( + color: + isActive + ? theme.primaryColor + : isDark + ? TColors.darkerGrey + : TColors.borderPrimary, + borderRadius: BorderRadius.circular( + TSizes.dividerHeight / 2, + ), + ), + ), + ); + }), + ), + ), + + // Step circles on top + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: List.generate(totalSteps, (index) { + return _buildStepCircle(index); + }), + ), + ], + ), + ), + SizedBox(height: TSizes.sm), + + // Step titles + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: List.generate(totalSteps, (index) { + return SizedBox( + width: TSizes.xl, + child: Text( + stepTitles[index], + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + style: theme.textTheme.labelMedium?.copyWith( + fontWeight: + index == currentStep + ? FontWeight.bold + : FontWeight.normal, + color: + index == currentStep + ? theme.primaryColor + : isDark + ? Colors.white70 + : TColors.textSecondary, + ), + ), + ); + }), + ), + ], + ); + } + + Widget _buildStepCircle(int index) { + final isActive = index <= currentStep; + return Container( + width: TSizes.xl, + height: TSizes.xl, + decoration: BoxDecoration( + color: + isActive + ? theme.primaryColor + : isDark + ? TColors.darkerGrey + : TColors.secondary, + shape: BoxShape.circle, + border: Border.all( + color: isActive ? theme.primaryColor : Colors.transparent, + width: 2, + ), + boxShadow: + isActive + ? [ + BoxShadow( + color: theme.primaryColor.withOpacity(0.3), + blurRadius: 4, + spreadRadius: 1, + ), + ] + : null, + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () => onStepTapped(index), + borderRadius: BorderRadius.circular(TSizes.xl), + child: Center( + child: + isActive + ? Icon( + index < currentStep ? Icons.check : Icons.circle, + color: Colors.white, + size: index < currentStep ? TSizes.iconSm : TSizes.iconXs, + ) + : Text( + '${index + 1}', + style: theme.textTheme.bodySmall?.copyWith( + color: isDark ? Colors.white : TColors.textSecondary, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + ); + } +} diff --git a/sigap-mobile/lib/src/shared/widgets/indicators/step_indicator/styles/standard_step_indicator.dart b/sigap-mobile/lib/src/shared/widgets/indicators/step_indicator/styles/standard_step_indicator.dart new file mode 100644 index 0000000..d05292f --- /dev/null +++ b/sigap-mobile/lib/src/shared/widgets/indicators/step_indicator/styles/standard_step_indicator.dart @@ -0,0 +1,263 @@ +import 'package:flutter/material.dart'; +import 'package:sigap/src/utils/constants/colors.dart'; +import 'package:sigap/src/utils/constants/sizes.dart'; + +class StandardStepIndicator extends StatelessWidget { + final int currentStep; + final int totalSteps; + final List stepTitles; + final Function(int) onStepTapped; + final ThemeData theme; + final bool isDark; + + const StandardStepIndicator({ + super.key, + required this.currentStep, + required this.totalSteps, + required this.stepTitles, + required this.onStepTapped, + required this.theme, + required this.isDark, + }); + + @override + Widget build(BuildContext context) { + // Special case for 2 steps + if (totalSteps == 2) { + return _buildTwoStepIndicator(context); + } + + // Normal case (3+ steps) + return _buildMultiStepIndicator(context); + } + + Widget _buildTwoStepIndicator(BuildContext context) { + final circleWidth = TSizes.xl; + + return Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: TSizes.md / 2), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // First step + Container( + width: circleWidth, + height: circleWidth, + decoration: BoxDecoration( + color: theme.primaryColor, + shape: BoxShape.circle, + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () => onStepTapped(0), + borderRadius: BorderRadius.circular(circleWidth), + child: Center( + child: + 0 < currentStep + ? Icon( + Icons.check, + color: Colors.white, + size: TSizes.iconSm, + ) + : Icon( + Icons.circle, + color: Colors.white, + size: TSizes.iconXs, + ), + ), + ), + ), + ), + + // Connector line that spans the entire space between circles + Expanded( + child: Container( + height: TSizes.dividerHeight, + color: + currentStep > 0 + ? theme.primaryColor + : isDark + ? TColors.darkerGrey + : TColors.borderPrimary, + ), + ), + + // Second step + Container( + width: circleWidth, + height: circleWidth, + decoration: BoxDecoration( + color: + currentStep >= 1 + ? theme.primaryColor + : isDark + ? TColors.darkerGrey + : TColors.secondary, + shape: BoxShape.circle, + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () => onStepTapped(1), + borderRadius: BorderRadius.circular(circleWidth), + child: Center( + child: + currentStep >= 1 + ? Icon( + currentStep > 1 ? Icons.check : Icons.circle, + color: Colors.white, + size: + currentStep > 1 + ? TSizes.iconSm + : TSizes.iconXs, + ) + : Text( + '2', + style: theme.textTheme.bodySmall?.copyWith( + color: + isDark + ? Colors.white + : TColors.textSecondary, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + ), + ], + ), + ), + SizedBox(height: TSizes.sm), + + // Step titles + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: List.generate(totalSteps, (index) { + return Text( + stepTitles[index], + style: theme.textTheme.labelMedium?.copyWith( + fontWeight: + index == currentStep ? FontWeight.bold : FontWeight.normal, + color: + index == currentStep + ? theme.primaryColor + : isDark + ? Colors.white70 + : TColors.textSecondary, + ), + ); + }), + ), + ], + ); + } + + Widget _buildMultiStepIndicator(BuildContext context) { + return Column( + children: [ + Row( + children: List.generate(totalSteps, (index) { + final isActive = index <= currentStep; + final isLast = index == totalSteps - 1; + + return Expanded( + child: Row( + children: [ + // Step circle + GestureDetector( + onTap: () => onStepTapped(index), + child: Container( + width: TSizes.xl, + height: TSizes.xl, + decoration: BoxDecoration( + color: + isActive + ? theme.primaryColor + : isDark + ? TColors.darkerGrey + : TColors.secondary, + shape: BoxShape.circle, + ), + child: Center( + child: + isActive + ? Icon( + index < currentStep + ? Icons.check + : Icons.circle, + color: Colors.white, + size: + index < currentStep + ? TSizes.iconSm + : TSizes.iconXs, + ) + : Text( + '${index + 1}', + style: theme.textTheme.bodySmall?.copyWith( + color: + isDark + ? Colors.white + : TColors.textSecondary, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + + // Connector line + if (!isLast) + Expanded( + child: Container( + height: TSizes.dividerHeight, + color: + index < currentStep + ? theme.primaryColor + : isDark + ? TColors.darkerGrey + : TColors.borderPrimary, + ), + ), + ], + ), + ); + }), + ), + SizedBox(height: TSizes.sm), + + // Step titles + Row( + children: List.generate(totalSteps, (index) { + return Expanded( + child: Text( + stepTitles[index], + textAlign: + index == 0 + ? TextAlign.start + : index == totalSteps - 1 + ? TextAlign.end + : TextAlign.center, + style: theme.textTheme.labelMedium?.copyWith( + fontWeight: + index == currentStep + ? FontWeight.bold + : FontWeight.normal, + color: + index == currentStep + ? theme.primaryColor + : isDark + ? Colors.white70 + : TColors.textSecondary, + ), + ), + ); + }), + ), + ], + ); + } +} diff --git a/sigap-mobile/lib/src/shared/widgets/state_screeen/secondary_state_screen.dart b/sigap-mobile/lib/src/shared/widgets/state_screeen/secondary_state_screen.dart index 85f9b37..c8e93d3 100644 --- a/sigap-mobile/lib/src/shared/widgets/state_screeen/secondary_state_screen.dart +++ b/sigap-mobile/lib/src/shared/widgets/state_screeen/secondary_state_screen.dart @@ -3,6 +3,9 @@ import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:sigap/src/features/auth/presentasion/widgets/auth_button.dart'; import 'package:sigap/src/utils/constants/colors.dart'; +import 'package:sigap/src/utils/constants/sizes.dart'; +import 'package:sigap/src/utils/device/device_utility.dart'; +import 'package:sigap/src/utils/helpers/helper_functions.dart'; class StateScreen extends StatelessWidget { const StateScreen({super.key}); @@ -16,20 +19,22 @@ class StateScreen extends StatelessWidget { final message = args['message'] as String; final buttonText = args['buttonText'] as String; final onButtonPressed = args['onButtonPressed'] as Function(); + final isDark = THelperFunctions.isDarkMode(context); // Set system overlay style + TDeviceUtils.setStatusBarColor(Colors.transparent); SystemChrome.setSystemUIOverlayStyle( - const SystemUiOverlayStyle( + SystemUiOverlayStyle( statusBarColor: Colors.transparent, - statusBarIconBrightness: Brightness.dark, + statusBarIconBrightness: isDark ? Brightness.light : Brightness.dark, ), ); return Scaffold( - backgroundColor: TColors.light, + backgroundColor: isDark ? TColors.dark : TColors.light, body: SafeArea( child: Padding( - padding: const EdgeInsets.all(24.0), + padding: const EdgeInsets.all(TSizes.defaultSpace), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, @@ -39,33 +44,37 @@ class StateScreen extends StatelessWidget { type == 'success' ? Icons.check_circle_outline : Icons.error_outline, - size: 100, + size: TSizes.imageThumbSize + TSizes.xl, color: type == 'success' ? TColors.success : TColors.error, ), - const SizedBox(height: 32), + const SizedBox(height: TSizes.spaceBtwSections), // Title Text( title, - style: TextStyle( - fontSize: 24, + style: Theme.of(context).textTheme.headlineMedium?.copyWith( fontWeight: FontWeight.bold, - color: TColors.textPrimary, + color: isDark ? TColors.white : TColors.textPrimary, ), textAlign: TextAlign.center, ), - const SizedBox(height: 16), + const SizedBox(height: TSizes.spaceBtwItems), // Message Text( message, - style: TextStyle(fontSize: 16, color: TColors.textSecondary), + style: Theme.of(context).textTheme.bodyLarge?.copyWith( + color: + isDark + ? TColors.white.withOpacity(0.7) + : TColors.textSecondary, + ), textAlign: TextAlign.center, ), - const SizedBox(height: 48), + SizedBox(height: THelperFunctions.screenHeight() * 0.05), // Button AuthButton(text: buttonText, onPressed: onButtonPressed), diff --git a/sigap-mobile/lib/src/shared/widgets/text/custom_text_field.dart b/sigap-mobile/lib/src/shared/widgets/text/custom_text_field.dart index 5b3b14d..379247b 100644 --- a/sigap-mobile/lib/src/shared/widgets/text/custom_text_field.dart +++ b/sigap-mobile/lib/src/shared/widgets/text/custom_text_field.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:sigap/src/utils/constants/colors.dart'; +import 'package:sigap/src/utils/constants/sizes.dart'; +import 'package:sigap/src/utils/helpers/helper_functions.dart'; class CustomTextField extends StatelessWidget { final String label; @@ -33,18 +35,19 @@ class CustomTextField extends StatelessWidget { @override Widget build(BuildContext context) { + final isDark = THelperFunctions.isDarkMode(context); + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, - style: TextStyle( - fontSize: 14, + style: Theme.of(context).textTheme.bodyLarge?.copyWith( fontWeight: FontWeight.w500, - color: TColors.textPrimary, + color: isDark ? TColors.white : TColors.textPrimary, ), ), - const SizedBox(height: 8), + const SizedBox(height: TSizes.sm), TextFormField( controller: controller, validator: validator, @@ -54,42 +57,46 @@ class CustomTextField extends StatelessWidget { textInputAction: textInputAction, maxLines: maxLines, onChanged: onChanged, - style: TextStyle(color: TColors.textPrimary, fontSize: 16), + style: Theme.of(context).textTheme.bodyLarge?.copyWith( + color: isDark ? TColors.white : TColors.textPrimary, + ), decoration: InputDecoration( hintText: hintText, - hintStyle: TextStyle(color: TColors.textSecondary, fontSize: 16), + hintStyle: Theme.of( + context, + ).textTheme.bodyMedium?.copyWith(color: TColors.textSecondary), errorText: errorText != null && errorText!.isNotEmpty ? errorText : null, contentPadding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 16, + horizontal: TSizes.md, + vertical: TSizes.md, ), suffixIcon: suffixIcon, filled: true, - fillColor: TColors.lightContainer, + fillColor: isDark ? TColors.darkContainer : TColors.lightContainer, border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(TSizes.inputFieldRadius), borderSide: BorderSide(color: TColors.borderPrimary, width: 1), ), enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(TSizes.inputFieldRadius), borderSide: BorderSide(color: TColors.borderPrimary, width: 1), ), focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(TSizes.inputFieldRadius), borderSide: BorderSide(color: TColors.primary, width: 1.5), ), errorBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(TSizes.inputFieldRadius), borderSide: BorderSide(color: TColors.error, width: 1), ), focusedErrorBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(TSizes.inputFieldRadius), borderSide: BorderSide(color: TColors.error, width: 1.5), ), ), ), - const SizedBox(height: 16), + const SizedBox(height: TSizes.spaceBtwInputFields), ], ); }