baseproject

This commit is contained in:
Gaurav Kumar
2025-09-06 19:21:52 +05:30
commit 01be9df2ed
254 changed files with 28342 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class ReusableDatePickerField extends StatelessWidget {
final String label;
final String? initialDate;
final TextEditingController controller;
final Function(String?)? onSaved;
const ReusableDatePickerField({
super.key,
required this.label,
required this.controller,
this.initialDate,
required this.onSaved,
});
@override
Widget build(BuildContext context) {
return TextFormField(
initialValue: initialDate,
controller: controller,
decoration: InputDecoration(
labelText: label,
border: const OutlineInputBorder(),
contentPadding:
const EdgeInsets.symmetric(vertical: 16.0, horizontal: 12.0),
suffixIcon: const Icon(Icons.calendar_today),
),
readOnly: true,
onTap: () async {
DateTime? selectedDate = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime(2101),
);
controller.text = selectedDate != null
? DateFormat('yyyy-MM-dd').format(selectedDate!)
: '';
},
onSaved: onSaved,
);
}
}

View File

@@ -0,0 +1,74 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class ReusableDateTimePickerField extends StatelessWidget {
final String label;
final String? initialDateTime;
final TextEditingController controller;
final Function(String?)? onSaved;
const ReusableDateTimePickerField({super.key,
required this.label,
required this.controller,
this.initialDateTime,
required this.onSaved,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: TextFormField(
initialValue: initialDateTime,
controller: controller,
decoration: InputDecoration(
labelText: label,
border: const OutlineInputBorder(),
contentPadding:
const EdgeInsets.symmetric(vertical: 16.0, horizontal: 12.0),
suffixIcon: const Icon(Icons.event),
),
readOnly: true,
onTap: () async {
DateTime? selectedDateTime = await showDateTimePicker(
context: context,
initialDateTime: DateTime.now(),
);
if (selectedDateTime != null) {
controller.text =
DateFormat('yyyy-MM-dd HH:mm').format(selectedDateTime);
}
},
onSaved: onSaved,
),
);
}
Future<DateTime?> showDateTimePicker({
required BuildContext context,
required DateTime initialDateTime,
}) async {
// Custom date-time picker implementation or package usage
// This is a placeholder for demonstration purposes.
return await showDatePicker(
context: context,
initialDate: initialDateTime,
firstDate: DateTime(2000),
lastDate: DateTime(2101),
).then((date) {
if (date != null) {
return showTimePicker(
context: context,
initialTime: TimeOfDay.fromDateTime(initialDateTime),
).then((time) {
if (time != null) {
return DateTime(
date.year, date.month, date.day, time.hour, time.minute);
}
return date;
});
}
return null;
});
}
}

View File

@@ -0,0 +1,74 @@
import 'package:flutter/material.dart';
class ReusableDropdownField extends StatelessWidget {
final String label;
final List<Map<String, dynamic>> options;
final String? value;
final String valueField; // ID key (dynamic)
final String uiField; // Name key (dynamic)
final void Function(String?)? onChanged;
final void Function(String?)? onSaved;
const ReusableDropdownField({
super.key,
required this.label,
required this.options,
required this.valueField, // Dynamic ID field
required this.uiField, // Dynamic Name field
this.value,
this.onChanged,
this.onSaved,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: DropdownButtonFormField<String>(
decoration: InputDecoration(
labelText: label,
labelStyle: const TextStyle(color: Colors.deepPurple),
contentPadding:
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide: const BorderSide(color: Colors.deepPurple, width: 2),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide: const BorderSide(color: Colors.deepPurple, width: 2),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide:
const BorderSide(color: Colors.deepPurpleAccent, width: 2),
),
),
value: (value != null && value!.isNotEmpty) ? value : null,
items: [
const DropdownMenuItem<String>(
value: '',
child: Text('Select an option'),
),
...options.map<DropdownMenuItem<String>>(
(item) => DropdownMenuItem<String>(
value: item[valueField].toString(),
child: Text(
item[uiField].toString(),
style: const TextStyle(fontSize: 16),
),
),
),
],
onChanged: onChanged,
onSaved: onSaved,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please select a $label';
}
return null;
},
),
);
}
}

View File

@@ -0,0 +1,77 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class ReusableTextField extends StatelessWidget {
final String label;
final TextEditingController? controller;
final TextInputType keyboardType;
final bool obscureText;
final int maxLines;
final List<TextInputFormatter>? inputFormatters;
final FormFieldValidator<String>? validator;
final Function(DateTime?)? onDateTimeSelected; // Callback for DateTime picker
final Function(String?)? onSaved;
final Function(String?)? onChanged;
final String? initialValue;
const ReusableTextField({
super.key,
required this.label,
this.controller,
this.keyboardType = TextInputType.text,
this.obscureText = false,
this.maxLines = 1,
this.inputFormatters,
this.validator,
this.onDateTimeSelected,
this.onSaved,
this.onChanged,
this.initialValue, // Added callback for DateTime picker
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: TextFormField(
initialValue: initialValue,
onSaved: onSaved,
onChanged: onChanged,
controller: controller,
keyboardType: keyboardType,
obscureText: obscureText,
maxLines: maxLines,
inputFormatters: inputFormatters,
validator: validator,
onTap: () async {
if (keyboardType == TextInputType.datetime &&
onDateTimeSelected != null) {
DateTime? dateTime = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime(2100),
);
// Agar user cancel kare, to current date set ho jaye
dateTime ??= DateTime.now();
TimeOfDay? timeOfDay = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
);
if (timeOfDay != null) {
dateTime = DateTime(dateTime.year, dateTime.month, dateTime.day,
timeOfDay.hour, timeOfDay.minute);
onDateTimeSelected!(dateTime);
}
}
},
decoration: InputDecoration(
labelText: label,
border: const OutlineInputBorder(),
),
),
);
}
}

View File

@@ -0,0 +1,69 @@
import 'package:base_project/resources/app_colors.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class MyCustomTextFormField extends StatelessWidget {
final String label;
final Widget? prefixIcon;
final Widget? suffixIcon;
final String? initialValue;
final bool? obscureText;
final TextEditingController controller;
final String? Function(String?)? validator;
final void Function(String)? onChanged;
final TextInputType? keyboardType;
final List<TextInputFormatter>? inputFormatters;
final int? maxLines;
final void Function()? onSuffixIconPressed;
const MyCustomTextFormField({
super.key,
required this.label,
this.prefixIcon,
this.suffixIcon,
this.initialValue,
required this.controller,
this.validator,
this.onChanged,
this.keyboardType,
this.inputFormatters,
this.maxLines = 1, this.obscureText, this.onSuffixIconPressed,
});
@override
Widget build(BuildContext context) {
return TextFormField(
controller: controller,
initialValue: initialValue,
validator: validator,
onChanged: onChanged,
cursorColor: AppColors.primary,
keyboardType: keyboardType,
inputFormatters: inputFormatters,
maxLines: maxLines,
obscureText: obscureText ?? false,
decoration: InputDecoration(
labelText: label,
labelStyle: const TextStyle(color: AppColors.primary),
prefixIcon: prefixIcon,
suffixIcon: suffixIcon,
filled: true,
fillColor: Colors.white,
border: InputBorder.none, // No border
contentPadding:
const EdgeInsets.symmetric(vertical: 15.0, horizontal: 12.0),
// For rounded corners
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide.none,
),
),
);
}
}

View File

@@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
class DrawerItem extends StatelessWidget {
final IconData icon;
final String title;
final VoidCallback onTap;
final Color color;
const DrawerItem({
super.key,
required this.icon,
required this.title,
required this.onTap,
this.color = Colors.blue, // Default color if none is provided
});
@override
Widget build(BuildContext context) {
return ListTile(
leading: Icon(icon, color: color),
title: Text(title),
onTap: onTap,
);
}
}

View File

@@ -0,0 +1,327 @@
import '../../Entity/angulardata/Test_visa/Test_visaView/Test_visa_entity_list_screen.dart';
import '../../Entity/angulardata/Test_visa/Test_visa_viewModel/Test_visa_view_model_screen.dart';
import '../../Entity/angulardata/Basicp/BasicpView/Basicp_entity_list_screen.dart';
import '../../Entity/angulardata/Basicp/Basicp_viewModel/Basicp_view_model_screen.dart';
import '../../Entity/angulardatatype/Ad8/Ad8View/Ad8_entity_list_screen.dart';
import '../../Entity/angulardatatype/Ad8/Ad8_viewModel/Ad8_view_model_screen.dart';
import '../../Entity/angulardatatype/Ad7/Ad7View/Ad7_entity_list_screen.dart';
import '../../Entity/angulardatatype/Ad7/Ad7_viewModel/Ad7_view_model_screen.dart';
import '../../Entity/angulardatatype/Adv5/Adv5View/Adv5_entity_list_screen.dart';
import '../../Entity/angulardatatype/Adv5/Adv5_viewModel/Adv5_view_model_screen.dart';
import '../../Entity/angulardatatype/Adv4/Adv4View/Adv4_entity_list_screen.dart';
import '../../Entity/angulardatatype/Adv4/Adv4_viewModel/Adv4_view_model_screen.dart';
import '../../Entity/angulardatatype/Adv3/Adv3View/Adv3_entity_list_screen.dart';
import '../../Entity/angulardatatype/Adv3/Adv3_viewModel/Adv3_view_model_screen.dart';
import '../../Entity/angulardatatype/Dv2/Dv2View/Dv2_entity_list_screen.dart';
import '../../Entity/angulardatatype/Dv2/Dv2_viewModel/Dv2_view_model_screen.dart';
import '../../Entity/angulardatatype/Adv1/Adv1View/Adv1_entity_list_screen.dart';
import '../../Entity/angulardatatype/Adv1/Adv1_viewModel/Adv1_view_model_screen.dart';
import '../../Entity/angulardatatype/Basicp3/Basicp3View/Basicp3_entity_list_screen.dart';
import '../../Entity/angulardatatype/Basicp3/Basicp3_viewModel/Basicp3_view_model_screen.dart';
import '../../Entity/angulardatatype/Basicp2/Basicp2View/Basicp2_entity_list_screen.dart';
import '../../Entity/angulardatatype/Basicp2/Basicp2_viewModel/Basicp2_view_model_screen.dart';
import '../../Entity/angulardatatype/Basicp1/Basicp1View/Basicp1_entity_list_screen.dart';
import '../../Entity/angulardatatype/Basicp1/Basicp1_viewModel/Basicp1_view_model_screen.dart';
import 'package:base_project/utils/image_constant.dart';
import 'package:base_project/commans/widgets/custome_drawe_item.dart';
import 'package:base_project/resources/app_colors.dart';
import 'package:base_project/routes/route_names.dart';
import 'package:base_project/utils/managers/user_manager.dart';
import 'package:base_project/view_model/profile/profile_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:provider/provider.dart';
class MyCustomDrawer extends StatelessWidget {
const MyCustomDrawer({super.key});
@override
Widget build(BuildContext context) {
final email = UserManager().email;
final userName = UserManager().userName;
final provider = Provider.of<ProfileViewModel>(context, listen: false);
return Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
UserAccountsDrawerHeader(
decoration: const BoxDecoration(
color: AppColors.primary,
),
currentAccountPicture: CircleAvatar(
radius: 60,
backgroundColor: AppColors.primary.withOpacity(0.3),
backgroundImage: provider.profileImageBytes != null
? MemoryImage(provider.profileImageBytes!)
: null,
child: provider.profileImageBytes != null
? null // Use backgroundImage for the actual image, so child should be null
: SvgPicture.asset(
ImageConstant.userProfileImg, // Placeholder SVG asset
// AppImages.userProfileImg, // Placeholder SVG asset
width: 60, // Adjust to fit the CircleAvatar
height: 60,
),
),
accountName: Text("Hello, $userName"),
accountEmail: Text(email.toString()),
),
DrawerItem(
color: AppColors.primary,
icon: Icons.person,
title: 'Profile',
onTap: () {
Navigator.pushNamed(context, RouteNames.profileView);
},
),
DrawerItem(
color: AppColors.primary,
icon: Icons.system_security_update,
title: 'System Parameters',
onTap: () {
// Add navigation or other logic here
Navigator.pushNamed(context, RouteNames.systemParamsView);
},
),
DrawerItem(
color: AppColors.primary,
icon: Icons.password,
title: 'change password',
onTap: () {
Navigator.pushNamed(context, RouteNames.changePasswordView);
},
),
// NEW MENU
DrawerItem(
color: AppColors.primary,
icon: Icons.chat_bubble,
title: 'Test_visa',
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChangeNotifierProvider(
create: (context) => Test_visaViewModelScreen(),
child: test_visa_entity_list_screen(),
),
),
);
},
),
DrawerItem(
color: AppColors.primary,
icon: Icons.chat_bubble,
title: 'Basicp',
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChangeNotifierProvider(
create: (context) => BasicpViewModelScreen(),
child: basicp_entity_list_screen(),
),
),
);
},
),
DrawerItem(
color: AppColors.primary,
icon: Icons.chat_bubble,
title: 'Ad8',
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChangeNotifierProvider(
create: (context) => Ad8ViewModelScreen(),
child: ad8_entity_list_screen(),
),
),
);
},
),
DrawerItem(
color: AppColors.primary,
icon: Icons.chat_bubble,
title: 'Ad7',
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChangeNotifierProvider(
create: (context) => Ad7ViewModelScreen(),
child: ad7_entity_list_screen(),
),
),
);
},
),
DrawerItem(
color: AppColors.primary,
icon: Icons.chat_bubble,
title: 'Adv5',
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChangeNotifierProvider(
create: (context) => Adv5ViewModelScreen(),
child: adv5_entity_list_screen(),
),
),
);
},
),
DrawerItem(
color: AppColors.primary,
icon: Icons.chat_bubble,
title: 'Adv4',
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChangeNotifierProvider(
create: (context) => Adv4ViewModelScreen(),
child: adv4_entity_list_screen(),
),
),
);
},
),
DrawerItem(
color: AppColors.primary,
icon: Icons.chat_bubble,
title: 'Adv3',
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChangeNotifierProvider(
create: (context) => Adv3ViewModelScreen(),
child: adv3_entity_list_screen(),
),
),
);
},
),
DrawerItem(
color: AppColors.primary,
icon: Icons.chat_bubble,
title: 'Dv2',
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChangeNotifierProvider(
create: (context) => Dv2ViewModelScreen(),
child: dv2_entity_list_screen(),
),
),
);
},
),
DrawerItem(
color: AppColors.primary,
icon: Icons.chat_bubble,
title: 'Adv1',
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChangeNotifierProvider(
create: (context) => Adv1ViewModelScreen(),
child: adv1_entity_list_screen(),
),
),
);
},
),
DrawerItem(
color: AppColors.primary,
icon: Icons.chat_bubble,
title: 'Basicp3',
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChangeNotifierProvider(
create: (context) => Basicp3ViewModelScreen(),
child: basicp3_entity_list_screen(),
),
),
);
},
),
DrawerItem(
color: AppColors.primary,
icon: Icons.chat_bubble,
title: 'Basicp2',
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChangeNotifierProvider(
create: (context) => Basicp2ViewModelScreen(),
child: basicp2_entity_list_screen(),
),
),
);
},
),
DrawerItem(
color: AppColors.primary,
icon: Icons.chat_bubble,
title: 'Basicp1',
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChangeNotifierProvider(
create: (context) => Basicp1ViewModelScreen(),
child: basicp_entity_list_screen(),
),
),
);
},
),
DrawerItem(
icon: Icons.logout,
color: Colors.red,
title: 'Logout',
onTap: () async {
await UserManager().clearUser();
Navigator.pushReplacementNamed(context, RouteNames.splashView);
},
),
],
),
);
}
}

View File

@@ -0,0 +1,38 @@
import 'package:base_project/resources/app_colors.dart';
import 'package:flutter/material.dart';
class MyCustomElevatedButton extends StatelessWidget {
final Color? backgroundColor;
final Widget child;
final bool isLoading;
final VoidCallback onPressed;
const MyCustomElevatedButton({
super.key,
this.backgroundColor = AppColors.primary,
required this.child,
required this.onPressed,
this.isLoading = false,
});
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: isLoading ? null : onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: backgroundColor,
minimumSize: const Size(double.infinity, 50), // Full width, height of 50
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8), // Rounded corners
),
),
child: isLoading
? const Center(
child: CircularProgressIndicator(
strokeWidth: 2.0, // Adjust thickness if needed
),
)
: child,
);
}
}

View File

@@ -0,0 +1,6 @@
export '/core/utils/size_utils.dart';
export '/routes/app_routes.dart';
export '/theme/app_decoration.dart';
export '/theme/custom_text_style.dart';
export '/theme/theme_helper.dart';
export '/widgets/custom_image_view.dart';

View File

@@ -0,0 +1,229 @@
import 'package:flutter/material.dart';
class UIConstants {
// Spacing
static const double spacing4 = 4.0;
static const double spacing6 = 6.0;
static const double spacing8 = 8.0;
static const double spacing12 = 12.0;
static const double spacing14 = 14.0;
static const double spacing16 = 16.0;
static const double spacing18 = 18.0;
static const double spacing20 = 20.0;
static const double spacing24 = 24.0;
static const double spacing32 = 32.0;
static const double spacing40 = 40.0;
static const double spacing48 = 48.0;
static const double spacing56 = 56.0;
static const double spacing64 = 64.0;
static const double spacing72 = 72.0;
static const double spacing80 = 80.0;
static const double spacing96 = 96.0;
// Border Radius
static const double radius4 = 4.0;
static const double radius8 = 8.0;
static const double radius12 = 12.0;
static const double radius16 = 16.0;
static const double radius20 = 20.0;
static const double radius24 = 24.0;
static const double radius32 = 32.0;
static const double radius40 = 40.0;
static const double radiusFull = 999.0;
// Elevation
static const double elevation0 = 0.0;
static const double elevation1 = 1.0;
static const double elevation2 = 2.0;
static const double elevation4 = 4.0;
static const double elevation8 = 8.0;
static const double elevation16 = 16.0;
static const double elevation24 = 24.0;
// Animation Durations
static const Duration durationFast = Duration(milliseconds: 150);
static const Duration durationNormal = Duration(milliseconds: 300);
static const Duration durationSlow = Duration(milliseconds: 500);
static const Duration durationVerySlow = Duration(milliseconds: 800);
// Animation Curves
static const Curve curveFast = Curves.easeInOut;
static const Curve curveNormal = Curves.easeInOutCubic;
static const Curve curveSlow = Curves.easeInOutQuart;
static const Curve curveBounce = Curves.bounceOut;
static const Curve curveElastic = Curves.elasticOut;
// Screen Breakpoints
static const double mobileBreakpoint = 600.0;
static const double tabletBreakpoint = 900.0;
static const double desktopBreakpoint = 1200.0;
// Input Field Heights
static const double inputHeightSmall = 40.0;
static const double inputHeightMedium = 48.0;
static const double inputHeightLarge = 56.0;
// Button Heights
static const double buttonHeightSmall = 36.0;
static const double buttonHeightMedium = 48.0;
static const double buttonHeightLarge = 56.0;
// Icon Sizes
static const double iconSizeSmall = 16.0;
static const double iconSizeMedium = 24.0;
static const double iconSizeLarge = 32.0;
static const double iconSizeXLarge = 48.0;
// Logo Sizes
static const double logoSizeSmall = 32.0;
static const double logoSizeMedium = 48.0;
static const double logoSizeLarge = 64.0;
static const double logoSizeXLarge = 96.0;
// Card Padding
static const EdgeInsets cardPaddingSmall = EdgeInsets.all(spacing12);
static const EdgeInsets cardPaddingMedium = EdgeInsets.all(spacing16);
static const EdgeInsets cardPaddingLarge = EdgeInsets.all(spacing24);
// Screen Padding
static const EdgeInsets screenPaddingSmall = EdgeInsets.all(spacing16);
static const EdgeInsets screenPaddingMedium = EdgeInsets.all(spacing24);
static const EdgeInsets screenPaddingLarge = EdgeInsets.all(spacing32);
// Horizontal Padding
static const EdgeInsets horizontalPaddingSmall =
EdgeInsets.symmetric(horizontal: spacing16);
static const EdgeInsets horizontalPaddingMedium =
EdgeInsets.symmetric(horizontal: spacing24);
static const EdgeInsets horizontalPaddingLarge =
EdgeInsets.symmetric(horizontal: spacing32);
// Vertical Padding
static const EdgeInsets verticalPaddingSmall =
EdgeInsets.symmetric(vertical: spacing16);
static const EdgeInsets verticalPaddingMedium =
EdgeInsets.symmetric(vertical: spacing24);
static const EdgeInsets verticalPaddingLarge =
EdgeInsets.symmetric(vertical: spacing32);
// Responsive Helpers
static bool isMobile(BuildContext context) {
return MediaQuery.of(context).size.width < mobileBreakpoint;
}
static bool isTablet(BuildContext context) {
final width = MediaQuery.of(context).size.width;
return width >= mobileBreakpoint && width < tabletBreakpoint;
}
static bool isDesktop(BuildContext context) {
return MediaQuery.of(context).size.width >= desktopBreakpoint;
}
static double getResponsiveValue(
BuildContext context, {
required double mobile,
required double tablet,
required double desktop,
}) {
if (isMobile(context)) return mobile;
if (isTablet(context)) return tablet;
return desktop;
}
static int getResponsiveInt(
BuildContext context, {
required int mobile,
required int tablet,
required int desktop,
}) {
if (isMobile(context)) return mobile;
if (isTablet(context)) return tablet;
return desktop;
}
static EdgeInsets getResponsivePadding(
BuildContext context, {
required EdgeInsets mobile,
required EdgeInsets tablet,
required EdgeInsets desktop,
}) {
if (isMobile(context)) return mobile;
if (isTablet(context)) return tablet;
return desktop;
}
static double getResponsiveSpacing(
BuildContext context, {
required double mobile,
required double tablet,
required double desktop,
}) {
if (isMobile(context)) return mobile;
if (isTablet(context)) return tablet;
return desktop;
}
// Animation Helpers
static Widget fadeIn({
required Widget child,
Duration duration = durationNormal,
Curve curve = curveNormal,
}) {
return TweenAnimationBuilder<double>(
duration: duration,
curve: curve,
tween: Tween(begin: 0.0, end: 1.0),
builder: (context, value, child) {
return Opacity(
opacity: value,
child: child,
);
},
child: child,
);
}
static Widget slideInUp({
required Widget child,
Duration duration = durationNormal,
Curve curve = curveNormal,
double offset = 50.0,
}) {
return TweenAnimationBuilder<Offset>(
duration: duration,
curve: curve,
tween: Tween(begin: Offset(0, offset), end: Offset.zero),
builder: (context, value, child) {
return Transform.translate(
offset: value,
child: child,
);
},
child: child,
);
}
static Widget scaleIn({
required Widget child,
Duration duration = durationNormal,
Curve curve = curveNormal,
double beginScale = 0.8,
}) {
return TweenAnimationBuilder<double>(
duration: duration,
curve: curve,
tween: Tween(begin: beginScale, end: 1.0),
builder: (context, value, child) {
return Transform.scale(
scale: value,
child: child,
);
},
child: child,
);
}
}

View File

@@ -0,0 +1,81 @@
// import 'package:flutter/material.dart';
// import 'package:connectivity_plus/connectivity_plus.dart';
// // For checking internet connectivity
// abstract class NetworkInfoI {
// Future<bool> isConnected();
// Future<ConnectivityResult> get connectivityResult;
// Stream<ConnectivityResult> get onConnectivityChanged;
// }
// class NetworkInfo implements NetworkInfoI {
// Connectivity connectivity;
// static final NetworkInfo _networkInfo = NetworkInfo._internal(Connectivity());
// factory NetworkInfo() {
// return _networkInfo;
// }
// NetworkInfo._internal(this.connectivity) {
// connectivity = this.connectivity;
// }
// ///checks internet is connected or not
// ///returns [true] if internet is connected
// ///else it will return [false]
// @override
// Future<bool> isConnected() async {
// final result = await connectivity.checkConnectivity();
// if (result != ConnectivityResult.none) {
// return true;
// }
// return false;
// }
// // to check type of internet connectivity
// @override
// Future<ConnectivityResult> get connectivityResult async {
// return connectivity.checkConnectivity();
// }
// //check the type on internet connection on changed of internet connection
// @override
// Stream<ConnectivityResult> get onConnectivityChanged =>
// connectivity.onConnectivityChanged;
// }
// abstract class Failure {}
// // General failures
// class ServerFailure extends Failure {}
// class CacheFailure extends Failure {}
// class NetworkFailure extends Failure {}
// class ServerException implements Exception {}
// class CacheException implements Exception {}
// class NetworkException implements Exception {}
// ///can be used for throwing [NoInternetException]
// class NoInternetException implements Exception {
// late String _message;
// NoInternetException([String message = 'NoInternetException Occurred']) {
// if (globalMessengerKey.currentState != null) {
// globalMessengerKey.currentState!
// .showSnackBar(SnackBar(content: Text(message)));
// }
// this._message = message;
// }
// @override
// String toString() {
// return _message;
// }
// }

View File

@@ -0,0 +1,266 @@
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import '../theme/dynamic_color_scheme.dart';
import 'package:flutter/foundation.dart' show compute;
class DynamicThemeProvider extends ChangeNotifier {
ColorScheme? _dynamicColorScheme;
List<Color> _logoColors = [];
bool _isUsingDynamicTheme = false;
bool _isLoading = false;
// Manual override
bool _useManualOverride = false;
Color? _manualPrimary;
Color? _manualSecondary;
Color? _manualTertiary;
// Getters
ColorScheme? get dynamicColorScheme => _dynamicColorScheme;
List<Color> get logoColors => _logoColors;
bool get isUsingDynamicTheme => _isUsingDynamicTheme;
bool get isLoading => _isLoading;
bool get useManualOverride => _useManualOverride;
Color? get manualPrimary => _manualPrimary;
Color? get manualSecondary => _manualSecondary;
Color? get manualTertiary => _manualTertiary;
// Set loading state
set isLoading(bool value) {
_isLoading = value;
notifyListeners();
}
// Generate dynamic theme from logo
Future<void> generateThemeFromLogo(Uint8List logoBytes) async {
try {
print("DynamicThemeProvider.generateThemeFromLogo called");
print("Logo bytes length: ${logoBytes.length}");
isLoading = true;
// Extract colors with lightweight, downscaled palette to avoid jank
print("Extracting colors from logo (lite in main isolate)...");
final colors =
await DynamicColorScheme.extractColorsFromBytesLite(logoBytes);
print("Colors extracted: ${colors.length}");
_logoColors = colors;
// Generate light theme
print("Generating light theme...");
final lightTheme =
DynamicColorScheme.generateDynamicColorScheme(colors, isDark: false);
// Generate dark theme
print("Generating dark theme...");
final darkTheme =
DynamicColorScheme.generateDynamicColorScheme(colors, isDark: true);
// Store the dynamic color scheme
_dynamicColorScheme = lightTheme;
_isUsingDynamicTheme = true;
print('Dynamic theme generated with ${colors.length} colors');
print('Primary: ${colors.isNotEmpty ? colors[0] : 'N/A'}');
print('Secondary: ${colors.length > 1 ? colors[1] : 'N/A'}');
print('Tertiary: ${colors.length > 2 ? colors[2] : 'N/A'}');
print('_isUsingDynamicTheme set to: $_isUsingDynamicTheme');
isLoading = false;
print("Notifying listeners...");
notifyListeners();
print("Listeners notified!");
} catch (e) {
print('Error generating dynamic theme: $e');
isLoading = false;
// Fallback to default theme
_isUsingDynamicTheme = false;
_dynamicColorScheme = null;
notifyListeners();
}
}
// Get current color scheme (dynamic or default)
ColorScheme getCurrentColorScheme(bool isDark) {
// Manual overrides take highest precedence
if (_useManualOverride && (_manualPrimary != null)) {
final base = [
_manualPrimary!,
_manualSecondary ?? _manualPrimary!,
_manualTertiary ?? (_manualSecondary ?? _manualPrimary!)
];
return DynamicColorScheme.generateDynamicColorScheme(base,
isDark: isDark);
}
if (_isUsingDynamicTheme && _dynamicColorScheme != null) {
// Return dynamic theme with appropriate brightness
if (isDark) {
return DynamicColorScheme.generateDynamicColorScheme(_logoColors,
isDark: true);
} else {
return _dynamicColorScheme!;
}
} else {
// Return default theme
return DynamicColorScheme.generateDynamicColorScheme([], isDark: isDark);
}
}
// Get gradient colors for UI elements
List<Color> getGradientColors() {
if (_useManualOverride && _manualPrimary != null) {
return DynamicColorScheme.generateGradientColors([
_manualPrimary!,
_manualSecondary ?? _manualPrimary!,
_manualTertiary ?? (_manualSecondary ?? _manualPrimary!)
]);
}
if (_isUsingDynamicTheme && _logoColors.isNotEmpty) {
return DynamicColorScheme.generateGradientColors(_logoColors);
} else {
return DynamicColorScheme.generateGradientColors([]);
}
}
// Get accent colors for semantic elements
Map<String, Color> getAccentColors() {
if (_useManualOverride && _manualPrimary != null) {
return DynamicColorScheme.generateAccentColors([
_manualPrimary!,
_manualSecondary ?? _manualPrimary!,
_manualTertiary ?? (_manualSecondary ?? _manualPrimary!)
]);
}
if (_isUsingDynamicTheme && _logoColors.isNotEmpty) {
return DynamicColorScheme.generateAccentColors(_logoColors);
} else {
return DynamicColorScheme.generateAccentColors([]);
}
}
// Reset to default theme
void resetToDefaultTheme() {
_isUsingDynamicTheme = false;
_dynamicColorScheme = null;
_logoColors = [];
_useManualOverride = false;
_manualPrimary = null;
_manualSecondary = null;
_manualTertiary = null;
notifyListeners();
}
// Enable manual override with provided colors (hex or Color)
void enableManualOverride(
{Color? primary, Color? secondary, Color? tertiary}) {
_useManualOverride = true;
if (primary != null) _manualPrimary = primary;
if (secondary != null) _manualSecondary = secondary;
if (tertiary != null) _manualTertiary = tertiary;
notifyListeners();
}
// Disable manual override
void disableManualOverride() {
_useManualOverride = false;
notifyListeners();
}
// Update manual colors
void setManualColors({Color? primary, Color? secondary, Color? tertiary}) {
if (primary != null) _manualPrimary = primary;
if (secondary != null) _manualSecondary = secondary;
if (tertiary != null) _manualTertiary = tertiary;
// Ensure override is on when user sets colors
_useManualOverride = true;
notifyListeners();
}
// Check if logo has specific color characteristics
bool hasWarmColors() {
if (_logoColors.isEmpty) return false;
for (final color in _logoColors) {
final hsl = HSLColor.fromColor(color);
// Warm colors: red, orange, yellow (0-60 degrees)
if (hsl.hue >= 0 && hsl.hue <= 60) {
return true;
}
}
return false;
}
bool hasCoolColors() {
if (_logoColors.isEmpty) return false;
for (final color in _logoColors) {
final hsl = HSLColor.fromColor(color);
// Cool colors: blue, green, purple (120-300 degrees)
if (hsl.hue >= 120 && hsl.hue <= 300) {
return true;
}
}
return false;
}
bool hasNeutralColors() {
if (_logoColors.isEmpty) return false;
for (final color in _logoColors) {
final hsl = HSLColor.fromColor(color);
// Neutral colors: low saturation
if (hsl.saturation < 0.3) {
return true;
}
}
return false;
}
// Get theme description
String getThemeDescription() {
if (!_isUsingDynamicTheme) {
return 'Default Theme';
}
List<String> characteristics = [];
if (hasWarmColors()) characteristics.add('Warm');
if (hasCoolColors()) characteristics.add('Cool');
if (hasNeutralColors()) characteristics.add('Neutral');
if (characteristics.isEmpty) {
characteristics.add('Dynamic');
}
return '${characteristics.join(', ')} Theme';
}
// Get color palette info
Map<String, dynamic> getColorPaletteInfo() {
if (_logoColors.isEmpty) {
return {
'primary': null,
'secondary': null,
'tertiary': null,
'totalColors': 0,
'description': 'No logo colors available',
};
}
return {
'primary': _logoColors.isNotEmpty ? _logoColors[0] : null,
'secondary': _logoColors.length > 1 ? _logoColors[1] : null,
'tertiary': _logoColors.length > 2 ? _logoColors[2] : null,
'totalColors': _logoColors.length,
'description': 'Generated from uploaded logo',
'characteristics': {
'warm': hasWarmColors(),
'cool': hasCoolColors(),
'neutral': hasNeutralColors(),
},
};
}
}

View File

@@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
class ThemeProvider extends ChangeNotifier {
bool _isDarkMode = false;
bool get isDarkMode => _isDarkMode;
void toggleTheme() {
_isDarkMode = !_isDarkMode;
notifyListeners();
}
void setTheme(bool isDark) {
if (_isDarkMode != isDark) {
_isDarkMode = isDark;
notifyListeners();
}
}
}

View File

@@ -0,0 +1,624 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/dynamic_theme_provider.dart';
import '../providers/theme_provider.dart';
class AppTheme {
static ThemeData getLightTheme(BuildContext context) {
final dynamicThemeProvider =
Provider.of<DynamicThemeProvider>(context, listen: false);
final themeProvider = Provider.of<ThemeProvider>(context, listen: false);
// Get color scheme (dynamic or default)
final colorScheme = dynamicThemeProvider.getCurrentColorScheme(false);
return ThemeData(
useMaterial3: true,
brightness: Brightness.light,
colorScheme: colorScheme,
textTheme: _getTextTheme(colorScheme),
appBarTheme: _getAppBarTheme(colorScheme),
cardTheme: _getCardTheme(colorScheme),
elevatedButtonTheme: _getElevatedButtonTheme(colorScheme),
outlinedButtonTheme: _getOutlinedButtonTheme(colorScheme),
textButtonTheme: _getTextButtonTheme(colorScheme),
inputDecorationTheme: _getInputDecorationTheme(colorScheme),
bottomNavigationBarTheme: _getBottomNavigationBarTheme(colorScheme),
floatingActionButtonTheme: _getFloatingActionButtonTheme(colorScheme),
dividerTheme: _getDividerTheme(colorScheme),
iconTheme: _getIconTheme(colorScheme),
chipTheme: _getChipTheme(colorScheme),
switchTheme: _getSwitchTheme(colorScheme),
checkboxTheme: _getCheckboxTheme(colorScheme),
radioTheme: _getRadioTheme(colorScheme),
sliderTheme: _getSliderTheme(colorScheme),
progressIndicatorTheme: _getProgressIndicatorTheme(colorScheme),
snackBarTheme: _getSnackBarTheme(colorScheme),
dialogTheme: _getDialogTheme(colorScheme),
bottomSheetTheme: _getBottomSheetTheme(colorScheme),
tooltipTheme: _getTooltipTheme(colorScheme),
popupMenuTheme: _getPopupMenuTheme(colorScheme),
drawerTheme: _getDrawerTheme(colorScheme),
listTileTheme: _getListTileTheme(colorScheme),
tabBarTheme: _getTabBarTheme(colorScheme),
dataTableTheme: _getDataTableTheme(colorScheme),
expansionTileTheme: _getExpansionTileTheme(colorScheme),
timePickerTheme: _getTimePickerTheme(colorScheme),
datePickerTheme: _getDatePickerTheme(colorScheme),
pageTransitionsTheme: _getPageTransitionsTheme(),
);
}
static ThemeData getDarkTheme(BuildContext context) {
final dynamicThemeProvider =
Provider.of<DynamicThemeProvider>(context, listen: false);
final themeProvider = Provider.of<ThemeProvider>(context, listen: false);
// Get color scheme (dynamic or default)
final colorScheme = dynamicThemeProvider.getCurrentColorScheme(true);
return ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
colorScheme: colorScheme,
textTheme: _getTextTheme(colorScheme),
appBarTheme: _getAppBarTheme(colorScheme),
cardTheme: _getCardTheme(colorScheme),
elevatedButtonTheme: _getElevatedButtonTheme(colorScheme),
outlinedButtonTheme: _getOutlinedButtonTheme(colorScheme),
textButtonTheme: _getTextButtonTheme(colorScheme),
inputDecorationTheme: _getInputDecorationTheme(colorScheme),
bottomNavigationBarTheme: _getBottomNavigationBarTheme(colorScheme),
floatingActionButtonTheme: _getFloatingActionButtonTheme(colorScheme),
dividerTheme: _getDividerTheme(colorScheme),
iconTheme: _getIconTheme(colorScheme),
chipTheme: _getChipTheme(colorScheme),
switchTheme: _getSwitchTheme(colorScheme),
checkboxTheme: _getCheckboxTheme(colorScheme),
radioTheme: _getRadioTheme(colorScheme),
sliderTheme: _getSliderTheme(colorScheme),
progressIndicatorTheme: _getProgressIndicatorTheme(colorScheme),
snackBarTheme: _getSnackBarTheme(colorScheme),
dialogTheme: _getDialogTheme(colorScheme),
bottomSheetTheme: _getBottomSheetTheme(colorScheme),
tooltipTheme: _getTooltipTheme(colorScheme),
popupMenuTheme: _getPopupMenuTheme(colorScheme),
drawerTheme: _getDrawerTheme(colorScheme),
listTileTheme: _getListTileTheme(colorScheme),
tabBarTheme: _getTabBarTheme(colorScheme),
dataTableTheme: _getDataTableTheme(colorScheme),
expansionTileTheme: _getExpansionTileTheme(colorScheme),
timePickerTheme: _getTimePickerTheme(colorScheme),
datePickerTheme: _getDatePickerTheme(colorScheme),
pageTransitionsTheme: _getPageTransitionsTheme(),
);
}
// Get theme based on current mode
// Get theme based on current mode
static ThemeData getTheme(BuildContext context) {
final themeProvider = Provider.of<ThemeProvider>(context, listen: false);
final dynamicThemeProvider =
Provider.of<DynamicThemeProvider>(context, listen: false);
// Force rebuild when dynamic theme changes
dynamicThemeProvider.isUsingDynamicTheme;
return themeProvider.isDarkMode
? getDarkTheme(context)
: getLightTheme(context);
}
// Text Theme
static TextTheme _getTextTheme(ColorScheme colorScheme) {
return TextTheme(
displayLarge: TextStyle(
fontSize: 57,
fontWeight: FontWeight.w400,
color: colorScheme.onSurface,
letterSpacing: -0.25,
),
displayMedium: TextStyle(
fontSize: 45,
fontWeight: FontWeight.w400,
color: colorScheme.onSurface,
letterSpacing: 0,
),
displaySmall: TextStyle(
fontSize: 36,
fontWeight: FontWeight.w400,
color: colorScheme.onSurface,
letterSpacing: 0,
),
headlineLarge: TextStyle(
fontSize: 32,
fontWeight: FontWeight.w400,
color: colorScheme.onSurface,
letterSpacing: 0,
),
headlineMedium: TextStyle(
fontSize: 28,
fontWeight: FontWeight.w400,
color: colorScheme.onSurface,
letterSpacing: 0,
),
headlineSmall: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w400,
color: colorScheme.onSurface,
letterSpacing: 0,
),
titleLarge: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w500,
color: colorScheme.onSurface,
letterSpacing: 0,
),
titleMedium: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: colorScheme.onSurface,
letterSpacing: 0.15,
),
titleSmall: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: colorScheme.onSurface,
letterSpacing: 0.1,
),
bodyLarge: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
color: colorScheme.onSurface,
letterSpacing: 0.5,
),
bodyMedium: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: colorScheme.onSurface,
letterSpacing: 0.25,
),
bodySmall: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: colorScheme.onSurface,
letterSpacing: 0.4,
),
labelLarge: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: colorScheme.onSurface,
letterSpacing: 0.1,
),
labelMedium: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: colorScheme.onSurface,
letterSpacing: 0.5,
),
labelSmall: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w500,
color: colorScheme.onSurface,
letterSpacing: 0.5,
),
);
}
// AppBar Theme
static AppBarTheme _getAppBarTheme(ColorScheme colorScheme) {
return AppBarTheme(
backgroundColor: colorScheme.surface,
foregroundColor: colorScheme.onSurface,
elevation: 0,
centerTitle: true,
titleTextStyle: TextStyle(
color: colorScheme.onSurface,
fontSize: 20,
fontWeight: FontWeight.w600,
),
iconTheme: IconThemeData(
color: colorScheme.onSurface,
size: 24,
),
actionsIconTheme: IconThemeData(
color: colorScheme.onSurface,
size: 24,
),
);
}
// Card Theme
static CardThemeData _getCardTheme(ColorScheme colorScheme) {
return CardThemeData(
color: colorScheme.surface,
elevation: 2,
shadowColor: colorScheme.shadow,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
margin: const EdgeInsets.all(8),
);
}
// Elevated Button Theme
static ElevatedButtonThemeData _getElevatedButtonTheme(
ColorScheme colorScheme) {
return ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.onPrimary,
elevation: 2,
shadowColor: colorScheme.shadow,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
textStyle: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
);
}
// Outlined Button Theme
static OutlinedButtonThemeData _getOutlinedButtonTheme(
ColorScheme colorScheme) {
return OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: colorScheme.primary,
side: BorderSide(color: colorScheme.outline, width: 1),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
textStyle: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
);
}
// Text Button Theme
static TextButtonThemeData _getTextButtonTheme(ColorScheme colorScheme) {
return TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: colorScheme.primary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
textStyle: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
);
}
// Input Decoration Theme
static InputDecorationTheme _getInputDecorationTheme(
ColorScheme colorScheme) {
return InputDecorationTheme(
filled: true,
fillColor: colorScheme.surfaceVariant,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: colorScheme.outline),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: colorScheme.outline),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: colorScheme.primary, width: 2),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: colorScheme.error),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: colorScheme.error, width: 2),
),
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
labelStyle: TextStyle(
color: colorScheme.onSurfaceVariant,
fontSize: 16,
),
hintStyle: TextStyle(
color: colorScheme.onSurfaceVariant,
fontSize: 16,
),
);
}
// Bottom Navigation Bar Theme
static BottomNavigationBarThemeData _getBottomNavigationBarTheme(
ColorScheme colorScheme) {
return BottomNavigationBarThemeData(
backgroundColor: colorScheme.surface,
selectedItemColor: colorScheme.primary,
unselectedItemColor: colorScheme.onSurfaceVariant,
type: BottomNavigationBarType.fixed,
elevation: 8,
);
}
// Floating Action Button Theme
static FloatingActionButtonThemeData _getFloatingActionButtonTheme(
ColorScheme colorScheme) {
return FloatingActionButtonThemeData(
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.onPrimary,
elevation: 6,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
);
}
// Divider Theme
static DividerThemeData _getDividerTheme(ColorScheme colorScheme) {
return DividerThemeData(
color: colorScheme.outlineVariant,
thickness: 1,
space: 1,
);
}
// Icon Theme
static IconThemeData _getIconTheme(ColorScheme colorScheme) {
return IconThemeData(
color: colorScheme.onSurface,
size: 24,
);
}
// Chip Theme
static ChipThemeData _getChipTheme(ColorScheme colorScheme) {
return ChipThemeData(
backgroundColor: colorScheme.surfaceVariant,
selectedColor: colorScheme.primaryContainer,
labelStyle: TextStyle(
color: colorScheme.onSurface,
fontSize: 14,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
);
}
// Switch Theme
// Switch Theme
static SwitchThemeData _getSwitchTheme(ColorScheme colorScheme) {
return SwitchThemeData(
thumbColor: MaterialStateProperty.all<Color?>(colorScheme.primary),
trackColor: MaterialStateProperty.all<Color?>(colorScheme.surfaceVariant),
);
}
// Checkbox Theme
// Checkbox Theme
static CheckboxThemeData _getCheckboxTheme(ColorScheme colorScheme) {
return CheckboxThemeData(
fillColor: MaterialStateProperty.resolveWith<Color?>((states) {
if (states.contains(MaterialState.selected)) {
return colorScheme.primary;
}
return colorScheme.surfaceVariant;
}),
checkColor: MaterialStateProperty.all<Color?>(colorScheme.onPrimary),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
),
);
}
// Radio Theme
static RadioThemeData _getRadioTheme(ColorScheme colorScheme) {
return RadioThemeData(
fillColor: MaterialStateProperty.resolveWith<Color?>((states) {
if (states.contains(MaterialState.selected)) {
return colorScheme.primary;
}
return colorScheme.surfaceVariant;
}),
);
}
// Slider Theme
static SliderThemeData _getSliderTheme(ColorScheme colorScheme) {
return SliderThemeData(
activeTrackColor: colorScheme.primary,
inactiveTrackColor: colorScheme.surfaceVariant,
thumbColor: colorScheme.primary,
overlayColor: colorScheme.primary.withOpacity(0.2),
);
}
// Progress Indicator Theme
static ProgressIndicatorThemeData _getProgressIndicatorTheme(
ColorScheme colorScheme) {
return ProgressIndicatorThemeData(
color: colorScheme.primary,
linearTrackColor: colorScheme.surfaceVariant,
);
}
// SnackBar Theme
static SnackBarThemeData _getSnackBarTheme(ColorScheme colorScheme) {
return SnackBarThemeData(
backgroundColor: colorScheme.surface,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
);
}
// Dialog Theme
static DialogThemeData _getDialogTheme(ColorScheme colorScheme) {
return DialogThemeData(
backgroundColor: colorScheme.surface,
elevation: 24,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
);
}
// Bottom Sheet Theme
static BottomSheetThemeData _getBottomSheetTheme(ColorScheme colorScheme) {
return BottomSheetThemeData(
backgroundColor: colorScheme.surface,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
);
}
// Tooltip Theme
static TooltipThemeData _getTooltipTheme(ColorScheme colorScheme) {
return TooltipThemeData(
decoration: BoxDecoration(
color: colorScheme.surface,
borderRadius: BorderRadius.circular(8),
),
textStyle: TextStyle(
color: colorScheme.onSurface,
fontSize: 14,
),
);
}
// Popup Menu Theme
static PopupMenuThemeData _getPopupMenuTheme(ColorScheme colorScheme) {
return PopupMenuThemeData(
color: colorScheme.surface,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
);
}
// Drawer Theme
static DrawerThemeData _getDrawerTheme(ColorScheme colorScheme) {
return DrawerThemeData(
backgroundColor: colorScheme.surface,
elevation: 16,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.horizontal(right: Radius.circular(16)),
),
);
}
// List Tile Theme
static ListTileThemeData _getListTileTheme(ColorScheme colorScheme) {
return ListTileThemeData(
tileColor: colorScheme.surface,
textColor: colorScheme.onSurface,
iconColor: colorScheme.onSurfaceVariant,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(12),
// ),
);
}
// Tab Bar Theme
// Tab Bar Theme
static TabBarThemeData _getTabBarTheme(ColorScheme colorScheme) {
return TabBarThemeData(
labelColor: colorScheme.primary,
unselectedLabelColor: colorScheme.onSurfaceVariant,
indicatorColor: colorScheme.primary,
);
}
// Data Table Theme
static DataTableThemeData _getDataTableTheme(ColorScheme colorScheme) {
return DataTableThemeData(
dataTextStyle: TextStyle(
color: colorScheme.onSurface,
fontSize: 14,
),
headingTextStyle: TextStyle(
color: colorScheme.onSurface,
fontSize: 14,
fontWeight: FontWeight.w600,
),
dividerThickness: 1,
dataRowColor: MaterialStateProperty.all<Color?>(colorScheme.surface),
headingRowColor:
MaterialStateProperty.all<Color?>(colorScheme.surfaceVariant),
);
}
// Expansion Tile Theme
static ExpansionTileThemeData _getExpansionTileTheme(
ColorScheme colorScheme) {
return ExpansionTileThemeData(
backgroundColor: colorScheme.surface,
collapsedBackgroundColor: colorScheme.surfaceVariant,
textColor: colorScheme.onSurface,
collapsedTextColor: colorScheme.onSurfaceVariant,
iconColor: colorScheme.primary,
collapsedIconColor: colorScheme.onSurfaceVariant,
);
}
// Time Picker Theme
static TimePickerThemeData _getTimePickerTheme(ColorScheme colorScheme) {
return TimePickerThemeData(
backgroundColor: colorScheme.surface,
hourMinuteTextColor: colorScheme.onSurface,
hourMinuteColor: colorScheme.surfaceVariant,
dayPeriodTextColor: colorScheme.onSurface,
dayPeriodColor: colorScheme.surfaceVariant,
dialHandColor: colorScheme.primary,
dialBackgroundColor: colorScheme.surfaceVariant,
dialTextColor: colorScheme.onSurface,
entryModeIconColor: colorScheme.onSurfaceVariant,
);
}
// Date Picker Theme
static DatePickerThemeData _getDatePickerTheme(ColorScheme colorScheme) {
return DatePickerThemeData(
backgroundColor: colorScheme.surface,
headerBackgroundColor: colorScheme.primary,
headerForegroundColor: colorScheme.onPrimary,
dayForegroundColor:
MaterialStateProperty.all<Color?>(colorScheme.onSurface),
dayBackgroundColor:
MaterialStateProperty.all<Color?>(colorScheme.surface),
todayForegroundColor:
MaterialStateProperty.all<Color?>(colorScheme.primary),
todayBackgroundColor:
MaterialStateProperty.all<Color?>(colorScheme.primaryContainer),
yearForegroundColor:
MaterialStateProperty.all<Color?>(colorScheme.onSurface),
yearBackgroundColor:
MaterialStateProperty.all<Color?>(colorScheme.surface),
);
}
// Page Transitions Theme
static PageTransitionsTheme _getPageTransitionsTheme() {
return const PageTransitionsTheme(
builders: {
TargetPlatform.android: CupertinoPageTransitionsBuilder(),
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
TargetPlatform.windows: CupertinoPageTransitionsBuilder(),
TargetPlatform.macOS: CupertinoPageTransitionsBuilder(),
TargetPlatform.linux: CupertinoPageTransitionsBuilder(),
},
);
}
}

View File

@@ -0,0 +1,126 @@
import 'package:flutter/material.dart';
class AppColorScheme {
// Primary Colors - Modern Blue
static const Color _primaryLight = Color(0xFF2563EB);
static const Color _primaryDark = Color(0xFF3B82F6);
// Secondary Colors - Purple
static const Color _secondaryLight = Color(0xFF7C3AED);
static const Color _secondaryDark = Color(0xFF8B5CF6);
// Tertiary Colors - Teal
static const Color _tertiaryLight = Color(0xFF0D9488);
static const Color _tertiaryDark = Color(0xFF14B8A6);
// Light Color Scheme
static const ColorScheme lightColorScheme = ColorScheme(
brightness: Brightness.light,
primary: _primaryLight,
onPrimary: Color(0xFFFFFFFF),
primaryContainer: Color(0xFFDBEAFE),
onPrimaryContainer: Color(0xFF001E3C),
secondary: _secondaryLight,
onSecondary: Color(0xFFFFFFFF),
secondaryContainer: Color(0xFFEDE9FE),
onSecondaryContainer: Color(0xFF21005D),
tertiary: _tertiaryLight,
onTertiary: Color(0xFFFFFFFF),
tertiaryContainer: Color(0xFFCCFBF1),
onTertiaryContainer: Color(0xFF00201C),
error: Color(0xFFDC2626),
onError: Color(0xFFFFFFFF),
errorContainer: Color(0xFFFEE2E2),
onErrorContainer: Color(0xFF7F1D1D),
background: Color(0xFFFAFAFA),
onBackground: Color(0xFF1A1A1A),
surface: Color(0xFFFFFFFF),
onSurface: Color(0xFF1A1A1A),
surfaceVariant: Color(0xFFF3F4F6),
onSurfaceVariant: Color(0xFF6B7280),
outline: Color(0xFFD1D5DB),
outlineVariant: Color(0xFFE5E7EB),
shadow: Color(0xFF000000),
scrim: Color(0xFF000000),
inverseSurface: Color(0xFF1F2937),
onInverseSurface: Color(0xFFF9FAFB),
inversePrimary: Color(0xFF93C5FD),
surfaceTint: _primaryLight,
);
// Dark Color Scheme
static const ColorScheme darkColorScheme = ColorScheme(
brightness: Brightness.dark,
primary: _primaryDark,
onPrimary: Color(0xFF000000),
primaryContainer: Color(0xFF1E3A8A),
onPrimaryContainer: Color(0xFFDBEAFE),
secondary: _secondaryDark,
onSecondary: Color(0xFF000000),
secondaryContainer: Color(0xFF4C1D95),
onSecondaryContainer: Color(0xFFEDE9FE),
tertiary: _tertiaryDark,
onTertiary: Color(0xFF000000),
tertiaryContainer: Color(0xFF0F766E),
onTertiaryContainer: Color(0xFFCCFBF1),
error: Color(0xFFEF4444),
onError: Color(0xFF000000),
errorContainer: Color(0xFF7F1D1D),
onErrorContainer: Color(0xFFFEE2E2),
background: Color(0xFF0F172A),
onBackground: Color(0xFFF8FAFC),
surface: Color(0xFF1E293B),
onSurface: Color(0xFFF8FAFC),
surfaceVariant: Color(0xFF334155),
onSurfaceVariant: Color(0xFFCBD5E1),
outline: Color(0xFF64748B),
outlineVariant: Color(0xFF475569),
shadow: Color(0xFF000000),
scrim: Color(0xFF000000),
inverseSurface: Color(0xFFF8FAFC),
onInverseSurface: Color(0xFF1E293B),
inversePrimary: Color(0xFF1E40AF),
surfaceTint: _primaryDark,
);
// Additional Custom Colors
static const Color success = Color(0xFF10B981);
static const Color warning = Color(0xFFF59E0B);
static const Color info = Color(0xFF3B82F6);
// Convenience getters for commonly used colors
static Color get primary => _primaryLight;
static Color get secondary => _secondaryLight;
static Color get tertiary => _tertiaryLight;
static Color get error => Color(0xFFDC2626);
// Gradient Colors
static const List<Color> primaryGradient = [
Color(0xFF2563EB),
Color(0xFF1D4ED8),
Color(0xFF1E40AF),
];
static const List<Color> secondaryGradient = [
Color(0xFF7C3AED),
Color(0xFF6D28D9),
Color(0xFF5B21B6),
];
static const List<Color> successGradient = [
Color(0xFF10B981),
Color(0xFF059669),
Color(0xFF047857),
];
static const List<Color> errorGradient = [
Color(0xFFEF4444),
Color(0xFFDC2626),
Color(0xFFB91C1C),
];
// Get color scheme based on brightness
static ColorScheme getColorScheme(Brightness brightness) {
return brightness == Brightness.dark ? darkColorScheme : lightColorScheme;
}
}

View File

@@ -0,0 +1,355 @@
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:palette_generator/palette_generator.dart';
class DynamicColorScheme {
static const Color _defaultPrimary = Color(0xFF3C82FF); // lighter blue
static const Color _defaultSecondary = Color(0xFF7C3AED);
static const Color _defaultTertiary = Color(0xFF0D9488);
static const Color _defaultSurface = Color(0xFFFFFFFF);
static const Color _defaultBackground = Color(0xFFFAFAFA);
// Extract dominant colors from logo image
static Future<List<Color>> extractColorsFromImage(
Uint8List imageBytes) async {
try {
// Convert bytes to image
final codec = await ui.instantiateImageCodec(imageBytes);
final frame = await codec.getNextFrame();
final image = frame.image;
// Generate palette from image
final paletteGenerator = await PaletteGenerator.fromImage(image);
List<Color> colors = [];
// Add dominant colors
if (paletteGenerator.dominantColor != null) {
colors.add(paletteGenerator.dominantColor!.color);
}
// Add vibrant colors
if (paletteGenerator.vibrantColor != null) {
colors.add(paletteGenerator.vibrantColor!.color);
}
// Add muted colors
if (paletteGenerator.mutedColor != null) {
colors.add(paletteGenerator.mutedColor!.color);
}
// Add light vibrant colors
if (paletteGenerator.lightVibrantColor != null) {
colors.add(paletteGenerator.lightVibrantColor!.color);
}
// Add dark vibrant colors
if (paletteGenerator.darkVibrantColor != null) {
colors.add(paletteGenerator.darkVibrantColor!.color);
}
// Ensure we have at least 3 colors
while (colors.length < 3) {
colors.add(_generateComplementaryColor(
colors.isNotEmpty ? colors.first : _defaultPrimary));
}
return colors.take(5).toList(); // Return top 5 colors
} catch (e) {
print('Error extracting colors: $e');
return [_defaultPrimary, _defaultSecondary, _defaultTertiary];
}
}
// Lightweight extraction using ImageProvider with downscaling to reduce jank
static Future<List<Color>> extractColorsFromBytesLite(
Uint8List imageBytes) async {
try {
final provider = MemoryImage(imageBytes);
// Downscale to limit processing cost
final resized = ResizeImage(provider, width: 256, height: 256);
final paletteGenerator =
await PaletteGenerator.fromImageProvider(resized);
final List<Color> colors = [];
if (paletteGenerator.dominantColor != null) {
colors.add(paletteGenerator.dominantColor!.color);
}
if (paletteGenerator.vibrantColor != null) {
colors.add(paletteGenerator.vibrantColor!.color);
}
if (paletteGenerator.mutedColor != null) {
colors.add(paletteGenerator.mutedColor!.color);
}
if (paletteGenerator.lightVibrantColor != null) {
colors.add(paletteGenerator.lightVibrantColor!.color);
}
if (paletteGenerator.darkVibrantColor != null) {
colors.add(paletteGenerator.darkVibrantColor!.color);
}
while (colors.length < 3) {
colors.add(_generateComplementaryColor(
colors.isNotEmpty ? colors.first : _defaultPrimary));
}
return colors.take(5).toList();
} catch (e) {
print('Error extracting colors (lite): $e');
return [_defaultPrimary, _defaultSecondary, _defaultTertiary];
}
}
// Generate complementary color
static Color _generateComplementaryColor(Color baseColor) {
final hsl = HSLColor.fromColor(baseColor);
return hsl.withHue((hsl.hue + 180) % 360).toColor();
}
// Generate dynamic color scheme from logo colors
static ColorScheme generateDynamicColorScheme(List<Color> logoColors,
{bool isDark = false}) {
if (logoColors.isEmpty) {
return _getDefaultColorScheme(isDark);
}
try {
// Extract primary, secondary, and tertiary colors
final primary = logoColors[0];
final secondary = logoColors.length > 1
? logoColors[1]
: _generateComplementaryColor(primary);
final tertiary = logoColors.length > 2
? logoColors[2]
: _generateComplementaryColor(secondary);
// Generate surface and background colors
final surface =
isDark ? _darkenColor(primary, 0.95) : _lightenColor(primary, 0.95);
final background =
isDark ? _darkenColor(primary, 0.98) : _lightenColor(primary, 0.98);
// Generate on-surface and on-background colors
final onSurface = isDark ? Colors.white : Colors.black;
final onBackground = isDark ? Colors.white : Colors.black;
// Generate outline and outline variant
final outline =
isDark ? _lightenColor(primary, 0.3) : _darkenColor(primary, 0.3);
final outlineVariant =
isDark ? _lightenColor(primary, 0.2) : _darkenColor(primary, 0.2);
// Generate surface variant
final surfaceVariant =
isDark ? _darkenColor(primary, 0.9) : _lightenColor(primary, 0.9);
// Generate error, warning, success, info colors
final error = isDark ? Color(0xFFF87171) : Color(0xFFEF4444);
final warning = isDark ? Color(0xFFFBBF24) : Color(0xFFF59E0B);
final success = isDark ? Color(0xFF34D399) : Color(0xFF10B981);
final info = isDark ? Color(0xFF60A5FA) : Color(0xFF3B82F6);
return ColorScheme(
brightness: isDark ? Brightness.dark : Brightness.light,
primary: primary,
onPrimary: _getContrastColor(primary),
primaryContainer: _generateContainerColor(primary, isDark),
onPrimaryContainer:
_getContrastColor(_generateContainerColor(primary, isDark)),
secondary: secondary,
onSecondary: _getContrastColor(secondary),
secondaryContainer: _generateContainerColor(secondary, isDark),
onSecondaryContainer:
_getContrastColor(_generateContainerColor(secondary, isDark)),
tertiary: tertiary,
onTertiary: _getContrastColor(tertiary),
tertiaryContainer: _generateContainerColor(tertiary, isDark),
onTertiaryContainer:
_getContrastColor(_generateContainerColor(tertiary, isDark)),
surface: surface,
onSurface: onSurface,
surfaceVariant: surfaceVariant,
onSurfaceVariant: _getContrastColor(surfaceVariant),
background: background,
onBackground: onBackground,
outline: outline,
outlineVariant: outlineVariant,
error: error,
onError: _getContrastColor(error),
errorContainer: _generateContainerColor(error, isDark),
onErrorContainer:
_getContrastColor(_generateContainerColor(error, isDark)),
shadow: isDark ? Colors.black : Colors.black12,
scrim: isDark ? Colors.black54 : Colors.black26,
inverseSurface: isDark ? background : surface,
onInverseSurface: isDark ? onBackground : onSurface,
inversePrimary:
isDark ? _lightenColor(primary, 0.8) : _darkenColor(primary, 0.8),
);
} catch (e) {
print('Error generating dynamic color scheme: $e');
return _getDefaultColorScheme(isDark);
}
}
// Get default color scheme
static ColorScheme _getDefaultColorScheme(bool isDark) {
if (isDark) {
return const ColorScheme.dark(
primary: Color(0xFF3B82F6),
secondary: Color(0xFF8B5CF6),
tertiary: Color(0xFF14B8A6),
);
} else {
return const ColorScheme.light(
primary: Color(0xFF2563EB),
secondary: Color(0xFF7C3AED),
tertiary: Color(0xFF0D9488),
);
}
}
// Generate container color
static Color _generateContainerColor(Color baseColor, bool isDark) {
if (isDark) {
return _lightenColor(baseColor, 0.2);
} else {
return _darkenColor(baseColor, 0.9);
}
}
// Lighten color
static Color _lightenColor(Color color, double amount) {
final hsl = HSLColor.fromColor(color);
return hsl
.withLightness((hsl.lightness + amount).clamp(0.0, 1.0))
.toColor();
}
// Darken color
static Color _darkenColor(Color color, double amount) {
final hsl = HSLColor.fromColor(color);
return hsl
.withLightness((hsl.lightness - amount).clamp(0.0, 1.0))
.toColor();
}
// Get contrast color (black or white)
static Color _getContrastColor(Color backgroundColor) {
final luminance = backgroundColor.computeLuminance();
return luminance > 0.5 ? Colors.black : Colors.white;
}
// Generate gradient colors from logo
static List<Color> generateGradientColors(List<Color> logoColors) {
if (logoColors.isEmpty) {
return [_defaultPrimary, _defaultSecondary, _defaultTertiary];
}
List<Color> gradientColors = [];
// Add primary colors
gradientColors.addAll(logoColors.take(3));
// Generate complementary colors if needed
while (gradientColors.length < 3) {
final lastColor = gradientColors.last;
gradientColors.add(_generateComplementaryColor(lastColor));
}
return gradientColors;
}
// Generate accent colors for specific UI elements
static Map<String, Color> generateAccentColors(List<Color> logoColors) {
if (logoColors.isEmpty) {
return {
'success': const Color(0xFF10B981),
'warning': const Color(0xFFF59E0B),
'error': const Color(0xFFEF4444),
'info': const Color(0xFF3B82F6),
};
}
final primary = logoColors[0];
return {
'success': _adjustColorForSuccess(primary),
'warning': _adjustColorForWarning(primary),
'error': _adjustColorForError(primary),
'info': _adjustColorForInfo(primary),
};
}
// Adjust colors for semantic meanings
static Color _adjustColorForSuccess(Color baseColor) {
final hsl = HSLColor.fromColor(baseColor);
return hsl.withHue(120).withSaturation(0.8).withLightness(0.5).toColor();
}
static Color _adjustColorForWarning(Color baseColor) {
final hsl = HSLColor.fromColor(baseColor);
return hsl.withHue(45).withSaturation(0.9).withLightness(0.6).toColor();
}
static Color _adjustColorForError(Color baseColor) {
final hsl = HSLColor.fromColor(baseColor);
return hsl.withHue(0).withSaturation(0.8).withLightness(0.5).toColor();
}
static Color _adjustColorForInfo(Color baseColor) {
final hsl = HSLColor.fromColor(baseColor);
return hsl.withHue(210).withSaturation(0.8).withLightness(0.5).toColor();
}
}
// Top-level helper to run in an isolate: returns list of color values (ints)
Future<List<int>> extractColorValuesFromImageBytes(Uint8List imageBytes) async {
try {
// Convert bytes to image
final codec = await ui.instantiateImageCodec(imageBytes);
final frame = await codec.getNextFrame();
final image = frame.image;
// Generate palette from image
final paletteGenerator = await PaletteGenerator.fromImage(image);
final List<int> values = [];
if (paletteGenerator.dominantColor != null) {
values.add(paletteGenerator.dominantColor!.color.value);
}
if (paletteGenerator.vibrantColor != null) {
values.add(paletteGenerator.vibrantColor!.color.value);
}
if (paletteGenerator.mutedColor != null) {
values.add(paletteGenerator.mutedColor!.color.value);
}
if (paletteGenerator.lightVibrantColor != null) {
values.add(paletteGenerator.lightVibrantColor!.color.value);
}
if (paletteGenerator.darkVibrantColor != null) {
values.add(paletteGenerator.darkVibrantColor!.color.value);
}
while (values.length < 3) {
// Fallback complementary color generation using default primary
values.add(DynamicColorScheme._generateComplementaryColor(
values.isNotEmpty
? Color(values.first)
: DynamicColorScheme._defaultPrimary,
).value);
}
return values.take(5).toList();
} catch (e) {
// Fallback to defaults
return [
DynamicColorScheme._defaultPrimary.value,
DynamicColorScheme._defaultSecondary.value,
DynamicColorScheme._defaultTertiary.value,
];
}
}

View File

@@ -0,0 +1,277 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
class AppTextTheme {
// Font Family
static const String _fontFamily = 'Inter';
// Light Text Theme
static TextTheme get lightTextTheme {
return GoogleFonts.interTextTheme().copyWith(
displayLarge: GoogleFonts.inter(
fontSize: 57,
fontWeight: FontWeight.w400,
letterSpacing: -0.25,
height: 1.12,
color: const Color(0xFF1A1A1A),
),
displayMedium: GoogleFonts.inter(
fontSize: 45,
fontWeight: FontWeight.w400,
letterSpacing: 0,
height: 1.16,
color: const Color(0xFF1A1A1A),
),
displaySmall: GoogleFonts.inter(
fontSize: 36,
fontWeight: FontWeight.w400,
letterSpacing: 0,
height: 1.22,
color: const Color(0xFF1A1A1A),
),
headlineLarge: GoogleFonts.inter(
fontSize: 32,
fontWeight: FontWeight.w600,
letterSpacing: 0,
height: 1.25,
color: const Color(0xFF1A1A1A),
),
headlineMedium: GoogleFonts.inter(
fontSize: 28,
fontWeight: FontWeight.w600,
letterSpacing: 0,
height: 1.29,
color: const Color(0xFF1A1A1A),
),
headlineSmall: GoogleFonts.inter(
fontSize: 24,
fontWeight: FontWeight.w600,
letterSpacing: 0,
height: 1.33,
color: const Color(0xFF1A1A1A),
),
titleLarge: GoogleFonts.inter(
fontSize: 22,
fontWeight: FontWeight.w600,
letterSpacing: 0,
height: 1.27,
color: const Color(0xFF1A1A1A),
),
titleMedium: GoogleFonts.inter(
fontSize: 16,
fontWeight: FontWeight.w600,
letterSpacing: 0.15,
height: 1.5,
color: const Color(0xFF1A1A1A),
),
titleSmall: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.w600,
letterSpacing: 0.1,
height: 1.43,
color: const Color(0xFF1A1A1A),
),
labelLarge: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.w600,
letterSpacing: 0.1,
height: 1.43,
color: const Color(0xFF1A1A1A),
),
labelMedium: GoogleFonts.inter(
fontSize: 12,
fontWeight: FontWeight.w600,
letterSpacing: 0.5,
height: 1.33,
color: const Color(0xFF1A1A1A),
),
labelSmall: GoogleFonts.inter(
fontSize: 11,
fontWeight: FontWeight.w600,
letterSpacing: 0.5,
height: 1.45,
color: const Color(0xFF1A1A1A),
),
bodyLarge: GoogleFonts.inter(
fontSize: 16,
fontWeight: FontWeight.w400,
letterSpacing: 0.5,
height: 1.5,
color: const Color(0xFF1A1A1A),
),
bodyMedium: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.w400,
letterSpacing: 0.25,
height: 1.43,
color: const Color(0xFF1A1A1A),
),
bodySmall: GoogleFonts.inter(
fontSize: 12,
fontWeight: FontWeight.w400,
letterSpacing: 0.4,
height: 1.33,
color: const Color(0xFF6B7280),
),
);
}
// Dark Text Theme
static TextTheme get darkTextTheme {
return GoogleFonts.interTextTheme().copyWith(
displayLarge: GoogleFonts.inter(
fontSize: 57,
fontWeight: FontWeight.w400,
letterSpacing: -0.25,
height: 1.12,
color: const Color(0xFFF8FAFC),
),
displayMedium: GoogleFonts.inter(
fontSize: 45,
fontWeight: FontWeight.w400,
letterSpacing: 0,
height: 1.16,
color: const Color(0xFFF8FAFC),
),
displaySmall: GoogleFonts.inter(
fontSize: 36,
fontWeight: FontWeight.w400,
letterSpacing: 0,
height: 1.22,
color: const Color(0xFFF8FAFC),
),
headlineLarge: GoogleFonts.inter(
fontSize: 32,
fontWeight: FontWeight.w600,
letterSpacing: 0,
height: 1.25,
color: const Color(0xFFF8FAFC),
),
headlineMedium: GoogleFonts.inter(
fontSize: 28,
fontWeight: FontWeight.w600,
letterSpacing: 0,
height: 1.29,
color: const Color(0xFFF8FAFC),
),
headlineSmall: GoogleFonts.inter(
fontSize: 24,
fontWeight: FontWeight.w600,
letterSpacing: 0,
height: 1.33,
color: const Color(0xFFF8FAFC),
),
titleLarge: GoogleFonts.inter(
fontSize: 22,
fontWeight: FontWeight.w600,
letterSpacing: 0,
height: 1.27,
color: const Color(0xFFF8FAFC),
),
titleMedium: GoogleFonts.inter(
fontSize: 16,
fontWeight: FontWeight.w600,
letterSpacing: 0.15,
height: 1.5,
color: const Color(0xFFF8FAFC),
),
titleSmall: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.w600,
letterSpacing: 0.1,
height: 1.43,
color: const Color(0xFFF8FAFC),
),
labelLarge: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.w600,
letterSpacing: 0.1,
height: 1.43,
color: const Color(0xFFF8FAFC),
),
labelMedium: GoogleFonts.inter(
fontSize: 12,
fontWeight: FontWeight.w600,
letterSpacing: 0.5,
height: 1.33,
color: const Color(0xFFF8FAFC),
),
labelSmall: GoogleFonts.inter(
fontSize: 11,
fontWeight: FontWeight.w600,
letterSpacing: 0.5,
height: 1.45,
color: const Color(0xFFF8FAFC),
),
bodyLarge: GoogleFonts.inter(
fontSize: 16,
fontWeight: FontWeight.w400,
letterSpacing: 0.5,
height: 1.5,
color: const Color(0xFFF8FAFC),
),
bodyMedium: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.w400,
letterSpacing: 0.25,
height: 1.43,
color: const Color(0xFFF8FAFC),
),
bodySmall: GoogleFonts.inter(
fontSize: 12,
fontWeight: FontWeight.w400,
letterSpacing: 0.4,
height: 1.33,
color: const Color(0xFFCBD5E1),
),
);
}
// Custom Text Styles for specific use cases
static TextStyle get heroTitle => GoogleFonts.inter(
fontSize: 48,
fontWeight: FontWeight.w700,
letterSpacing: -0.5,
height: 1.1,
);
static TextStyle get sectionTitle => GoogleFonts.inter(
fontSize: 20,
fontWeight: FontWeight.w600,
letterSpacing: 0,
height: 1.4,
);
static TextStyle get cardTitle => GoogleFonts.inter(
fontSize: 18,
fontWeight: FontWeight.w600,
letterSpacing: 0,
height: 1.33,
);
static TextStyle get buttonText => GoogleFonts.inter(
fontSize: 16,
fontWeight: FontWeight.w600,
letterSpacing: 0.1,
height: 1.5,
);
static TextStyle get caption => GoogleFonts.inter(
fontSize: 12,
fontWeight: FontWeight.w400,
letterSpacing: 0.4,
height: 1.33,
);
static TextStyle get overline => GoogleFonts.inter(
fontSize: 10,
fontWeight: FontWeight.w600,
letterSpacing: 1.5,
height: 1.6,
);
// Get text theme based on brightness
static TextTheme getTextTheme(Brightness brightness) {
return brightness == Brightness.dark ? darkTextTheme : lightTextTheme;
}
}

View File

@@ -0,0 +1,16 @@
import 'package:intl/date_symbol_data_local.dart';
import 'package:intl/intl.dart';
const String dateTimeFormatPattern = 'dd/MM/yyyy';
extension DateTimeExtension on DateTime {
String format({
String pattern = dateTimeFormatPattern,
String? locale,
}) {
if (locale != null && locale.isNotEmpty) {
initializeDateFormatting(locale);
}
return DateFormat(pattern, locale).format(this);
}
}

View File

@@ -0,0 +1,93 @@
import 'package:flutter/material.dart';
// These are the Viewport values of your Figma Design.
// These are used in the code as a reference to create your UI Responsively.
const num FIGMA_DESIGN_WIDTH = 428;
const num FIGMA_DESIGN_HEIGHT = 926;
const num FIGMA_DESIGN_STATUS_BAR = 0;
extension ResponsiveExtension on num {
double get _width => SizeUtils.width;
double get _height => SizeUtils.height;
double get h => ((this * _width) / FIGMA_DESIGN_WIDTH);
double get v =>
(this * _height) / (FIGMA_DESIGN_HEIGHT - FIGMA_DESIGN_STATUS_BAR);
double get adaptSize {
var height = v;
var width = h;
return height < width ? height.toDoubleValue() : width.toDoubleValue();
}
double get fSize => adaptSize;
}
extension FormatExtension on double {
double toDoubleValue({int fractionDigits = 2}) {
return double.parse(toStringAsFixed(fractionDigits));
}
double isNonZero({num defaultValue = 0.0}) {
return this > 0 ? this : defaultValue.toDouble();
}
}
enum DeviceType { mobile, tablet, desktop }
typedef ResponsiveBuild = Widget Function(
BuildContext context, Orientation orientation, DeviceType deviceType);
class Sizer extends StatelessWidget {
const Sizer({super.key, required this.builder});
/// Builds the widget whenever the orientation changes.
final ResponsiveBuild builder;
@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) {
return OrientationBuilder(builder: (context, orientation) {
SizeUtils.setScreenSize(constraints, orientation);
return builder(context, orientation, SizeUtils.deviceType);
});
});
}
}
// ignore_for_file: must_be_immutable
// ignore_for_file: must_be_immutable
class SizeUtils {
/// Device's BoxConstraints
static late BoxConstraints boxConstraints;
/// Device's Orientation
static late Orientation orientation;
/// Type of Device
///
/// This can either be mobile or tablet
static late DeviceType deviceType;
/// Device's Height
static late double height;
/// Device's Width
static late double width;
static void setScreenSize(
BoxConstraints constraints,
Orientation currentOrientation,
) {
boxConstraints = constraints;
orientation = currentOrientation;
if (orientation == Orientation.portrait) {
width =
boxConstraints.maxWidth.isNonZero(defaultValue: FIGMA_DESIGN_WIDTH);
height = boxConstraints.maxHeight.isNonZero();
} else {
width =
boxConstraints.maxHeight.isNonZero(defaultValue: FIGMA_DESIGN_WIDTH);
height = boxConstraints.maxWidth.isNonZero();
}
deviceType = DeviceType.mobile;
}
}

View File

@@ -0,0 +1,13 @@
bool isValidEmail(String? inputString, {bool isRequired = false}) {
bool isInputStringValid = false;
if (!isRequired && (inputString == null ? true : inputString.isEmpty)) {
isInputStringValid = true;
}
if (inputString != null && inputString.isNotEmpty) {
const pattern =
r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
final regExp = RegExp(pattern);
isInputStringValid = regExp.hasMatch(inputString);
}
return isInputStringValid;
}

View File

@@ -0,0 +1,81 @@
class AppExceptions implements Exception {
final String? _message;
final String? _prefix;
AppExceptions([this._message, this._prefix]);
@override
String toString() {
return "${_prefix ?? ''}${_message ?? 'An unknown error occurred'}";
}
}
// Network error when data fetch fails
class FetchDataException extends AppExceptions {
FetchDataException([String? message])
: super(message ?? "Network Error: Failed to communicate with the server. Please check your internet connection and try again.",
"Error During Communication: ");
}
// Error for invalid or malformed requests
class BadRequestException extends AppExceptions {
BadRequestException([String? message])
: super(message ?? "Client Error: The request sent to the server was malformed or contained invalid parameters.",
"Invalid Request: ");
}
// Error for unauthorized access
class UnauthorizedException extends AppExceptions {
UnauthorizedException([String? message])
: super(message ?? "Authorization Error: You are not authorized to perform this action. Please log in with appropriate credentials.",
"Unauthorized: ");
}
// Error when a resource is not found
class NotFoundException extends AppExceptions {
NotFoundException([String? message])
: super(message ?? "Resource Not Found: The requested resource could not be found on the server. It may have been moved or deleted.",
"Not Found: ");
}
// Error for server-side issues
class InternalServerErrorException extends AppExceptions {
InternalServerErrorException([String? message])
: super(message ?? "Server Error: An unexpected error occurred on the server. Please try again later or contact support.",
"Internal Server Error: ");
}
// Error when user input is invalid
class InvalidInputException extends AppExceptions {
InvalidInputException([String? message])
: super(message ?? "Validation Error: The provided input does not match the required format. Please correct the errors and try again.",
"Invalid Input: ");
}
// Error when a request times out
class TimeoutException extends AppExceptions {
TimeoutException([String? message])
: super(message ?? "Request Timeout: The server took too long to respond. Please check your connection and try again.",
"Timeout: ");
}
// Error when a request conflicts with the current state
class ConflictException extends AppExceptions {
ConflictException([String? message])
: super(message ?? "Conflict Error: The request could not be processed because of a conflict with the current state of the resource.",
"Conflict: ");
}
// Error when the service is unavailable
class ServiceUnavailableException extends AppExceptions {
ServiceUnavailableException([String? message])
: super(message ?? "Service Unavailable: The server is currently unable to handle the request. Please try again later.",
"Service Unavailable: ");
}
// Error when access to a resource is forbidden
class ForbiddenException extends AppExceptions {
ForbiddenException([String? message])
: super(message ?? "Forbidden: You do not have the necessary permissions to access this resource.",
"Forbidden: ");
}

View File

@@ -0,0 +1,7 @@
abstract class BaseNetworkService {
Future<dynamic> getGetApiResponse(String? url);
Future<dynamic> getPostApiResponse(String? url, dynamic body);
Future<dynamic> getPutApiResponse(String? url, dynamic body);
Future<dynamic> getDeleteApiResponse(String? url);
}

View File

@@ -0,0 +1,157 @@
import 'dart:io';
import 'package:base_project/utils/managers/user_manager.dart';
import 'package:dio/dio.dart';
import '../exceptions/app_exceptions.dart';
import 'base_network_service.dart';
class NetworkApiService extends BaseNetworkService {
final Dio _dio = Dio();
NetworkApiService() {
// Optionally configure Dio, e.g. add interceptors
_dio.options.connectTimeout = const Duration(seconds: 30); // 30 seconds
_dio.options.receiveTimeout = const Duration(seconds: 30);
}
@override
Future<dynamic> getGetApiResponse(String? url) async {
try {
final token = UserManager().token;
// print("token..$token");
final headers = {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json', // Add other headers if needed
};
final response = await _dio.get(
url!,
options: Options(headers: headers),
);
return _handleResponse(response);
} on DioException catch (e) {
return _handleDioError(e);
} on SocketException {
throw FetchDataException('No Internet Connection');
} catch (e) {
throw FetchDataException('An unexpected error occurred: $e');
}
}
@override
Future<dynamic> getPostApiResponse(String? url, dynamic body) async {
try {
final token = UserManager().token;
final headers = {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json', // Add other headers if needed
};
final response = await _dio.post(
url!,
data: body,
options: Options(headers: headers),
);
return _handleResponse(response);
} on DioException catch (e) {
return _handleDioError(e);
} on SocketException {
throw FetchDataException('No Internet Connection');
} catch (e) {
throw FetchDataException('An unexpected error occurred: $e');
}
}
@override
Future<dynamic> getPutApiResponse(String? url, dynamic body) async {
try {
final token = UserManager().token;
final headers = {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json', // Add other headers if needed
};
final response = await _dio.put(
url!,
data: body,
options: Options(headers: headers),
);
return _handleResponse(response);
} on DioException catch (e) {
return _handleDioError(e);
} on SocketException {
throw FetchDataException('No Internet Connection');
} catch (e) {
throw FetchDataException('An unexpected error occurred: $e');
}
}
@override
Future<dynamic> getDeleteApiResponse(String? url) async {
try {
final token = UserManager().token;
final headers = {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json', // Add other headers if needed
};
final response = await _dio.delete(
url!,
options: Options(headers: headers),
);
return _handleResponse(response);
} on DioException catch (e) {
return _handleDioError(e);
} on SocketException {
throw FetchDataException('No Internet Connection');
} catch (e) {
throw FetchDataException('An unexpected error occurred: $e');
}
}
dynamic _handleResponse(Response response) {
switch (response.statusCode) {
case 200:
case 201:
try {
// Handle empty body
if (response.data == null || response.data.toString().isEmpty) {
return null;
}
return response.data;
} catch (e) {
throw FetchDataException('Error parsing response: $e');
}
case 400:
throw BadRequestException('Bad request: ${response.data}');
case 401:
throw UnauthorizedException('Unauthorized request: ${response.data}');
case 403:
throw ForbiddenException('Forbidden request: ${response.data}');
case 404:
throw NotFoundException('Not found: ${response.data}');
case 500:
throw InternalServerErrorException('Server error: ${response.data}');
default:
throw FetchDataException(
'Error while communicating with server: ${response.statusCode}');
}
}
dynamic _handleDioError(DioException error) {
switch (error.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
throw FetchDataException("Request timed out.");
case DioExceptionType.badResponse:
return _handleResponse(error.response!);
case DioExceptionType.cancel:
throw FetchDataException("Request was cancelled.");
case DioExceptionType.connectionError:
throw FetchDataException("Connection failed due to internet issue.");
default:
throw FetchDataException("Unexpected error occurred.");
}
}
}

View File

@@ -0,0 +1,142 @@
import 'dart:io';
import 'package:dio/dio.dart';
import '../exceptions/app_exceptions.dart';
import 'no_token_base_network_service.dart';
class NoTokenNetworkApiService extends NoTokenBaseNetworkService {
final Dio _dio = Dio();
NetworkApiService() {
// Optionally configure Dio, e.g. add interceptors
_dio.options.connectTimeout = const Duration(seconds: 30); // 30 seconds
_dio.options.receiveTimeout = const Duration(seconds: 30);
}
@override
Future<dynamic> getGetApiResponse(String? url) async {
try {
final headers = {
'Content-Type': 'application/json', // Add other headers if needed
};
final response = await _dio.get(
url!,
options: Options(headers: headers),
);
return _handleResponse(response);
} on DioException catch (e) {
return _handleDioError(e);
} on SocketException {
throw FetchDataException('No Internet Connection');
} catch (e) {
throw FetchDataException('An unexpected error occurred: $e');
}
}
@override
Future<dynamic> getPostApiResponse(String? url, dynamic body) async {
try {
final headers = {
'Content-Type': 'application/json', // Add other headers if needed
};
final response = await _dio.post(
url!,
data: body,
options: Options(headers: headers),
);
return _handleResponse(response);
} on DioException catch (e) {
return _handleDioError(e);
} on SocketException {
throw FetchDataException('No Internet Connection');
} catch (e) {
throw FetchDataException('An unexpected error occurred: $e');
}
}
@override
Future<dynamic> getPutApiResponse(String? url, dynamic body) async {
try {
final headers = {
'Content-Type': 'application/json', // Add other headers if needed
};
final response = await _dio.put(
url!,
data: body,
options: Options(headers: headers),
);
return _handleResponse(response);
} on DioException catch (e) {
return _handleDioError(e);
} on SocketException {
throw FetchDataException('No Internet Connection');
} catch (e) {
throw FetchDataException('An unexpected error occurred: $e');
}
}
@override
Future<dynamic> getDeleteApiResponse(String? url) async {
try {
final headers = {
'Content-Type': 'application/json', // Add other headers if needed
};
final response = await _dio.delete(
url!,
options: Options(headers: headers),
);
return _handleResponse(response);
} on DioException catch (e) {
return _handleDioError(e);
} on SocketException {
throw FetchDataException('No Internet Connection');
} catch (e) {
throw FetchDataException('An unexpected error occurred: $e');
}
}
dynamic _handleResponse(Response response) {
switch (response.statusCode) {
case 200:
case 201:
try {
// Handle empty body
if (response.data == null || response.data.toString().isEmpty) {
return null;
}
return response.data;
} catch (e) {
throw FetchDataException('Error parsing response: $e');
}
case 400:
throw BadRequestException('Bad request: ${response.data}');
case 401:
throw UnauthorizedException('Unauthorized request: ${response.data}');
case 403:
throw ForbiddenException('Forbidden request: ${response.data}');
case 404:
throw NotFoundException('Not found: ${response.data}');
case 500:
throw InternalServerErrorException('Server error: ${response.data}');
default:
throw FetchDataException(
'Error while communicating with server: ${response.statusCode}');
}
}
dynamic _handleDioError(DioException error) {
switch (error.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
throw FetchDataException("Request timed out.");
case DioExceptionType.badResponse:
return _handleResponse(error.response!);
case DioExceptionType.cancel:
throw FetchDataException("Request was cancelled.");
case DioExceptionType.connectionError:
throw FetchDataException("Connection failed due to internet issue.");
default:
throw FetchDataException("Unexpected error occurred.");
}
}
}

View File

@@ -0,0 +1,7 @@
abstract class NoTokenBaseNetworkService {
Future<dynamic> getGetApiResponse(String? url);
Future<dynamic> getPostApiResponse(String? url, dynamic body);
Future<dynamic> getPutApiResponse(String? url, dynamic body);
Future<dynamic> getDeleteApiResponse(String? url);
}

View File

@@ -0,0 +1,36 @@
// import 'package:flutter_local_notifications/flutter_local_notifications.dart';
// class NotificationService {
// final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
// Future<void> initialize() async {
// const AndroidInitializationSettings initializationSettingsAndroid =
// AndroidInitializationSettings('@mipmap/ic_launcher');
// const InitializationSettings initializationSettings =
// InitializationSettings(android: initializationSettingsAndroid);
// await flutterLocalNotificationsPlugin.initialize(initializationSettings);
// }
// Future<void> showNotification(int id, String title, String body) async {
// const AndroidNotificationDetails androidPlatformChannelSpecifics =
// AndroidNotificationDetails(
// 'channel_id',
// 'channel_name',
// channelDescription: 'channel_description',
// importance: Importance.max,
// priority: Priority.high,
// );
// const NotificationDetails platformChannelSpecifics =
// NotificationDetails(android: androidPlatformChannelSpecifics);
// await flutterLocalNotificationsPlugin.show(
// id,
// title,
// body,
// platformChannelSpecifics,
// );
// }
// }

View File

@@ -0,0 +1,23 @@
import 'package:base_project/data/response/status.dart';
class ApiResponse<T> {
Status? status;
T? data;
String? message;
ApiResponse(this.status, this.data, this.message);
// Named constructor for loading state
ApiResponse.loading() : status = Status.LOADING;
// Named constructor for completed state
ApiResponse.success(this.data) : status = Status.SUCCESS;
// Named constructor for error state
ApiResponse.error(this.message) : status = Status.ERROR;
@override
String toString() {
return "Status: $status \n Message: $message \n Data: $data";
}
}

View File

@@ -0,0 +1 @@
enum Status {LOADING,SUCCESS,ERROR}

104
base_project/lib/main.dart Normal file
View File

@@ -0,0 +1,104 @@
import 'package:base_project/resources/app_colors.dart';
import 'package:base_project/routes/route_names.dart';
import 'package:base_project/view_model/auth/auth_view_model.dart';
import 'package:base_project/view_model/profile/profile_view_model.dart';
import 'package:base_project/view_model/system_params/system_params_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'core/providers/dynamic_theme_provider.dart';
import 'core/providers/theme_provider.dart';
import 'core/theme/app_theme.dart';
import 'routes/app_routes.dart';
import 'utils/managers/user_manager.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await UserManager().initialize();
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
runApp(MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => AuthViewModel()),
ChangeNotifierProvider(create: (context) => ProfileViewModel()),
ChangeNotifierProvider(create: (context) => SystemParamsViewModel()),
ChangeNotifierProvider(create: (context) => ThemeProvider()),
ChangeNotifierProvider(create: (context) => DynamicThemeProvider()),
],
child: const MyApp(),
));
}
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
class MyApp extends StatelessWidget {
const MyApp({super.key});
// @override
// Widget build(BuildContext context) {
// // Set the navigation bar color when the app starts
// SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
// systemNavigationBarColor:
// AppColors.primary, // Set your desired color here
// systemNavigationBarIconBrightness: Brightness.light, // Icons color
// ));
// return MaterialApp(
// theme: ThemeData(
// primaryColor: AppColors.primary,
// visualDensity: VisualDensity.adaptivePlatformDensity,
// useMaterial3: false,
// scaffoldBackgroundColor: Colors.grey[200],
// drawerTheme: DrawerThemeData(backgroundColor: Colors.grey[200]),
// iconTheme: const IconThemeData(color: AppColors.primary),
// appBarTheme: const AppBarTheme(
// scrolledUnderElevation: 0, backgroundColor: AppColors.primary)),
// title: 'Base Project',
// debugShowCheckedModeBanner: false,
// // home: const SysParameter(),
// initialRoute: RouteNames.splashView,
// onGenerateRoute: AppRoutes.generateRoutes,
// );
// }
// }
@override
Widget build(BuildContext context) {
return Consumer2<ThemeProvider, DynamicThemeProvider>(
builder: (context, themeProvider, dynamicThemeProvider, child) {
final theme = AppTheme.getTheme(context);
// Set system UI overlay style
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness:
themeProvider.isDarkMode ? Brightness.light : Brightness.dark,
systemNavigationBarColor: theme.colorScheme.surface,
systemNavigationBarIconBrightness:
themeProvider.isDarkMode ? Brightness.light : Brightness.dark,
));
return MaterialApp(
title: 'AuthSec Flutter',
debugShowCheckedModeBanner: false,
theme: AppTheme.getLightTheme(context),
darkTheme: AppTheme.getDarkTheme(context),
themeMode:
themeProvider.isDarkMode ? ThemeMode.dark : ThemeMode.light,
initialRoute: RouteNames.splashView,
onGenerateRoute: AppRoutes.generateRoutes,
navigatorKey: navigatorKey,
builder: (context, child) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(
textScaler: MediaQuery.of(context).textScaler.clamp(
minScaleFactor: 0.8,
maxScaleFactor: 1.4,
),
),
child: child!,
);
},
);
},
);
}
}

View File

@@ -0,0 +1,210 @@
class SystemParamsModel {
final int? id;
final int? schedulerTime;
final String? leaseTaxCode;
final int? vesselConfProcessLimit;
final int? rowToDisplay;
final int? linkToDisplay;
final int? rowToAdd;
final int? lovRowToDisplay;
final int? lovLinkToDisplay;
final String? oidserverName;
final String? oidBase;
final String? oidAdminUser;
final int? oidServerPort;
final int? userDefaultGroup;
final String? defaultDepartment;
final String? defaultPosition;
final String? singleCharge;
final String? firstDayOftheWeek;
final int? hourPerShift;
final int? cnBillingFrequency;
final String? billingDepartmentCode;
final String? basePriceList;
final String? nonContainerServiceOrder;
final int? ediMaeSchedulerONOFF;
final String? ediSchedulerONOFF;
final String? uploadLogo;
final String? uploadLogoName;
final String? uploadLogoPath;
final String? companyDisplayName;
final bool? isRegitrationAllowed;
final List<dynamic>? sysParamUploads;
SystemParamsModel({
this.id,
this.schedulerTime,
this.leaseTaxCode,
this.vesselConfProcessLimit,
this.rowToDisplay,
this.linkToDisplay,
this.rowToAdd,
this.lovRowToDisplay,
this.lovLinkToDisplay,
this.oidserverName,
this.oidBase,
this.oidAdminUser,
this.oidServerPort,
this.userDefaultGroup,
this.defaultDepartment,
this.defaultPosition,
this.singleCharge,
this.firstDayOftheWeek,
this.hourPerShift,
this.cnBillingFrequency,
this.billingDepartmentCode,
this.basePriceList,
this.nonContainerServiceOrder,
this.ediMaeSchedulerONOFF,
this.ediSchedulerONOFF,
this.uploadLogo,
this.uploadLogoName,
this.uploadLogoPath,
this.companyDisplayName,
this.isRegitrationAllowed,
this.sysParamUploads,
});
factory SystemParamsModel.fromJson(Map<String, dynamic> json) {
return SystemParamsModel(
id: json['id'],
schedulerTime: json['schedulerTime'],
leaseTaxCode: json['leaseTaxCode'],
vesselConfProcessLimit: json['vesselConfProcessLimit'],
rowToDisplay: json['rowToDisplay'],
linkToDisplay: json['linkToDisplay'],
rowToAdd: json['rowToAdd'],
lovRowToDisplay: json['lovRowToDisplay'],
lovLinkToDisplay: json['lovLinkToDisplay'],
oidserverName: json['oidserverName'],
oidBase: json['oidBase'],
oidAdminUser: json['oidAdminUser'],
oidServerPort: json['oidServerPort'],
userDefaultGroup: json['userDefaultGroup'],
defaultDepartment: json['defaultDepartment'],
defaultPosition: json['defaultPosition'],
singleCharge: json['singleCharge'],
firstDayOftheWeek: json['firstDayOftheWeek'],
hourPerShift: json['hourPerShift'],
cnBillingFrequency: json['cnBillingFrequency'],
billingDepartmentCode: json['billingDepartmentCode'],
basePriceList: json['basePriceList'],
nonContainerServiceOrder: json['nonContainerServiceOrder'],
ediMaeSchedulerONOFF: json['ediMaeSchedulerONOFF'],
ediSchedulerONOFF: json['ediSchedulerONOFF'],
uploadLogo: json['upload_Logo'],
uploadLogoName: json['upload_Logo_name'],
uploadLogoPath: json['upload_Logo_path'],
companyDisplayName: json['Company_Display_Name'],
isRegitrationAllowed: json['isRegitrationAllowed'],
sysParamUploads: json['sysParamUploads'],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'schedulerTime': schedulerTime,
'leaseTaxCode': leaseTaxCode,
'vesselConfProcessLimit': vesselConfProcessLimit,
'rowToDisplay': rowToDisplay,
'linkToDisplay': linkToDisplay,
'rowToAdd': rowToAdd,
'lovRowToDisplay': lovRowToDisplay,
'lovLinkToDisplay': lovLinkToDisplay,
'oidserverName': oidserverName,
'oidBase': oidBase,
'oidAdminUser': oidAdminUser,
'oidServerPort': oidServerPort,
'userDefaultGroup': userDefaultGroup,
'defaultDepartment': defaultDepartment,
'defaultPosition': defaultPosition,
'singleCharge': singleCharge,
'firstDayOftheWeek': firstDayOftheWeek,
'hourPerShift': hourPerShift,
'cnBillingFrequency': cnBillingFrequency,
'billingDepartmentCode': billingDepartmentCode,
'basePriceList': basePriceList,
'nonContainerServiceOrder': nonContainerServiceOrder,
'ediMaeSchedulerONOFF': ediMaeSchedulerONOFF,
'ediSchedulerONOFF': ediSchedulerONOFF,
'upload_Logo': uploadLogo,
'upload_Logo_name': uploadLogoName,
'upload_Logo_path': uploadLogoPath,
'Company_Display_Name': companyDisplayName,
'isRegitrationAllowed': isRegitrationAllowed,
'sysParamUploads': sysParamUploads,
};
}
SystemParamsModel copyWith({
int? id,
int? schedulerTime,
String? leaseTaxCode,
int? vesselConfProcessLimit,
int? rowToDisplay,
int? linkToDisplay,
int? rowToAdd,
int? lovRowToDisplay,
int? lovLinkToDisplay,
String? oidserverName,
String? oidBase,
String? oidAdminUser,
int? oidServerPort,
int? userDefaultGroup,
String? defaultDepartment,
String? defaultPosition,
String? singleCharge,
String? firstDayOftheWeek,
int? hourPerShift,
int? cnBillingFrequency,
String? billingDepartmentCode,
String? basePriceList,
String? nonContainerServiceOrder,
int? ediMaeSchedulerONOFF,
String? ediSchedulerONOFF,
String? uploadLogo,
String? uploadLogoName,
String? uploadLogoPath,
String? companyDisplayName,
bool? isRegitrationAllowed,
List<dynamic>? sysParamUploads,
}) {
return SystemParamsModel(
id: id ?? this.id,
schedulerTime: schedulerTime ?? this.schedulerTime,
leaseTaxCode: leaseTaxCode ?? this.leaseTaxCode,
vesselConfProcessLimit:
vesselConfProcessLimit ?? this.vesselConfProcessLimit,
rowToDisplay: rowToDisplay ?? this.rowToDisplay,
linkToDisplay: linkToDisplay ?? this.linkToDisplay,
rowToAdd: rowToAdd ?? this.rowToAdd,
lovRowToDisplay: lovRowToDisplay ?? this.lovRowToDisplay,
lovLinkToDisplay: lovLinkToDisplay ?? this.lovLinkToDisplay,
oidserverName: oidserverName ?? this.oidserverName,
oidBase: oidBase ?? this.oidBase,
oidAdminUser: oidAdminUser ?? this.oidAdminUser,
oidServerPort: oidServerPort ?? this.oidServerPort,
userDefaultGroup: userDefaultGroup ?? this.userDefaultGroup,
defaultDepartment: defaultDepartment ?? this.defaultDepartment,
defaultPosition: defaultPosition ?? this.defaultPosition,
singleCharge: singleCharge ?? this.singleCharge,
firstDayOftheWeek: firstDayOftheWeek ?? this.firstDayOftheWeek,
hourPerShift: hourPerShift ?? this.hourPerShift,
cnBillingFrequency: cnBillingFrequency ?? this.cnBillingFrequency,
billingDepartmentCode:
billingDepartmentCode ?? this.billingDepartmentCode,
basePriceList: basePriceList ?? this.basePriceList,
nonContainerServiceOrder:
nonContainerServiceOrder ?? this.nonContainerServiceOrder,
ediMaeSchedulerONOFF: ediMaeSchedulerONOFF ?? this.ediMaeSchedulerONOFF,
ediSchedulerONOFF: ediSchedulerONOFF ?? this.ediSchedulerONOFF,
uploadLogo: uploadLogo ?? this.uploadLogo,
uploadLogoName: uploadLogoName ?? this.uploadLogoName,
uploadLogoPath: uploadLogoPath ?? this.uploadLogoPath,
companyDisplayName: companyDisplayName ?? this.companyDisplayName,
isRegitrationAllowed: isRegitrationAllowed ?? this.isRegitrationAllowed,
sysParamUploads: sysParamUploads ?? this.sysParamUploads,
);
}
}

View File

@@ -0,0 +1,57 @@
import 'dart:convert';
class LoggedUserModel {
final String token;
final String userId;
final String fullname;
final String? username;
final String email;
final String firstName;
final List<String> roles;
LoggedUserModel({
required this.token,
required this.userId,
required this.fullname,
this.username,
required this.email,
required this.firstName,
required this.roles,
});
// Factory constructor to create a UserModel from a JSON map
factory LoggedUserModel.fromJson(Map<String, dynamic> json) {
return LoggedUserModel(
token: json['token'] as String,
userId: json['userId'] as String,
fullname: json['fullname'] as String,
username: json['username'] as String?,
email: json['email'] as String,
firstName: json['firstName'] as String,
roles: List<String>.from(json['roles'] as List<dynamic>),
);
}
// Method to convert UserModel to JSON map
Map<String, dynamic> toJson() {
return {
'token': token,
'userId': userId,
'fullname': fullname,
'username': username,
'email': email,
'firstName': firstName,
'roles': roles,
};
}
// Method to convert UserModel to a JSON string
String toJsonString() {
return json.encode(toJson());
}
// Factory constructor to create a UserModel from a JSON string
factory LoggedUserModel.fromJsonString(String jsonString) {
return LoggedUserModel.fromJson(json.decode(jsonString));
}
}

View File

@@ -0,0 +1,49 @@
class UserModelAdmin {
final String id;
final String userId;
final String userName;
final String fullName;
final String email;
final bool isPunchIn;
final bool isPunchOut;
final bool onBreak;
UserModelAdmin({
required this.id,
required this.userId,
required this.userName,
required this.fullName,
required this.email,
required this.isPunchIn,
required this.isPunchOut,
required this.onBreak,
});
// Factory method to create a User from JSON
factory UserModelAdmin.fromJson(Map<String, dynamic> json) {
return UserModelAdmin(
id: json['id'].toString(),
userId: json['userId'].toString(),
userName: json['userName'] ?? '',
fullName: json['fullName'] ?? '',
email: json['email'] ?? '',
isPunchIn: json['isPunchIn'] ?? false,
isPunchOut: json['isPunchOut'] ?? false,
onBreak: json['onBreak'] ?? false,
);
}
// Convert User to JSON
Map<String, dynamic> toJson() {
return {
'id': id,
'userId': userId,
'userName': userName,
'fullName': fullName,
'email': email,
'isPunchIn': isPunchIn,
'isPunchOut': isPunchOut,
'onBreak': onBreak,
};
}
}

View File

@@ -0,0 +1,26 @@
class UserProfile {
final dynamic userId;
final String username;
final String email;
final dynamic mobNo;
final String fullName;
UserProfile({
required this.userId,
required this.username,
required this.email,
required this.mobNo,
required this.fullName,
});
factory UserProfile.fromJson(Map<String, dynamic> json) {
return UserProfile(
userId: json['userId'],
username: json['username'],
email: json['email'],
mobNo: json['mob_no'],
fullName: json['fullName'],
);
}
}

View File

@@ -0,0 +1,71 @@
import 'package:base_project/data/network/no_token_base_network_service.dart';
import 'package:base_project/resources/api_constants.dart';
import '../data/network/no-token_network_api_service.dart';
class AuthRepo {
final NoTokenBaseNetworkService _service = NoTokenNetworkApiService();
Future<dynamic> loginApi(dynamic body) async {
try {
final res =
await _service.getPostApiResponse(ApiConstants.loginEndpoint, body);
return res;
} catch (e) {
rethrow;
}
}
Future<dynamic> getOtpApi(dynamic body) async {
try {
final res =
await _service.getPostApiResponse(ApiConstants.getOtpEndpoint, body);
return res;
} catch (e) {
rethrow;
}
}
Future<dynamic> verifyOtpApi(dynamic body) async {
print(' boody is $body');
try {
final email = Uri.encodeComponent((body['email'] ?? '').toString());
final otp = Uri.encodeComponent((body['otp'] ?? '').toString());
final url = "${ApiConstants.verifyEndpoint}?email=$email&otp=$otp";
final res = await _service.getPostApiResponse(url, null);
return res;
} catch (e) {
rethrow;
}
}
Future<dynamic> resendOtpApi(dynamic body) async {
try {
final res = await _service.getPostApiResponse(
ApiConstants.createAcEndpoint, body);
return res;
} catch (e) {
rethrow;
}
}
Future<dynamic> createUserApi(dynamic body) async {
try {
final res = await _service.getPostApiResponse(
ApiConstants.createUserEndpoint, body);
return res;
} catch (e) {
rethrow;
}
}
Future<dynamic> createAcApi(dynamic body) async {
try {
final res = await _service.getPostApiResponse(
ApiConstants.createAcEndpoint, body);
return res;
} catch (e) {
rethrow;
}
}
}

View File

@@ -0,0 +1,82 @@
import 'package:base_project/data/network/base_network_service.dart';
import 'package:base_project/data/network/network_api_service.dart';
import 'package:base_project/resources/api_constants.dart';
class ProfileRepo {
final BaseNetworkService _networkService = NetworkApiService();
Future<dynamic> getProfileImgApi() async {
try {
final response = _networkService
.getGetApiResponse(ApiConstants.getUserProfileImgEndpoint);
return response;
} catch (e) {
rethrow;
}
}
Future<dynamic> getProfileApi() async {
try {
final response = _networkService
.getGetApiResponse(ApiConstants.getUserProfileEndpoint);
return response;
} catch (e) {
rethrow;
}
}
Future<dynamic> updateProfileApi(dynamic data, dynamic uId) async {
final uri = Uri.parse("${ApiConstants.updateUserProfileEndpoint}/$uId");
try {
final response =
await _networkService.getPutApiResponse(uri.toString(), data);
return response;
} catch (e) {
rethrow;
}
}
Future<dynamic> updateProfileImg(dynamic data) async {
try {
final response = await _networkService.getPostApiResponse(
ApiConstants.updateUserProfileImgEndpoint, data);
return response;
} catch (e) {
rethrow;
}
}
Future<dynamic> changPassApi(dynamic data) async {
try {
final response = await _networkService.getPostApiResponse(
ApiConstants.changePasswordEndpoint, data);
print('chan res $response');
return response;
} catch (e) {
rethrow;
}
}
Future<dynamic> forgotPassApi(dynamic data) async {
try {
final response = await _networkService.getPostApiResponse(
ApiConstants.forgotPasswordEndpoint, data);
return response;
} catch (e) {
rethrow;
}
}
// Update System Account
Future<dynamic> updateAccountApi(dynamic accId, dynamic data) async {
final uri = Uri.parse("${ApiConstants.updateAcEndpoint}/$accId");
try {
final response =
await _networkService.getPutApiResponse(uri.toString(), data);
return response;
} catch (e) {
rethrow;
}
}
}

View File

@@ -0,0 +1,80 @@
import 'package:base_project/data/network/base_network_service.dart';
import 'package:base_project/data/network/network_api_service.dart';
import 'package:base_project/resources/api_constants.dart';
class SystemParamsRepo {
final BaseNetworkService _networkService = NetworkApiService();
Future<dynamic> getProfileImgApi() async {
try {
final response = _networkService
.getGetApiResponse(ApiConstants.getUserProfileImgEndpoint);
return response;
} catch (e) {
rethrow;
}
}
Future<dynamic> getProfileApi() async {
try {
final response = _networkService
.getGetApiResponse(ApiConstants.getUserProfileEndpoint);
return response;
} catch (e) {
rethrow;
}
}
Future<dynamic> updateProfileApi(dynamic data, dynamic uId) async {
final uri = Uri.parse("${ApiConstants.updateUserProfileEndpoint}/$uId");
try {
final response =
await _networkService.getPutApiResponse(uri.toString(), data);
return response;
} catch (e) {
rethrow;
}
}
Future<dynamic> updateProfileImg(dynamic data) async {
try {
final response = await _networkService.getPostApiResponse(
ApiConstants.updateUserProfileImgEndpoint, data);
return response;
} catch (e) {
rethrow;
}
}
Future<dynamic> getSystemParameters() async {
final uri = Uri.parse("${ApiConstants.getSystemParameters}");
try {
final response = _networkService.getGetApiResponse(uri.toString());
return response;
} catch (e) {
rethrow;
}
}
Future<dynamic> updateSystemParameters(dynamic body) async {
try {
final uri = Uri.parse("${ApiConstants.updateSystemParams}");
final response = _networkService.getPutApiResponse(uri.toString(), body);
return response;
} catch (e) {
rethrow;
}
}
Future<dynamic> uploadSystemParamLogo(dynamic data) async {
try {
final uri = Uri.parse("${ApiConstants.uploadSystemParamImg}");
final response =
await _networkService.getPostApiResponse(uri.toString(), data);
return response;
} catch (e) {
rethrow;
}
}
}

View File

@@ -0,0 +1,26 @@
class ApiConstants {
static const baseUrl = 'http://localhost:9292';
// USER AUTH API'S //
static const loginEndpoint = "$baseUrl/token/session";
static const getOtpEndpoint = "$baseUrl/token/user/send_email";
static const resendOtpEndpoint = "$baseUrl/token/user/resend_otp";
static const verifyEndpoint = "$baseUrl/token/user/otp_verification";
static const createUserEndpoint = "$baseUrl/token/addOneAppUser";
static const createAcEndpoint =
"$baseUrl/token/users/sysaccount/savesysaccount";
static const updateAcEndpoint =
"$baseUrl/token/users/sysaccount/savesysaccount"; // PUT {accId}
// PROFILE API'S //
static const getUserProfileEndpoint = '$baseUrl/api/user-profile';
static const getUserProfileImgEndpoint = '$baseUrl/api/retrieve-image';
static const updateUserProfileEndpoint = '$baseUrl/api/updateAppUserDto';
static const updateUserProfileImgEndpoint = '$baseUrl/api/upload';
static const changePasswordEndpoint = '$baseUrl/api/reset_password';
static const forgotPasswordEndpoint = '$baseUrl/api/resources/forgotpassword';
// SYSTEM PARAMS API'S //
static const uploadSystemParamImg = '$baseUrl/api/logos/upload?ref=test';
static const getSystemParameters = '$baseUrl/sysparam/getSysParams';
static const updateSystemParams = '$baseUrl/sysparam/updateSysParams';
}

View File

@@ -0,0 +1,64 @@
import 'package:flutter/material.dart';
class AppColors{
// App Main Colors
static const Color primary = Color.fromARGB(255, 8, 50, 112); // Vibrant blue
static const Color primary2 = Color(0xff7647EB); // Purple
static const Color accent = Color(0xffb88cec); // Soft Lavender
static const Color secondary = Color(0xffD2D8EC); // Soft Blue-Gray
// Additional Colors
static const Color complementary = Color(0xffFFA726); // Vibrant Orange
static const Color neutral = Color(0xffF5F5F5); // Light Gray or White
static const Color darkContrast = Color(0xff212121); // Charcoal Gray
// Text colors
static const Color textPrimary = Color(0xff282D40);
static const Color textSecondary = Color (0xffCACCD1);
static const Color textWhite = Colors.white;
// Background colors
static const Color background = Color(0xfff6f6f6);
static const Color btmNavBackground = Color(0xffededed);
static const Color tint = Color(0xffFFFFFF);
static Color? appbarBg = Colors.grey[900];
// Button colors punch in and out
static const Color clockInButtonColor1 = Color(0xfff84756);
static const Color clockInButtonColor2 = Color(0xfff42f40);
static const Color clockOutButtonColor1 = Color(0xff4CAF50);
static const Color clockOutButtonColor2 = Color(0xff2E7D32);
// Button colors break in and out
static const Color startBreakBtnColor1 = Color(0xfff88b47);
static const Color startBreakBtnColor2 = Color(0xfff45d2f);
static const Color endBreakBtnColor1 = Color(0xff2d49d6);
static const Color endBreakBtnColor2 = Color(0xFF1976D2);
// Validation colors
static const Color error = Color(0xffFF4C4C);
static const Color errorAccent = Color(0xffFFDBDB);
static const Color successAccent = Color(0xffDBFFEC);
static const Color success = Color(0xff00D261);
static const Color warning = Color (0xFFF57C00);
static const Color info = Color (0xFF1976D2);
// Neutral shades
static const Color grey = Color(0xff9E9E9E);
static const Color lightGrey = Color(0xffBDBDBD);
static const Color darkGrey = Color(0xff757575);
static const Color darkGrey2 = Color(0xff2e2e2e);
// Admin Card color
static const List<Color> breakIndicator = [Color(0xffdd5c0c),Color(0xffff6200)];
static const List<Color> presentIndicator = [Color(0xff2ca63c), Color(0xff1f8f2d)];
static const List<Color> absentIndicator = [Color(0xffc51f1f),Color(0xffea2424)];
}

View File

@@ -0,0 +1,87 @@
import 'package:base_project/routes/route_names.dart';
import 'package:base_project/view/auth/get_otp.dart';
import 'package:base_project/view/auth/login.dart';
import 'package:base_project/view/auth/register_acc.dart';
import 'package:base_project/view/auth/signup.dart';
import 'package:base_project/view/auth/verify_otp.dart';
import 'package:base_project/view/dashboard/home.dart';
import 'package:base_project/view/dashboard/profile/change_password.dart';
import 'package:base_project/view/dashboard/profile/profile.dart';
import 'package:base_project/view/splash_screen.dart';
import 'package:base_project/view/system_parameters/system_parameters.dart';
import 'package:flutter/material.dart';
class AppRoutes {
static Route<dynamic> generateRoutes(RouteSettings routeSettings) {
switch (routeSettings.name) {
case RouteNames.splashView:
return MaterialPageRoute(
builder: (context) => const SplashScreen(),
);
case RouteNames.loginView:
return MaterialPageRoute(
builder: (context) => const LoginView(),
);
case RouteNames.signUpView:
return MaterialPageRoute(
builder: (context) => const SignupView(),
);
case RouteNames.getOtpView:
return MaterialPageRoute(
builder: (context) => GetOtpView(),
);
case RouteNames.verifyOtpView:
final args = routeSettings.arguments;
String? email;
if (args is Map) {
email = args['email']?.toString();
}
return MaterialPageRoute(
builder: (context) => VerifyOtpView(email: email),
);
case RouteNames.registerAccView:
return MaterialPageRoute(
builder: (context) => const RegisterAccView(),
);
case RouteNames.homeView:
return MaterialPageRoute(
builder: (context) => const HomeView(),
);
case RouteNames.profileView:
return MaterialPageRoute(
builder: (context) => const ProfileView(),
);
// case RouteNames.editProfileView:
// return MaterialPageRoute(
// builder: (context) => const EditProfile(),
// );
case RouteNames.changePasswordView:
return MaterialPageRoute(
builder: (context) => ChangePassword(),
);
case RouteNames.systemParamsView:
return MaterialPageRoute(
builder: (context) => const SystemParametersView(),
);
default:
return MaterialPageRoute(
builder: (context) {
print("Checking Default route");
// SplashViewModel().checkNavigation(context);
// return const Scaffold(
// body: Center(
// child: Text("No route defined"),
// ),
// );
// If we're on System Parameters, do not auto-redirect
if (routeSettings.name == RouteNames.systemParamsView) {
return const SystemParametersView();
}
// Fallback to Splash so SplashViewModel can decide (login vs home)
return const SplashScreen();
},
);
}
}
}

View File

@@ -0,0 +1,18 @@
class RouteNames {
// AUTH VIEW //
static const splashView = '/splash';
static const loginView = '/login';
static const signUpView = '/signUp';
static const getOtpView = '/getOtp';
static const verifyOtpView = '/verifyOtp';
static const registerAccView = '/registerAccount';
static const createAccView = '/createAccount';
// MAIN APP VIEW //
static const homeView = '/home';
static const profileView = '/profile';
static const editProfileView = '/editProfile';
static const changePasswordView = '/changePassword';
static const systemParamsView = '/systemParams';
}

View File

@@ -0,0 +1,221 @@
import 'package:base_project/view_model/system_params/system_params_view_model.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../../core/constants/ui_constants.dart';
import '../../../core/providers/theme_provider.dart';
import '../theme_toggle.dart';
class ModernAppBar extends StatelessWidget implements PreferredSizeWidget {
final String title;
final List<Widget>? actions;
final Widget? leading;
final bool automaticallyImplyLeading;
final Color? backgroundColor;
final Color? foregroundColor;
final double? elevation;
final bool centerTitle;
final Widget? titleWidget;
final VoidCallback? onMenuPressed;
final VoidCallback? onProfilePressed;
final String? userAvatar;
final ImageProvider<Object>? userAvatarImage;
final String? userName;
final bool showThemeToggle;
final bool showUserProfile;
final bool showLogoInTitle;
final bool showBackButton;
final ImageProvider<Object>? logoImage;
const ModernAppBar({
super.key,
required this.title,
this.actions,
this.leading,
this.automaticallyImplyLeading = true,
this.backgroundColor,
this.foregroundColor,
this.elevation,
this.centerTitle = true,
this.titleWidget,
this.onMenuPressed,
this.onProfilePressed,
this.userAvatar,
this.userAvatarImage,
this.userName,
this.showThemeToggle = true,
this.showUserProfile = true,
this.showLogoInTitle = true,
this.showBackButton = false,
this.logoImage,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final isDarkMode = theme.brightness == Brightness.dark;
final Widget computedTitle = _buildTitle(context, theme, colorScheme);
return AppBar(
title: computedTitle,
centerTitle: titleWidget != null
? centerTitle
: (showLogoInTitle ? false : centerTitle),
backgroundColor: backgroundColor ?? colorScheme.surface,
foregroundColor: foregroundColor ?? colorScheme.onSurface,
elevation: elevation ?? 0,
automaticallyImplyLeading: automaticallyImplyLeading,
leading: leading ??
(automaticallyImplyLeading
? (showBackButton
? IconButton(
icon: Icon(
Icons.arrow_back,
color: foregroundColor ?? colorScheme.onSurface,
),
onPressed: () {
Navigator.of(context).pop();
},
)
: IconButton(
icon: Icon(
Icons.menu,
color: foregroundColor ?? colorScheme.onSurface,
),
onPressed: onMenuPressed ??
() {
Scaffold.of(context).openDrawer();
},
))
: null),
actions: [
// Theme Toggle
if (showThemeToggle) ...[
Consumer<ThemeProvider>(
builder: (context, themeProvider, child) {
return ThemeToggle(
isDarkMode: themeProvider.isDarkMode,
onThemeChanged: (isDark) {
themeProvider.setTheme(isDark);
},
size: UIConstants.iconSizeMedium,
);
},
),
const SizedBox(width: UIConstants.spacing8),
],
// User Profile
if (showUserProfile) ...[
GestureDetector(
onTap: onProfilePressed,
child: Container(
margin: const EdgeInsets.only(right: UIConstants.spacing16),
padding: const EdgeInsets.all(UIConstants.spacing4),
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: colorScheme.outline.withOpacity(0.3),
width: 2,
),
),
child: CircleAvatar(
radius: UIConstants.iconSizeMedium / 2,
backgroundColor: colorScheme.primaryContainer,
backgroundImage: userAvatarImage ??
(userAvatar != null ? NetworkImage(userAvatar!) : null),
child: userAvatarImage == null && userAvatar == null
? Icon(
Icons.person,
color: colorScheme.onPrimaryContainer,
size: UIConstants.iconSizeMedium * 0.6,
)
: null,
),
),
),
],
// Custom Actions
if (actions != null) ...actions!,
],
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(UIConstants.radius16),
),
),
);
}
@override
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
Widget _buildTitle(
BuildContext context, ThemeData theme, ColorScheme colorScheme) {
if (titleWidget != null) return titleWidget!;
final effectiveLogo = logoImage ?? _resolveLogoImage(context);
if (showLogoInTitle) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 64,
height: 64,
decoration: BoxDecoration(
color: (backgroundColor ?? colorScheme.surface),
borderRadius: BorderRadius.circular(UIConstants.radius12),
border: Border.all(
color: colorScheme.outline.withOpacity(0.15), width: 1),
),
clipBehavior: Clip.antiAlias,
child: FittedBox(
fit: BoxFit.contain,
alignment: Alignment.center,
child: effectiveLogo != null
? Image(
image: effectiveLogo,
filterQuality: FilterQuality.high,
)
: Icon(
Icons.image_not_supported_outlined,
color: colorScheme.onSurface.withOpacity(0.5),
),
),
),
const SizedBox(width: UIConstants.spacing12),
Text(
title,
style: theme.textTheme.headlineSmall?.copyWith(
color: foregroundColor ?? colorScheme.onSurface,
fontWeight: FontWeight.w600,
),
overflow: TextOverflow.ellipsis,
),
],
);
}
return Text(
title,
style: theme.textTheme.headlineSmall?.copyWith(
color: foregroundColor ?? colorScheme.onSurface,
fontWeight: FontWeight.w600,
),
);
}
ImageProvider<Object>? _resolveLogoImage(BuildContext context) {
try {
final sysVm = Provider.of<SystemParamsViewModel>(context, listen: true);
if (sysVm.profileImageBytes != null &&
sysVm.profileImageBytes!.isNotEmpty) {
return MemoryImage(sysVm.profileImageBytes!);
}
} catch (_) {}
return const AssetImage('assets/images/image_not_found.png');
}
}

View File

@@ -0,0 +1,342 @@
import 'package:flutter/material.dart';
import '../../../core/constants/ui_constants.dart';
enum ModernButtonType {
primary,
secondary,
outline,
text,
danger,
}
enum ModernButtonSize {
small,
medium,
large,
}
class ModernButton extends StatefulWidget {
final String text;
final VoidCallback? onPressed;
final ModernButtonType type;
final ModernButtonSize size;
final bool isLoading;
final bool isDisabled;
final Widget? icon;
final bool isIconOnly;
final double? width;
final double? height;
final EdgeInsetsGeometry? padding;
final BorderRadius? borderRadius;
final String? tooltip;
const ModernButton({
super.key,
required this.text,
this.onPressed,
this.type = ModernButtonType.primary,
this.size = ModernButtonSize.medium,
this.isLoading = false,
this.isDisabled = false,
this.icon,
this.isIconOnly = false,
this.width,
this.height,
this.padding,
this.borderRadius,
this.tooltip,
});
@override
State<ModernButton> createState() => _ModernButtonState();
}
class _ModernButtonState extends State<ModernButton>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _scaleAnimation;
late Animation<double> _elevationAnimation;
bool _isPressed = false;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: UIConstants.durationFast,
vsync: this,
);
_scaleAnimation = Tween<double>(
begin: 1.0,
end: 0.95,
).animate(CurvedAnimation(
parent: _animationController,
curve: UIConstants.curveFast,
));
_elevationAnimation = Tween<double>(
begin: 1.0,
end: 0.0,
).animate(CurvedAnimation(
parent: _animationController,
curve: UIConstants.curveFast,
));
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
void _onTapDown(TapDownDetails details) {
if (!widget.isDisabled && !widget.isLoading) {
setState(() {
_isPressed = true;
});
_animationController.forward();
}
}
void _onTapUp(TapUpDetails details) {
if (!widget.isDisabled && !widget.isLoading) {
setState(() {
_isPressed = false;
});
_animationController.reverse();
}
}
void _onTapCancel() {
if (!widget.isDisabled && !widget.isLoading) {
setState(() {
_isPressed = false;
});
_animationController.reverse();
}
}
bool get _canPress =>
!widget.isDisabled && !widget.isLoading && widget.onPressed != null;
Color _getBackgroundColor(ThemeData theme) {
if (!_canPress) {
return theme.colorScheme.surfaceVariant;
}
switch (widget.type) {
case ModernButtonType.primary:
return theme.colorScheme.primary;
case ModernButtonType.secondary:
return theme.colorScheme.secondary;
case ModernButtonType.outline:
case ModernButtonType.text:
return Colors.transparent;
case ModernButtonType.danger:
return theme.colorScheme.error;
}
}
Color _getTextColor(ThemeData theme) {
if (!_canPress) {
return theme.colorScheme.onSurfaceVariant;
}
switch (widget.type) {
case ModernButtonType.primary:
case ModernButtonType.secondary:
case ModernButtonType.danger:
return theme.colorScheme.onPrimary;
case ModernButtonType.outline:
return theme.colorScheme.primary;
case ModernButtonType.text:
return theme.colorScheme.primary;
}
}
Color _getBorderColor(ThemeData theme) {
if (!_canPress) {
return theme.colorScheme.outline;
}
switch (widget.type) {
case ModernButtonType.outline:
return theme.colorScheme.primary;
case ModernButtonType.danger:
return theme.colorScheme.error;
default:
return Colors.transparent;
}
}
double _getHeight() {
if (widget.height != null) return widget.height!;
switch (widget.size) {
case ModernButtonSize.small:
return UIConstants.buttonHeightSmall +
4; // extra room to avoid text clip
case ModernButtonSize.medium:
return UIConstants.buttonHeightMedium + 4;
case ModernButtonSize.large:
return UIConstants.buttonHeightLarge + 4;
}
}
EdgeInsetsGeometry _getPadding() {
if (widget.padding != null) return widget.padding!;
switch (widget.size) {
case ModernButtonSize.small:
return const EdgeInsets.symmetric(
horizontal: UIConstants.spacing16,
vertical: UIConstants.spacing6,
);
case ModernButtonSize.medium:
return const EdgeInsets.symmetric(
horizontal: UIConstants.spacing24,
vertical: UIConstants.spacing14,
);
case ModernButtonSize.large:
return const EdgeInsets.symmetric(
horizontal: UIConstants.spacing32,
vertical: UIConstants.spacing18,
);
}
}
double _getBorderRadius() {
if (widget.borderRadius != null) {
return widget.borderRadius!.topLeft.x;
}
switch (widget.size) {
case ModernButtonSize.small:
return UIConstants.radius8;
case ModernButtonSize.medium:
return UIConstants.radius16;
case ModernButtonSize.large:
return UIConstants.radius20;
}
}
TextStyle _getTextStyle(ThemeData theme) {
switch (widget.size) {
case ModernButtonSize.small:
return theme.textTheme.labelMedium?.copyWith(
fontWeight: FontWeight.w600,
) ??
const TextStyle();
case ModernButtonSize.medium:
return theme.textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.w600,
) ??
const TextStyle();
case ModernButtonSize.large:
return theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
) ??
const TextStyle();
}
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: GestureDetector(
onTapDown: _onTapDown,
onTapUp: _onTapUp,
onTapCancel: _onTapCancel,
child: Container(
width: widget.width,
height: _getHeight(),
decoration: BoxDecoration(
color: _getBackgroundColor(theme),
borderRadius: BorderRadius.circular(_getBorderRadius()),
border: Border.all(
color: _getBorderColor(theme),
width: widget.type == ModernButtonType.outline ? 1.5 : 0,
),
boxShadow: _canPress
? [
BoxShadow(
color: _getBackgroundColor(theme).withOpacity(0.3),
blurRadius: 8 * _elevationAnimation.value,
offset: Offset(0, 4 * _elevationAnimation.value),
),
]
: null,
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: _canPress ? widget.onPressed : null,
borderRadius: BorderRadius.circular(_getBorderRadius()),
child: Container(
padding: _getPadding(),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (widget.isLoading) ...[
SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
_getTextColor(theme),
),
),
),
if (!widget.isIconOnly) ...[
const SizedBox(width: UIConstants.spacing12),
],
] else ...[
if (widget.icon != null && !widget.isIconOnly) ...[
IconTheme(
data: IconThemeData(
color: _getTextColor(theme),
size: _getHeight() * 0.4,
),
child: widget.icon!,
),
const SizedBox(width: UIConstants.spacing12),
],
],
if (!widget.isIconOnly) ...[
Flexible(
child: Text(
widget.text,
style: _getTextStyle(theme).copyWith(
color: _getTextColor(theme),
height: 1.2,
),
strutStyle: const StrutStyle(
height: 1.2,
forceStrutHeight: true,
),
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
),
),
],
],
),
),
),
),
),
),
);
},
);
}
}

View File

@@ -0,0 +1,176 @@
import 'package:flutter/material.dart';
import '../../../core/constants/ui_constants.dart';
class QuickActionButton extends StatefulWidget {
final String label;
final IconData icon;
final VoidCallback? onTap;
final Color? backgroundColor;
final Color? iconColor;
final Color? labelColor;
final double? size;
final bool isLoading;
final bool isDisabled;
const QuickActionButton({
super.key,
required this.label,
required this.icon,
this.onTap,
this.backgroundColor,
this.iconColor,
this.labelColor,
this.size,
this.isLoading = false,
this.isDisabled = false,
});
@override
State<QuickActionButton> createState() => _QuickActionButtonState();
}
class _QuickActionButtonState extends State<QuickActionButton>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _scaleAnimation;
late Animation<double> _elevationAnimation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: UIConstants.durationFast,
vsync: this,
);
_scaleAnimation = Tween<double>(
begin: 1.0,
end: 0.95,
).animate(CurvedAnimation(
parent: _animationController,
curve: UIConstants.curveFast,
));
_elevationAnimation = Tween<double>(
begin: 1.0,
end: 0.0,
).animate(CurvedAnimation(
parent: _animationController,
curve: UIConstants.curveFast,
));
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
void _onTapDown(TapDownDetails details) {
if (widget.onTap != null && !widget.isDisabled && !widget.isLoading) {
_animationController.forward();
}
}
void _onTapUp(TapUpDetails details) {
if (widget.onTap != null && !widget.isDisabled && !widget.isLoading) {
_animationController.reverse();
}
}
void _onTapCancel() {
if (widget.onTap != null && !widget.isDisabled && !widget.isLoading) {
_animationController.reverse();
}
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final buttonSize = widget.size ?? UIConstants.logoSizeMedium;
final backgroundColor =
widget.backgroundColor ?? colorScheme.primaryContainer;
final iconColor = widget.iconColor ?? colorScheme.onPrimaryContainer;
final labelColor = widget.labelColor ?? colorScheme.onSurface;
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: GestureDetector(
onTapDown: _onTapDown,
onTapUp: _onTapUp,
onTapCancel: _onTapCancel,
child: Container(
width: buttonSize,
height: buttonSize,
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(UIConstants.radius16),
boxShadow: [
BoxShadow(
color: backgroundColor.withOpacity(0.3),
blurRadius: 8 * _elevationAnimation.value,
offset: Offset(0, 4 * _elevationAnimation.value),
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: (widget.onTap != null &&
!widget.isDisabled &&
!widget.isLoading)
? widget.onTap
: null,
borderRadius: BorderRadius.circular(UIConstants.radius16),
child: Padding(
padding: const EdgeInsets.all(UIConstants.spacing12),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Icon
if (widget.isLoading)
SizedBox(
width: buttonSize * 0.3,
height: buttonSize * 0.3,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor:
AlwaysStoppedAnimation<Color>(iconColor),
),
)
else
Icon(
widget.icon,
color: iconColor,
size: buttonSize * 0.4,
),
const SizedBox(height: UIConstants.spacing8),
// Label
Text(
widget.label,
style: theme.textTheme.labelMedium?.copyWith(
color: labelColor,
fontWeight: FontWeight.w600,
),
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
),
),
),
),
);
},
);
}
}

View File

@@ -0,0 +1,355 @@
import 'package:flutter/material.dart';
import '../../../core/constants/ui_constants.dart';
enum DashboardCardType {
primary,
secondary,
success,
warning,
info,
danger,
}
class DashboardCard extends StatefulWidget {
final String title;
final String? subtitle;
final String? value;
final num? numericValue;
final String? valueSuffix;
final Duration? animationDuration;
final IconData? icon;
final DashboardCardType type;
final VoidCallback? onTap;
final Widget? trailing;
final bool isLoading;
final Color? customColor;
final double? elevation;
final BorderRadius? borderRadius;
const DashboardCard({
super.key,
required this.title,
this.subtitle,
this.value,
this.numericValue,
this.valueSuffix,
this.animationDuration,
this.icon,
this.type = DashboardCardType.primary,
this.onTap,
this.trailing,
this.isLoading = false,
this.customColor,
this.elevation,
this.borderRadius,
});
@override
State<DashboardCard> createState() => _DashboardCardState();
}
class _DashboardCardState extends State<DashboardCard>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _scaleAnimation;
late Animation<double> _elevationAnimation;
bool _isPressed = false;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: UIConstants.durationFast,
vsync: this,
);
_scaleAnimation = Tween<double>(
begin: 1.0,
end: 0.95,
).animate(CurvedAnimation(
parent: _animationController,
curve: UIConstants.curveFast,
));
_elevationAnimation = Tween<double>(
begin: 1.0,
end: 0.0,
).animate(CurvedAnimation(
parent: _animationController,
curve: UIConstants.curveFast,
));
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
void _onTapDown(TapDownDetails details) {
if (widget.onTap != null && !widget.isLoading) {
setState(() {
_isPressed = true;
});
_animationController.forward();
}
}
void _onTapUp(TapUpDetails details) {
if (widget.onTap != null && !widget.isLoading) {
setState(() {
_isPressed = false;
});
_animationController.reverse();
}
}
void _onTapCancel() {
if (widget.onTap != null && !widget.isLoading) {
setState(() {
_isPressed = false;
});
_animationController.reverse();
}
}
Color _getCardColor(ThemeData theme) {
if (widget.customColor != null) return widget.customColor!;
switch (widget.type) {
case DashboardCardType.primary:
return theme.colorScheme.primary;
case DashboardCardType.secondary:
return theme.colorScheme.secondary;
case DashboardCardType.success:
return theme.colorScheme.tertiary;
case DashboardCardType.warning:
return const Color(0xFFF59E0B);
case DashboardCardType.info:
return theme.colorScheme.primary;
case DashboardCardType.danger:
return theme.colorScheme.error;
}
}
Color _getIconColor(ThemeData theme) {
if (widget.customColor != null) return Colors.white;
switch (widget.type) {
case DashboardCardType.primary:
case DashboardCardType.secondary:
case DashboardCardType.success:
case DashboardCardType.warning:
case DashboardCardType.info:
case DashboardCardType.danger:
return Colors.white;
}
}
double _getElevation() {
if (widget.elevation != null) return widget.elevation!;
return UIConstants.elevation4;
}
BorderRadius _getBorderRadius() {
if (widget.borderRadius != null) return widget.borderRadius!;
return BorderRadius.circular(UIConstants.radius16);
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final cardColor = _getCardColor(theme);
final iconColor = _getIconColor(theme);
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: GestureDetector(
onTapDown: _onTapDown,
onTapUp: _onTapUp,
onTapCancel: _onTapCancel,
child: Container(
decoration: BoxDecoration(
borderRadius: _getBorderRadius(),
color: cardColor,
boxShadow: [
BoxShadow(
color: cardColor.withOpacity(0.3),
blurRadius: 8 * _elevationAnimation.value,
offset: Offset(0, 4 * _elevationAnimation.value),
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: widget.onTap,
borderRadius: _getBorderRadius(),
child: Padding(
padding: const EdgeInsets.all(UIConstants.spacing16),
child: LayoutBuilder(
builder: (context, constraints) {
final isTight = constraints.maxHeight < 150;
final isVeryTight = constraints.maxHeight < 130;
final spacing16 = isTight
? UIConstants.spacing8
: UIConstants.spacing16;
final spacing12 = isTight
? UIConstants.spacing6
: UIConstants.spacing12;
final iconSize = isTight
? UIConstants.iconSizeMedium
: UIConstants.iconSizeLarge;
final titleStyle =
theme.textTheme.titleMedium?.copyWith(
color: Colors.white,
fontWeight: FontWeight.w600,
fontSize: isVeryTight ? 12 : (isTight ? 14 : null),
);
final subtitleStyle =
theme.textTheme.bodyMedium?.copyWith(
color: Colors.white.withOpacity(0.8),
fontSize: isVeryTight ? 10 : (isTight ? 12 : null),
);
final valueStyle =
theme.textTheme.headlineSmall?.copyWith(
color: Colors.white,
fontWeight: FontWeight.w700,
fontSize: isVeryTight ? 16 : (isTight ? 18 : null),
);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header Row
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Icon
if (widget.icon != null)
Container(
padding: EdgeInsets.all(spacing12),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(
UIConstants.radius12),
),
child: Icon(
widget.icon,
color: iconColor,
size: iconSize,
),
),
// Trailing Widget
if (widget.trailing != null) widget.trailing!,
],
),
SizedBox(height: spacing16),
// Title
Text(
widget.title,
style: titleStyle,
maxLines: isTight ? 1 : 2,
overflow: TextOverflow.ellipsis,
),
// Subtitle
if (widget.subtitle != null && !isVeryTight) ...[
SizedBox(height: spacing12),
Text(
widget.subtitle!,
style: subtitleStyle,
maxLines: isTight ? 1 : 2,
overflow: TextOverflow.ellipsis,
),
],
// Value (animated if numeric)
SizedBox(height: spacing16),
if (widget.numericValue != null)
TweenAnimationBuilder<double>(
tween: Tween<double>(
begin: 0,
end: widget.numericValue!.toDouble()),
duration: widget.animationDuration ??
UIConstants.durationNormal,
curve: UIConstants.curveNormal,
builder: (context, value, child) {
final text =
_formatNumber(value, widget.valueSuffix);
final valueText = Text(
text,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: valueStyle,
);
return isTight
? FittedBox(
alignment: Alignment.centerLeft,
child: valueText)
: valueText;
},
)
else if (widget.value != null)
Builder(builder: (context) {
final valueText = Text(
widget.value!,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: valueStyle,
);
return isTight
? FittedBox(
alignment: Alignment.centerLeft,
child: valueText)
: valueText;
}),
// Loading Indicator
if (widget.isLoading) ...[
SizedBox(height: spacing16),
const LinearProgressIndicator(
backgroundColor: Colors.white24,
valueColor:
AlwaysStoppedAnimation<Color>(Colors.white),
),
],
],
);
},
),
),
),
),
),
),
);
},
);
}
String _formatNumber(double value, String? suffix) {
String formatted;
if (value >= 1000000) {
formatted = "${(value / 1000000).toStringAsFixed(1)}M";
} else if (value >= 1000) {
formatted = "${(value / 1000).toStringAsFixed(1)}K";
} else {
if (value == value.roundToDouble()) {
formatted = value.toInt().toString();
} else {
formatted = value.toStringAsFixed(1);
}
}
if (suffix != null && suffix.isNotEmpty) {
return "$formatted$suffix";
}
return formatted;
}
}

View File

@@ -0,0 +1,147 @@
import 'package:flutter/material.dart';
import '../../../../core/constants/ui_constants.dart';
class SystemParameterSection extends StatelessWidget {
final String title;
final String? subtitle;
final IconData icon;
final List<Widget> children;
final bool isExpanded;
final VoidCallback? onToggle;
final Color? iconColor;
const SystemParameterSection({
super.key,
required this.title,
this.subtitle,
required this.icon,
required this.children,
this.isExpanded = true,
this.onToggle,
this.iconColor,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return Container(
margin: const EdgeInsets.only(bottom: UIConstants.spacing16),
decoration: BoxDecoration(
color: colorScheme.surface,
borderRadius: BorderRadius.circular(UIConstants.radius16),
boxShadow: [
BoxShadow(
color: colorScheme.shadow.withOpacity(0.08),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
border: Border.all(
color: colorScheme.outline.withOpacity(0.1),
width: 1,
),
),
child: Column(
children: [
// Section Header
InkWell(
onTap: onToggle,
borderRadius: const BorderRadius.vertical(
top: Radius.circular(UIConstants.radius16),
),
child: Container(
padding: const EdgeInsets.all(UIConstants.spacing20),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
colorScheme.primaryContainer.withOpacity(0.3),
colorScheme.secondaryContainer.withOpacity(0.1),
],
),
borderRadius: const BorderRadius.vertical(
top: Radius.circular(UIConstants.radius16),
),
),
child: Row(
children: [
// Icon
Container(
padding: const EdgeInsets.all(UIConstants.spacing12),
decoration: BoxDecoration(
color: iconColor?.withOpacity(0.1) ??
colorScheme.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(UIConstants.radius12),
),
child: Icon(
icon,
color: iconColor ?? colorScheme.primary,
size: UIConstants.iconSizeMedium,
),
),
const SizedBox(width: UIConstants.spacing16),
// Title and Subtitle
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: theme.textTheme.titleLarge?.copyWith(
color: colorScheme.onSurface,
fontWeight: FontWeight.w600,
),
),
if (subtitle != null) ...[
const SizedBox(height: UIConstants.spacing4),
Text(
subtitle!,
style: theme.textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
],
],
),
),
// Expand/Collapse Icon
if (onToggle != null)
AnimatedRotation(
turns: isExpanded ? 0.5 : 0,
duration: UIConstants.durationFast,
child: Icon(
Icons.keyboard_arrow_down,
color: colorScheme.onSurfaceVariant,
size: UIConstants.iconSizeMedium,
),
),
],
),
),
),
// Section Content
AnimatedContainer(
duration: UIConstants.durationFast,
curve: UIConstants.curveNormal,
height: isExpanded ? null : 0,
child: isExpanded
? Container(
padding: const EdgeInsets.all(UIConstants.spacing20),
child: Column(
children: children,
),
)
: const SizedBox.shrink(),
),
],
),
);
}
}

View File

@@ -0,0 +1,357 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import '../../../core/constants/ui_constants.dart';
import '../buttons/modern_button.dart';
class ModernImagePicker extends StatefulWidget {
final String? label;
final String? hint;
final Uint8List? imageBytes;
final File? imageFile;
final String? imageUrl;
final VoidCallback? onPickImage;
final VoidCallback? onRemoveImage;
final bool isLoading;
final double? height;
final double? width;
final BorderRadius? borderRadius;
final String? errorText;
final bool showRemoveButton;
final IconData? placeholderIcon;
final String? placeholderText;
const ModernImagePicker({
super.key,
this.label,
this.hint,
this.imageBytes,
this.imageFile,
this.imageUrl,
this.onPickImage,
this.onRemoveImage,
this.isLoading = false,
this.height = 200,
this.width,
this.borderRadius,
this.errorText,
this.showRemoveButton = true,
this.placeholderIcon,
this.placeholderText,
});
@override
State<ModernImagePicker> createState() => _ModernImagePickerState();
}
class _ModernImagePickerState extends State<ModernImagePicker>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _scaleAnimation;
late Animation<double> _fadeAnimation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: UIConstants.durationFast,
vsync: this,
);
_scaleAnimation = Tween<double>(
begin: 1.0,
end: 0.95,
).animate(CurvedAnimation(
parent: _animationController,
curve: UIConstants.curveNormal,
));
_fadeAnimation = Tween<double>(
begin: 1.0,
end: 0.8,
).animate(CurvedAnimation(
parent: _animationController,
curve: UIConstants.curveNormal,
));
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
bool get hasImage =>
widget.imageBytes != null ||
widget.imageFile != null ||
(widget.imageUrl != null && widget.imageUrl!.isNotEmpty);
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Label
if (widget.label != null) ...[
Text(
widget.label!,
style: theme.textTheme.titleMedium?.copyWith(
color: colorScheme.onSurface,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: UIConstants.spacing12),
],
// Image Container
GestureDetector(
onTapDown: (_) => _animationController.forward(),
onTapUp: (_) => _animationController.reverse(),
onTapCancel: () => _animationController.reverse(),
onTap: widget.onPickImage,
child: AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: FadeTransition(
opacity: _fadeAnimation,
child: Container(
height: widget.height,
width: widget.width ?? double.infinity,
decoration: BoxDecoration(
borderRadius: widget.borderRadius ??
BorderRadius.circular(UIConstants.radius16),
border: Border.all(
color: widget.errorText != null
? colorScheme.error
: colorScheme.outline.withOpacity(0.3),
width: 2,
),
boxShadow: [
BoxShadow(
color: colorScheme.shadow.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: ClipRRect(
borderRadius: widget.borderRadius ??
BorderRadius.circular(UIConstants.radius16),
child: Stack(
children: [
// Image or Placeholder
if (hasImage)
_buildImageWidget()
else
_buildPlaceholder(theme, colorScheme),
// Loading Overlay
if (widget.isLoading)
_buildLoadingOverlay(colorScheme),
// Remove Button
if (hasImage &&
widget.showRemoveButton &&
widget.onRemoveImage != null)
_buildRemoveButton(colorScheme),
],
),
),
),
),
);
},
),
),
// Hint Text
if (widget.hint != null) ...[
const SizedBox(height: UIConstants.spacing8),
Text(
widget.hint!,
style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
],
// Error Text
if (widget.errorText != null) ...[
const SizedBox(height: UIConstants.spacing8),
Text(
widget.errorText!,
style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.error,
),
),
],
// Action Buttons
const SizedBox(height: UIConstants.spacing16),
Row(
children: [
Expanded(
child: ModernButton(
text: hasImage ? 'Change Image' : 'Pick Image',
type: ModernButtonType.outline,
size: ModernButtonSize.medium,
icon: Icon(hasImage ? Icons.edit : Icons.add_photo_alternate),
onPressed: widget.isLoading ? null : widget.onPickImage,
isLoading: widget.isLoading,
),
),
if (hasImage && widget.onRemoveImage != null) ...[
const SizedBox(width: UIConstants.spacing12),
ModernButton(
text: 'Remove',
type: ModernButtonType.danger,
size: ModernButtonSize.medium,
icon: Icon(Icons.delete_outline),
onPressed: widget.isLoading ? null : widget.onRemoveImage,
),
],
],
),
],
);
}
Widget _buildImageWidget() {
if (widget.imageBytes != null) {
return Image.memory(
widget.imageBytes!,
width: double.infinity,
height: double.infinity,
fit: BoxFit.cover,
);
} else if (widget.imageFile != null) {
return Image.file(
widget.imageFile!,
width: double.infinity,
height: double.infinity,
fit: BoxFit.cover,
);
} else if (widget.imageUrl != null && widget.imageUrl!.isNotEmpty) {
return Image.network(
widget.imageUrl!,
width: double.infinity,
height: double.infinity,
fit: BoxFit.cover,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
);
},
errorBuilder: (context, error, stackTrace) {
return _buildPlaceholder(
Theme.of(context), Theme.of(context).colorScheme);
},
);
}
return const SizedBox.shrink();
}
Widget _buildPlaceholder(ThemeData theme, ColorScheme colorScheme) {
return Container(
width: double.infinity,
height: double.infinity,
color: colorScheme.surfaceVariant.withOpacity(0.3),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
widget.placeholderIcon ?? Icons.add_photo_alternate,
size: UIConstants.iconSizeXLarge,
color: colorScheme.onSurfaceVariant,
),
const SizedBox(height: UIConstants.spacing12),
Text(
widget.placeholderText ?? 'Tap to select image',
style: theme.textTheme.bodyLarge?.copyWith(
color: colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
),
const SizedBox(height: UIConstants.spacing8),
Text(
'JPG, PNG, GIF up to 10MB',
style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),
textAlign: TextAlign.center,
),
],
),
);
}
Widget _buildLoadingOverlay(ColorScheme colorScheme) {
return Container(
width: double.infinity,
height: double.infinity,
color: colorScheme.surface.withOpacity(0.8),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(
color: colorScheme.primary,
),
const SizedBox(height: UIConstants.spacing12),
Text(
'Uploading...',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurface,
),
),
],
),
),
);
}
Widget _buildRemoveButton(ColorScheme colorScheme) {
return Positioned(
top: UIConstants.spacing8,
right: UIConstants.spacing8,
child: Container(
decoration: BoxDecoration(
color: colorScheme.error,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: colorScheme.shadow.withOpacity(0.3),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: IconButton(
onPressed: widget.onRemoveImage,
icon: Icon(
Icons.close,
color: colorScheme.onError,
size: UIConstants.iconSizeSmall,
),
padding: const EdgeInsets.all(UIConstants.spacing4),
constraints: const BoxConstraints(
minWidth: 32,
minHeight: 32,
),
),
),
);
}
}

View File

@@ -0,0 +1,298 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../../../../core/constants/ui_constants.dart';
class ModernTextField extends StatefulWidget {
final String? label;
final String? hint;
final String? helperText;
final String? errorText;
final TextEditingController? controller;
final TextInputType? keyboardType;
final bool obscureText;
final bool enabled;
final bool readOnly;
final int? maxLines;
final int? maxLength;
final Widget? prefixIcon;
final Widget? suffixIcon;
final VoidCallback? onSuffixIconPressed;
final String? Function(String?)? validator;
final void Function(String)? onChanged;
final void Function(String)? onSubmitted;
final List<TextInputFormatter>? inputFormatters;
final FocusNode? focusNode;
final VoidCallback? onTap;
final bool autofocus;
final TextCapitalization textCapitalization;
final TextInputAction? textInputAction;
final bool expands;
final double? height;
final EdgeInsetsGeometry? contentPadding;
const ModernTextField({
super.key,
this.label,
this.hint,
this.helperText,
this.errorText,
this.controller,
this.keyboardType,
this.obscureText = false,
this.enabled = true,
this.readOnly = false,
this.maxLines = 1,
this.maxLength,
this.prefixIcon,
this.suffixIcon,
this.onSuffixIconPressed,
this.validator,
this.onChanged,
this.onSubmitted,
this.inputFormatters,
this.focusNode,
this.onTap,
this.autofocus = false,
this.textCapitalization = TextCapitalization.none,
this.textInputAction,
this.expands = false,
this.height,
this.contentPadding,
});
@override
State<ModernTextField> createState() => _ModernTextFieldState();
}
class _ModernTextFieldState extends State<ModernTextField>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _fadeAnimation;
late Animation<double> _scaleAnimation;
bool _isFocused = false;
bool _hasError = false;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: UIConstants.durationFast,
vsync: this,
);
_fadeAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _animationController,
curve: UIConstants.curveFast,
));
_scaleAnimation = Tween<double>(
begin: 0.95,
end: 1.0,
).animate(CurvedAnimation(
parent: _animationController,
curve: UIConstants.curveFast,
));
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
void _onFocusChange(bool hasFocus) {
setState(() {
_isFocused = hasFocus;
});
if (hasFocus) {
_animationController.forward();
} else {
_animationController.reverse();
}
}
void _onChanged(String value) {
setState(() {
_hasError = false;
});
if (widget.onChanged != null) {
widget.onChanged!(value);
}
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (widget.label != null) ...[
Padding(
padding: const EdgeInsets.only(bottom: UIConstants.spacing8),
child: Text(
widget.label!,
style: theme.textTheme.labelLarge?.copyWith(
color: _isFocused
? colorScheme.primary
: colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w600,
),
),
),
],
Focus(
onFocusChange: _onFocusChange,
child: Container(
height: widget.height ?? UIConstants.inputHeightMedium,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(UIConstants.radius16),
color: _isFocused
? colorScheme.surfaceVariant.withOpacity(0.5)
: colorScheme.surfaceVariant.withOpacity(0.3),
border: Border.all(
color: _hasError
? colorScheme.error
: _isFocused
? colorScheme.primary
: colorScheme.outline.withOpacity(0.3),
width: _isFocused ? 2.0 : 1.5,
),
boxShadow: _isFocused
? [
BoxShadow(
color: colorScheme.primary.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, 2),
),
]
: null,
),
child: TextFormField(
controller: widget.controller,
focusNode: widget.focusNode,
keyboardType: widget.keyboardType,
obscureText: widget.obscureText,
enabled: widget.enabled,
readOnly: widget.readOnly,
maxLines: widget.maxLines,
maxLength: widget.maxLength,
autofocus: widget.autofocus,
textCapitalization: widget.textCapitalization,
textInputAction: widget.textInputAction,
expands: widget.expands,
inputFormatters: widget.inputFormatters,
validator: (value) {
if (widget.validator != null) {
final result = widget.validator!(value);
setState(() {
_hasError = result != null;
});
return result;
}
return null;
},
onChanged: _onChanged,
onFieldSubmitted: widget.onSubmitted,
onTap: widget.onTap,
style: theme.textTheme.bodyLarge?.copyWith(
color: colorScheme.onSurface,
),
decoration: InputDecoration(
hintText: widget.hint,
hintStyle: theme.textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant.withOpacity(0.6),
),
prefixIcon: widget.prefixIcon != null
? Padding(
padding: const EdgeInsets.only(
left: UIConstants.spacing16),
child: IconTheme(
data: IconThemeData(
color: _isFocused
? colorScheme.primary
: colorScheme.onSurfaceVariant,
size: UIConstants.iconSizeMedium,
),
child: widget.prefixIcon!,
),
)
: null,
suffixIcon: widget.suffixIcon != null
? Padding(
padding: const EdgeInsets.only(
right: UIConstants.spacing16),
child: IconTheme(
data: IconThemeData(
color: _isFocused
? colorScheme.primary
: colorScheme.onSurfaceVariant,
size: UIConstants.iconSizeMedium,
),
child: widget.suffixIcon!,
),
)
: null,
border: InputBorder.none,
contentPadding: widget.contentPadding ??
const EdgeInsets.symmetric(
horizontal: UIConstants.spacing20,
vertical: UIConstants.spacing16,
),
counterText: '',
),
),
),
),
if (widget.helperText != null && !_hasError) ...[
Padding(
padding: const EdgeInsets.only(top: UIConstants.spacing8),
child: Text(
widget.helperText!,
style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
),
],
if (widget.errorText != null && _hasError) ...[
Padding(
padding: const EdgeInsets.only(top: UIConstants.spacing8),
child: Row(
children: [
Icon(
Icons.error_outline,
size: UIConstants.iconSizeSmall,
color: colorScheme.error,
),
const SizedBox(width: UIConstants.spacing8),
Expanded(
child: Text(
widget.errorText!,
style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.error,
),
),
),
],
),
),
],
],
),
);
},
);
}
}

View File

@@ -0,0 +1,252 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../../core/constants/ui_constants.dart';
import '../../../utils/managers/user_manager.dart';
import '../../../view_model/profile/profile_view_model.dart';
import '../buttons/modern_button.dart';
class ModernDrawer extends StatelessWidget {
final List<DrawerItem> items;
final Widget? header;
final Widget? footer;
final Color? backgroundColor;
final double? elevation;
const ModernDrawer({
super.key,
required this.items,
this.header,
this.footer,
this.backgroundColor,
this.elevation,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final email = UserManager().email;
final userName = UserManager().userName;
return Drawer(
backgroundColor: backgroundColor ?? colorScheme.surface,
elevation: elevation ?? UIConstants.elevation8,
child: Column(
children: [
// Header
header ??
_buildDefaultHeader(context, theme, colorScheme, userName, email),
// Navigation Items
Expanded(
child: ListView.builder(
padding: EdgeInsets.zero,
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return _buildDrawerItem(context, theme, colorScheme, item);
},
),
),
// Footer
if (footer != null) footer!,
// Logout Section
_buildLogoutSection(context, theme, colorScheme),
],
),
);
}
Widget _buildDefaultHeader(
BuildContext context,
ThemeData theme,
ColorScheme colorScheme,
String? userName,
String? email,
) {
return Consumer<ProfileViewModel>(
builder: (context, provider, child) {
return Container(
padding: const EdgeInsets.all(UIConstants.spacing24),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
colorScheme.primary,
colorScheme.secondary,
colorScheme.tertiary,
],
),
),
child: SafeArea(
child: Column(
children: [
// Profile Picture
Container(
width: UIConstants.logoSizeLarge,
height: UIConstants.logoSizeLarge,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: Colors.white,
width: 3,
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: CircleAvatar(
radius: UIConstants.logoSizeLarge / 2,
backgroundColor: Colors.white.withOpacity(0.2),
backgroundImage: provider.profileImageBytes != null
? MemoryImage(provider.profileImageBytes!)
: null,
child: provider.profileImageBytes != null
? null
: Icon(
Icons.person,
size: UIConstants.iconSizeLarge,
color: Colors.white,
),
),
),
const SizedBox(height: UIConstants.spacing16),
// User Name
Text(
"Hello, ${userName ?? 'User'}",
style: theme.textTheme.titleLarge?.copyWith(
color: Colors.white,
fontWeight: FontWeight.w600,
),
textAlign: TextAlign.center,
),
// User Email
if (email != null) ...[
const SizedBox(height: UIConstants.spacing8),
Text(
email,
style: theme.textTheme.bodyMedium?.copyWith(
color: Colors.white.withOpacity(0.8),
),
textAlign: TextAlign.center,
),
],
],
),
),
);
},
);
}
Widget _buildDrawerItem(
BuildContext context,
ThemeData theme,
ColorScheme colorScheme,
DrawerItem item,
) {
return Container(
margin: const EdgeInsets.symmetric(
horizontal: UIConstants.spacing16,
vertical: UIConstants.spacing4,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(UIConstants.radius12),
color: item.isSelected
? colorScheme.primaryContainer.withOpacity(0.3)
: Colors.transparent,
),
child: ListTile(
leading: Container(
padding: const EdgeInsets.all(UIConstants.spacing8),
decoration: BoxDecoration(
color: item.isSelected
? colorScheme.primaryContainer
: colorScheme.surfaceVariant.withOpacity(0.3),
borderRadius: BorderRadius.circular(UIConstants.radius8),
),
child: Icon(
item.icon,
color: item.isSelected
? colorScheme.onPrimaryContainer
: item.color ?? colorScheme.onSurfaceVariant,
size: UIConstants.iconSizeMedium,
),
),
title: Text(
item.title,
style: theme.textTheme.titleMedium?.copyWith(
color:
item.isSelected ? colorScheme.onSurface : colorScheme.onSurface,
fontWeight: item.isSelected ? FontWeight.w600 : FontWeight.w500,
),
),
subtitle: item.subtitle != null
? Text(
item.subtitle!,
style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),
)
: null,
trailing: item.trailing,
onTap: () => item.onTap(context),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(UIConstants.radius12),
),
),
);
}
Widget _buildLogoutSection(
BuildContext context,
ThemeData theme,
ColorScheme colorScheme,
) {
return Container(
padding: const EdgeInsets.all(UIConstants.spacing24),
child: ModernButton(
text: 'Logout',
type: ModernButtonType.danger,
size: ModernButtonSize.medium,
icon: Icon(Icons.logout),
onPressed: () async {
await UserManager().clearUser();
if (context.mounted) {
Navigator.pushReplacementNamed(context, '/splash');
}
},
),
);
}
}
class DrawerItem {
final IconData icon;
final String title;
final String? subtitle;
final void Function(BuildContext) onTap;
final Color? color;
final bool isSelected;
final Widget? trailing;
const DrawerItem({
required this.icon,
required this.title,
this.subtitle,
required this.onTap,
this.color,
this.isSelected = false,
this.trailing,
});
}

View File

@@ -0,0 +1,275 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../core/constants/ui_constants.dart';
import '../../core/providers/dynamic_theme_provider.dart';
class ThemePreview extends StatelessWidget {
const ThemePreview({super.key});
@override
Widget build(BuildContext context) {
return Consumer<DynamicThemeProvider>(
builder: (context, dynamicThemeProvider, child) {
if (!dynamicThemeProvider.isUsingDynamicTheme) {
return const SizedBox.shrink();
}
final colorScheme = dynamicThemeProvider.dynamicColorScheme;
final logoColors = dynamicThemeProvider.logoColors;
final paletteInfo = dynamicThemeProvider.getColorPaletteInfo();
if (colorScheme == null || logoColors.isEmpty) {
return const SizedBox.shrink();
}
return Container(
margin: const EdgeInsets.all(UIConstants.spacing16),
padding: const EdgeInsets.all(UIConstants.spacing20),
decoration: BoxDecoration(
color: colorScheme.surface,
borderRadius: BorderRadius.circular(UIConstants.radius16),
border: Border.all(
color: colorScheme.outline.withOpacity(0.2),
),
boxShadow: [
BoxShadow(
color: colorScheme.shadow.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header
Row(
children: [
Icon(
Icons.palette,
color: colorScheme.primary,
size: UIConstants.iconSizeLarge,
),
const SizedBox(width: UIConstants.spacing12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Dynamic Theme Generated',
style:
Theme.of(context).textTheme.titleLarge?.copyWith(
color: colorScheme.onSurface,
fontWeight: FontWeight.w600,
),
),
Text(
dynamicThemeProvider.getThemeDescription(),
style:
Theme.of(context).textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
],
),
),
IconButton(
onPressed: () => dynamicThemeProvider.resetToDefaultTheme(),
icon: Icon(
Icons.refresh,
color: colorScheme.primary,
),
tooltip: 'Reset to Default Theme',
),
],
),
const SizedBox(height: UIConstants.spacing20),
// Color Palette
Text(
'Color Palette',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: colorScheme.onSurface,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: UIConstants.spacing12),
// Color Swatches
Wrap(
spacing: UIConstants.spacing12,
runSpacing: UIConstants.spacing12,
children: logoColors.take(5).map((color) {
final index = logoColors.indexOf(color);
final labels = [
'Primary',
'Secondary',
'Tertiary',
'Accent 1',
'Accent 2'
];
return Column(
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
border: Border.all(
color: colorScheme.outline.withOpacity(0.3),
width: 2,
),
boxShadow: [
BoxShadow(
color: color.withOpacity(0.3),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Center(
child: Icon(
Icons.check,
color: _getContrastColor(color),
size: 24,
),
),
),
const SizedBox(height: UIConstants.spacing4),
Text(
index < labels.length
? labels[index]
: 'Color ${index + 1}',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500,
),
),
],
);
}).toList(),
),
const SizedBox(height: UIConstants.spacing20),
// Theme Info
Container(
padding: const EdgeInsets.all(UIConstants.spacing16),
decoration: BoxDecoration(
color: colorScheme.surfaceVariant.withOpacity(0.3),
borderRadius: BorderRadius.circular(UIConstants.radius12),
border: Border.all(
color: colorScheme.outline.withOpacity(0.2),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Theme Information',
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: colorScheme.onSurface,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: UIConstants.spacing8),
_buildInfoRow(
'Total Colors', '${paletteInfo['totalColors']}'),
_buildInfoRow('Description', paletteInfo['description']),
if (paletteInfo['characteristics'] != null) ...[
_buildInfoRow(
'Warm Colors',
paletteInfo['characteristics']['warm']
? 'Yes'
: 'No'),
_buildInfoRow(
'Cool Colors',
paletteInfo['characteristics']['cool']
? 'Yes'
: 'No'),
_buildInfoRow(
'Neutral Colors',
paletteInfo['characteristics']['neutral']
? 'Yes'
: 'No'),
],
],
),
),
const SizedBox(height: UIConstants.spacing16),
// Preview Buttons
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.onPrimary,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(UIConstants.radius12),
),
),
child: const Text('Primary Button'),
),
),
const SizedBox(width: UIConstants.spacing12),
Expanded(
child: OutlinedButton(
onPressed: () {},
style: OutlinedButton.styleFrom(
foregroundColor: colorScheme.primary,
side: BorderSide(color: colorScheme.outline),
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(UIConstants.radius12),
),
),
child: const Text('Secondary Button'),
),
),
],
),
],
),
);
},
);
}
Widget _buildInfoRow(String label, String value) {
return Padding(
padding: const EdgeInsets.only(bottom: UIConstants.spacing4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
Text(
value,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
],
),
);
}
Color _getContrastColor(Color backgroundColor) {
final luminance = backgroundColor.computeLuminance();
return luminance > 0.5 ? Colors.black : Colors.white;
}
}

View File

@@ -0,0 +1,120 @@
import 'package:flutter/material.dart';
import '../../core/constants/ui_constants.dart';
class ThemeToggle extends StatefulWidget {
final bool isDarkMode;
final ValueChanged<bool> onThemeChanged;
final double size;
final Color? backgroundColor;
final Color? iconColor;
const ThemeToggle({
super.key,
required this.isDarkMode,
required this.onThemeChanged,
this.size = 48.0,
this.backgroundColor,
this.iconColor,
});
@override
State<ThemeToggle> createState() => _ThemeToggleState();
}
class _ThemeToggleState extends State<ThemeToggle>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _rotationAnimation;
late Animation<double> _scaleAnimation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: UIConstants.durationNormal,
vsync: this,
);
_rotationAnimation = Tween<double>(
begin: 0.0,
end: 0.5,
).animate(CurvedAnimation(
parent: _animationController,
curve: UIConstants.curveNormal,
));
_scaleAnimation = Tween<double>(
begin: 1.0,
end: 0.8,
).animate(CurvedAnimation(
parent: _animationController,
curve: UIConstants.curveNormal,
));
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
void _toggleTheme() {
_animationController.forward().then((_) {
widget.onThemeChanged(!widget.isDarkMode);
_animationController.reverse();
});
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: Container(
width: widget.size,
height: widget.size,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: widget.backgroundColor ??
(widget.isDarkMode
? colorScheme.primaryContainer
: colorScheme.surfaceVariant),
boxShadow: [
BoxShadow(
color: colorScheme.shadow.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: _toggleTheme,
borderRadius: BorderRadius.circular(widget.size / 2),
child: Center(
child: Transform.rotate(
angle: _rotationAnimation.value * 2 * 3.14159,
child: Icon(
widget.isDarkMode ? Icons.light_mode : Icons.dark_mode,
size: widget.size * 0.5,
color: widget.iconColor ??
(widget.isDarkMode
? colorScheme.onPrimaryContainer
: colorScheme.onSurfaceVariant),
),
),
),
),
),
),
);
},
);
}
}

View File

@@ -0,0 +1,662 @@
import 'package:base_project/core/app_export.dart';
import 'package:flutter/material.dart';
import '../utils/color_constants.dart';
import '../utils/size_utils.dart';
class AppDecoration {
// Gradient decorations
static BoxDecoration get gradientOnErrorContainerToOnErrorContainer =>
BoxDecoration(
gradient: LinearGradient(
begin: const Alignment(0.32, 0.2),
end: const Alignment(0.75, 0.83),
colors: [
theme.colorScheme.onErrorContainer.withOpacity(0.7),
theme.colorScheme.onErrorContainer.withOpacity(0.7),
theme.colorScheme.onErrorContainer.withOpacity(0.7)
],
),
);
static BoxDecoration get fillBlueGray => BoxDecoration(
color: appTheme.blueGray100,
);
static BoxDecoration get fillBlue5001 => BoxDecoration(
color: ColorConstant.blue5001,
);
static BoxDecoration get outlineGray5002 => BoxDecoration(
color: ColorConstant.gray5002,
border: Border.all(
color: ColorConstant.gray5002,
width: getHorizontalSize(
1,
),
),
);
static BoxDecoration get outlineBlueA70001 => BoxDecoration(
border: Border.all(
color: ColorConstant.blueA70001,
width: getHorizontalSize(
1,
),
),
);
static BoxDecoration get outlineGray60026 => BoxDecoration(
color: ColorConstant.whiteA700,
boxShadow: [
BoxShadow(
color: ColorConstant.gray60026,
spreadRadius: getHorizontalSize(
2,
),
blurRadius: getHorizontalSize(
2,
),
offset: const Offset(
0,
2.41,
),
),
],
);
static BoxDecoration get txtFillBluegray100 => BoxDecoration(
color: ColorConstant.blueGray100,
);
static BoxDecoration get fillBlueA700 => BoxDecoration(
color: ColorConstant.blueA700,
);
static BoxDecoration get fillBluegray50 => BoxDecoration(
color: ColorConstant.blueGray50,
);
static BoxDecoration get fillBlack => BoxDecoration(
color: appTheme.black900,
);
static BoxDecoration get fillGray => BoxDecoration(
color: appTheme.gray10001,
);
static BoxDecoration get fillGray700 => BoxDecoration(
color: appTheme.gray700,
);
static BoxDecoration get fillLightGreenA => BoxDecoration(
color: appTheme.lightGreenA200,
);
static BoxDecoration get fillOnErrorContainer => BoxDecoration(
color: theme.colorScheme.onErrorContainer.withOpacity(1),
);
static BoxDecoration get fillPrimary => BoxDecoration(
color: theme.colorScheme.primary,
);
static BoxDecoration get outlineBlueA7002 => BoxDecoration(
color: ColorConstant.whiteA700,
border: Border.all(
color: ColorConstant.blueA700,
width: getHorizontalSize(
1,
),
),
);
static BoxDecoration get outlineBlueA7001 => BoxDecoration(
color: ColorConstant.whiteA700,
border: Border.all(
color: ColorConstant.blueA700,
width: getHorizontalSize(
1,
),
),
boxShadow: [
BoxShadow(
color: ColorConstant.gray60019,
spreadRadius: getHorizontalSize(
2,
),
blurRadius: getHorizontalSize(
2,
),
offset: const Offset(
0,
12,
),
),
],
);
static BoxDecoration get outlineGray70011 => BoxDecoration(
color: ColorConstant.whiteA700,
boxShadow: [
BoxShadow(
color: ColorConstant.gray70011,
spreadRadius: getHorizontalSize(
2,
),
blurRadius: getHorizontalSize(
2,
),
offset: const Offset(
0,
0,
),
),
],
);
static BoxDecoration get outlineGray600191 => const BoxDecoration();
static BoxDecoration get txtOutlineBlueA700 => BoxDecoration(
border: Border.all(
color: ColorConstant.blueA700,
width: getHorizontalSize(
1,
),
),
);
static BoxDecoration get outlineBlue50 => BoxDecoration(
color: ColorConstant.whiteA700,
border: Border.all(
color: ColorConstant.blue50,
width: getHorizontalSize(
1,
),
),
);
static BoxDecoration get txtFillBlueA700 => BoxDecoration(
color: ColorConstant.blueA700,
);
static BoxDecoration get outlineBlack90019 => BoxDecoration(
color: ColorConstant.blueA700,
boxShadow: [
BoxShadow(
color: ColorConstant.black90019,
spreadRadius: getHorizontalSize(
2,
),
blurRadius: getHorizontalSize(
2,
),
offset: const Offset(
0,
2,
),
),
],
);
static BoxDecoration get outlineBluegray100 => BoxDecoration(
color: ColorConstant.gray50,
border: Border(
bottom: BorderSide(
color: ColorConstant.blueGray100,
width: getHorizontalSize(
1,
),
),
),
);
static BoxDecoration get fillGray50 => BoxDecoration(
color: ColorConstant.gray50,
);
static BoxDecoration get outlineBluegray10001 => BoxDecoration(
color: ColorConstant.whiteA700,
border: Border.all(
color: ColorConstant.blueGray10001,
width: getHorizontalSize(
1,
),
),
boxShadow: [
BoxShadow(
color: ColorConstant.black90033,
spreadRadius: getHorizontalSize(
2,
),
blurRadius: getHorizontalSize(
2,
),
offset: const Offset(
0,
1,
),
),
],
);
static BoxDecoration get fillBlack900b2 => BoxDecoration(
color: ColorConstant.black900B2,
);
static BoxDecoration get outlineBlack90033 => BoxDecoration(
border: Border.all(
color: ColorConstant.black90033,
width: getHorizontalSize(
1,
),
),
);
static BoxDecoration get outlineBlack90011 => BoxDecoration(
color: ColorConstant.whiteA700,
boxShadow: [
BoxShadow(
color: ColorConstant.black90011,
spreadRadius: getHorizontalSize(
2,
),
blurRadius: getHorizontalSize(
2,
),
offset: const Offset(
0,
0,
),
),
],
);
static BoxDecoration get outlineGray30001 => BoxDecoration(
border: Border.all(
color: ColorConstant.gray30001,
width: getHorizontalSize(
1,
),
),
);
static BoxDecoration get outlineBlue200 => BoxDecoration(
border: Border.all(
color: ColorConstant.blue200,
width: getHorizontalSize(
1,
),
),
);
static BoxDecoration get outlineGray60019 => BoxDecoration(
color: ColorConstant.whiteA700,
boxShadow: [
BoxShadow(
color: ColorConstant.gray60019,
spreadRadius: getHorizontalSize(
2,
),
blurRadius: getHorizontalSize(
2,
),
offset: const Offset(
0,
12,
),
),
],
);
static BoxDecoration get outlineGray700261 => BoxDecoration(
color: ColorConstant.whiteA700,
boxShadow: [
BoxShadow(
color: ColorConstant.gray70026,
spreadRadius: getHorizontalSize(
2,
),
blurRadius: getHorizontalSize(
2,
),
offset: const Offset(
0,
0,
),
),
],
);
static BoxDecoration get fillWhiteA700 => BoxDecoration(
color: ColorConstant.whiteA700,
);
static BoxDecoration get outlineBlueA700 => BoxDecoration(
color: ColorConstant.gray50,
border: Border.all(
color: ColorConstant.blueA700,
width: getHorizontalSize(
2,
),
strokeAlign: strokeAlignOutside,
),
);
static BoxDecoration get fillBlue900 => BoxDecoration(
color: ColorConstant.blue900,
);
static BoxDecoration get outlineBluegray1002 => BoxDecoration(
color: ColorConstant.whiteA700,
border: Border(
top: BorderSide(
color: ColorConstant.blueGray100,
width: getHorizontalSize(
1,
),
),
bottom: BorderSide(
color: ColorConstant.blueGray100,
width: getHorizontalSize(
1,
),
),
),
);
static BoxDecoration get fillRed100 => BoxDecoration(
color: ColorConstant.red100,
);
static BoxDecoration get outlineBluegray1001 => BoxDecoration(
color: ColorConstant.whiteA700,
border: Border.all(
color: ColorConstant.blueGray100,
width: getHorizontalSize(
1,
),
),
);
static BoxDecoration get outlineYellow9003f => BoxDecoration(
color: ColorConstant.whiteA700,
border: Border.all(
color: ColorConstant.yellow9003f,
width: getHorizontalSize(
1,
),
strokeAlign: strokeAlignOutside,
),
);
static BoxDecoration get fillBlue50 => BoxDecoration(
color: ColorConstant.blue50,
);
static BoxDecoration get outlineGray70026 => BoxDecoration(
color: ColorConstant.whiteA70099,
boxShadow: [
BoxShadow(
color: ColorConstant.gray70026,
spreadRadius: getHorizontalSize(
2,
),
blurRadius: getHorizontalSize(
2,
),
offset: const Offset(
0,
0,
),
),
],
);
static BoxDecoration get txtOutlineBlack9000c => BoxDecoration(
color: ColorConstant.gray100,
border: Border.all(
color: ColorConstant.black9000c,
width: getHorizontalSize(
1,
),
),
);
static BoxDecoration get fillRed700 => BoxDecoration(
color: ColorConstant.red700,
);
static BoxDecoration get fillGray5003 => BoxDecoration(
color: ColorConstant.gray5003,
);
static BoxDecoration get fillGray200 => BoxDecoration(
color: ColorConstant.gray200,
);
static BoxDecoration get outlineBluegray50 => BoxDecoration(
color: ColorConstant.whiteA700,
border: Border.all(
color: ColorConstant.blueGray50,
width: getHorizontalSize(
1,
),
),
);
// Outline decorations
static BoxDecoration get outlineOnPrimaryContainer => BoxDecoration(
color: appTheme.whiteA700,
border: Border.all(
color: theme.colorScheme.onPrimaryContainer,
width: 1.h,
),
);
// Outline decorations
static BoxDecoration get outlineBlack => const BoxDecoration();
// Fill decorations
static BoxDecoration get fillBlueA => BoxDecoration(
color: appTheme.blueA200,
);
static BoxDecoration get fillGray800 => BoxDecoration(
color: appTheme.gray800,
);
static BoxDecoration get fillOnError => BoxDecoration(
color: theme.colorScheme.onError,
);
static BoxDecoration get fillPink => BoxDecoration(
color: appTheme.pink400,
);
static BoxDecoration get fillPrimary1 => BoxDecoration(
color: theme.colorScheme.primary.withOpacity(0.05),
);
static BoxDecoration get fillPrimary2 => BoxDecoration(
color: theme.colorScheme.primary.withOpacity(0.1),
);
static BoxDecoration get fillWhiteA => BoxDecoration(
color: appTheme.whiteA700,
);
static BoxDecoration get fillYellow => BoxDecoration(
color: appTheme.yellow600,
);
// Outline decorations
static BoxDecoration get outlineGray => BoxDecoration(
color: appTheme.whiteA700.withOpacity(0.6),
border: Border.all(
color: appTheme.gray300,
width: 1.h,
),
);
static BoxDecoration get outlineGray300 => BoxDecoration(
color: appTheme.whiteA700.withOpacity(0.6),
border: Border(
bottom: BorderSide(
color: appTheme.gray300,
width: 1.h,
),
),
);
static BoxDecoration get outlineGray3001 => BoxDecoration(
color: appTheme.whiteA700,
border: Border(
bottom: BorderSide(
color: appTheme.gray300,
width: 1.h,
),
),
);
static BoxDecoration get outlineGray3002 => BoxDecoration(
border: Border(
bottom: BorderSide(
color: appTheme.gray300,
width: 1.h,
),
),
);
static BoxDecoration get outlineGray3003 => BoxDecoration(
border: Border.all(
color: appTheme.gray300,
width: 1.h,
),
);
static BoxDecoration get outlineGray3004 => BoxDecoration(
border: Border(
left: BorderSide(
color: appTheme.gray300,
width: 1.h,
),
right: BorderSide(
color: appTheme.gray300,
width: 1.h,
),
),
);
static BoxDecoration get outlineGray3005 => BoxDecoration(
color: appTheme.whiteA700.withOpacity(0.05),
border: Border.all(
color: appTheme.gray300,
width: 1.h,
),
);
static BoxDecoration get outlineGray3006 => BoxDecoration(
border: Border(
bottom: BorderSide(
color: appTheme.gray300,
width: 1.h,
),
),
);
}
class BorderRadiusStyle {
static BorderRadius customBorderTL50 = BorderRadius.only(
topLeft: Radius.circular(
getHorizontalSize(
50,
),
),
bottomLeft: Radius.circular(
getHorizontalSize(
50,
),
),
);
static BorderRadius customBorderTL10 = BorderRadius.only(
topLeft: Radius.circular(
getHorizontalSize(
10,
),
),
topRight: Radius.circular(
getHorizontalSize(
10,
),
),
);
static BorderRadius circleBorder9 = BorderRadius.circular(
getHorizontalSize(
9,
),
);
static BorderRadius circleBorder22 = BorderRadius.circular(
getHorizontalSize(
22,
),
);
static BorderRadius roundedBorder16 = BorderRadius.circular(
getHorizontalSize(
16,
),
);
static BorderRadius circleBorder12 = BorderRadius.circular(
getHorizontalSize(
12,
),
);
static BorderRadius roundedBorder6 = BorderRadius.circular(
getHorizontalSize(
6,
),
);
static BorderRadius circleBorder25 = BorderRadius.circular(
getHorizontalSize(
25,
),
);
static BorderRadius roundedBorder3 = BorderRadius.circular(
getHorizontalSize(
3,
),
);
static BorderRadius circleBorder30 = BorderRadius.circular(
getHorizontalSize(
30,
),
);
static BorderRadius circleBorder76 = BorderRadius.circular(
getHorizontalSize(
76,
),
);
static BorderRadius txtRoundedBorder6 = BorderRadius.circular(
getHorizontalSize(
6,
),
);
static BorderRadius circleBorder61 = BorderRadius.circular(
getHorizontalSize(
61,
),
);
static BorderRadius get roundedBorder8 => BorderRadius.circular(
8.h,
);
// Circle borders
static BorderRadius get circleBorder24 => BorderRadius.circular(
24.h,
);
static BorderRadius get circleBorder50 => BorderRadius.circular(
50.h,
);
// Rounded borders
static BorderRadius get roundedBorder10 => BorderRadius.circular(
10.h,
);
static BorderRadius get roundedBorder15 => BorderRadius.circular(
15.h,
);
static BorderRadius get roundedBorder19 => BorderRadius.circular(
19.h,
);
static BorderRadius get roundedBorder5 => BorderRadius.circular(
5.h,
);
static BorderRadius get circleBorder20 => BorderRadius.circular(
20.h,
);
// Custom borders
static BorderRadius get customBorderBL12 => BorderRadius.vertical(
bottom: Radius.circular(12.h),
);
static BorderRadius get customBorderTL12 => BorderRadius.vertical(
top: Radius.circular(12.h),
);
static BorderRadius get roundedBorder24 => BorderRadius.circular(
24.h,
);
}
// Comment/Uncomment the below code based on your Flutter SDK version.
// For Flutter SDK Version 3.7.2 or greater.
double get strokeAlignInside => BorderSide.strokeAlignInside;
double get strokeAlignCenter => BorderSide.strokeAlignCenter;
double get strokeAlignOutside => BorderSide.strokeAlignOutside;
// For Flutter SDK Version 3.7.1 or less.
// StrokeAlign get strokeAlignInside => StrokeAlign.inside;
//
// StrokeAlign get strokeAlignCenter => StrokeAlign.center;
//
// StrokeAlign get strokeAlignOutside => StrokeAlign.outside;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,143 @@
import 'package:flutter/material.dart';
import '../core/app_export.dart';
/// A class that offers pre-defined button styles for customizing button appearance.
class CustomButtonStyles {
static BoxDecoration get fullyBlack => BoxDecoration(
borderRadius: BorderRadius.circular(24.h),
color: Colors.black,
);
static BoxDecoration
get gradientOnErrorContainerToOnErrorContainerDecoration => BoxDecoration(
borderRadius: BorderRadius.circular(24.h),
boxShadow: [
BoxShadow(
color: appTheme.black900.withOpacity(0.5),
spreadRadius: 2.h,
blurRadius: 2.h,
offset: const Offset(
4,
38,
),
)
],
gradient: LinearGradient(
begin: const Alignment(0.32, 0),
end: const Alignment(0.75, 0),
colors: [
theme.colorScheme.onErrorContainer.withOpacity(0.7),
theme.colorScheme.onErrorContainer.withOpacity(0.7)
],
),
);
static BoxDecoration get gradientGrayBToOnErrorContainerDecoration =>
BoxDecoration(
borderRadius: BorderRadius.circular(24.h),
boxShadow: [
BoxShadow(
color: appTheme.black900.withOpacity(0.5),
spreadRadius: 2.h,
blurRadius: 2.h,
offset: const Offset(
4,
38,
),
)
],
color: Colors.black,
gradient: LinearGradient(
begin: const Alignment(0.32, 0),
end: const Alignment(0.75, 0),
colors: [
appTheme.black900,
theme.colorScheme.onErrorContainer.withOpacity(1)
],
),
);
static BoxDecoration get gradientWhiteAToWhiteADecoration => BoxDecoration(
borderRadius: BorderRadius.circular(17.h),
gradient: LinearGradient(
begin: const Alignment(0.32, 0),
end: const Alignment(0.75, 0),
colors: [
appTheme.whiteA700.withOpacity(0.7),
appTheme.whiteA700.withOpacity(0.7)
],
),
);
// Outline button style
static ButtonStyle get outlineBlack => ElevatedButton.styleFrom(
backgroundColor: appTheme.blueA100,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.h),
),
shadowColor: appTheme.black900.withOpacity(0.4),
elevation: 2,
);
// text button style
static ButtonStyle get none => ButtonStyle(
backgroundColor: WidgetStateProperty.all<Color>(Colors.transparent),
elevation: WidgetStateProperty.all<double>(0),
);
// Filled button style
static ButtonStyle get fillBlueA => ElevatedButton.styleFrom(
backgroundColor: appTheme.blueA20001,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(21.h),
),
);
static ButtonStyle get fillPrimary => ElevatedButton.styleFrom(
backgroundColor: theme.colorScheme.primary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.h),
),
);
static ButtonStyle get fillPrimaryTL16 => ElevatedButton.styleFrom(
backgroundColor: theme.colorScheme.primary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.h),
),
);
static ButtonStyle get fillWhiteA => ElevatedButton.styleFrom(
backgroundColor: appTheme.whiteA700,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.h),
),
);
// Gradient button style
static BoxDecoration get gradientGrayBToWhiteADecoration => BoxDecoration(
borderRadius: BorderRadius.circular(24.h),
boxShadow: [
BoxShadow(
color: theme.colorScheme.primary.withOpacity(0.5),
spreadRadius: 2.h,
blurRadius: 2.h,
offset: const Offset(
4,
38,
),
)
],
gradient: LinearGradient(
begin: const Alignment(0.32, 0),
end: const Alignment(0.75, 0),
colors: [appTheme.gray100B2, appTheme.whiteA700.withOpacity(0.7)],
),
);
static BoxDecoration get gradientWhiteAToWhiteATL25Decoration =>
BoxDecoration(
borderRadius: BorderRadius.circular(25.h),
gradient: LinearGradient(
begin: const Alignment(0.32, 0),
end: const Alignment(0.75, 0),
colors: [
appTheme.whiteA700.withOpacity(0.7),
appTheme.whiteA700.withOpacity(0.7)
],
),
);
}

View File

@@ -0,0 +1,492 @@
import 'package:flutter/material.dart';
import '../core/app_export.dart';
extension on TextStyle {
TextStyle get poppins {
return copyWith(
fontFamily: 'Poppins',
);
}
TextStyle get openSans {
return copyWith(
fontFamily: 'Open Sans',
);
}
TextStyle get roboto {
return copyWith(
fontFamily: 'Roboto',
);
}
TextStyle get montserrat {
return copyWith(
fontFamily: 'Montserrat',
);
}
TextStyle get dMSans {
return copyWith(
fontFamily: 'DM Sans',
);
}
TextStyle get sourceSansPro {
return copyWith(
fontFamily: 'Source Sans Pro',
);
}
TextStyle get dMMono {
return copyWith(
fontFamily: 'DM Mono',
);
}
TextStyle get sFProText {
return copyWith(
fontFamily: 'SF Pro Text',
);
}
TextStyle get hammersmithOne {
return copyWith(
fontFamily: 'Hammersmith One',
);
}
TextStyle get urbanist {
return copyWith(
fontFamily: 'Urbanist',
);
}
TextStyle get sFPro {
return copyWith(
fontFamily: 'SF Pro',
);
}
}
/// A collection of pre-defined text styles for customizing text appearance,
/// categorized by different font families and weights.
/// Additionally, this class includes extensions on [TextStyle] to easily apply specific font families to text.
class CustomTextStyles {
static get titleLargePoppinsBlack40 =>
theme.textTheme.titleMedium!.poppins.copyWith(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 40.fSize,
);
static get titleSmallPoppinsWhite =>
theme.textTheme.titleSmall!.poppins.copyWith(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 22.fSize,
);
static get titleSmallRed700 => theme.textTheme.titleSmall!.copyWith(
color: appTheme.red700,
fontWeight: FontWeight.w700,
);
static get titleLargePoppinsBlack =>
theme.textTheme.titleMedium!.poppins.copyWith(
color: Colors.black,
fontSize: 30.fSize,
);
static get titleMediumMediumWhit => theme.textTheme.titleMedium!.copyWith(
fontSize: 16.fSize, fontWeight: FontWeight.w500, color: Colors.white);
// Headline text style
static get headlineLargePoppinsBlack900 =>
theme.textTheme.headlineLarge!.poppins.copyWith(
color: appTheme.black900,
fontWeight: FontWeight.w700,
);
// Label text style
static get labelLargeCyan900 => theme.textTheme.labelLarge!.copyWith(
color: appTheme.cyan900,
);
static get labelLargeGray500 => theme.textTheme.labelLarge!.copyWith(
color: appTheme.gray500,
fontWeight: FontWeight.w500,
);
// Title text style
static get titleSmallMontserratBlack900 =>
theme.textTheme.titleSmall!.montserrat.copyWith(
color: appTheme.black900,
fontSize: 14.fSize,
fontWeight: FontWeight.w700,
);
static get titleSmallPoppinsWhiteA700 =>
theme.textTheme.titleSmall!.poppins.copyWith(
color: appTheme.whiteA700,
fontSize: 14.fSize,
fontWeight: FontWeight.w700,
);
static get headlineSmallBlack900 => theme.textTheme.headlineSmall!.copyWith(
color: appTheme.black900,
fontWeight: FontWeight.w500,
);
static get headlineSmallBlack900_1 => theme.textTheme.headlineSmall!.copyWith(
color: appTheme.black900,
);
// Headline text style
static get headlineLargeGray900 => theme.textTheme.headlineLarge!.copyWith(
color: appTheme.gray900,
);
// Title text style
static get titleMedium18 => theme.textTheme.titleMedium!.copyWith(
fontSize: 18.fSize,
);
static get titleMediumErrorContainer => theme.textTheme.titleMedium!.copyWith(
color: theme.colorScheme.errorContainer,
fontWeight: FontWeight.w500,
);
static get titleMediumPrimary => theme.textTheme.titleMedium!.copyWith(
color: theme.colorScheme.primary,
fontSize: 18.fSize,
);
static get titleSmallDeeppurpleA400 => theme.textTheme.titleSmall!.copyWith(
color: appTheme.deepPurpleA400,
fontWeight: FontWeight.w700,
);
static get titleSmallErrorContainer => theme.textTheme.titleSmall!.copyWith(
color: theme.colorScheme.errorContainer,
);
static get titleSmallGray600 => theme.textTheme.titleSmall!.copyWith(
color: appTheme.gray600,
fontSize: 14.fSize,
fontWeight: FontWeight.w600,
);
static get titleSmallPoppinsBlack900 =>
theme.textTheme.titleSmall!.poppins.copyWith(
color: appTheme.black900,
);
static get titleSmallPoppinsDeeppurpleA400 =>
theme.textTheme.titleSmall!.poppins.copyWith(
color: appTheme.deepPurpleA400,
fontWeight: FontWeight.w700,
);
static get titleSmallPoppinsGray90001 =>
theme.textTheme.titleSmall!.poppins.copyWith(
color: appTheme.gray90001,
);
static get titleSmallPoppinsPrimaryContainer =>
theme.textTheme.titleSmall!.poppins.copyWith(
color: theme.colorScheme.primaryContainer,
fontWeight: FontWeight.w600,
);
static get titleSmallPoppinsPrimaryContainer_1 =>
theme.textTheme.titleSmall!.poppins.copyWith(
color: theme.colorScheme.primaryContainer,
);
static get titleSmallPrimaryContainer => theme.textTheme.titleSmall!.copyWith(
color: theme.colorScheme.primaryContainer,
);
// Body text style
static get bodyMediumPoppinsBluegray50 =>
theme.textTheme.bodyMedium!.poppins.copyWith(
color: appTheme.blueGray50,
);
static get bodySmallMontserratBluegray400 =>
theme.textTheme.bodySmall!.montserrat.copyWith(
color: appTheme.blueGray400,
fontSize: 10.fSize,
fontWeight: FontWeight.w400,
);
static get bodySmallOpenSansOnPrimaryContainer =>
theme.textTheme.bodySmall!.openSans.copyWith(
color: theme.colorScheme.onPrimaryContainer.withOpacity(1),
fontWeight: FontWeight.w400,
);
static get bodySmallRegular => theme.textTheme.bodySmall!.copyWith(
fontSize: 10.fSize,
fontWeight: FontWeight.w400,
);
static get bodySmallYellow900 => theme.textTheme.bodySmall!.copyWith(
color: appTheme.yellow900,
fontSize: 12.fSize,
fontWeight: FontWeight.w400,
);
// Headline text style
static get headlineLargeBold => theme.textTheme.headlineLarge!.copyWith(
fontSize: 32.fSize,
fontWeight: FontWeight.w700,
);
static get headlineLargeExtraBold => theme.textTheme.headlineLarge!.copyWith(
fontSize: 32.fSize,
fontWeight: FontWeight.w800,
);
static get headlineSmallOnPrimaryContainer =>
theme.textTheme.headlineSmall!.copyWith(
color: theme.colorScheme.onPrimaryContainer.withOpacity(1),
fontSize: 24.fSize,
);
static get headlineSmallSemiBold => theme.textTheme.headlineSmall!.copyWith(
fontSize: 24.fSize,
fontWeight: FontWeight.w600,
);
// Label text style
static get labelLargeBluegray700 => theme.textTheme.labelLarge!.copyWith(
color: appTheme.blueGray700,
);
static get labelLargeMontserratCyan900 =>
theme.textTheme.labelLarge!.montserrat.copyWith(
color: appTheme.cyan900,
fontWeight: FontWeight.w700,
);
static get labelLargeMontserratGray500 =>
theme.textTheme.labelLarge!.montserrat.copyWith(
color: appTheme.gray500,
);
static get labelLargeMontserratTeal100 =>
theme.textTheme.labelLarge!.montserrat.copyWith(
color: appTheme.teal100,
fontWeight: FontWeight.w700,
);
static get labelMediumGray500 => theme.textTheme.labelMedium!.copyWith(
color: appTheme.gray500,
);
static get labelMediumGreen80001 => theme.textTheme.labelMedium!.copyWith(
color: appTheme.green80001,
);
static get labelMediumMontserratCyan900 =>
theme.textTheme.labelMedium!.montserrat.copyWith(
color: appTheme.cyan900,
fontWeight: FontWeight.w700,
);
static get labelMediumOpenSans =>
theme.textTheme.labelMedium!.openSans.copyWith(
fontWeight: FontWeight.w700,
);
static get labelMediumRed700 => theme.textTheme.labelMedium!.copyWith(
color: appTheme.red700,
);
static get labelSmallBold => theme.textTheme.labelSmall!.copyWith(
fontWeight: FontWeight.w700,
);
// Open text style
static get openSansOnPrimaryContainer => TextStyle(
color: theme.colorScheme.onPrimaryContainer,
fontSize: 6.fSize,
fontWeight: FontWeight.w400,
).openSans;
// Title text style
static get titleMediumMedium => theme.textTheme.titleMedium!.copyWith(
fontSize: 16.fSize,
fontWeight: FontWeight.w500,
);
static get titleSmallPoppinsOnPrimaryContainer =>
theme.textTheme.titleSmall!.poppins.copyWith(
color: theme.colorScheme.onPrimaryContainer.withOpacity(1),
fontWeight: FontWeight.w500,
);
static get titleSmallPoppinsOnPrimaryContainerMedium =>
theme.textTheme.titleSmall!.poppins.copyWith(
color: theme.colorScheme.onPrimaryContainer.withOpacity(1),
fontWeight: FontWeight.w500,
);
static get titleSmallPoppinsOnPrimaryContainer_1 =>
theme.textTheme.titleSmall!.poppins.copyWith(
color: theme.colorScheme.onPrimaryContainer.withOpacity(1),
);
static get bodySmallOpenSansOnErrorContainer =>
theme.textTheme.bodySmall!.openSans.copyWith(
color: theme.colorScheme.onErrorContainer.withOpacity(1),
fontSize: 8.fSize,
);
// Headline text style
static get headlineLargeSemiBold => theme.textTheme.headlineLarge!.copyWith(
fontSize: 30.fSize,
fontWeight: FontWeight.w600,
);
static get headlineSmallOnErrorContainer =>
theme.textTheme.headlineSmall!.copyWith(
color: theme.colorScheme.onErrorContainer.withOpacity(1),
fontSize: 24.fSize,
);
// Label text style
static get labelLargeDMSansOnPrimaryContainer =>
theme.textTheme.labelLarge!.dMSans.copyWith(
color: theme.colorScheme.onPrimaryContainer,
fontSize: 13.fSize,
);
static get labelLargeDMSansOnPrimaryContainer_1 =>
theme.textTheme.labelLarge!.dMSans.copyWith(
color: theme.colorScheme.onPrimaryContainer,
);
static get labelLargeErrorContainer => theme.textTheme.labelLarge!.copyWith(
color: theme.colorScheme.errorContainer,
);
static get labelLarge_1 => theme.textTheme.labelLarge!;
// Open text style
static get openSansOnErrorContainer => TextStyle(
color: theme.colorScheme.onErrorContainer,
fontSize: 6.fSize,
fontWeight: FontWeight.w400,
).openSans;
// Title text style
static get titleMediumBlack900 => theme.textTheme.titleMedium!.copyWith(
color: appTheme.black900,
);
static get titleMediumBlack900Medium => theme.textTheme.titleMedium!.copyWith(
color: appTheme.black900,
fontSize: 16.fSize,
fontWeight: FontWeight.w500,
);
static get titleMediumGray300 => theme.textTheme.titleMedium!.copyWith(
color: appTheme.gray300,
);
static get titleMediumGray50 => theme.textTheme.titleMedium!.copyWith(
color: appTheme.gray50,
fontWeight: FontWeight.w700,
);
static get titleMediumSourceSansPro =>
theme.textTheme.titleMedium!.sourceSansPro.copyWith(
fontSize: 16.fSize,
);
static get titleSmallPoppinsOnErrorContainer =>
theme.textTheme.titleSmall!.poppins.copyWith(
color: theme.colorScheme.onErrorContainer.withOpacity(1),
);
static get titleMediumWhiteA700 => theme.textTheme.titleMedium!.copyWith(
color: appTheme.whiteA700,
);
// Body text style
static get bodyLargeGray50 => theme.textTheme.bodyLarge!.copyWith(
color: appTheme.gray50,
);
// Label text style
static get labelLargeAmber300 => theme.textTheme.labelLarge!.copyWith(
color: appTheme.amber300,
);
static get labelLargePoppinsGray50 =>
theme.textTheme.labelLarge!.poppins.copyWith(
color: appTheme.gray50,
fontWeight: FontWeight.w700,
);
static get labelLargePrimary => theme.textTheme.labelLarge!.copyWith(
color: theme.colorScheme.primary,
);
static get labelLargePrimaryBold => theme.textTheme.labelLarge!.copyWith(
color: theme.colorScheme.primary,
fontWeight: FontWeight.w700,
);
static get labelLargePrimaryExtraBold => theme.textTheme.labelLarge!.copyWith(
color: theme.colorScheme.primary,
fontWeight: FontWeight.w800,
);
static get labelLargeSFProTextErrorContainer =>
theme.textTheme.labelLarge!.sFProText.copyWith(
color: theme.colorScheme.errorContainer,
fontWeight: FontWeight.w700,
);
static get labelLargeSFProTextErrorContainer_1 =>
theme.textTheme.labelLarge!.sFProText.copyWith(
color: theme.colorScheme.errorContainer,
);
static get labelLargeSFProTextPrimary =>
theme.textTheme.labelLarge!.sFProText.copyWith(
color: theme.colorScheme.primary,
fontWeight: FontWeight.w500,
);
static get labelLargeSFProTextPrimaryMedium =>
theme.textTheme.labelLarge!.sFProText.copyWith(
color: theme.colorScheme.primary.withOpacity(0.53),
fontWeight: FontWeight.w500,
);
static get labelLargeSFProTextPrimaryMedium_1 =>
theme.textTheme.labelLarge!.sFProText.copyWith(
color: theme.colorScheme.primary.withOpacity(0.56),
fontWeight: FontWeight.w500,
);
static get labelLargeSFProTextPrimary_1 =>
theme.textTheme.labelLarge!.sFProText.copyWith(
color: theme.colorScheme.primary.withOpacity(0.7),
);
static get labelLargeSFProTextPrimary_2 =>
theme.textTheme.labelLarge!.sFProText.copyWith(
color: theme.colorScheme.primary,
);
static get labelLargeSFProTextRed600 =>
theme.textTheme.labelLarge!.sFProText.copyWith(
color: appTheme.red600,
);
static get labelLargeSFProTextWhiteA700 =>
theme.textTheme.labelLarge!.sFProText.copyWith(
color: appTheme.whiteA700,
fontWeight: FontWeight.w500,
);
static get labelLargeWhiteA700 => theme.textTheme.labelLarge!.copyWith(
color: appTheme.whiteA700,
);
static get labelLargeWhiteA700ExtraBold =>
theme.textTheme.labelLarge!.copyWith(
color: appTheme.whiteA700,
fontWeight: FontWeight.w800,
);
static get labelLargeWhiteA700_1 => theme.textTheme.labelLarge!.copyWith(
color: appTheme.whiteA700.withOpacity(0.9),
);
static get labelLargeWhiteA700_2 => theme.textTheme.labelLarge!.copyWith(
color: appTheme.whiteA700.withOpacity(0.9),
);
static get labelMediumPrimary => theme.textTheme.labelMedium!.copyWith(
color: theme.colorScheme.primary,
fontWeight: FontWeight.w800,
);
static get labelMediumSFProTextRed600 =>
theme.textTheme.labelMedium!.sFProText.copyWith(
color: appTheme.red600,
);
static get labelMediumWhiteA700 => theme.textTheme.labelMedium!.copyWith(
color: appTheme.whiteA700,
fontWeight: FontWeight.w600,
);
static get labelMediumWhiteA700ExtraBold =>
theme.textTheme.labelMedium!.copyWith(
color: appTheme.whiteA700.withOpacity(0.7),
fontWeight: FontWeight.w800,
);
static get labelMediumWhiteA700ExtraBold_1 =>
theme.textTheme.labelMedium!.copyWith(
color: appTheme.whiteA700,
fontWeight: FontWeight.w800,
);
// Title text style
static get titleLargeWhiteA700 => theme.textTheme.titleLarge!.copyWith(
color: appTheme.whiteA700,
);
static get titleLargeWhiteA700_1 => theme.textTheme.titleLarge!.copyWith(
color: appTheme.whiteA700,
);
static get titleMediumDeeporange300 => theme.textTheme.titleMedium!.copyWith(
color: appTheme.deepOrange300,
fontSize: 18.fSize,
fontWeight: FontWeight.w800,
);
static get titleMediumPoppins =>
theme.textTheme.titleMedium!.poppins.copyWith(
fontSize: 18.fSize,
fontWeight: FontWeight.w600,
);
static get titleMediumPoppinsGray50 =>
theme.textTheme.titleMedium!.poppins.copyWith(
color: appTheme.gray50,
fontSize: 18.fSize,
);
static get titleMediumRed600 => theme.textTheme.titleMedium!.copyWith(
color: appTheme.red600,
);
static get titleMediumSFProText => theme.textTheme.titleMedium!.sFProText;
}

View File

@@ -0,0 +1,263 @@
import 'package:flutter/material.dart';
import '../core/app_export.dart';
String _appTheme = "lightCode";
LightCodeColors get appTheme => ThemeHelper().themeColor();
ThemeData get theme => ThemeHelper().themeData();
/// Helper class for managing themes and colors.
// ignore_for_file: must_be_immutable
// ignore_for_file: must_be_immutable
class ThemeHelper {
// A map of custom color themes supported by the app
final Map<String, LightCodeColors> _supportedCustomColor = {
'lightCode': LightCodeColors()
};
// A map of color schemes supported by the app
final Map<String, ColorScheme> _supportedColorScheme = {
'lightCode': ColorSchemes.lightCodeColorScheme
};
/// Changes the app theme to [newTheme].
void changeTheme(String newTheme) {
_appTheme = newTheme;
}
/// Returns the lightCode colors for the current theme.
LightCodeColors _getThemeColors() {
return _supportedCustomColor[_appTheme] ?? LightCodeColors();
}
/// Returns the current theme data.
ThemeData _getThemeData() {
var colorScheme =
_supportedColorScheme[_appTheme] ?? ColorSchemes.lightCodeColorScheme;
return ThemeData(
visualDensity: VisualDensity.standard,
colorScheme: colorScheme,
textTheme: TextThemes.textTheme(colorScheme),
scaffoldBackgroundColor: appTheme.gray100,
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: colorScheme.primary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
shadowColor: appTheme.black900.withOpacity(0.4),
elevation: 2,
visualDensity: const VisualDensity(
vertical: -4,
horizontal: -4,
),
padding: EdgeInsets.zero,
),
),
dividerTheme: DividerThemeData(
thickness: 1,
space: 1,
color: colorScheme.onPrimaryContainer.withOpacity(0.2),
),
);
}
/// Returns the lightCode colors for the current theme.
LightCodeColors themeColor() => _getThemeColors();
/// Returns the current theme data.
ThemeData themeData() => _getThemeData();
}
/// Class containing the supported text theme styles.
class TextThemes {
static TextTheme textTheme(ColorScheme colorScheme) => TextTheme(
bodyMedium: TextStyle(
color: colorScheme.onPrimaryContainer.withOpacity(1),
fontSize: 13.fSize,
fontFamily: 'Roboto',
fontWeight: FontWeight.w400,
),
bodySmall: TextStyle(
color: appTheme.gray500,
fontSize: 8.fSize,
fontFamily: 'Poppins',
fontWeight: FontWeight.w300,
),
headlineLarge: TextStyle(
color: appTheme.black900,
fontSize: 30.fSize,
fontFamily: 'Poppins',
fontWeight: FontWeight.w600,
),
headlineSmall: TextStyle(
color: appTheme.black900,
fontSize: 25.fSize,
fontFamily: 'Poppins',
fontWeight: FontWeight.w400,
),
labelLarge: TextStyle(
color: colorScheme.onPrimaryContainer.withOpacity(1),
fontSize: 12.fSize,
fontFamily: 'Poppins',
fontWeight: FontWeight.w500,
),
labelMedium: TextStyle(
color: colorScheme.onPrimaryContainer.withOpacity(1),
fontSize: 10.fSize,
fontFamily: 'Poppins',
fontWeight: FontWeight.w500,
),
labelSmall: TextStyle(
color: colorScheme.onPrimaryContainer.withOpacity(1),
fontSize: 8.fSize,
fontFamily: 'Open Sans',
fontWeight: FontWeight.w600,
),
titleLarge: TextStyle(
color: appTheme.black900,
fontSize: 20.fSize,
fontFamily: 'Poppins',
fontWeight: FontWeight.w400,
),
titleMedium: TextStyle(
color: appTheme.black900,
fontSize: 18.fSize,
fontFamily: 'Poppins',
fontWeight: FontWeight.w600,
),
titleSmall: TextStyle(
color: appTheme.black900,
fontSize: 14.fSize,
fontFamily: 'Montserrat',
fontWeight: FontWeight.w700,
),
);
}
/// Class containing the supported color schemes.
class ColorSchemes {
static const lightCodeColorScheme = ColorScheme.light(
primary: Color(0XFF8DB1F7),
primaryContainer: Color(0XFF2E353E),
errorContainer: Color(0XFF777777),
onPrimary: Color(0XFF1C2026),
onPrimaryContainer: Color(0X75FFFFFF),
);
}
/// Class containing custom colors for a lightCode theme.
class LightCodeColors {
// Black
Color get black900 => const Color(0XFF000000);
// Blue
Color get blueA200 => const Color(0XFF5285E8);
// BlueGray
Color get blueGray400 => const Color(0XFF888888);
Color get blueGray50 => const Color(0XFFF1F1F1);
Color get blueGray700 => const Color(0XFF535D67);
Color get blueGray800 => const Color(0XFF454B55);
// Cyan
Color get cyan600 => const Color(0XFF1DA1BE);
Color get cyan900 => const Color(0XFF105955);
// Gray
Color get gray100 => const Color(0XFFF0F5F4);
Color get gray500 => const Color(0XFFAAAAAA);
Color get gray700 => const Color(0XFF636363);
Color get gray900 => const Color(0XFF1E232C);
// Green
Color get green50 => const Color(0XFFD9F9DA);
Color get green800 => const Color(0XFF159021);
Color get green80001 => const Color(0XFF0CAC13);
Color get gray50 => const Color(0XFFF7F8F9);
// Blue
Color get blue400 => const Color(0XFF34AADF);
Color get blueA100 => const Color(0XFF8DB1F7);
Color get blueA400 => const Color(0XFF337FFF);
// BlueGray
Color get blueGray200 => const Color(0XFFB8BCCA);
Color get blueGray40001 => const Color(0XFF888888);
// DeepOrange
Color get deepOrangeA400 => const Color(0XFFFF4500);
// Indigo
Color get indigo50 => const Color(0XFFE8ECF4);
// LightGreen
Color get lightGreenA200 => const Color(0XFFC7FC6C);
Color get lightGreenA20001 => const Color(0XFFC0FE53);
// Red
Color get red100 => const Color(0XFFF8C6CC);
Color get red600 => const Color(0XFFE73E3E);
Color get red700 => const Color(0XFFDA2037);
// Teal
Color get teal100 => const Color(0XFFA5E0DD);
// Yellow
Color get yellow900 => const Color(0XFFEE7429);
Color get gray600 => const Color(0XFF6A707C);
// DeepPurple
Color get deepPurpleA400 => const Color(0XFF5030E5);
// Gray
Color get gray10001 => const Color(0XFFF0F5F4);
Color get gray90001 => const Color(0XFF1B1919);
// Red
Color get red500 => const Color(0XFFF14336);
// White
Color get whiteA700 => const Color(0XFFFFFFFF);
// GrayB
Color get gray100B2 => const Color(0XB2F7F7F7);
Color get gray300 => const Color(0XFFE4E4E6);
// Amber
Color get amberA700 => const Color(0XFFFFAE00);
// LightBlue
Color get lightBlue900 => const Color(0XFF006699);
Color get lightBlueA200 => const Color(0XFF33CCFF);
Color get lightBlueA20001 => const Color(0XFF36C5F0);
// BlueGray
Color get blueGray100 => const Color(0XFFD9D9D9);
// LightGreen
Color get lightGreen100 => const Color(0XFFDDF8BB);
Color get lightGreenA20000 => const Color(0X00BBFB4C);
// Lime
Color get limeA200 => const Color(0XFFF8FF4A);
// Yellow
Color get yellow400 => const Color(0XFFE8FF61);
// Amber
Color get amber300 => const Color(0XFFFFDB61);
Color get blueA20001 => const Color(0XFF4C7FE4);
Color get blueA20002 => const Color(0XFF5285E8);
// DeepOrange
Color get deepOrange300 => const Color(0XFFFF9969);
Color get deepOrangeA200 => const Color(0XFFFF6C2C);
Color get gray200 => const Color(0XFFEFEFEF);
Color get gray800 => const Color(0XFF383838);
// Green
Color get green600 => const Color(0XFF239F57);
Color get green900 => const Color(0XFF096A2F);
Color get greenA700 => const Color(0XFF13A445);
// Orange
Color get orange900 => const Color(0XFFD15C0B);
// Pink
Color get pink400 => const Color(0XFFD44164);
Color get red900 => const Color(0XFFAF000D);
// Yellow
Color get yellow600 => const Color(0XFFFFDA2B);
}

View File

@@ -0,0 +1,172 @@
import 'package:flutter/material.dart';
class ColorConstant {
static Color gray5001 = fromHex('#f6f7fb');
static Color purple211 = const Color.fromARGB(255, 93, 63, 211);
static Color gray5002 = fromHex('#f8f9fa');
static Color black900B2 = fromHex('#b2000000');
static Color gray5003 = fromHex('#fafcff');
static Color lightBlue100 = fromHex('#b0e5fc');
static Color gray80049 = fromHex('#493c3c43');
static Color yellow9003f = fromHex('#3feb9612');
static Color iris = fromHex('5D3FD3');
static Color red200 = fromHex('#fa9a9a');
static Color gray4004c = fromHex('#4cc4c4c4');
static Color blueA200 = fromHex('#468ee5');
static Color greenA100 = fromHex('#b5eacd');
static Color black9003f = fromHex('#3f000000');
static Color gray30099 = fromHex('#99e4e4e4');
static Color black90087 = fromHex('#87000000');
static Color whiteA70099 = fromHex('#99ffffff');
static Color black90001 = fromHex('#000000');
static Color blueGray90002 = fromHex('#24363c');
static Color blueGray90001 = fromHex('#2e3637');
static Color blueGray700 = fromHex('#535763');
static Color blueGray900 = fromHex('#262b35');
static Color black90003 = fromHex('#0b0a0a');
static Color black90002 = fromHex('#090b0d');
static Color redA700 = fromHex('#d80027');
static Color black90004 = fromHex('#000000');
static Color gray400 = fromHex('#c4c4c4');
static Color blue900 = fromHex('#003399');
static Color blueGray100 = fromHex('#d6dae2');
static Color blue700 = fromHex('#1976d2');
static Color blueGray300 = fromHex('#9ea8ba');
static Color amber500 = fromHex('#feb909');
static Color redA200 = fromHex('#fe555d');
static Color gray80099 = fromHex('#993c3c43');
static Color black9000c = fromHex('#0c000000');
static Color gray200 = fromHex('#efefef');
static Color gray60026 = fromHex('#266d6d6d');
static Color blue50 = fromHex('#e0ebff');
static Color indigo400 = fromHex('#4168d7');
static Color blueGray1006c = fromHex('#6cd1d3d4');
static Color black90011 = fromHex('#11000000');
static Color gray40001 = fromHex('#b3b3b3');
static Color whiteA70067 = fromHex('#67ffffff');
static Color gray10001 = fromHex('#fbf1f2');
static Color black90019 = fromHex('#19000000');
static Color blueGray40001 = fromHex('#888888');
static Color whiteA700 = fromHex('#ffffff');
static Color blueGray50 = fromHex('#eaecf0');
static Color red700 = fromHex('#d03329');
static Color blueA700 = fromHex('#0061ff');
static Color blueGray10001 = fromHex('#d6d6d6');
static Color gray60019 = fromHex('#197e7e7e');
static Color green600 = fromHex('#349765');
static Color blueA70001 = fromHex('#0068ff');
static Color gray50 = fromHex('#f9fbff');
static Color red100 = fromHex('#f6d6d4');
static Color blueGray20001 = fromHex('#adb5bd');
static Color black900 = fromHex('#000919');
static Color blueGray800 = fromHex('#37334d');
static Color blue5001 = fromHex('#eef4ff');
static Color deepOrange400 = fromHex('#d58c48');
static Color deepOrangeA400 = fromHex('#ff4b00');
static Color gray70011 = fromHex('#11555555');
static Color indigoA20033 = fromHex('#334871e3');
static Color gray90002 = fromHex('#0d062d');
static Color gray700 = fromHex('#666666');
static Color blueGray200 = fromHex('#bac1ce');
static Color blueGray400 = fromHex('#74839d');
static Color blue800 = fromHex('#2953c7');
static Color blueGray600 = fromHex('#5f6c86');
static Color gray900 = fromHex('#2a2a2a');
static Color gray90001 = fromHex('#212529');
static Color gray300 = fromHex('#d2efe0');
static Color gray30001 = fromHex('#e3e4e5');
static Color gray100 = fromHex('#f3f4f5');
static Color black90075 = fromHex('#75000000');
static Color deepOrangeA10033 = fromHex('#33dfa874');
static Color gray70026 = fromHex('#26555555');
static Color black90033 = fromHex('#33000000');
static Color blue200 = fromHex('#a6c8ff');
static Color purple900 = Colors.purple.shade900;
static Color fromHex(String hexString) {
final buffer = StringBuffer();
if (hexString.length == 6 || hexString.length == 7) buffer.write('ff');
buffer.write(hexString.replaceFirst('#', ''));
return Color(int.parse(buffer.toString(), radix: 16));
}
}

View File

@@ -0,0 +1,61 @@
import 'package:another_flushbar/flushbar.dart';
import 'package:base_project/resources/app_colors.dart';
import 'package:flutter/material.dart';
class FlushBarMessageUtil {
static void showFlushBar({
required BuildContext context,
required String message,
FlushBarType flushBarType = FlushBarType.info,
int durationInSeconds = 3,
}) {
Flushbar(
message: message,
duration: Duration(seconds: durationInSeconds),
backgroundColor: _getBackgroundColor(flushBarType),
margin: const EdgeInsets.all(8.0),
borderRadius: BorderRadius.circular(8.0),
flushbarPosition: FlushbarPosition.BOTTOM,
icon: _getIcon(flushBarType),
leftBarIndicatorColor: _getBackgroundColor(flushBarType),
).show(context);
}
static Color _getBackgroundColor(FlushBarType flushBarType) {
switch (flushBarType) {
case FlushBarType.success:
return AppColors.success;
case FlushBarType.error:
return AppColors.error;
case FlushBarType.warning:
return AppColors.warning;
case FlushBarType.info:
return AppColors.info;
case FlushBarType.general:
return AppColors.darkGrey;
default:
return AppColors.info;
}
}
static Icon _getIcon(FlushBarType flushBarType) {
switch (flushBarType) {
case FlushBarType.success:
return const Icon(Icons.check_circle, color: Colors.white);
case FlushBarType.error:
return const Icon(Icons.error, color: Colors.white);
case FlushBarType.warning:
return const Icon(Icons.warning, color: Colors.white);
case FlushBarType.info:
return const Icon(Icons.info, color: Colors.white);
case FlushBarType.general:
return const Icon(Icons.notifications, color: Colors.white);
default:
return const Icon(Icons.info, color: Colors.white);
}
}
}
enum FlushBarType { success, error, warning, info, general }

View File

@@ -0,0 +1,35 @@
class FormValidators {
// Validator for non-empty fields
static String? validateNotEmpty(String? value) {
return value == null || value.isEmpty ? 'This field cannot be empty' : null;
}
// Validator for email format
static String? validateEmail(String? value) {
const emailPattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$';
final regex = RegExp(emailPattern);
return value == null || !regex.hasMatch(value) ? 'Enter a valid email address' : null;
}
// Validator for phone number format
static String? validatePhoneNumber(String? value) {
const phonePattern = r'^\+?[1-9]\d{1,14}$'; // E.164 format
final regex = RegExp(phonePattern);
return value == null || !regex.hasMatch(value) ? 'Enter a valid phone number' : null;
}
// Validator for min length
static String? validateMinLength(String? value, int minLength) {
return value == null || value.length < minLength ? 'Must be at least $minLength characters long' : null;
}
// Validator for max length
static String? validateMaxLength(String? value, int maxLength) {
return value != null && value.length > maxLength ? 'Must be no more than $maxLength characters long' : null;
}
// Validator for matching passwords
static String? validatePasswordMatch(String? value, String password) {
return value != password ? 'Passwords do not match' : null;
}
}

View File

@@ -0,0 +1,26 @@
class ImageConstant {
// Image folder path
static String imagePath = 'assets/images';
// All images
static String imgArrowleft = '$imagePath/img_arrowleft.svg';
static String userProfileImg = "$imagePath/user-line.svg";
static String imgFire = '$imagePath/img_fire.svg';
static String imgSearchWhiteA70020x20 =
'$imagePath/img_search_white_a700_20x20.svg';
static String imgMenu = '$imagePath/img_menu.svg';
static String imgOverflowmenuWhiteA700 =
'$imagePath/img_overflowmenu_white_a700.svg';
static String imgArrowleftBlueGray900 =
'$imagePath/img_arrowleft_blue_gray_900.svg';
static String bubbles = 'assets/icon/bubbles.svg';
static String close = 'assets/icon/close.svg';
static String fail = 'assets/icon/fail.svg';
}

View File

@@ -0,0 +1,100 @@
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
class UserManager {
static const String _userKey = 'user';
static const String _accountKey = 'sys_account';
static UserManager? _instance;
static Map<String, dynamic>? _cachedUser;
static Map<String, dynamic>? _cachedAccount;
// Private constructor for singleton
UserManager._privateConstructor();
// Factory constructor to get the singleton instance
factory UserManager() {
_instance ??= UserManager._privateConstructor();
return _instance!;
}
// Initialize and load user data
Future<void> initialize() async {
if (_cachedUser == null) {
final prefs = await SharedPreferences.getInstance();
final userData = prefs.getString(_userKey);
if (userData != null) {
_cachedUser = jsonDecode(userData) as Map<String, dynamic>;
}
}
if (_cachedAccount == null) {
final prefs = await SharedPreferences.getInstance();
final accountData = prefs.getString(_accountKey);
if (accountData != null) {
_cachedAccount = jsonDecode(accountData) as Map<String, dynamic>;
}
}
}
// Accessor for user token
String? get token {
return _cachedUser?['token'];
}
// Accessor for user name
String? get userName {
return _cachedUser?['fullname'];
}
// Accessor for user email
String? get email {
return _cachedUser?['email'];
}
// Accessor for user id
int? get userId {
return _cachedUser?['userId'];
}
// Accessor for user roles (Assuming it's a list of roles in the user data)
List<String>? get roles {
if (_cachedUser?['roles'] != null) {
return List<String>.from(_cachedUser!['roles']);
}
return null;
}
// Save user data and cache it
Future<void> setUser(Map<String, dynamic> user) async {
final prefs = await SharedPreferences.getInstance();
final userData = jsonEncode(user);
await prefs.setString(_userKey, userData);
_cachedUser = user; // Update cache
}
// Save account data and cache it
Future<void> setAccount(Map<String, dynamic> account) async {
final prefs = await SharedPreferences.getInstance();
final accountData = jsonEncode(account);
await prefs.setString(_accountKey, accountData);
_cachedAccount = account;
}
Map<String, dynamic>? get account => _cachedAccount;
String? get accountId {
final id = _cachedAccount?['account_id'] ?? _cachedAccount?['id'];
return id?.toString();
}
// Clear user data and cache
Future<void> clearUser() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove(_userKey);
await prefs.remove(_accountKey);
await prefs.remove('isLoggedIn');
await prefs.clear();
_cachedUser = null; // Clear cache
_cachedAccount = null;
}
}

View File

@@ -0,0 +1,120 @@
import 'package:flutter/material.dart';
// This is where the magic happens.
// This functions are responsible to make UI responsive across all the mobile devices.
Size size = WidgetsBinding.instance.window.physicalSize /
WidgetsBinding.instance.window.devicePixelRatio;
// Caution! If you think these are static values and are used to build a static UI, you mustnt.
// These are the Viewport values of your Figma Design.
// These are used in the code as a reference to create your UI Responsively.
const num FIGMA_DESIGN_WIDTH = 428;
const num FIGMA_DESIGN_HEIGHT = 926;
const num FIGMA_DESIGN_STATUS_BAR = 47;
///This method is used to get device viewport width.
get width {
return size.width;
}
///This method is used to get device viewport height.
get height {
num statusBar =
MediaQueryData.fromView(WidgetsBinding.instance.window).viewPadding.top;
num bottomBar = MediaQueryData.fromView(WidgetsBinding.instance.window)
.viewPadding
.bottom;
num screenHeight = size.height - statusBar - bottomBar;
return screenHeight;
}
///This method is used to set padding/margin (for the left and Right side) & width of the screen or widget according to the Viewport width.
double getHorizontalSize(double px) {
return ((px * width) / FIGMA_DESIGN_WIDTH);
}
///This method is used to set padding/margin (for the top and bottom side) & height of the screen or widget according to the Viewport height.
double getVerticalSize(double px) {
return ((px * height) / (FIGMA_DESIGN_HEIGHT - FIGMA_DESIGN_STATUS_BAR));
}
///This method is used to set smallest px in image height and width
double getSize(double px) {
var height = getVerticalSize(px);
var width = getHorizontalSize(px);
if (height < width) {
return height.toInt().toDouble();
} else {
return width.toInt().toDouble();
}
}
///This method is used to set text font size according to Viewport
double getFontSize(double px) {
return getSize(px);
}
///This method is used to set padding responsively
EdgeInsetsGeometry getPadding({
double? all,
double? left,
double? top,
double? right,
double? bottom,
}) {
return getMarginOrPadding(
all: all,
left: left,
top: top,
right: right,
bottom: bottom,
);
}
///This method is used to set margin responsively
EdgeInsetsGeometry getMargin({
double? all,
double? left,
double? top,
double? right,
double? bottom,
}) {
return getMarginOrPadding(
all: all,
left: left,
top: top,
right: right,
bottom: bottom,
);
}
///This method is used to get padding or margin responsively
EdgeInsetsGeometry getMarginOrPadding({
double? all,
double? left,
double? top,
double? right,
double? bottom,
}) {
if (all != null) {
left = all;
top = all;
right = all;
bottom = all;
}
return EdgeInsets.only(
left: getHorizontalSize(
left ?? 0,
),
top: getVerticalSize(
top ?? 0,
),
right: getHorizontalSize(
right ?? 0,
),
bottom: getVerticalSize(
bottom ?? 0,
),
);
}

View File

@@ -0,0 +1,41 @@
import 'package:base_project/resources/app_colors.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
class ToastMessageUtil {
static void showToast({
required String message,
ToastType toastType = ToastType.info,
ToastGravity gravity = ToastGravity.TOP,
int durationInSeconds = 2,
}) {
Fluttertoast.showToast(
msg: message,
toastLength: Toast.LENGTH_SHORT,
gravity: gravity,
timeInSecForIosWeb: durationInSeconds,
backgroundColor: _getBackgroundColor(toastType),
textColor: Colors.white,
fontSize: 16.0,
);
}
static Color? _getBackgroundColor(ToastType toastType) {
switch (toastType) {
case ToastType.success:
return AppColors.success;
case ToastType.error:
return AppColors.error;
case ToastType.warning:
return AppColors.warning;
case ToastType.info:
return AppColors.info;
case ToastType.general:
return Colors.grey[900];
default:
return AppColors.info;
}
}
}
enum ToastType { success, error, warning, info, general }

View File

@@ -0,0 +1,54 @@
class TextFieldValidator {
// Email validation using RegExp for strict validation
static String? validateEmail(String? value) {
if (value == null || value.isEmpty) {
return 'Email is required';
}
// Strict email pattern
String pattern =
r"^[a-zA-Z0-9]+([._-]?[a-zA-Z0-9]+)*@[a-zA-Z0-9]+([._-]?[a-zA-Z0-9]+)*\.[a-zA-Z]{2,7}$";
RegExp regex = RegExp(pattern);
if (!regex.hasMatch(value)) {
return 'Enter a valid email address';
}
return null;
}
// Simple field validation (non-empty check)
static String? validateField(String? value) {
if (value == null || value.isEmpty) {
return 'This field is required';
}
return null;
}
// Password validation (simple length check, can be expanded for complexity)
static String? validatePassword(String? value) {
if (value == null || value.isEmpty) {
return 'Password is required';
}
// Minimum 8 characters check
// if (value.length < 8) {
// return 'Password must be at least 8 characters long';
// }
return null;
}
// Confirm password validation
static String? validateConfirmPassword(String? value, String? password) {
if (value == null || value.isEmpty) {
return 'Confirm password is required';
}
if (value != password) {
return 'Passwords do not match';
}
return null;
}
}

View File

@@ -0,0 +1,277 @@
import 'package:base_project/commans/widgets/custom_textform_field.dart';
import 'package:base_project/commans/widgets/custome_elevated_button.dart';
import 'package:base_project/core/constants/ui_constants.dart';
import 'package:base_project/core/providers/dynamic_theme_provider.dart';
import 'package:base_project/utils/validator/text_feild_validator.dart';
import 'package:base_project/view/auth/sign_up_view.dart';
import 'package:base_project/view_model/auth/auth_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:iconsax/iconsax.dart';
import 'package:provider/provider.dart';
class AdminRegView extends StatefulWidget {
AdminRegView({super.key});
@override
State<AdminRegView> createState() => _AdminRegViewState();
}
class _AdminRegViewState extends State<AdminRegView>
with TickerProviderStateMixin {
// Controllers and FormKey declarations
final TextEditingController _companyNameController = TextEditingController();
final TextEditingController _adminNameController = TextEditingController();
final TextEditingController _emailController = TextEditingController();
final TextEditingController _phoneController = TextEditingController();
final TextEditingController _workspaceController = TextEditingController();
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
late AnimationController _pageController;
late AnimationController _cardController;
late Animation<double> _fade;
late Animation<Offset> _slide;
@override
void initState() {
super.initState();
_pageController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 900));
_cardController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 700));
_fade = CurvedAnimation(parent: _pageController, curve: Curves.easeInOut);
_slide = Tween<Offset>(begin: const Offset(0, 0.15), end: Offset.zero)
.animate(CurvedAnimation(
parent: _pageController, curve: Curves.easeOutCubic));
_pageController.forward();
Future.delayed(
const Duration(milliseconds: 250), () => _cardController.forward());
}
@override
void dispose() {
_companyNameController.dispose();
_adminNameController.dispose();
_emailController.dispose();
_phoneController.dispose();
_workspaceController.dispose();
_pageController.dispose();
_cardController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
return Consumer<DynamicThemeProvider>(
builder: (context, theme, child) {
final colorScheme = theme.getCurrentColorScheme(
Theme.of(context).brightness == Brightness.dark,
);
return Scaffold(
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.transparent,
leading: IconButton(
onPressed: () => Navigator.pop(context),
icon: const Icon(Icons.arrow_back_ios_new),
color: Colors.white,
),
centerTitle: true,
),
body: Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
colorScheme.primary,
colorScheme.primary.withOpacity(0.85),
colorScheme.secondary.withOpacity(0.65),
],
stops: const [0.0, 0.6, 1.0],
),
),
child: Center(
child: FadeTransition(
opacity: _fade,
child: SlideTransition(
position: _slide,
child: SingleChildScrollView(
padding: UIConstants.getResponsivePadding(
context,
mobile: const EdgeInsets.all(UIConstants.spacing16),
tablet: const EdgeInsets.all(UIConstants.spacing24),
desktop: const EdgeInsets.all(UIConstants.spacing32),
),
child: Container(
width: UIConstants.getResponsiveValue(
context,
mobile: double.infinity,
tablet: 520,
desktop: 640,
),
padding: UIConstants.cardPaddingLarge,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.96),
borderRadius:
BorderRadius.circular(UIConstants.radius24),
border: Border.all(
color: Colors.white.withOpacity(0.3), width: 1),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.15),
blurRadius: 28,
offset: const Offset(0, 16),
spreadRadius: 8,
),
],
),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 72,
height: 72,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: colorScheme.primary.withOpacity(0.1),
border: Border.all(
color: Colors.white.withOpacity(0.4),
width: 1),
),
child: Icon(Icons.business_center_outlined,
size: 36, color: colorScheme.primary),
),
],
),
const SizedBox(height: UIConstants.spacing16),
Text(
"Register Your Company",
textAlign: TextAlign.center,
style: textTheme.headlineMedium?.copyWith(
color: colorScheme.primary,
fontWeight: FontWeight.w800,
letterSpacing: 0.2,
),
),
const SizedBox(height: UIConstants.spacing8),
Text(
"Create your workspace and admin account",
textAlign: TextAlign.center,
style: textTheme.bodyMedium?.copyWith(
color: Colors.black.withOpacity(0.65),
),
),
const SizedBox(height: UIConstants.spacing24),
// Company Name
MyCustomTextFormField(
controller: _companyNameController,
label: "Company Name",
prefixIcon: const Icon(Icons.business),
validator: TextFieldValidator.validateField,
),
const SizedBox(height: UIConstants.spacing16),
// Admin Full Name
MyCustomTextFormField(
controller: _adminNameController,
label: "Admin Name",
prefixIcon: const Icon(Iconsax.profile_circle),
validator: TextFieldValidator.validateField,
),
const SizedBox(height: UIConstants.spacing16),
// Company Email
MyCustomTextFormField(
controller: _emailController,
label: "Company Email",
prefixIcon: const Icon(Icons.alternate_email),
validator: TextFieldValidator.validateEmail,
keyboardType: TextInputType.emailAddress,
),
const SizedBox(height: UIConstants.spacing16),
// Phone Number
MyCustomTextFormField(
controller: _phoneController,
label: "Admin Phone Number",
prefixIcon: const Icon(Icons.phone_outlined),
validator: TextFieldValidator.validateField,
keyboardType: TextInputType.phone,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
],
),
const SizedBox(height: UIConstants.spacing16),
// Workspace/Branch Name
MyCustomTextFormField(
controller: _workspaceController,
label: "Workspace/Branch Name",
prefixIcon: const Icon(Icons.location_city),
validator: TextFieldValidator.validateField,
),
const SizedBox(height: UIConstants.spacing24),
// Register Button
Consumer<AuthViewModel>(
builder: (context, provider, child) {
return MyCustomElevatedButton(
isLoading: provider.isLoading,
child: const Text("Register Company"),
onPressed: () async {
if (_formKey.currentState!.validate()) {
final Map<String, dynamic> signUpData = {
"companyName":
_companyNameController.text.trim(),
"email": _emailController.text.trim(),
"mobile": _phoneController.text.trim(),
"workspace":
_workspaceController.text.trim(),
};
await Provider.of<AuthViewModel>(context,
listen: false)
.createAcc(context, signUpData);
if (mounted) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SignUpView(
email:
_emailController.text.trim(),
),
),
);
}
}
},
);
},
),
],
),
),
),
),
),
),
),
),
);
},
);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
import 'package:flutter/material.dart';
class RegisterAccView extends StatelessWidget {
const RegisterAccView
({super.key});
@override
Widget build(BuildContext context) {
return const Scaffold();
}
}

View File

@@ -0,0 +1,355 @@
import 'package:base_project/core/constants/ui_constants.dart';
import 'package:base_project/core/providers/dynamic_theme_provider.dart';
import 'package:base_project/routes/route_names.dart';
import 'package:base_project/shared/widgets/app_bar/modern_app_bar.dart';
import 'package:base_project/shared/widgets/buttons/modern_button.dart';
import 'package:base_project/shared/widgets/inputs/modern_text_field.dart';
import 'package:base_project/utils/validator/text_feild_validator.dart';
import 'package:base_project/view_model/auth/auth_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:iconsax/iconsax.dart';
import 'package:provider/provider.dart';
class SignUpView extends StatefulWidget {
final String email;
SignUpView({super.key, required this.email});
@override
State<SignUpView> createState() => _SignUpViewState();
}
class _SignUpViewState extends State<SignUpView> with TickerProviderStateMixin {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final TextEditingController _firstNameController = TextEditingController();
final TextEditingController _lastNameController = TextEditingController();
final TextEditingController _phoneController = TextEditingController();
final TextEditingController _dobController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
final TextEditingController _confirmPasswordController =
TextEditingController();
final ValueNotifier<bool> _obscurePassword = ValueNotifier<bool>(true);
final ValueNotifier<bool> _obscureConfirm = ValueNotifier<bool>(true);
late AnimationController _animationController;
late Animation<double> _fadeAnimation;
late Animation<Offset> _slideAnimation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: UIConstants.durationSlow,
vsync: this,
);
_fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: _animationController, curve: UIConstants.curveNormal),
);
_slideAnimation =
Tween<Offset>(begin: const Offset(0, 0.2), end: Offset.zero).animate(
CurvedAnimation(
parent: _animationController, curve: UIConstants.curveNormal));
WidgetsBinding.instance
.addPostFrameCallback((_) => _animationController.forward());
}
@override
void dispose() {
_firstNameController.dispose();
_lastNameController.dispose();
_phoneController.dispose();
_dobController.dispose();
_passwordController.dispose();
_confirmPasswordController.dispose();
_obscurePassword.dispose();
_obscureConfirm.dispose();
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final isIOS = Theme.of(context).platform == TargetPlatform.iOS;
final textTheme = Theme.of(context).textTheme;
final size = MediaQuery.sizeOf(context);
return Consumer<DynamicThemeProvider>(
builder: (context, dynamicThemeProvider, child) {
final colorScheme = dynamicThemeProvider.getCurrentColorScheme(
Theme.of(context).brightness == Brightness.dark,
);
return Scaffold(
appBar: ModernAppBar(
title: 'Sign Up',
automaticallyImplyLeading: true,
leading: IconButton(
icon: Icon(
isIOS ? Icons.arrow_back_ios_new_rounded : Icons.arrow_back),
onPressed: () {
Navigator.pushNamedAndRemoveUntil(
context,
RouteNames.loginView,
(Route<dynamic> route) => false,
);
},
),
showThemeToggle: false,
showUserProfile: false,
),
body: AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return FadeTransition(
opacity: _fadeAnimation,
child: SlideTransition(
position: _slideAnimation,
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
colorScheme.primary.withOpacity(0.1),
colorScheme.secondary.withOpacity(0.05),
colorScheme.surface,
],
stops: const [0.0, 0.3, 1.0],
),
),
child: SingleChildScrollView(
padding: UIConstants.getResponsivePadding(
context,
mobile: UIConstants.screenPaddingMedium,
tablet: UIConstants.screenPaddingLarge,
desktop: UIConstants.screenPaddingLarge,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildHeaderCard(context, textTheme, colorScheme),
SizedBox(
height: UIConstants.getResponsiveSpacing(
context,
mobile: UIConstants.spacing32,
tablet: UIConstants.spacing40,
desktop: UIConstants.spacing48,
)),
_buildFormCard(context, textTheme, colorScheme, size),
],
),
),
),
),
);
},
),
);
},
);
}
Widget _buildHeaderCard(
BuildContext context, TextTheme textTheme, ColorScheme colorScheme) {
return Container(
padding: UIConstants.getResponsivePadding(
context,
mobile: UIConstants.cardPaddingLarge,
tablet: UIConstants.cardPaddingLarge,
desktop: UIConstants.cardPaddingLarge,
),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.95),
borderRadius: BorderRadius.circular(UIConstants.radius24),
border:
Border.all(color: colorScheme.surface.withOpacity(0.2), width: 1),
boxShadow: [
BoxShadow(
color: colorScheme.primary.withOpacity(0.1),
blurRadius: 20,
offset: const Offset(0, 10),
spreadRadius: 5,
),
],
),
child: Column(
children: [
Text(
"Create an Account!",
textAlign: TextAlign.center,
style: textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.w700,
color: colorScheme.primary,
),
),
const SizedBox(height: UIConstants.spacing8),
Text(
"Join us today, it's quick and easy.",
style: textTheme.bodyMedium
?.copyWith(color: colorScheme.onSurfaceVariant),
textAlign: TextAlign.center,
),
],
),
);
}
Widget _buildFormCard(BuildContext context, TextTheme textTheme,
ColorScheme colorScheme, Size size) {
return Form(
key: _formKey,
child: Container(
padding: UIConstants.getResponsivePadding(
context,
mobile: UIConstants.cardPaddingLarge,
tablet: UIConstants.cardPaddingLarge,
desktop: UIConstants.cardPaddingLarge,
),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.95),
borderRadius: BorderRadius.circular(UIConstants.radius24),
border:
Border.all(color: colorScheme.surface.withOpacity(0.2), width: 1),
boxShadow: [
BoxShadow(
color: colorScheme.primary.withOpacity(0.1),
blurRadius: 20,
offset: const Offset(0, 10),
spreadRadius: 5,
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ModernTextField(
label: 'First Name',
hint: 'Enter your first name',
controller: _firstNameController,
prefixIcon: const Icon(Icons.person),
validator: TextFieldValidator.validateField,
),
SizedBox(height: UIConstants.spacing20),
ModernTextField(
label: 'Last Name',
hint: 'Enter your last name',
controller: _lastNameController,
prefixIcon: const Icon(Iconsax.profile_circle),
validator: TextFieldValidator.validateField,
),
SizedBox(height: UIConstants.spacing20),
ModernTextField(
label: 'Phone Number',
hint: 'Enter your phone number',
controller: _phoneController,
prefixIcon: const Icon(Icons.phone_outlined),
validator: TextFieldValidator.validateField,
keyboardType: const TextInputType.numberWithOptions(),
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
),
SizedBox(height: UIConstants.spacing20),
GestureDetector(
onTap: () async {
DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(1900),
lastDate: DateTime.now(),
);
pickedDate ??= DateTime.now();
_dobController.text =
"${pickedDate.day}/${pickedDate.month}/${pickedDate.year}";
},
child: AbsorbPointer(
child: ModernTextField(
label: 'Date of Birth',
hint: 'DD/MM/YYYY',
controller: _dobController,
prefixIcon: const Icon(Icons.cake_outlined),
validator: TextFieldValidator.validateField,
),
),
),
SizedBox(height: UIConstants.spacing20),
ValueListenableBuilder<bool>(
valueListenable: _obscurePassword,
builder: (context, value, _) {
return ModernTextField(
label: 'Password',
hint: 'Enter a strong password',
controller: _passwordController,
prefixIcon: const Icon(Icons.lock_outline),
obscureText: value,
validator: TextFieldValidator.validatePassword,
suffixIcon: IconButton(
icon: Icon(value ? Icons.visibility_off : Icons.visibility),
onPressed: () => _obscurePassword.value = !value,
),
);
},
),
SizedBox(height: UIConstants.spacing20),
ValueListenableBuilder<bool>(
valueListenable: _obscureConfirm,
builder: (context, value, _) {
return ModernTextField(
label: 'Confirm Password',
hint: 'Re-enter your password',
controller: _confirmPasswordController,
prefixIcon: const Icon(Icons.lock_outline),
obscureText: value,
validator: (val) {
if (val != _passwordController.text) {
return 'Passwords do not match';
}
return null;
},
suffixIcon: IconButton(
icon: Icon(value ? Icons.visibility_off : Icons.visibility),
onPressed: () => _obscureConfirm.value = !value,
),
);
},
),
SizedBox(
height: UIConstants.getResponsiveSpacing(
context,
mobile: UIConstants.spacing32,
tablet: UIConstants.spacing40,
desktop: UIConstants.spacing48,
)),
Consumer<AuthViewModel>(
builder: (context, provider, child) {
return ModernButton(
text: 'Sign Up',
type: ModernButtonType.primary,
size: ModernButtonSize.large,
isLoading: provider.isLoading,
icon: const Icon(Icons.person_add_alt_1),
onPressed: () {
if (_formKey.currentState!.validate()) {
final Map<String, dynamic> signUpData = {
"first_name": _firstNameController.text,
"last_name": _lastNameController.text,
"email": widget.email,
"mob_no": _phoneController.text,
"date_of_birth": _dobController.text,
"new_password": _passwordController.text,
"confirm_password": _confirmPasswordController.text,
"usrGrpId": 1,
};
// provider.signUp(context, signUpData);
// Use the new combined registration method
provider.signUpWithAccountCreation(context, signUpData);
}
},
);
},
),
],
),
),
);
}
}

View File

@@ -0,0 +1,21 @@
import 'package:flutter/material.dart';
class SignupView extends StatelessWidget {
const SignupView
({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
onPressed: () {
Navigator.pop(context);
},
icon: Icon(Icons.adaptive.arrow_back),
),
title: const Text("SignUp"),
),
);
}
}

View File

@@ -0,0 +1,331 @@
import 'package:base_project/utils/toast_messages/toast_message_util.dart';
import 'package:base_project/view/auth/admin_reg_view.dart';
import 'package:base_project/view_model/auth/auth_view_model.dart';
import 'package:flutter/material.dart';
import 'package:pinput/pinput.dart';
import 'package:base_project/commans/widgets/custome_elevated_button.dart';
import 'package:provider/provider.dart';
import 'package:base_project/core/constants/ui_constants.dart';
import 'package:base_project/core/providers/dynamic_theme_provider.dart';
import 'sign_up_view.dart';
class VerifyOtpView extends StatefulWidget {
final String? email;
const VerifyOtpView({super.key, this.email});
@override
_VerifyOtpViewState createState() => _VerifyOtpViewState();
}
class _VerifyOtpViewState extends State<VerifyOtpView>
with TickerProviderStateMixin {
final TextEditingController _otpController = TextEditingController();
late AnimationController _pageController;
late AnimationController _formController;
late Animation<double> _fade;
late Animation<Offset> _slide;
@override
void initState() {
super.initState();
_pageController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 800));
_formController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 600));
_fade = CurvedAnimation(parent: _pageController, curve: Curves.easeInOut);
_slide = Tween<Offset>(begin: const Offset(0, 0.2), end: Offset.zero)
.animate(
CurvedAnimation(parent: _pageController, curve: Curves.easeOut));
_pageController.forward();
Future.delayed(
const Duration(milliseconds: 250), () => _formController.forward());
}
@override
void dispose() {
_otpController.dispose();
_pageController.dispose();
_formController.dispose();
super.dispose();
}
void _handleVerifyButtonPress() {
// Get the OTP value from the controller
final otp = _otpController.text;
// Validate OTP value
if (otp.isEmpty || otp.length < 6) {
// Handle invalid OTP
print("Invalid OTP: OTP must be 6 digits.");
ToastMessageUtil.showToast(
message: "Invalid OTP: OTP must be 6 digits.",
toastType: ToastType.error);
} else {}
}
@override
Widget build(BuildContext context) {
return Consumer<DynamicThemeProvider>(
builder: (context, dynamicThemeProvider, child) {
final colorScheme = dynamicThemeProvider.getCurrentColorScheme(
Theme.of(context).brightness == Brightness.dark,
);
return Scaffold(
appBar: AppBar(
backgroundColor: colorScheme.surface,
elevation: 0,
leading: IconButton(
onPressed: () => Navigator.pop(context),
icon: const Icon(Icons.arrow_back_ios_new),
color: colorScheme.primary,
),
title: Text(
'Verify OTP',
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: colorScheme.primary,
fontWeight: FontWeight.w700,
),
),
centerTitle: true,
),
body: Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
colorScheme.primary,
colorScheme.primary.withOpacity(0.85),
colorScheme.secondary.withOpacity(0.65),
],
stops: const [0.0, 0.6, 1.0],
),
),
child: Center(
child: FadeTransition(
opacity: _fade,
child: SlideTransition(
position: _slide,
child: Container(
width: UIConstants.getResponsiveValue(
context,
mobile: double.infinity,
tablet: 450,
desktop: 500,
),
margin: const EdgeInsets.all(UIConstants.spacing16),
padding: UIConstants.cardPaddingLarge,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.95),
borderRadius: BorderRadius.circular(UIConstants.radius24),
border: Border.all(
color: Colors.white.withOpacity(0.3),
width: 1,
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.15),
blurRadius: 25,
offset: const Offset(0, 15),
spreadRadius: 8,
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(
Icons.verified,
color: colorScheme.primary,
size: UIConstants.iconSizeXLarge,
),
const SizedBox(height: UIConstants.spacing16),
Text(
'Enter the 6-digit code sent to',
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(
color: colorScheme.onSurface.withOpacity(0.8),
),
textAlign: TextAlign.center,
),
Text(
widget.email ?? 'your email',
style:
Theme.of(context).textTheme.titleMedium?.copyWith(
color: colorScheme.primary,
fontWeight: FontWeight.w700,
),
textAlign: TextAlign.center,
),
const SizedBox(height: UIConstants.spacing24),
// Pinput with theming
Pinput(
length: 6,
controller: _otpController,
pinAnimationType: PinAnimationType.fade,
defaultPinTheme: PinTheme(
width: 54,
height: 60,
textStyle: TextStyle(
fontSize: 20,
color: colorScheme.primary,
fontWeight: FontWeight.w700,
),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(
color: colorScheme.primary.withOpacity(0.25)),
borderRadius:
BorderRadius.circular(UIConstants.radius12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 6),
),
],
),
),
focusedPinTheme: PinTheme(
width: 56,
height: 62,
textStyle: TextStyle(
fontSize: 20,
color: colorScheme.primary,
fontWeight: FontWeight.w700,
),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(
color: colorScheme.primary, width: 2),
borderRadius:
BorderRadius.circular(UIConstants.radius12),
boxShadow: [
BoxShadow(
color: colorScheme.primary.withOpacity(0.15),
blurRadius: 14,
offset: const Offset(0, 8),
),
],
),
),
onCompleted: (_) => _handleVerifyButtonPress(),
),
const SizedBox(height: UIConstants.spacing24),
// Verify button
Consumer<AuthViewModel>(
builder: (context, provider, child) {
return Column(
children: [
MyCustomElevatedButton(
isLoading: provider.isLoading,
onPressed: () async {
if (_otpController.text.length < 6) {
ToastMessageUtil.showToast(
message: 'Enter 6-digit OTP',
toastType: ToastType.error,
);
return;
}
final data = {
'email': widget.email,
'otp': _otpController.text,
};
final error =
await provider.verifyOtp(context, data);
if (error == null && mounted) {
Navigator.push(
context,
// MaterialPageRoute(
// builder: (context) =>
// AdminRegView()),
MaterialPageRoute(
builder: (context) => SignUpView(
email: widget.email ?? '',
),
),
);
} else if (error != null) {
// Inline error under the input
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(
SnackBar(
content: Text(error),
backgroundColor: Colors.red,
),
);
}
},
child: const Text('Verify OTP'),
),
],
);
},
),
const SizedBox(height: UIConstants.spacing16),
// Resend timer / action
Consumer<AuthViewModel>(
builder: (context, provider, child) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
provider.isResendEnabled
? "Didn't receive code?"
: 'Resend in ${provider.start}s',
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(
color: colorScheme.onSurface
.withOpacity(0.7),
),
),
const SizedBox(width: 8),
TextButton(
onPressed: provider.isResendEnabled
? () {
provider.resendOtp(
context, {'email': widget.email});
}
: null,
child: Text(
'Resend',
style: TextStyle(
color: provider.isResendEnabled
? colorScheme.primary
: Colors.grey,
fontWeight: FontWeight.w700,
),
),
),
],
);
},
),
],
),
),
),
),
),
),
);
},
);
}
}

View File

@@ -0,0 +1,626 @@
import 'package:base_project/core/constants/ui_constants.dart';
import 'package:base_project/core/theme/color_scheme.dart';
import 'package:base_project/routes/route_names.dart';
import 'package:base_project/shared/widgets/app_bar/modern_app_bar.dart';
import 'package:base_project/shared/widgets/cards/dashboard_card.dart';
import 'package:base_project/shared/widgets/buttons/quick_action_button.dart';
import 'package:base_project/shared/widgets/navigation/modern_drawer.dart';
import 'package:base_project/utils/managers/user_manager.dart';
import 'package:provider/provider.dart';
import 'package:base_project/view_model/system_params/system_params_view_model.dart';
import 'package:flutter/material.dart';
import '../../Entity/angulardatatype/Basicp1/Basicp1View/Basicp1_entity_list_screen.dart';
import '../../Entity/angulardatatype/Basicp1/Basicp1_viewModel/Basicp1_view_model_screen.dart';
class HomeView extends StatefulWidget {
const HomeView({super.key});
@override
State<HomeView> createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView> with TickerProviderStateMixin {
// Animation Controllers
late AnimationController _pageAnimationController;
late AnimationController _cardsAnimationController;
late AnimationController _actionsAnimationController;
// Animations
late Animation<double> _pageFadeAnimation;
late Animation<Offset> _pageSlideAnimation;
late Animation<double> _cardsScaleAnimation;
late Animation<double> _actionsScaleAnimation;
// Dashboard stats (replace with real API data)
List<Map<String, dynamic>> _dashboardStats = [];
final List<Map<String, dynamic>> _quickActions = [
{
'label': 'Profile',
'icon': Icons.person,
'onTap': () {},
'backgroundColor': null, // Use theme default
},
{
'label': 'Settings',
'icon': Icons.settings,
'onTap': () {},
'backgroundColor': null,
},
{
'label': 'Security',
'icon': Icons.security,
'onTap': () {},
'backgroundColor': null,
},
{
'label': 'Reports',
'icon': Icons.analytics,
'onTap': () {},
'backgroundColor': null,
},
];
final List<DrawerItem> _drawerItems = [
DrawerItem(
icon: Icons.person,
title: 'Profile',
subtitle: 'Manage your account',
onTap: (context) {
Navigator.pushNamed(context, RouteNames.profileView);
},
),
DrawerItem(
icon: Icons.system_security_update,
title: 'System Parameters',
subtitle: 'Configure system settings',
onTap: (context) {
Navigator.pushNamed(context, RouteNames.systemParamsView);
},
),
DrawerItem(
icon: Icons.password,
title: 'Change Password',
subtitle: 'Update your password',
onTap: (context) {
Navigator.pushNamed(context, RouteNames.changePasswordView);
},
),
// NEW ITEMS
DrawerItem(
icon: Icons.data_object,
title: 'Basicp1 Management',
subtitle: 'Manage Basicp1 entities',
onTap: (context) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChangeNotifierProvider(
create: (context) => Basicp1ViewModelScreen(),
child: const Basicp1EntityListScreen(),
),
),
);
},
),
];
@override
void initState() {
super.initState();
_initializeAnimations();
_startPageAnimation();
_loadDashboardData();
}
void _initializeAnimations() {
// Page Animation Controller
_pageAnimationController = AnimationController(
duration: UIConstants.durationSlow,
vsync: this,
);
// Cards Animation Controller
_cardsAnimationController = AnimationController(
duration: UIConstants.durationNormal,
vsync: this,
);
// Actions Animation Controller
_actionsAnimationController = AnimationController(
duration: UIConstants.durationNormal,
vsync: this,
);
// Page Animations
_pageFadeAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _pageAnimationController,
curve: UIConstants.curveNormal,
));
_pageSlideAnimation = Tween<Offset>(
begin: const Offset(0, 0.3),
end: Offset.zero,
).animate(CurvedAnimation(
parent: _pageAnimationController,
curve: UIConstants.curveNormal,
));
// Cards Animations
_cardsScaleAnimation = Tween<double>(
begin: 0.8,
end: 1.0,
).animate(CurvedAnimation(
parent: _cardsAnimationController,
curve: UIConstants.curveElastic,
));
// Actions Animations
_actionsScaleAnimation = Tween<double>(
begin: 0.8,
end: 1.0,
).animate(CurvedAnimation(
parent: _actionsAnimationController,
curve: UIConstants.curveElastic,
));
}
void _startPageAnimation() async {
await Future.delayed(const Duration(milliseconds: 300));
_pageAnimationController.forward();
await Future.delayed(const Duration(milliseconds: 400));
_cardsAnimationController.forward();
await Future.delayed(const Duration(milliseconds: 300));
_actionsAnimationController.forward();
}
Future<void> _loadDashboardData() async {
// TODO: replace with actual API calls, e.g., via a DashboardViewModel
// Simulate network delay
await Future.delayed(const Duration(milliseconds: 400));
setState(() {
_dashboardStats = [
{
'title': 'Total Users',
'subtitle': 'Active accounts',
'numericValue': 1234,
'valueSuffix': '',
'icon': Icons.people,
'type': DashboardCardType.primary,
'onTap': () {},
},
{
'title': 'System Uptime',
'subtitle': 'All systems operational',
'numericValue': 99.9,
'valueSuffix': '%',
'icon': Icons.check_circle,
'type': DashboardCardType.success,
'onTap': () {},
},
{
'title': 'Security Alerts',
'subtitle': 'Last 24 hours',
'numericValue': 2,
'valueSuffix': '',
'icon': Icons.security,
'type': DashboardCardType.info,
'onTap': () {},
},
{
'title': 'Avg. Response Time',
'subtitle': 'System performance',
'numericValue': 2.3,
'valueSuffix': 's',
'icon': Icons.speed,
'type': DashboardCardType.secondary,
'onTap': () {},
},
];
});
}
@override
void dispose() {
_pageAnimationController.dispose();
_cardsAnimationController.dispose();
_actionsAnimationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final size = MediaQuery.of(context).size;
final userName = UserManager().userName;
return Scaffold(
backgroundColor: colorScheme.background,
appBar: ModernAppBar(
title: 'Dashboard',
centerTitle: false,
showLogoInTitle: true,
logoImage: _getDashboardLogoImage(colorScheme),
userAvatar: null,
userAvatarImage: _getDashboardLogoImage(colorScheme),
userName: userName,
onProfilePressed: () {
Navigator.pushNamed(context, RouteNames.profileView);
},
),
drawer: ModernDrawer(
items: _drawerItems,
),
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
colorScheme.background,
colorScheme.surfaceVariant.withOpacity(0.1),
colorScheme.primaryContainer.withOpacity(0.05),
],
),
),
child: SafeArea(
child: SingleChildScrollView(
padding: UIConstants.getResponsivePadding(
context,
mobile: const EdgeInsets.all(UIConstants.spacing24),
tablet: const EdgeInsets.all(UIConstants.spacing32),
desktop: const EdgeInsets.all(UIConstants.spacing40),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Welcome Section
_buildWelcomeSection(theme, colorScheme, userName),
SizedBox(
height: UIConstants.getResponsiveSpacing(
context,
mobile: UIConstants.spacing32,
tablet: UIConstants.spacing40,
desktop: UIConstants.spacing48,
)),
// Dashboard Stats
_buildDashboardStats(theme, colorScheme),
SizedBox(
height: UIConstants.getResponsiveSpacing(
context,
mobile: UIConstants.spacing40,
tablet: UIConstants.spacing56,
desktop: UIConstants.spacing72,
)),
// Quick Actions
_buildQuickActions(theme, colorScheme),
SizedBox(
height: UIConstants.getResponsiveSpacing(
context,
mobile: UIConstants.spacing40,
tablet: UIConstants.spacing56,
desktop: UIConstants.spacing72,
)),
// Recent Activity Section
_buildRecentActivity(theme, colorScheme),
],
),
),
),
),
);
}
ImageProvider<Object>? _getDashboardLogoImage(ColorScheme cs) {
// Prefer dynamic logo from system parameters if present
try {
final sysVm = Provider.of<SystemParamsViewModel>(context, listen: true);
if (sysVm.profileImageBytes != null &&
sysVm.profileImageBytes!.isNotEmpty) {
return MemoryImage(sysVm.profileImageBytes!);
}
} catch (_) {
// Provider not available – fall through to default asset
}
// Default asset logo
return const AssetImage('assets/images/image_not_found.png');
}
Widget _buildWelcomeSection(
ThemeData theme, ColorScheme colorScheme, String? userName) {
return AnimatedBuilder(
animation: _pageAnimationController,
builder: (context, child) {
return FadeTransition(
opacity: _pageFadeAnimation,
child: SlideTransition(
position: _pageSlideAnimation,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Welcome back, ${userName ?? 'User'}! 👋',
style: theme.textTheme.headlineMedium?.copyWith(
color: colorScheme.onBackground,
fontWeight: FontWeight.w700,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: UIConstants.spacing8),
Text(
'Here\'s what\'s happening with your system today',
style: theme.textTheme.bodyLarge?.copyWith(
color: colorScheme.onSurfaceVariant,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
);
},
);
}
// Removed in-body logo: AppBar now displays the logo.
Widget _buildDashboardStats(ThemeData theme, ColorScheme colorScheme) {
return AnimatedBuilder(
animation: _cardsAnimationController,
builder: (context, child) {
return Transform.scale(
scale: _cardsScaleAnimation.value,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'System Overview',
style: theme.textTheme.titleLarge?.copyWith(
color: colorScheme.onBackground,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: UIConstants.spacing24),
// Responsive Grid
LayoutBuilder(
builder: (context, constraints) {
final crossAxisCount = UIConstants.isMobile(context)
? 2
: UIConstants.isTablet(context)
? 3
: 4;
// Use fixed mainAxisExtent per breakpoint to avoid overflow
final double mainAxisExtent = UIConstants.isMobile(context)
? 130
: (UIConstants.isTablet(context) ? 160 : 180);
return GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
crossAxisSpacing: UIConstants.spacing16,
mainAxisSpacing: UIConstants.spacing16,
mainAxisExtent: mainAxisExtent,
),
itemCount: _dashboardStats.length,
itemBuilder: (context, index) {
final stat = _dashboardStats[index];
return DashboardCard(
title: stat['title'],
subtitle: stat['subtitle'],
value: stat['value'],
numericValue: stat['numericValue'],
valueSuffix: stat['valueSuffix'],
animationDuration: UIConstants.durationNormal,
icon: stat['icon'],
type: stat['type'],
onTap: stat['onTap'],
);
},
);
},
),
],
),
);
},
);
}
Widget _buildQuickActions(ThemeData theme, ColorScheme colorScheme) {
return AnimatedBuilder(
animation: _actionsAnimationController,
builder: (context, child) {
return Transform.scale(
scale: _actionsScaleAnimation.value,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Quick Actions',
style: theme.textTheme.titleLarge?.copyWith(
color: colorScheme.onBackground,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: UIConstants.spacing24),
// Responsive Grid
LayoutBuilder(
builder: (context, constraints) {
final crossAxisCount = UIConstants.isMobile(context)
? 2
: UIConstants.isTablet(context)
? 4
: 6;
return GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
crossAxisSpacing: UIConstants.spacing16,
mainAxisSpacing: UIConstants.spacing16,
childAspectRatio: 1.0,
),
itemCount: _quickActions.length,
itemBuilder: (context, index) {
final action = _quickActions[index];
return QuickActionButton(
label: action['label'],
icon: action['icon'],
onTap: action['onTap'],
backgroundColor: action['backgroundColor'],
size: UIConstants.logoSizeMedium,
);
},
);
},
),
],
),
);
},
);
}
Widget _buildRecentActivity(ThemeData theme, ColorScheme colorScheme) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Recent Activity',
style: theme.textTheme.titleLarge?.copyWith(
color: colorScheme.onBackground,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: UIConstants.spacing24),
Container(
padding: UIConstants.cardPaddingMedium,
decoration: BoxDecoration(
color: colorScheme.surface,
borderRadius: BorderRadius.circular(UIConstants.radius16),
boxShadow: [
BoxShadow(
color: colorScheme.shadow.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Column(
children: [
_buildActivityItem(
theme,
colorScheme,
Icons.security,
'Security scan completed',
'System security check finished successfully',
'2 minutes ago',
AppColorScheme.success,
),
const Divider(height: UIConstants.spacing24),
_buildActivityItem(
theme,
colorScheme,
Icons.update,
'System updated',
'Latest security patches installed',
'1 hour ago',
AppColorScheme.info,
),
const Divider(height: UIConstants.spacing24),
_buildActivityItem(
theme,
colorScheme,
Icons.backup,
'Backup completed',
'Daily backup process finished',
'3 hours ago',
colorScheme.primary,
),
],
),
),
],
);
}
Widget _buildActivityItem(
ThemeData theme,
ColorScheme colorScheme,
IconData icon,
String title,
String subtitle,
String time,
Color iconColor,
) {
return Row(
children: [
Container(
padding: const EdgeInsets.all(UIConstants.spacing8),
decoration: BoxDecoration(
color: iconColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(UIConstants.radius8),
),
child: Icon(
icon,
color: iconColor,
size: UIConstants.iconSizeMedium,
),
),
const SizedBox(width: UIConstants.spacing16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: theme.textTheme.titleMedium?.copyWith(
color: colorScheme.onSurface,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: UIConstants.spacing4),
Text(
subtitle,
style: theme.textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
],
),
),
Text(
time,
style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
],
);
}
}

View File

@@ -0,0 +1,263 @@
import 'package:base_project/core/constants/ui_constants.dart';
import 'package:base_project/core/providers/dynamic_theme_provider.dart';
import 'package:base_project/shared/widgets/app_bar/modern_app_bar.dart';
import 'package:base_project/shared/widgets/inputs/modern_text_field.dart';
import 'package:base_project/shared/widgets/buttons/modern_button.dart';
import 'package:base_project/view_model/profile/profile_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
class AccountUpdateView extends StatefulWidget {
const AccountUpdateView({super.key});
@override
State<AccountUpdateView> createState() => _AccountUpdateViewState();
}
class _AccountUpdateViewState extends State<AccountUpdateView>
with TickerProviderStateMixin {
final _formKey = GlobalKey<FormState>();
late TextEditingController _companyName;
late TextEditingController _workspace;
late TextEditingController _gstNumber;
late TextEditingController _mobile;
late TextEditingController _email; // read-only
late TextEditingController _pancard;
late TextEditingController _working;
bool _active = true;
late AnimationController _anim;
late Animation<double> _fade;
late Animation<Offset> _slide;
@override
void initState() {
super.initState();
_companyName = TextEditingController();
_workspace = TextEditingController();
_gstNumber = TextEditingController();
_mobile = TextEditingController();
_email = TextEditingController();
_pancard = TextEditingController();
_working = TextEditingController();
_anim =
AnimationController(duration: UIConstants.durationSlow, vsync: this);
_fade = CurvedAnimation(parent: _anim, curve: UIConstants.curveNormal);
_slide = Tween<Offset>(begin: const Offset(0, .2), end: Offset.zero)
.animate(
CurvedAnimation(parent: _anim, curve: UIConstants.curveNormal));
WidgetsBinding.instance.addPostFrameCallback((_) {
final vm = Provider.of<ProfileViewModel>(context, listen: false);
final acc = vm.sysAccount ?? {};
_companyName.text = (acc['companyName'] ?? '').toString();
_workspace.text = (acc['workspace'] ?? '').toString();
_gstNumber.text = (acc['gstNumber'] ?? '').toString();
_mobile.text = (acc['mobile'] ?? '').toString();
_email.text = (acc['email'] ?? '').toString();
_pancard.text = (acc['pancard'] ?? '').toString();
_working.text = (acc['working'] ?? '').toString();
_active = acc['active'] == true;
_anim.forward();
});
}
@override
void dispose() {
_companyName.dispose();
_workspace.dispose();
_gstNumber.dispose();
_mobile.dispose();
_email.dispose();
_pancard.dispose();
_working.dispose();
_anim.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Consumer<DynamicThemeProvider>(
builder: (context, theme, child) {
final colorScheme = theme.getCurrentColorScheme(
Theme.of(context).brightness == Brightness.dark,
);
return Scaffold(
appBar: ModernAppBar(
title: 'Account Update',
automaticallyImplyLeading: true,
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.pop(context),
),
showThemeToggle: false,
showUserProfile: false,
),
body: AnimatedBuilder(
animation: _anim,
builder: (context, _) {
return FadeTransition(
opacity: _fade,
child: SlideTransition(
position: _slide,
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
colorScheme.primary.withOpacity(0.1),
colorScheme.secondary.withOpacity(0.05),
colorScheme.surface,
],
stops: const [0.0, 0.3, 1.0],
),
),
child: SingleChildScrollView(
padding: UIConstants.getResponsivePadding(
context,
mobile: UIConstants.screenPaddingMedium,
tablet: UIConstants.screenPaddingLarge,
desktop: UIConstants.screenPaddingLarge,
),
child: _buildFormCard(colorScheme),
),
),
),
);
},
),
);
},
);
}
Widget _buildFormCard(ColorScheme colorScheme) {
return Consumer<ProfileViewModel>(
builder: (context, vm, _) {
return Form(
key: _formKey,
child: Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.95),
borderRadius: BorderRadius.circular(UIConstants.radius24),
border: Border.all(
color: colorScheme.surface.withOpacity(0.2), width: 1),
boxShadow: [
BoxShadow(
color: colorScheme.primary.withOpacity(0.1),
blurRadius: 20,
offset: const Offset(0, 10),
spreadRadius: 5,
),
],
),
padding: UIConstants.cardPaddingLarge,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ModernTextField(
label: 'Company Name',
hint: 'Enter company name',
controller: _companyName,
prefixIcon: const Icon(Icons.business),
validator: (v) =>
(v == null || v.isEmpty) ? 'Required' : null,
),
SizedBox(height: UIConstants.spacing20),
ModernTextField(
label: 'Workspace',
hint: 'Enter workspace',
controller: _workspace,
prefixIcon: const Icon(Icons.apartment_outlined),
validator: (v) =>
(v == null || v.isEmpty) ? 'Required' : null,
),
SizedBox(height: UIConstants.spacing20),
ModernTextField(
label: 'GST Number',
hint: 'Enter GST number',
controller: _gstNumber,
prefixIcon: const Icon(Icons.confirmation_number_outlined),
),
SizedBox(height: UIConstants.spacing20),
ModernTextField(
label: 'Mobile',
hint: 'Enter mobile number',
controller: _mobile,
keyboardType: TextInputType.phone,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
prefixIcon: const Icon(Icons.phone_outlined),
),
SizedBox(height: UIConstants.spacing20),
AbsorbPointer(
child: ModernTextField(
label: 'Email (read-only)',
hint: 'Email',
controller: _email,
prefixIcon: const Icon(Icons.email_outlined),
),
),
SizedBox(height: UIConstants.spacing20),
ModernTextField(
label: 'PAN Card',
hint: 'Enter PAN',
controller: _pancard,
prefixIcon: const Icon(Icons.badge_outlined),
),
SizedBox(height: UIConstants.spacing20),
ModernTextField(
label: 'Working',
hint: 'Working status',
controller: _working,
prefixIcon: const Icon(Icons.work_outline),
),
SizedBox(height: UIConstants.spacing20),
Row(
children: [
Switch(
value: _active,
onChanged: (v) => setState(() => _active = v),
),
const SizedBox(width: 8),
const Text('Active')
],
),
SizedBox(
height: UIConstants.getResponsiveSpacing(
context,
mobile: UIConstants.spacing32,
tablet: UIConstants.spacing40,
desktop: UIConstants.spacing48,
)),
ModernButton(
text: 'Update Account',
type: ModernButtonType.primary,
size: ModernButtonSize.large,
isLoading: vm.isUpdating,
icon: const Icon(Icons.save_outlined),
onPressed: () {
if (!_formKey.currentState!.validate()) return;
vm.updateAccount(context, {
'companyName': _companyName.text.trim(),
'workspace': _workspace.text.trim(),
'gstNumber': _gstNumber.text.trim(),
'mobile': _mobile.text.trim(),
'email': _email.text.trim(),
'pancard': _pancard.text.trim(),
'working': _working.text.trim(),
'active': _active,
});
},
),
],
),
),
);
},
);
}
}

View File

@@ -0,0 +1,401 @@
import 'package:base_project/commans/widgets/custom_textform_field.dart';
import 'package:base_project/commans/widgets/custome_elevated_button.dart';
import 'package:base_project/resources/app_colors.dart';
import 'package:base_project/utils/managers/user_manager.dart';
import 'package:base_project/utils/validator/text_feild_validator.dart';
import 'package:base_project/view_model/profile/profile_view_model.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:base_project/core/providers/dynamic_theme_provider.dart';
import 'package:base_project/core/constants/ui_constants.dart';
import 'package:base_project/shared/widgets/app_bar/modern_app_bar.dart';
import 'package:base_project/shared/widgets/inputs/modern_text_field.dart';
import 'package:base_project/shared/widgets/buttons/modern_button.dart';
class ChangePassword extends StatefulWidget {
const ChangePassword({super.key});
@override
State<ChangePassword> createState() => _ChangePasswordState();
}
class _ChangePasswordState extends State<ChangePassword>
with TickerProviderStateMixin {
final ValueNotifier<bool> _obscureTextCurrentPass = ValueNotifier<bool>(true);
final ValueNotifier<bool> _obscureTextNewPass = ValueNotifier<bool>(true);
final ValueNotifier<bool> _obscureTextConfirmPass = ValueNotifier<bool>(true);
final TextEditingController _currentPassController = TextEditingController();
final TextEditingController _newPassController = TextEditingController();
final TextEditingController _confirmPassController = TextEditingController();
// Global key for the form
final _formKey = GlobalKey<FormState>();
late AnimationController _animationController;
late Animation<double> _fadeAnimation;
late Animation<Offset> _slideAnimation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: UIConstants.durationSlow,
vsync: this,
);
_fadeAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _animationController,
curve: UIConstants.curveNormal,
));
_slideAnimation = Tween<Offset>(
begin: const Offset(0, 0.3),
end: Offset.zero,
).animate(CurvedAnimation(
parent: _animationController,
curve: UIConstants.curveNormal,
));
WidgetsBinding.instance.addPostFrameCallback((_) {
_animationController.forward();
});
}
@override
void dispose() {
_currentPassController.dispose();
_newPassController.dispose();
_confirmPassController.dispose();
_obscureTextCurrentPass.dispose();
_obscureTextNewPass.dispose();
_obscureTextConfirmPass.dispose();
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Consumer<DynamicThemeProvider>(
builder: (context, dynamicThemeProvider, child) {
final colorScheme = dynamicThemeProvider.getCurrentColorScheme(
Theme.of(context).brightness == Brightness.dark);
final textTheme = Theme.of(context).textTheme;
return Scaffold(
appBar: ModernAppBar(
title: 'Change Password',
automaticallyImplyLeading: true,
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () => Navigator.pop(context),
),
showThemeToggle: false,
showUserProfile: false,
),
body: AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return FadeTransition(
opacity: _fadeAnimation,
child: SlideTransition(
position: _slideAnimation,
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
colorScheme.primary.withOpacity(0.1),
colorScheme.secondary.withOpacity(0.05),
colorScheme.surface,
],
stops: const [0.0, 0.3, 1.0],
),
),
child: SingleChildScrollView(
padding: UIConstants.getResponsivePadding(
context,
mobile: UIConstants.screenPaddingMedium,
tablet: UIConstants.screenPaddingLarge,
desktop: UIConstants.screenPaddingLarge,
),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Security Header Section
_buildSecurityHeader(
context, colorScheme, textTheme),
SizedBox(
height: UIConstants.getResponsiveSpacing(
context,
mobile: UIConstants.spacing32,
tablet: UIConstants.spacing40,
desktop: UIConstants.spacing48,
)),
// Password Form Section
_buildPasswordForm(context, colorScheme, textTheme),
],
),
),
),
),
),
);
},
),
);
},
);
}
Widget _buildSecurityHeader(
BuildContext context, ColorScheme colorScheme, TextTheme textTheme) {
return Container(
padding: UIConstants.getResponsivePadding(
context,
mobile: UIConstants.cardPaddingLarge,
tablet: UIConstants.cardPaddingLarge,
desktop: UIConstants.cardPaddingLarge,
),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.95),
borderRadius: BorderRadius.circular(UIConstants.radius24),
border: Border.all(
color: colorScheme.surface.withOpacity(0.2),
width: 1,
),
boxShadow: [
BoxShadow(
color: colorScheme.primary.withOpacity(0.1),
blurRadius: 20,
offset: const Offset(0, 10),
spreadRadius: 5,
),
],
),
child: Column(
children: [
// Security Icon
Container(
padding: const EdgeInsets.all(UIConstants.spacing20),
decoration: BoxDecoration(
color: colorScheme.primary.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(
Icons.security,
size: UIConstants.getResponsiveValue(
context,
mobile: 40,
tablet: 48,
desktop: 56,
),
color: colorScheme.primary,
),
),
SizedBox(height: UIConstants.spacing24),
Text(
'Change Password',
style: textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.w700,
color: colorScheme.primary,
),
textAlign: TextAlign.center,
),
SizedBox(height: UIConstants.spacing12),
Text(
'Update your account security by changing your password. Make sure to use a strong password.',
style: textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant,
),
textAlign: TextAlign.center,
),
],
),
);
}
Widget _buildPasswordForm(
BuildContext context, ColorScheme colorScheme, TextTheme textTheme) {
return Container(
padding: UIConstants.getResponsivePadding(
context,
mobile: UIConstants.cardPaddingLarge,
tablet: UIConstants.cardPaddingLarge,
desktop: UIConstants.cardPaddingLarge,
),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.95),
borderRadius: BorderRadius.circular(UIConstants.radius24),
border: Border.all(
color: colorScheme.surface.withOpacity(0.2),
width: 1,
),
boxShadow: [
BoxShadow(
color: colorScheme.primary.withOpacity(0.1),
blurRadius: 20,
offset: const Offset(0, 10),
spreadRadius: 5,
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Password Details',
style: textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w600,
color: colorScheme.primary,
),
),
SizedBox(height: UIConstants.spacing24),
// Current Password Field
ValueListenableBuilder(
valueListenable: _obscureTextCurrentPass,
builder: (context, value, child) {
return ModernTextField(
label: 'Current Password',
hint: 'Enter your current password',
controller: _currentPassController,
prefixIcon: Icon(Icons.lock_outline),
suffixIcon: IconButton(
icon: Icon(
_obscureTextCurrentPass.value
? Icons.visibility_off
: Icons.visibility,
),
onPressed: () {
_obscureTextCurrentPass.value =
!_obscureTextCurrentPass.value;
},
),
obscureText: _obscureTextCurrentPass.value,
validator: TextFieldValidator.validatePassword,
);
},
),
SizedBox(
height: UIConstants.getResponsiveSpacing(
context,
mobile: UIConstants.spacing20,
tablet: UIConstants.spacing24,
desktop: UIConstants.spacing24,
)),
// New Password Field
ValueListenableBuilder(
valueListenable: _obscureTextNewPass,
builder: (context, value, child) {
return ModernTextField(
label: 'New Password',
hint: 'Enter your new password',
controller: _newPassController,
prefixIcon: Icon(Icons.lock_outline),
suffixIcon: IconButton(
icon: Icon(
_obscureTextNewPass.value
? Icons.visibility_off
: Icons.visibility,
),
onPressed: () {
_obscureTextNewPass.value = !_obscureTextNewPass.value;
},
),
obscureText: _obscureTextNewPass.value,
validator: TextFieldValidator.validatePassword,
);
},
),
SizedBox(
height: UIConstants.getResponsiveSpacing(
context,
mobile: UIConstants.spacing20,
tablet: UIConstants.spacing24,
desktop: UIConstants.spacing24,
)),
// Confirm Password Field
ValueListenableBuilder(
valueListenable: _obscureTextConfirmPass,
builder: (context, value, child) {
return ModernTextField(
label: 'Confirm New Password',
hint: 'Confirm your new password',
controller: _confirmPassController,
prefixIcon: Icon(Icons.lock_outline),
suffixIcon: IconButton(
icon: Icon(
_obscureTextConfirmPass.value
? Icons.visibility_off
: Icons.visibility,
),
onPressed: () {
_obscureTextConfirmPass.value =
!_obscureTextConfirmPass.value;
},
),
obscureText: _obscureTextConfirmPass.value,
validator: (value) {
return TextFieldValidator.validateConfirmPassword(
value, _newPassController.text);
},
);
},
),
SizedBox(
height: UIConstants.getResponsiveSpacing(
context,
mobile: UIConstants.spacing32,
tablet: UIConstants.spacing40,
desktop: UIConstants.spacing48,
)),
// Change Password Button
Consumer<ProfileViewModel>(
builder: (context, provider, _) {
return ModernButton(
text: 'Change Password',
type: ModernButtonType.primary,
size: ModernButtonSize.large,
isLoading: provider.isUpdating,
icon: Icon(Icons.security),
onPressed: () {
if (_formKey.currentState!.validate()) {
final uId = UserManager().userId;
final data = {
"userId": uId,
"oldPassword": _currentPassController.text,
"newPassword": _newPassController.text,
"confirmPassword": _confirmPassController.text,
};
provider.changePassword(data);
}
},
);
},
),
],
),
);
}
}

View File

@@ -0,0 +1,419 @@
import 'package:base_project/commans/widgets/custom_textform_field.dart';
import 'package:base_project/commans/widgets/custome_elevated_button.dart';
import 'package:base_project/model/user/user_profile.dart';
import 'package:base_project/resources/app_colors.dart';
import 'package:base_project/view_model/profile/profile_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/svg.dart';
import 'package:provider/provider.dart';
import 'package:base_project/core/providers/dynamic_theme_provider.dart';
import 'package:base_project/core/constants/ui_constants.dart';
import 'package:base_project/shared/widgets/app_bar/modern_app_bar.dart';
import 'package:base_project/shared/widgets/inputs/modern_text_field.dart';
import 'package:base_project/shared/widgets/buttons/modern_button.dart';
import '../../../utils/image_constant.dart';
class EditProfile extends StatefulWidget {
final UserProfile userProfile;
const EditProfile({super.key, required this.userProfile});
@override
State<EditProfile> createState() => _EditProfileState();
}
class _EditProfileState extends State<EditProfile>
with TickerProviderStateMixin {
late TextEditingController _emailController;
late TextEditingController _userNameController;
late TextEditingController _mobNoController;
late TextEditingController _fullNameController;
late AnimationController _animationController;
late Animation<double> _fadeAnimation;
late Animation<Offset> _slideAnimation;
@override
void initState() {
super.initState();
_emailController = TextEditingController(text: widget.userProfile.email);
_userNameController =
TextEditingController(text: widget.userProfile.username);
_mobNoController =
TextEditingController(text: widget.userProfile.mobNo.toString());
_fullNameController =
TextEditingController(text: widget.userProfile.fullName.toString());
_animationController = AnimationController(
duration: UIConstants.durationSlow,
vsync: this,
);
_fadeAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _animationController,
curve: UIConstants.curveNormal,
));
_slideAnimation = Tween<Offset>(
begin: const Offset(0, 0.3),
end: Offset.zero,
).animate(CurvedAnimation(
parent: _animationController,
curve: UIConstants.curveNormal,
));
WidgetsBinding.instance.addPostFrameCallback((_) {
_animationController.forward();
});
}
@override
void dispose() {
_emailController.dispose();
_userNameController.dispose();
_mobNoController.dispose();
_fullNameController.dispose();
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Consumer<DynamicThemeProvider>(
builder: (context, dynamicThemeProvider, child) {
final colorScheme = dynamicThemeProvider.getCurrentColorScheme(
Theme.of(context).brightness == Brightness.dark);
final textTheme = Theme.of(context).textTheme;
return Scaffold(
appBar: ModernAppBar(
title: 'Edit Profile',
automaticallyImplyLeading: true,
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () => Navigator.pop(context),
),
showThemeToggle: false,
showUserProfile: false,
),
body: AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return FadeTransition(
opacity: _fadeAnimation,
child: SlideTransition(
position: _slideAnimation,
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
colorScheme.primary.withOpacity(0.1),
colorScheme.secondary.withOpacity(0.05),
colorScheme.surface,
],
stops: const [0.0, 0.3, 1.0],
),
),
child: SingleChildScrollView(
padding: UIConstants.getResponsivePadding(
context,
mobile: UIConstants.screenPaddingMedium,
tablet: UIConstants.screenPaddingLarge,
desktop: UIConstants.screenPaddingLarge,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Profile Photo Section
_buildProfilePhotoSection(context, colorScheme),
SizedBox(
height: UIConstants.getResponsiveSpacing(
context,
mobile: UIConstants.spacing32,
tablet: UIConstants.spacing40,
desktop: UIConstants.spacing48,
)),
// Form Section
_buildFormSection(context, colorScheme, textTheme),
],
),
),
),
),
);
},
),
);
},
);
}
Widget _buildProfilePhotoSection(
BuildContext context, ColorScheme colorScheme) {
return Container(
padding: UIConstants.getResponsivePadding(
context,
mobile: UIConstants.cardPaddingLarge,
tablet: UIConstants.cardPaddingLarge,
desktop: UIConstants.cardPaddingLarge,
),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.95),
borderRadius: BorderRadius.circular(UIConstants.radius24),
border: Border.all(
color: colorScheme.surface.withOpacity(0.2),
width: 1,
),
boxShadow: [
BoxShadow(
color: colorScheme.primary.withOpacity(0.1),
blurRadius: 20,
offset: const Offset(0, 10),
spreadRadius: 5,
),
],
),
child: Column(
children: [
Text(
'Profile Picture',
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w600,
color: colorScheme.primary,
),
),
SizedBox(height: UIConstants.spacing24),
Consumer<ProfileViewModel>(
builder: (context, provider, _) {
return Center(
child: Stack(
alignment: Alignment.center,
children: [
Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: colorScheme.primary.withOpacity(0.3),
width: 3,
),
boxShadow: [
BoxShadow(
color: colorScheme.primary.withOpacity(0.2),
blurRadius: 20,
offset: const Offset(0, 10),
spreadRadius: 5,
),
],
),
child: CircleAvatar(
radius: UIConstants.getResponsiveValue(
context,
mobile: 60,
tablet: 70,
desktop: 80,
),
backgroundColor: colorScheme.primary.withOpacity(0.1),
backgroundImage: provider.profileImageBytes != null
? MemoryImage(provider.profileImageBytes!)
: null,
child: provider.profileImageBytes != null
? null
: Icon(
Icons.person,
size: UIConstants.getResponsiveValue(
context,
mobile: 50,
tablet: 60,
desktop: 70,
),
color: colorScheme.primary,
),
),
),
Positioned(
bottom: 0,
right: 0,
child: Container(
decoration: BoxDecoration(
color: colorScheme.primary,
shape: BoxShape.circle,
border: Border.all(
color: Colors.white,
width: 3,
),
boxShadow: [
BoxShadow(
color: colorScheme.primary.withOpacity(0.3),
blurRadius: 10,
offset: const Offset(0, 5),
spreadRadius: 2,
),
],
),
child: IconButton(
icon: const Icon(Icons.camera_alt),
color: Colors.white,
onPressed: () {
provider.pickImg(context);
},
),
),
),
],
),
);
},
),
],
),
);
}
Widget _buildFormSection(
BuildContext context, ColorScheme colorScheme, TextTheme textTheme) {
return Container(
padding: UIConstants.getResponsivePadding(
context,
mobile: UIConstants.cardPaddingLarge,
tablet: UIConstants.cardPaddingLarge,
desktop: UIConstants.cardPaddingLarge,
),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.95),
borderRadius: BorderRadius.circular(UIConstants.radius24),
border: Border.all(
color: colorScheme.surface.withOpacity(0.2),
width: 1,
),
boxShadow: [
BoxShadow(
color: colorScheme.primary.withOpacity(0.1),
blurRadius: 20,
offset: const Offset(0, 10),
spreadRadius: 5,
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Personal Information',
style: textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w600,
color: colorScheme.primary,
),
),
SizedBox(height: UIConstants.spacing24),
// Full Name Field
ModernTextField(
label: 'Full Name',
hint: 'Enter your full name',
controller: _fullNameController,
prefixIcon: Icon(Icons.person_outline),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Full name is required';
}
return null;
},
),
SizedBox(
height: UIConstants.getResponsiveSpacing(
context,
mobile: UIConstants.spacing20,
tablet: UIConstants.spacing24,
desktop: UIConstants.spacing24,
)),
// Email Field
ModernTextField(
label: 'Email Address',
hint: 'Enter your email',
controller: _emailController,
prefixIcon: Icon(Icons.email_outlined),
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Email is required';
}
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$')
.hasMatch(value)) {
return 'Please enter a valid email';
}
return null;
},
),
SizedBox(
height: UIConstants.getResponsiveSpacing(
context,
mobile: UIConstants.spacing20,
tablet: UIConstants.spacing24,
desktop: UIConstants.spacing24,
)),
// Phone Field
ModernTextField(
label: 'Phone Number',
hint: 'Enter your phone number',
controller: _mobNoController,
prefixIcon: Icon(Icons.phone_outlined),
keyboardType: TextInputType.phone,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(10),
],
validator: (value) {
if (value == null || value.isEmpty) {
return 'Phone number is required';
}
if (value.length != 10) {
return 'Phone number must be 10 digits';
}
return null;
},
),
SizedBox(
height: UIConstants.getResponsiveSpacing(
context,
mobile: UIConstants.spacing32,
tablet: UIConstants.spacing40,
desktop: UIConstants.spacing48,
)),
// Save Button
Consumer<ProfileViewModel>(
builder: (context, provider, _) {
return ModernButton(
text: 'Save Changes',
type: ModernButtonType.primary,
size: ModernButtonSize.large,
isLoading: provider.isUpdating,
icon: Icon(Icons.save_outlined),
onPressed: () {
final data = {
'email': _emailController.text,
'fullName': _fullNameController.text,
'mob_no': _mobNoController.text,
};
provider.updateProfile(context, data);
},
);
},
),
],
),
);
}
}

View File

@@ -0,0 +1,573 @@
import 'package:base_project/data/response/status.dart';
import 'package:base_project/resources/app_colors.dart';
import 'package:base_project/routes/route_names.dart';
import 'package:base_project/utils/image_constant.dart';
import 'package:base_project/utils/managers/user_manager.dart';
import 'package:base_project/view/dashboard/profile/edit_profile.dart';
import 'package:base_project/view_model/profile/profile_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
import 'package:base_project/core/providers/dynamic_theme_provider.dart';
import 'package:base_project/core/constants/ui_constants.dart';
import 'package:base_project/shared/widgets/app_bar/modern_app_bar.dart';
import 'account_update.dart';
class ProfileView extends StatefulWidget {
const ProfileView({super.key});
@override
State<ProfileView> createState() => _ProfileViewState();
}
class _ProfileViewState extends State<ProfileView>
with TickerProviderStateMixin {
late final ProfileViewModel provider;
late AnimationController _animationController;
late Animation<double> _fadeAnimation;
late Animation<Offset> _slideAnimation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: UIConstants.durationSlow,
vsync: this,
);
_fadeAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _animationController,
curve: UIConstants.curveNormal,
));
_slideAnimation = Tween<Offset>(
begin: const Offset(0, 0.3),
end: Offset.zero,
).animate(CurvedAnimation(
parent: _animationController,
curve: UIConstants.curveNormal,
));
WidgetsBinding.instance.addPostFrameCallback((_) {
provider = Provider.of<ProfileViewModel>(context, listen: false);
provider.getProfile();
provider.getProfileImg();
_animationController.forward();
});
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Consumer<DynamicThemeProvider>(
builder: (context, dynamicThemeProvider, child) {
final colorScheme = dynamicThemeProvider.getCurrentColorScheme(
Theme.of(context).brightness == Brightness.dark);
final textTheme = Theme.of(context).textTheme;
final size = MediaQuery.of(context).size;
return Scaffold(
appBar: ModernAppBar(
title: 'Profile',
automaticallyImplyLeading: true,
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () => Navigator.pop(context),
),
showThemeToggle: false,
showUserProfile: false,
),
body: AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return FadeTransition(
opacity: _fadeAnimation,
child: SlideTransition(
position: _slideAnimation,
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
colorScheme.primary.withOpacity(0.1),
colorScheme.secondary.withOpacity(0.05),
colorScheme.surface,
],
stops: const [0.0, 0.3, 1.0],
),
),
child: SingleChildScrollView(
padding: UIConstants.getResponsivePadding(
context,
mobile: UIConstants.screenPaddingMedium,
tablet: UIConstants.screenPaddingLarge,
desktop: UIConstants.screenPaddingLarge,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Profile Header Section
_buildProfileHeader(
context, colorScheme, textTheme, size),
SizedBox(
height: UIConstants.getResponsiveSpacing(
context,
mobile: UIConstants.spacing32,
tablet: UIConstants.spacing40,
desktop: UIConstants.spacing48,
)),
// Profile Actions Section
_buildProfileActions(context, colorScheme),
],
),
),
),
),
);
},
),
);
},
);
}
Widget _buildProfileHeader(BuildContext context, ColorScheme colorScheme,
TextTheme textTheme, Size size) {
return Consumer<ProfileViewModel>(
builder: (context, value, child) {
switch (provider.userProfile.status) {
case null:
case Status.LOADING:
return Container(
height: UIConstants.getResponsiveValue(
context,
mobile: 200.0,
tablet: 240.0,
desktop: 280.0,
),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.95),
borderRadius: BorderRadius.circular(UIConstants.radius24),
border: Border.all(
color: colorScheme.surface.withOpacity(0.2),
width: 1,
),
boxShadow: [
BoxShadow(
color: colorScheme.primary.withOpacity(0.1),
blurRadius: 20,
offset: const Offset(0, 10),
spreadRadius: 5,
),
],
),
child: const Center(
child: CircularProgressIndicator(),
),
);
case Status.SUCCESS:
return Container(
padding: UIConstants.getResponsivePadding(
context,
mobile: UIConstants.cardPaddingLarge,
tablet: UIConstants.cardPaddingLarge,
desktop: UIConstants.cardPaddingLarge,
),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.95),
borderRadius: BorderRadius.circular(UIConstants.radius24),
border: Border.all(
color: colorScheme.surface.withOpacity(0.2),
width: 1,
),
boxShadow: [
BoxShadow(
color: colorScheme.primary.withOpacity(0.1),
blurRadius: 20,
offset: const Offset(0, 10),
spreadRadius: 5,
),
],
),
child: Column(
children: [
// Profile Avatar with Enhanced Styling
Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: colorScheme.primary.withOpacity(0.3),
width: 3,
),
boxShadow: [
BoxShadow(
color: colorScheme.primary.withOpacity(0.2),
blurRadius: 20,
offset: const Offset(0, 10),
spreadRadius: 5,
),
],
),
child: CircleAvatar(
radius: UIConstants.getResponsiveValue(
context,
mobile: 50,
tablet: 60,
desktop: 70,
),
backgroundColor: colorScheme.primary.withOpacity(0.1),
backgroundImage: provider.profileImageBytes != null
? MemoryImage(provider.profileImageBytes!)
: null,
child: provider.profileImageBytes != null
? null
: Icon(
Icons.person,
size: UIConstants.getResponsiveValue(
context,
mobile: 40,
tablet: 50,
desktop: 60,
),
color: colorScheme.primary,
),
),
),
SizedBox(
height: UIConstants.getResponsiveSpacing(
context,
mobile: UIConstants.spacing24,
tablet: UIConstants.spacing32,
desktop: UIConstants.spacing40,
)),
// User Name
Text(
provider.userProfile.data?.fullName.toString() ?? "N/A",
style: textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.w700,
color: colorScheme.primary,
),
textAlign: TextAlign.center,
),
SizedBox(height: UIConstants.spacing8),
// Email
Container(
padding: const EdgeInsets.symmetric(
horizontal: UIConstants.spacing16,
vertical: UIConstants.spacing8,
),
decoration: BoxDecoration(
color: colorScheme.surfaceVariant.withOpacity(0.3),
borderRadius: BorderRadius.circular(UIConstants.radius12),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.email_outlined,
size: UIConstants.iconSizeSmall,
color: colorScheme.onSurfaceVariant,
),
const SizedBox(width: UIConstants.spacing8),
Text(
provider.userProfile.data?.email.toString() ?? "N/A",
style: textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
],
),
),
SizedBox(height: UIConstants.spacing12),
// Phone Number
Container(
padding: const EdgeInsets.symmetric(
horizontal: UIConstants.spacing16,
vertical: UIConstants.spacing8,
),
decoration: BoxDecoration(
color: colorScheme.surfaceVariant.withOpacity(0.3),
borderRadius: BorderRadius.circular(UIConstants.radius12),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.phone_outlined,
size: UIConstants.iconSizeSmall,
color: colorScheme.onSurfaceVariant,
),
const SizedBox(width: UIConstants.spacing8),
Text(
provider.userProfile.data?.mobNo.toString() ?? "N/A",
style: textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
],
),
),
],
),
);
case Status.ERROR:
return Container(
height: UIConstants.getResponsiveValue(
context,
mobile: 200,
tablet: 240,
desktop: 280,
),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.95),
borderRadius: BorderRadius.circular(UIConstants.radius24),
border: Border.all(
color: colorScheme.error.withOpacity(0.3),
width: 1,
),
),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.error_outline,
size: UIConstants.iconSizeXLarge,
color: colorScheme.error,
),
const SizedBox(height: UIConstants.spacing16),
Text(
'Failed to load profile',
style: textTheme.bodyLarge?.copyWith(
color: colorScheme.error,
),
),
],
),
),
);
}
},
);
}
Widget _buildProfileActions(BuildContext context, ColorScheme colorScheme) {
return Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.95),
borderRadius: BorderRadius.circular(UIConstants.radius24),
border: Border.all(
color: colorScheme.surface.withOpacity(0.2),
width: 1,
),
boxShadow: [
BoxShadow(
color: colorScheme.primary.withOpacity(0.1),
blurRadius: 20,
offset: const Offset(0, 10),
spreadRadius: 5,
),
],
),
child: Column(
children: [
_buildModernListTile(
context,
colorScheme: colorScheme,
icon: Icons.edit_outlined,
title: 'Edit Profile',
subtitle: 'Update your personal information',
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => EditProfile(
userProfile: provider.userProfile.data!,
),
),
);
},
),
_buildDivider(colorScheme),
_buildModernListTile(
context,
colorScheme: colorScheme,
icon: Icons.manage_accounts_outlined,
title: 'Account Update',
subtitle: 'Update company/workspace details',
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const AccountUpdateView(),
),
);
},
),
_buildDivider(colorScheme),
_buildModernListTile(
context,
colorScheme: colorScheme,
icon: Icons.lock_outline,
title: 'Change Password',
subtitle: 'Update your account security',
onTap: () {
Navigator.pushNamed(context, RouteNames.changePasswordView);
},
),
_buildDivider(colorScheme),
_buildModernListTile(
context,
colorScheme: colorScheme,
icon: Icons.logout,
title: 'Logout',
subtitle: 'Sign out of your account',
iconColor: colorScheme.error,
onTap: () => _showLogoutDialog(context, colorScheme),
),
],
),
);
}
Widget _buildModernListTile(
BuildContext context, {
required ColorScheme colorScheme,
required IconData icon,
required String title,
required String subtitle,
required VoidCallback onTap,
Color? iconColor,
}) {
return Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(UIConstants.radius16),
child: Padding(
padding: UIConstants.getResponsivePadding(
context,
mobile: const EdgeInsets.all(UIConstants.spacing20),
tablet: const EdgeInsets.all(UIConstants.spacing24),
desktop: const EdgeInsets.all(UIConstants.spacing24),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(UIConstants.spacing12),
decoration: BoxDecoration(
color: (iconColor ?? colorScheme.primary).withOpacity(0.1),
borderRadius: BorderRadius.circular(UIConstants.radius12),
),
child: Icon(
icon,
color: iconColor ?? colorScheme.primary,
size: UIConstants.iconSizeMedium,
),
),
const SizedBox(width: UIConstants.spacing16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
color: colorScheme.onSurface,
),
),
const SizedBox(height: UIConstants.spacing4),
Text(
subtitle,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
],
),
),
Icon(
Icons.arrow_forward_ios,
color: colorScheme.onSurfaceVariant,
size: UIConstants.iconSizeSmall,
),
],
),
),
),
);
}
Widget _buildDivider(ColorScheme colorScheme) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: UIConstants.spacing20),
height: 1,
color: colorScheme.surfaceVariant.withOpacity(0.3),
);
}
void _showLogoutDialog(BuildContext context, ColorScheme colorScheme) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(UIConstants.radius20),
),
title: Row(
children: [
Icon(
Icons.logout,
color: colorScheme.error,
size: UIConstants.iconSizeMedium,
),
const SizedBox(width: UIConstants.spacing12),
const Text("Confirm Logout"),
],
),
content: const Text("Are you sure you want to log out?"),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(
"Cancel",
style: TextStyle(color: colorScheme.onSurfaceVariant),
),
),
ElevatedButton(
onPressed: () async {
await UserManager().clearUser();
Navigator.of(context).pop();
Navigator.pushReplacementNamed(context, RouteNames.loginView);
},
style: ElevatedButton.styleFrom(
backgroundColor: colorScheme.error,
foregroundColor: colorScheme.onError,
),
child: const Text("Log Out"),
),
],
);
},
);
}
}

View File

@@ -0,0 +1,492 @@
import 'dart:io';
import 'dart:math' as math;
import 'package:base_project/core/constants/ui_constants.dart';
import 'package:base_project/view_model/splash_view_model.dart';
import 'package:base_project/view_model/system_params/system_params_view_model.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../core/providers/dynamic_theme_provider.dart';
class SplashScreen extends StatefulWidget {
const SplashScreen({super.key});
@override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen>
with TickerProviderStateMixin {
late AnimationController _logoController;
late AnimationController _textController;
late AnimationController _particleController;
late AnimationController _progressController;
late Animation<double> _logoScale;
late Animation<double> _logoOpacity;
late Animation<double> _textOpacity;
late Animation<double> _textSlide;
late Animation<double> _progressValue;
late Animation<double> _particleRotation;
@override
void initState() {
super.initState();
_initializeAnimations();
_startSplashSequence();
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
// Listen to dynamic theme changes to ensure colors update
final dynamicThemeProvider =
Provider.of<DynamicThemeProvider>(context, listen: false);
dynamicThemeProvider.addListener(_onThemeChanged);
}
void _onThemeChanged() {
// Force rebuild when dynamic theme changes
if (mounted) {
print('Splash Screen: Theme changed, rebuilding...');
setState(() {});
}
}
// Method to force refresh colors (can be called externally if needed)
void refreshColors() {
if (mounted) {
print('Splash Screen: Colors refreshed manually');
setState(() {});
}
}
void _initializeAnimations() {
// Logo animation controller
_logoController = AnimationController(
duration: UIConstants.durationSlow,
vsync: this,
);
// Text animation controller
_textController = AnimationController(
duration: UIConstants.durationNormal,
vsync: this,
);
// Particle animation controller
_particleController = AnimationController(
duration: const Duration(seconds: 3),
vsync: this,
);
// Progress animation controller
_progressController = AnimationController(
duration: const Duration(seconds: 3),
vsync: this,
);
// Logo animations
_logoScale = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _logoController,
curve: Curves.elasticOut,
));
_logoOpacity = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _logoController,
curve: Curves.easeInOut,
));
// Text animations
_textOpacity = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _textController,
curve: Curves.easeInOut,
));
_textSlide = Tween<double>(
begin: 50.0,
end: 0.0,
).animate(CurvedAnimation(
parent: _textController,
curve: Curves.easeOutCubic,
));
// Progress animation
_progressValue = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _progressController,
curve: Curves.easeInOut,
));
// Particle rotation
_particleRotation = Tween<double>(
begin: 0.0,
end: 2 * math.pi,
).animate(CurvedAnimation(
parent: _particleController,
curve: Curves.linear,
));
}
void _startSplashSequence() async {
// Start logo animation
await _logoController.forward();
// Start text animation
await Future.delayed(const Duration(milliseconds: 300));
_textController.forward();
// Start progress and particle animations
await Future.delayed(const Duration(milliseconds: 200));
_progressController.forward();
_particleController.repeat();
// Check navigation after animations
SplashViewModel().checkNavigation(context);
}
@override
void dispose() {
// Remove listener before disposing
try {
final dynamicThemeProvider =
Provider.of<DynamicThemeProvider>(context, listen: false);
dynamicThemeProvider.removeListener(_onThemeChanged);
} catch (e) {
// Provider might not be available during dispose
}
_logoController.dispose();
_textController.dispose();
_particleController.dispose();
_progressController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
// Always use dynamic theme provider for consistent colors
return Consumer<DynamicThemeProvider>(
builder: (context, dynamicThemeProvider, child) {
// Get the current dynamic color scheme
final colorScheme = dynamicThemeProvider.getCurrentColorScheme(
Theme.of(context).brightness == Brightness.dark);
// Debug information for theme tracking
if (dynamicThemeProvider.isUsingDynamicTheme) {
print(
'Splash Screen: Using dynamic theme with primary color: ${colorScheme.primary}');
} else {
print(
'Splash Screen: Using default theme with primary color: ${colorScheme.primary}');
}
return Scaffold(
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
colorScheme.primary,
colorScheme.primaryContainer,
colorScheme.secondary,
],
stops: const [0.0, 0.5, 1.0],
),
),
child: Stack(
children: [
// Animated background particles
_buildParticleBackground(),
// Main content
SafeArea(
child: Padding(
padding: UIConstants.screenPaddingLarge,
child: Column(
children: [
// Top section with logo
Expanded(
flex: 3,
child: _buildLogoSection(colorScheme),
),
// Middle section with text
Expanded(
flex: 2,
child: _buildTextSection(colorScheme),
),
// Bottom section with progress
Expanded(
flex: 1,
child: _buildProgressSection(colorScheme),
),
],
),
),
),
],
),
),
);
},
);
}
Widget _buildParticleBackground() {
return Consumer<DynamicThemeProvider>(
builder: (context, dynamicThemeProvider, child) {
final colorScheme = dynamicThemeProvider.getCurrentColorScheme(
Theme.of(context).brightness == Brightness.dark);
return AnimatedBuilder(
animation: _particleRotation,
builder: (context, child) {
return CustomPaint(
painter: ParticlePainter(
rotation: _particleRotation.value,
color: colorScheme.primary.withOpacity(0.15),
),
size: Size.infinite,
);
},
);
},
);
}
Widget _buildLogoSection(ColorScheme colorScheme) {
return Center(
child: AnimatedBuilder(
animation: _logoController,
builder: (context, child) {
return Transform.scale(
scale: _logoScale.value,
child: Opacity(
opacity: _logoOpacity.value,
child: Container(
width: UIConstants.logoSizeXLarge * 2,
height: UIConstants.logoSizeXLarge * 2,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: colorScheme.surface.withOpacity(0.2),
border: Border.all(
color: colorScheme.primary.withOpacity(0.3),
width: 2,
),
boxShadow: [
BoxShadow(
color: colorScheme.primary.withOpacity(0.2),
blurRadius: 20,
spreadRadius: 5,
),
BoxShadow(
color: colorScheme.secondary.withOpacity(0.1),
blurRadius: 40,
spreadRadius: 10,
),
],
),
child: Center(
child: Consumer<SystemParamsViewModel>(
builder: (context, provider, _) {
if (provider.profileImg != null) {
return ClipOval(
child: Image.file(
File(provider.profileImg!.path),
fit: BoxFit.cover,
width: UIConstants.logoSizeXLarge * 1.5,
height: UIConstants.logoSizeXLarge * 1.5,
),
);
} else {
return Icon(
Icons.security,
size: UIConstants.logoSizeXLarge,
color: colorScheme.onSurface,
);
}
},
),
),
),
),
);
},
),
);
}
Widget _buildTextSection(ColorScheme colorScheme) {
return AnimatedBuilder(
animation: _textController,
builder: (context, child) {
return Transform.translate(
offset: Offset(0, _textSlide.value),
child: Opacity(
opacity: _textOpacity.value,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"AuthSec",
style: Theme.of(context).textTheme.displayLarge?.copyWith(
color: colorScheme.onSurface,
fontWeight: FontWeight.bold,
letterSpacing: 2.0,
),
),
const SizedBox(height: UIConstants.spacing16),
Text(
"Secure Authentication System",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: colorScheme.onSurface.withOpacity(0.9),
fontWeight: FontWeight.w300,
letterSpacing: 1.0,
),
),
const SizedBox(height: UIConstants.spacing24),
Container(
padding: UIConstants.cardPaddingMedium,
decoration: BoxDecoration(
color: colorScheme.surface.withOpacity(0.2),
borderRadius: BorderRadius.circular(UIConstants.radius20),
border: Border.all(
color: colorScheme.primary.withOpacity(0.3),
width: 1,
),
boxShadow: [
BoxShadow(
color: colorScheme.primary.withOpacity(0.1),
blurRadius: 10,
spreadRadius: 2,
),
],
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.shield,
color: colorScheme.primary,
size: UIConstants.iconSizeMedium,
),
const SizedBox(width: UIConstants.spacing12),
Text(
"Enterprise Security",
style:
Theme.of(context).textTheme.titleMedium?.copyWith(
color: colorScheme.onSurface,
fontWeight: FontWeight.w500,
),
),
],
),
),
],
),
),
);
},
);
}
Widget _buildProgressSection(ColorScheme colorScheme) {
return AnimatedBuilder(
animation: _progressController,
builder: (context, child) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Progress bar
Container(
width: double.infinity,
height: 4,
decoration: BoxDecoration(
color: colorScheme.surface.withOpacity(0.3),
borderRadius: BorderRadius.circular(UIConstants.radiusFull),
border: Border.all(
color: colorScheme.primary.withOpacity(0.2),
width: 1,
),
),
child: FractionallySizedBox(
alignment: Alignment.centerLeft,
widthFactor: _progressValue.value,
child: Container(
decoration: BoxDecoration(
color: colorScheme.primary,
borderRadius: BorderRadius.circular(UIConstants.radiusFull),
boxShadow: [
BoxShadow(
color: colorScheme.primary.withOpacity(0.5),
blurRadius: 10,
spreadRadius: 2,
),
],
),
),
),
),
const SizedBox(height: UIConstants.spacing16),
Text(
"Initializing...",
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurface.withOpacity(0.8),
fontWeight: FontWeight.w400,
),
),
],
);
},
);
}
}
class ParticlePainter extends CustomPainter {
final double rotation;
final Color color;
ParticlePainter({required this.rotation, required this.color});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = color
..style = PaintingStyle.fill;
final center = Offset(size.width / 2, size.height / 2);
final radius = math.min(size.width, size.height) / 3;
// Draw rotating particles
for (int i = 0; i < 8; i++) {
final angle = (i * math.pi / 4) + rotation;
final x = center.dx + radius * math.cos(angle);
final y = center.dy + radius * math.sin(angle);
canvas.drawCircle(
Offset(x, y),
3,
paint,
);
}
}
@override
bool shouldRepaint(ParticlePainter oldDelegate) => true;
}

View File

@@ -0,0 +1,21 @@
import 'package:flutter/material.dart';
class EditSystemParams extends StatelessWidget {
const EditSystemParams({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
onPressed: () {
Navigator.pop(context);
},
icon: Icon(Icons.adaptive.arrow_back),
),
title: const Text("Edit System Parameters"),
),
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,384 @@
import 'dart:async';
import 'package:base_project/repository/auth_repo.dart';
import 'package:base_project/routes/route_names.dart';
import 'package:base_project/utils/flushbar_messages/flush_message_util.dart';
import 'package:base_project/utils/managers/user_manager.dart';
import 'package:base_project/utils/toast_messages/toast_message_util.dart';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AuthViewModel extends ChangeNotifier {
final AuthRepo _authRepo = AuthRepo();
bool _isLoading = false;
bool get isLoading => _isLoading;
setLoading(bool value) {
_isLoading = value;
notifyListeners();
}
Timer? _timer;
int _start = 30;
bool _isResendEnabled = false;
int get start => _start;
bool get isResendEnabled => _isResendEnabled;
void startTimer() {
_isResendEnabled = false;
_start = 30; // Set the timer duration (in seconds)
_timer = Timer.periodic(const Duration(seconds: 1), (Timer timer) {
if (_start == 0) {
_timer?.cancel();
_isResendEnabled = true;
notifyListeners();
} else {
_start--;
print(_start);
notifyListeners();
}
});
}
var accId = "";
var uEmail = "";
Future<void> login(BuildContext context, dynamic data) async {
setLoading(true);
try {
final value = await _authRepo.loginApi(data);
print('Value is -$value');
// Check the response structure
if (value['operationStatus'] == 'SUCCESS') {
final item = value['item'];
await UserManager().setUser(item);
// Try to persist sys account from login response if present
try {
final dynamic accountCandidate =
item['sysAccount'] ?? item['account'] ?? item['sys_account'];
if (accountCandidate is Map<String, dynamic>) {
await UserManager().setAccount(accountCandidate);
}
} catch (_) {}
final prefs = await SharedPreferences.getInstance();
prefs.setBool('isLoggedIn', true);
// Handle successful login
ToastMessageUtil.showToast(
message: 'Logged in as ${value['item']['email']}',
toastType: ToastType.general);
// nav to home
Navigator.pushReplacementNamed(context, RouteNames.homeView);
} else {
print('Error got ');
// Handle login failure with backend message if available
final backendMessage = (value is Map)
? (value['operationMessage'] ?? value['message'] ?? value['msg'])
: null;
final message = (backendMessage?.toString().trim().isNotEmpty == true)
? backendMessage.toString()
: "Failed!! Email or Password is incorrect";
FlushBarMessageUtil.showFlushBar(
context: context,
message: message,
flushBarType: FlushBarType.error,
);
}
} catch (error, stackTrace) {
print("Error -$error");
print("st -$stackTrace");
// Handle any other errors
FlushBarMessageUtil.showFlushBar(
context: context,
message: error.toString(),
flushBarType: FlushBarType.error,
);
} finally {
setLoading(false);
}
}
Future<void> signUp(BuildContext context, dynamic data) async {
setLoading(true);
final createUserData = {...data, 'account_id': accId};
print("CreateUserData--$createUserData");
_authRepo.createUserApi(createUserData).then((value) {
print(value);
ToastMessageUtil.showToast(
message: "Account Created", toastType: ToastType.success);
setLoading(false);
Navigator.pushReplacementNamed(context, RouteNames.loginView);
}).onError(
(error, stackTrace) {
print(error);
print(stackTrace);
FlushBarMessageUtil.showFlushBar(
context: context,
message: error.toString(),
flushBarType: FlushBarType.error);
setLoading(false);
},
);
}
// New method for combined registration flow (account + user)
Future<void> signUpWithAccountCreation(
BuildContext context, dynamic data) async {
setLoading(true);
try {
// Step 1: Create account with just email and password
final accountData = {
"email": data["email"],
};
print("Creating account with data: $accountData");
final accountResponse = await _authRepo.createAcApi(accountData);
print("Account creation response: $accountResponse");
// Extract account ID from response
final accountId = accountResponse['account_id']?.toString();
if (accountId == null) {
throw Exception("Account ID not received from server");
}
// Step 2: Create user with account ID
final createUserData = {...data, 'account_id': accountId};
print("Creating user with data: $createUserData");
final userResponse = await _authRepo.createUserApi(createUserData);
print("User creation response: $userResponse");
ToastMessageUtil.showToast(
message: "Account Created Successfully",
toastType: ToastType.success);
// Navigate to login
Navigator.pushReplacementNamed(context, RouteNames.loginView);
} catch (error, stackTrace) {
print("SignUp error: $error");
print("Stack trace: $stackTrace");
String errorMessage = "Registration failed. Please try again.";
if (error.toString().contains('already exist')) {
errorMessage = "Email already exists. Please use a different email.";
} else if (error.toString().contains('account_id')) {
errorMessage = "Account creation failed. Please try again.";
}
FlushBarMessageUtil.showFlushBar(
context: context,
message: errorMessage,
flushBarType: FlushBarType.error);
} finally {
setLoading(false);
}
}
Future<void> createAcc(BuildContext context, dynamic data) async {
setLoading(true);
_authRepo.createAcApi(data).then(
(value) {
print("Value--$value");
accId = value['account_id'].toString();
setLoading(false);
ToastMessageUtil.showToast(
message: "Success creating account", toastType: ToastType.success);
// Navigator.pushNamed(context, RouteNames.signUp,arguments: uEmail);
},
).onError(
(error, stackTrace) {
print("error--$error");
setLoading(false);
ToastMessageUtil.showToast(
message: "Error registering your account",
toastType: ToastType.error);
},
);
}
Future<void> getOtp(BuildContext context, dynamic data) async {
setLoading(true);
try {
final value = await _authRepo.getOtpApi(data);
print(value);
// Handle non-exception responses like { msg: "... already exist" }
if (value is Map) {
final map = Map<String, dynamic>.from(value);
final message = (map['msg'] ?? map['message'] ?? '').toString();
final operationStatus = (map['operationStatus'] ?? '').toString();
final otpSent = (map['otp_sent'] == true) || (map['otpSent'] == true);
// Block when backend says email already exists
if (message.toLowerCase().contains('already exist')) {
ToastMessageUtil.showToast(
message: 'Email already exists',
toastType: ToastType.error,
);
return; // do not navigate
}
// Consider success scenarios (handle variants like "Otp send successfully")
final lowerMsg = message.toLowerCase();
final messageLooksSuccess = lowerMsg.contains('otp sent') ||
lowerMsg.contains('sent successfully') ||
(lowerMsg.contains('otp') && lowerMsg.contains('success')) ||
lowerMsg.contains('send successfully');
if (operationStatus == 'SUCCESS' || otpSent || messageLooksSuccess) {
ToastMessageUtil.showToast(
message: message.isNotEmpty ? message : 'OTP sent successfully',
toastType: ToastType.success,
);
// Start resend countdown when OTP is sent
startTimer();
Navigator.pushNamed(
context,
RouteNames.verifyOtpView,
arguments: {'email': data['email']},
);
return;
}
// Fallback: show backend message if present
if (message.isNotEmpty) {
ToastMessageUtil.showToast(
message: message,
toastType: ToastType.error,
);
return;
}
}
// Unknown response structure
ToastMessageUtil.showToast(
message: 'Failed to send OTP',
toastType: ToastType.error,
);
} catch (error) {
// Handle any other exceptions
print("getOtp error: $error");
final msg = error.toString();
String uiMessage = 'Failed to send OTP';
if (msg.contains('already exist') || msg.contains('already exists')) {
uiMessage = 'Email already exists';
} else if (msg.contains('invalid') || msg.contains('Invalid')) {
uiMessage = 'Invalid email address';
} else if (msg.contains('timeout')) {
uiMessage = 'Request timed out. Please try again.';
}
ToastMessageUtil.showToast(
message: uiMessage,
toastType: ToastType.error,
);
} finally {
setLoading(false);
}
}
Future<void> resendOtp(BuildContext context, dynamic data) async {
startTimer();
await _authRepo.resendOtpApi(data).then((value) {
print(value);
ToastMessageUtil.showToast(
message: "OTP resend successfully",
toastType: ToastType.success,
);
}).onError(
(error, stackTrace) {
print(error);
ToastMessageUtil.showToast(
message: "Failed to resend OTP",
toastType: ToastType.error,
);
},
);
}
/// Returns null on success, or a human-readable error message on failure
Future<String?> verifyOtp(BuildContext context, dynamic queryParams) async {
print("Verifying otp");
setLoading(true);
try {
final value = await _authRepo.verifyOtpApi(queryParams);
print(value);
// Backend returns 200 with EntityResponse("OTP Verified")
// and 400 with EntityResponse("Wrong OTP") or MessageResponse("... not exist")
if (value is Map) {
final map = Map<String, dynamic>.from(value);
final message = (map['msg'] ?? map['message'] ?? '').toString();
final lower = message.toLowerCase();
if (lower.contains('otp verified')) {
ToastMessageUtil.showToast(
message: 'OTP Verified',
toastType: ToastType.success,
);
uEmail = queryParams['email'];
cancelTimer();
return null;
}
if (lower.contains('wrong otp')) {
ToastMessageUtil.showToast(
message: 'Wrong OTP',
toastType: ToastType.error,
);
return 'Wrong OTP';
}
if (lower.contains('not exist')) {
ToastMessageUtil.showToast(
message: 'User not found',
toastType: ToastType.error,
);
return 'User not found';
}
// Fallback: show message if present
if (message.isNotEmpty) {
ToastMessageUtil.showToast(
message: message,
toastType: ToastType.error,
);
return message;
}
}
ToastMessageUtil.showToast(
message: 'OTP verification failed',
toastType: ToastType.error,
);
return 'OTP verification failed';
} catch (error) {
print(error);
ToastMessageUtil.showToast(
message: "OTP verification failed",
toastType: ToastType.error,
);
return 'OTP verification failed';
} finally {
setLoading(false);
}
}
void cancelTimer() {
_timer?.cancel();
}
@override
void dispose() {
cancelTimer(); // Ensure the timer is cancelled
super.dispose();
}
}

View File

@@ -0,0 +1,209 @@
import 'dart:convert';
import 'dart:developer';
import 'package:base_project/data/response/api_response.dart';
import 'package:base_project/model/user/user_profile.dart';
import 'package:base_project/repository/profile/profile_repo.dart';
import 'package:base_project/utils/managers/user_manager.dart';
import 'package:base_project/utils/toast_messages/toast_message_util.dart';
import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:image_picker/image_picker.dart';
class ProfileViewModel extends ChangeNotifier {
final _repo = ProfileRepo();
Map<String, dynamic>? _sysAccount;
Map<String, dynamic>? get sysAccount => _sysAccount;
ApiResponse<UserProfile> userProfile = ApiResponse.loading();
Uint8List? _profileImageBytes; // Variable for storing fetched image bytes
bool _isUpdating = false;
bool get isUpdating => _isUpdating;
XFile? _profileImage;
XFile? get profileImg => _profileImage;
// Setters
setUserProfile(ApiResponse<UserProfile> val) {
userProfile = val;
notifyListeners();
}
setUpdating(bool val) {
_isUpdating = val;
notifyListeners();
}
setImage(XFile? val) {
_profileImage = val;
notifyListeners();
}
setProfileImageBytes(Uint8List? val) {
_profileImageBytes = val;
notifyListeners();
}
// Picking an image from the gallery
void pickImg(BuildContext context) async {
final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
if (image != null) {
setImage(image);
updateImage(context);
} else {
ToastMessageUtil.showToast(
message: "No image picked !!", toastType: ToastType.warning);
}
}
// Fetching the user profile details
Future<void> getProfile() async {
setUserProfile(ApiResponse.loading());
_repo.getProfileApi().then((value) {
log("Profile Data--$value");
setUserProfile(ApiResponse.success(UserProfile.fromJson(value)));
try {
_sysAccount =
UserManager().account ?? value['sysAccount'] ?? value['account'];
} catch (_) {}
}).onError((error, stackTrace) {
log("ERROR--$error");
setUserProfile(ApiResponse.error(error.toString()));
ToastMessageUtil.showToast(
message: "Error fetching profile data", toastType: ToastType.error);
});
}
Future<void> updateAccount(
BuildContext context, Map<String, dynamic> form) async {
setUpdating(true);
try {
final accId = UserManager().accountId;
if (accId == null) {
throw Exception('Account not found');
}
final body = {
'companyName': form['companyName'],
'workspace': form['workspace'],
'gstNumber': form['gstNumber'],
'mobile': form['mobile'],
'email': form['email'],
'pancard': form['pancard'],
'working': form['working'],
'active': form['active'],
};
final res = await _repo.updateAccountApi(accId, body);
if (res is Map<String, dynamic>) {
_sysAccount = res;
await UserManager().setAccount(res);
}
ToastMessageUtil.showToast(
message: 'Account Updated', toastType: ToastType.success);
Navigator.pop(context);
} catch (e) {
ToastMessageUtil.showToast(
message: 'Failed to update account', toastType: ToastType.error);
} finally {
setUpdating(false);
}
}
// Fetching the profile image
Future<void> getProfileImg() async {
_repo.getProfileImgApi().then((value) {
String base64Image = value['image'];
// Remove data URL prefix and decode base64
Uint8List imageBytes = base64Decode(base64Image.split(',').last);
setProfileImageBytes(imageBytes); // Store image bytes
}).onError((error, stackTrace) {
log("Error Fetching Image--$error");
ToastMessageUtil.showToast(
message: "Error fetching profile image", toastType: ToastType.error);
});
}
// Updating the profile details
Future<void> updateProfile(BuildContext context, dynamic data) async {
setUpdating(true);
final uId = UserManager().userId;
data['userId'] = uId.toString();
print("Data--$data");
_repo.updateProfileApi(data, uId).then((value) {
log("Profile Update--$value");
setUpdating(false);
getProfile();
ToastMessageUtil.showToast(
message: "Profile Updated", toastType: ToastType.success);
Navigator.pop(context); // Close dialog/screen after update
}).onError((error, stackTrace) {
log("Error Updating Profile--$error");
setUpdating(false);
ToastMessageUtil.showToast(
message: "Error updating profile!!", toastType: ToastType.error);
});
}
// Updating the profile image
Future<void> updateImage(BuildContext context) async {
if (_profileImage == null) return; // Ensure an image is selected
setUpdating(true);
Uint8List fileBytes = await _profileImage!.readAsBytes();
FormData formData = FormData.fromMap({
'imageFile': MultipartFile.fromBytes(
fileBytes,
filename: _profileImage!.name,
),
});
_repo.updateProfileImg(formData).then((value) {
log("Image Update--$value");
setUpdating(false);
getProfileImg(); // Refresh the displayed image
ToastMessageUtil.showToast(
message: "Profile Image Updated", toastType: ToastType.success);
}).onError((error, stackTrace) {
log("Error Updating Image--$error");
setUpdating(false);
ToastMessageUtil.showToast(
message: "Error updating image!!", toastType: ToastType.error);
});
}
// Changing the password
Future<void> changePassword(dynamic data) async {
setUpdating(true);
print("BODY--$data");
_repo.changPassApi(data).then((value) {
print("Password Change--$value");
setUpdating(false);
ToastMessageUtil.showToast(
message: "Password updated", toastType: ToastType.success);
}).onError((error, stackTrace) {
print("Error Changing Password--$error");
setUpdating(false);
ToastMessageUtil.showToast(
message: "Error updating password!!", toastType: ToastType.error);
});
}
// Helper method to generate headers
// Network headers are now centralized in NetworkApiService via UserManager
// Method to return image bytes (can be used in the UI to display)
Uint8List? get profileImageBytes => _profileImageBytes;
void reset() {
_profileImage = null;
_profileImageBytes = null;
userProfile = ApiResponse.loading(); // Or any default state
notifyListeners();
}
}

View File

@@ -0,0 +1,44 @@
import 'package:base_project/routes/route_names.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class SplashViewModel {
Future<void> checkNavigation(BuildContext context) async {
print("Checking navigation...");
try {
final prefs = await SharedPreferences.getInstance();
final isLoggedIn = prefs.getBool('isLoggedIn') ?? false;
// Wait for splash animations to complete (3 seconds total)
await Future.delayed(const Duration(seconds: 3));
if (!context.mounted) return; // Check if widget is still mounted
if (!isLoggedIn) {
print("Navigating to login...");
// Navigate to login with fade transition
Navigator.pushReplacementNamed(
context,
RouteNames.loginView,
);
} else {
print("Navigating to home...");
// Navigate to home with fade transition
Navigator.pushReplacementNamed(
context,
RouteNames.homeView,
);
}
} catch (e) {
print("Error in splash navigation: $e");
// Fallback to login on error
if (context.mounted) {
Navigator.pushReplacementNamed(
context,
RouteNames.loginView,
);
}
}
}
}

View File

@@ -0,0 +1,497 @@
import 'dart:developer';
import 'dart:typed_data';
import 'dart:convert';
import 'package:base_project/model/system_params_model.dart';
import 'package:base_project/repository/system_params_repo.dart';
import 'package:base_project/utils/managers/user_manager.dart';
import 'package:base_project/utils/toast_messages/toast_message_util.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../core/providers/dynamic_theme_provider.dart';
class SystemParamsViewModel extends ChangeNotifier {
final _repo = SystemParamsRepo();
Uint8List? _profileImageBytes;
XFile? _profileImage;
bool _loading = false;
// Store system parameters as model
SystemParamsModel? _systemParams;
Map<String, dynamic> _systemParamsList = {};
// Getter for system parameters model
SystemParamsModel? get systemParams => _systemParams;
// Getter for system parameters list (for backward compatibility)
Map<String, dynamic> get systemParamsList => _systemParamsList;
// Getter for profile image bytes
Uint8List? get profileImageBytes => _profileImageBytes;
// Getter for profile image
XFile? get profileImg => _profileImage;
// Getter for loading status
bool get loading => _loading;
// Setter for loading status with notification to listeners
set loading(bool value) {
_loading = value;
notifyListeners();
}
// Setter for profile image
setImage(XFile? val) {
_profileImage = val;
// Avoid notify during build; schedule for next frame
WidgetsBinding.instance.addPostFrameCallback((_) {
notifyListeners();
});
}
// Setter for profile image bytes
setProfileImageBytes(Uint8List? val) {
_profileImageBytes = val;
// Avoid notify during build; schedule for next frame
WidgetsBinding.instance.addPostFrameCallback((_) {
notifyListeners();
});
// Persist to SharedPreferences for future sessions
_persistLogoToLocal(val);
}
// Method to pick an image from the gallery
void pickImg(BuildContext context) async {
final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
if (image != null) {
setImage(image);
// Convert image to bytes and store
final bytes = await image.readAsBytes();
setProfileImageBytes(bytes);
// Generate dynamic theme IMMEDIATELY when image is selected
print("Image selected - generating dynamic theme immediately...");
await _generateDynamicThemeFromLogo(context);
print("Dynamic theme generated immediately after image selection!");
// Show success message
ToastMessageUtil.showToast(
message:
"Logo selected! Dynamic theme applied. Click 'Upload Logo' to save permanently.",
toastType: ToastType.success);
} else {
ToastMessageUtil.showToast(
message: "No image picked !!", toastType: ToastType.warning);
}
}
// Method to upload logo image separately (like edit profile)
Future<void> uploadLogo(BuildContext context) async {
if (_profileImage == null) {
ToastMessageUtil.showToast(
message: "Please pick an image first!", toastType: ToastType.warning);
return;
}
loading = true;
try {
Uint8List fileBytes = await _profileImage!.readAsBytes();
print("Logo file size: ${fileBytes.length} bytes");
// Generate dynamic theme IMMEDIATELY after image selection (before API call)
print("Generating dynamic theme immediately...");
await _generateDynamicThemeFromLogo(context);
print(
"Dynamic theme generated immediately - user sees instant feedback!");
// Now proceed with API upload
FormData formData = FormData.fromMap({
'imageFile': MultipartFile.fromBytes(
fileBytes,
filename: _profileImage!.name,
),
});
dynamic uploadResult;
try {
uploadResult = await _repo.uploadSystemParamLogo(formData);
print("----LOGO-UPLOADED----");
print(uploadResult);
} catch (e) {
// If backend returns intermittent error but file actually uploaded,
// avoid showing a scary error immediately. Log and proceed to metadata update.
print("Upload API error (may be intermittent): $e");
}
// Try to update system parameters regardless (backend may have the file already)
bool metaUpdated = false;
try {
await _updateLogoInSystemParams();
metaUpdated = true;
} catch (e) {
print("Logo metadata update failed: $e");
}
loading = false;
if (metaUpdated || uploadResult != null) {
ToastMessageUtil.showToast(
message: "Company logo uploaded! Dynamic theme applied.",
toastType: ToastType.success);
} else {
ToastMessageUtil.showToast(
message: "Logo upload may have failed. Please retry.",
toastType: ToastType.warning);
}
} catch (error) {
loading = false;
print("Error uploading logo: $error");
ToastMessageUtil.showToast(
message: "Error uploading logo: ${error.toString()}",
toastType: ToastType.error);
// Even if API fails, theme is already applied - user keeps the visual benefit!
print(
"API failed but dynamic theme is already applied - user keeps the visual benefit!");
}
}
// Generate dynamic theme from logo
Future<void> _generateDynamicThemeFromLogo(BuildContext context) async {
try {
print("_generateDynamicThemeFromLogo called");
print("_profileImageBytes is null: ${_profileImageBytes == null}");
print("_profileImageBytes length: ${_profileImageBytes?.length ?? 0}");
if (_profileImageBytes != null) {
final dynamicThemeProvider =
Provider.of<DynamicThemeProvider>(context, listen: false);
print("DynamicThemeProvider found: ${dynamicThemeProvider != null}");
print(
"Before theme generation - isUsingDynamicTheme: ${dynamicThemeProvider.isUsingDynamicTheme}");
await dynamicThemeProvider.generateThemeFromLogo(_profileImageBytes!);
print(
"After theme generation - isUsingDynamicTheme: ${dynamicThemeProvider.isUsingDynamicTheme}");
print("Dynamic theme generated successfully from logo!");
} else {
print("_profileImageBytes is null, cannot generate theme");
}
} catch (error) {
print("Error generating dynamic theme: $error");
}
}
// Update system parameters to include logo information
Future<void> _updateLogoInSystemParams() async {
try {
final logoData = {
'upload_Logo': _profileImageBytes,
'upload_Logo_name': _profileImage?.name ?? 'logo.png',
'upload_Logo_path': _profileImage?.path ?? '',
};
await _repo.updateSystemParameters(logoData);
print("Logo info updated in system parameters");
} catch (error) {
print("Error updating logo info in system params: $error");
}
}
// Method to fetch system parameters and store them in the model
Future<void> getSystemParams() async {
loading = true;
try {
final value = await _repo.getSystemParameters();
print("----SYSTEM-PARAMS----");
// Normalize response to Map<String, dynamic>
Map<String, dynamic> normalized = {};
if (value is Map<String, dynamic>) {
normalized = value;
} else if (value != null) {
try {
normalized = Map<String, dynamic>.from(value as Map);
} catch (_) {
normalized = {};
}
}
// Store in both formats for backward compatibility
_systemParamsList = normalized;
_systemParams = SystemParamsModel.fromJson(normalized);
// Load existing logo if available
_loadExistingLogo(value);
loading = false;
print(normalized);
} catch (error) {
loading = false;
print("Error-$error");
}
}
// Load existing logo from system parameters
void _loadExistingLogo(Map<String, dynamic> systemParams) {
try {
// Check if logo data exists in system parameters
if (systemParams['upload_Logo'] != null) {
// If logo is stored as base64 string
if (systemParams['upload_Logo'] is String) {
final String base64Image = systemParams['upload_Logo'];
if (base64Image.isNotEmpty) {
// Remove data URL prefix if present and decode base64
String cleanBase64 = base64Image;
if (base64Image.contains(',')) {
cleanBase64 = base64Image.split(',').last;
}
try {
final Uint8List imageBytes = base64Decode(cleanBase64);
setProfileImageBytes(imageBytes);
print("Existing logo loaded from system parameters");
} catch (e) {
print("Error decoding logo: $e");
}
}
}
// If logo is stored as bytes
else if (systemParams['upload_Logo'] is List) {
final List<int> logoBytes =
List<int>.from(systemParams['upload_Logo']);
setProfileImageBytes(Uint8List.fromList(logoBytes));
print("Existing logo loaded from system parameters");
}
}
} catch (error) {
print("Error loading existing logo: $error");
}
// Also attempt to load from local storage if not present in systemParams
if (_profileImageBytes == null) {
_loadLogoFromLocal();
}
}
// Method to update system parameters
Future<void> updateSystemParams(Map<String, dynamic> body) async {
loading = true;
try {
// Convert the form data to proper backend format
final backendData = _convertFormDataToBackendFormat(body);
final value = await _repo.updateSystemParameters(backendData);
print("----SYSTEM-PARAMS-UPDATED----");
loading = false;
print(value);
ToastMessageUtil.showToast(
message: "System parameters updated!!", toastType: ToastType.success);
} catch (error) {
loading = false;
print("Error-$error");
ToastMessageUtil.showToast(
message: error.toString(), toastType: ToastType.error);
}
}
// Convert form data to backend format
Map<String, dynamic> _convertFormDataToBackendFormat(
Map<String, dynamic> formData) {
final Map<String, dynamic> backendData = {};
// Map form field names to backend field names
for (String key in formData.keys) {
switch (key) {
case 'Scheduler Timer':
backendData['schedulerTime'] = int.tryParse(formData[key]) ?? 0;
break;
case 'Lease Tax Code':
backendData['leaseTaxCode'] = formData[key];
break;
case 'Vessel Confirmation':
backendData['vesselConfProcessLimit'] =
int.tryParse(formData[key]) ?? 0;
break;
case 'Row to Display':
backendData['rowToDisplay'] = int.tryParse(formData[key]) ?? 0;
break;
case 'Link to Display':
backendData['linkToDisplay'] = int.tryParse(formData[key]) ?? 0;
break;
case 'Row to Add':
backendData['rowToAdd'] = int.tryParse(formData[key]) ?? 0;
break;
case 'LOV Row to Display':
backendData['lovRowToDisplay'] = int.tryParse(formData[key]) ?? 0;
break;
case 'LOV Link to Display':
backendData['lovLinkToDisplay'] = int.tryParse(formData[key]) ?? 0;
break;
case 'OID Server Name':
backendData['oidserverName'] = formData[key];
break;
case 'OID Base':
backendData['oidBase'] = formData[key];
break;
case 'OID Admin User':
backendData['oidAdminUser'] = formData[key];
break;
case 'OID Server Port':
backendData['oidServerPort'] = int.tryParse(formData[key]) ?? 0;
break;
case 'User Default Group':
backendData['userDefaultGroup'] = int.tryParse(formData[key]) ?? 0;
break;
case 'Default Department':
backendData['defaultDepartment'] = formData[key];
break;
case 'Default Position':
backendData['defaultPosition'] = formData[key];
break;
case 'Single Charge':
backendData['singleCharge'] = formData[key];
break;
case 'First Day of the Week':
backendData['firstDayOftheWeek'] = formData[key];
break;
case 'Hours Per Shift':
backendData['hourPerShift'] = int.tryParse(formData[key]) ?? 0;
break;
case 'CN Billing Frequency':
backendData['cnBillingFrequency'] = int.tryParse(formData[key]) ?? 0;
break;
case 'Billing Department Code':
backendData['billingDepartmentCode'] = formData[key];
break;
case 'Base Price List':
backendData['basePriceList'] = formData[key];
break;
case 'Non-Container Service Order':
backendData['nonContainerServiceOrder'] = formData[key];
break;
case 'EDI MAE Scheduler':
backendData['ediMaeSchedulerONOFF'] =
int.tryParse(formData[key]) ?? 0;
break;
case 'EDI Scheduler':
backendData['ediSchedulerONOFF'] = formData[key];
break;
case 'Company Name':
backendData['Company_Display_Name'] = formData[key];
break;
case 'Registration Allowed':
backendData['isRegitrationAllowed'] = formData[key] == 'true';
break;
default:
// For any unmapped fields, pass through as is
backendData[key] = formData[key];
break;
}
}
// Add logo upload fields if available
if (_profileImageBytes != null) {
backendData['upload_Logo'] = _profileImageBytes;
backendData['upload_Logo_name'] = _profileImage?.name ?? 'logo.png';
backendData['upload_Logo_path'] = _profileImage?.path ?? '';
}
return backendData;
}
// Updating the profile image
Future<void> updateImage(BuildContext context) async {
if (_profileImage == null) return; // Ensure an image is selected
loading = true;
Uint8List fileBytes = await _profileImage!.readAsBytes();
FormData formData = FormData.fromMap({
'imageFile': MultipartFile.fromBytes(
fileBytes,
filename: _profileImage!.name,
),
});
_repo.updateProfileImg(formData).then((value) {
log("Image Update--$value");
loading = false;
ToastMessageUtil.showToast(
message: "Profile Image Updated", toastType: ToastType.success);
}).onError((error, stackTrace) {
log("Error Updating Image--$error");
loading = false;
// setUpdating(false);
ToastMessageUtil.showToast(
message: "Error updating image!!", toastType: ToastType.error);
});
}
// Method to clear logo and reset theme
Future<void> clearLogo(BuildContext context) async {
try {
// Clear the image data
setImage(null);
setProfileImageBytes(null);
// Reset to default theme
final dynamicThemeProvider =
Provider.of<DynamicThemeProvider>(context, listen: false);
dynamicThemeProvider.resetToDefaultTheme();
print("Logo cleared and theme reset to default");
// Update system parameters to remove logo info
await _updateLogoInSystemParams();
// Remove locally persisted logo
final prefs = await SharedPreferences.getInstance();
await prefs.remove('cached_logo_bytes');
ToastMessageUtil.showToast(
message: "Logo cleared! Theme reset to default.",
toastType: ToastType.info);
} catch (error) {
print("Error clearing logo: $error");
ToastMessageUtil.showToast(
message: "Error clearing logo: ${error.toString()}",
toastType: ToastType.error);
}
}
// Persist logo bytes locally
Future<void> _persistLogoToLocal(Uint8List? bytes) async {
try {
final prefs = await SharedPreferences.getInstance();
if (bytes == null || bytes.isEmpty) {
await prefs.remove('cached_logo_bytes');
return;
}
final base64Str = base64Encode(bytes);
await prefs.setString('cached_logo_bytes', base64Str);
} catch (e) {
print('Error persisting logo locally: $e');
}
}
// Load logo bytes from local storage
Future<void> _loadLogoFromLocal() async {
try {
final prefs = await SharedPreferences.getInstance();
final base64Str = prefs.getString('cached_logo_bytes');
if (base64Str != null && base64Str.isNotEmpty) {
final bytes = base64Decode(base64Str);
setProfileImageBytes(bytes);
print('Loaded logo from local storage');
}
} catch (e) {
print('Error loading logo from local storage: $e');
}
}
}

View File

@@ -0,0 +1,44 @@
import 'package:flutter/material.dart';
import '/widgets/custom_image_view.dart';
// ignore: must_be_immutable
class AppbarImage extends StatelessWidget {
AppbarImage(
{super.key, required this.height,
required this.width,
this.imagePath,
this.svgPath,
this.margin,
this.onTap});
double height;
double width;
String? imagePath;
String? svgPath;
EdgeInsetsGeometry? margin;
Function? onTap;
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
onTap?.call();
},
child: Padding(
padding: margin ?? EdgeInsets.zero,
child: CustomImageView(
svgPath: svgPath,
imagePath: imagePath,
height: height,
width: width,
fit: BoxFit.contain,
),
),
);
}
}

View File

@@ -0,0 +1,36 @@
import 'package:flutter/material.dart';
import '../../core/app_export.dart';
import '../../utils/image_constant.dart';
import '../custom_icon_button.dart'; // ignore: must_be_immutable
// ignore_for_file: must_be_immutable
// ignore_for_file: must_be_immutable
class AppbarLeadingIconbutton extends StatelessWidget {
AppbarLeadingIconbutton({super.key, this.imagePath, this.margin, this.onTap});
String? imagePath;
EdgeInsetsGeometry? margin;
Function? onTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
onTap?.call();
},
child: Padding(
padding: margin ?? EdgeInsets.zero,
child: CustomIconButton(
height: 32.adaptSize,
width: 32.adaptSize,
decoration: IconButtonStyleHelper.outlineIndigo,
child: CustomImageView(
imagePath: ImageConstant.imgArrowleft,
),
),
),
);
}
}

View File

@@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
import '../../utils/color_constants.dart';
import '../../theme/app_style.dart';
// ignore: must_be_immutable
class AppbarTitle extends StatelessWidget {
AppbarTitle({super.key, required this.text, this.margin, this.onTap});
String text;
EdgeInsetsGeometry? margin;
Function? onTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
onTap?.call();
},
child: Padding(
padding: margin ?? EdgeInsets.zero,
child: Text(
text,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.left,
style: AppStyle.txtGilroySemiBold24.copyWith(
color: ColorConstant.blueGray900,
),
),
),
);
}
}

View File

@@ -0,0 +1,56 @@
import 'package:flutter/material.dart';
import '../../utils/size_utils.dart';
// ignore: must_be_immutable
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
CustomAppBar(
{super.key, required this.height,
this.leadingWidth,
this.leading,
this.title,
this.bottom,
this.flexibleSpace,
this.centerTitle,
this.actions});
double height;
double? leadingWidth;
Widget? leading;
Widget? title;
PreferredSize? flexibleSpace;
TabBar? bottom;
bool? centerTitle;
List<Widget>? actions;
@override
Widget build(BuildContext context) {
return AppBar(
elevation: 0,
toolbarHeight: height,
automaticallyImplyLeading: false,
backgroundColor: Colors.transparent,
leadingWidth: leadingWidth ?? 0,
leading: leading,
title: title,
bottom: bottom,
flexibleSpace: flexibleSpace,
titleSpacing: 0,
centerTitle: centerTitle ?? false,
actions: actions,
);
}
@override
Size get preferredSize => Size(
size.width,
height,
);
}

View File

@@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
class BaseButton extends StatelessWidget {
const BaseButton(
{super.key,
required this.text,
this.onPressed,
this.buttonStyle,
this.buttonTextStyle,
this.isDisabled,
this.height,
this.width,
this.margin,
this.alignment});
final String text;
final VoidCallback? onPressed;
final ButtonStyle? buttonStyle;
final TextStyle? buttonTextStyle;
final bool? isDisabled;
final double? height;
final double? width;
final EdgeInsets? margin;
final Alignment? alignment;
@override
Widget build(BuildContext context) {
return const SizedBox.shrink();
}
}

View File

@@ -0,0 +1,92 @@
import 'package:flutter/material.dart';
import '../utils/size_utils.dart';
class customFloatingButton extends StatelessWidget {
const customFloatingButton({super.key,
this.shape,
this.variant,
this.alignment,
this.margin,
this.onTap,
this.width,
this.height,
this.child,
this.color,
});
final FloatingButtonShape? shape;
final FloatingButtonVariant? variant;
final Alignment? alignment;
final EdgeInsetsGeometry? margin;
final VoidCallback? onTap;
final double? width;
final double? height;
final Widget? child;
final Color? color;
@override
Widget build(BuildContext context) {
return alignment != null
? Align(
alignment: alignment ?? Alignment.center,
child: _buildFabWidget(),
)
: _buildFabWidget();
}
Widget _buildFabWidget() {
return Padding(
padding: margin ?? EdgeInsets.zero,
child: FloatingActionButton(
backgroundColor: _setColor(),
onPressed: onTap,
child: child,
),
);
}
BoxDecoration _buildDecoration() {
return BoxDecoration(
color: _setColor(),
borderRadius: _setBorderRadius(),
);
}
Color _setColor() {
if (color != null) {
return color!;
}
switch (variant) {
case FloatingButtonVariant.FillBlueA700:
return const Color(0xFF1976D2); // Example color for FillBlueA700
default:
return Colors.black;
}
}
BorderRadius _setBorderRadius() {
switch (shape) {
case FloatingButtonShape.RoundedBorder6:
return BorderRadius.circular(
getHorizontalSize(
6.00,
),
);
default:
return BorderRadius.circular(
getHorizontalSize(
6.00,
),
);
}
}
}
enum FloatingButtonShape {
RoundedBorder6,
}
enum FloatingButtonVariant {
FillBlueA700,
}

View File

@@ -0,0 +1,135 @@
import '/widgets/custom_image_view.dart';
import 'package:flutter/material.dart';
import '../utils/color_constants.dart';
import '../utils/image_constant.dart';
import '../utils/size_utils.dart';
class CustomBottomBar extends StatefulWidget {
CustomBottomBar({super.key, this.onChanged});
Function(BottomBarEnum)? onChanged;
@override
_CustomBottomBarState createState() => _CustomBottomBarState();
}
class _CustomBottomBarState extends State<CustomBottomBar> {
int selectedIndex = 0;
List<BottomMenuModel> bottomMenuList = [
BottomMenuModel(
icon: ImageConstant.imgFire,
type: BottomBarEnum.Fire,
),
BottomMenuModel(
icon: ImageConstant.imgSearchWhiteA70020x20,
type: BottomBarEnum.Searchwhitea70020x20,
),
BottomMenuModel(
icon: ImageConstant.imgMenu,
type: BottomBarEnum.Menu,
),
BottomMenuModel(
icon: ImageConstant.imgOverflowmenuWhiteA700,
type: BottomBarEnum.Overflowmenuwhitea700,
)
];
@override
Widget build(BuildContext context) {
return Container(
margin: getMargin(
left: 16,
right: 16,
),
decoration: BoxDecoration(
color: ColorConstant.blueA700,
borderRadius: BorderRadius.circular(
getHorizontalSize(
14,
),
),
),
child: BottomNavigationBar(
backgroundColor: Colors.transparent,
showSelectedLabels: false,
showUnselectedLabels: false,
elevation: 0,
currentIndex: selectedIndex,
type: BottomNavigationBarType.fixed,
items: List.generate(bottomMenuList.length, (index) {
return BottomNavigationBarItem(
icon: CustomImageView(
svgPath: bottomMenuList[index].icon,
height: getSize(
24,
),
width: getSize(
24,
),
color: ColorConstant.whiteA700,
),
activeIcon: CustomImageView(
svgPath: bottomMenuList[index].icon,
height: getSize(
24,
),
width: getSize(
24,
),
color: ColorConstant.whiteA700,
),
label: '',
);
}),
onTap: (index) {
selectedIndex = index;
widget.onChanged?.call(bottomMenuList[index].type);
setState(() {});
},
),
);
}
}
enum BottomBarEnum {
Fire,
Searchwhitea70020x20,
Menu,
Overflowmenuwhitea700,
}
class BottomMenuModel {
BottomMenuModel({required this.icon, required this.type});
String icon;
BottomBarEnum type;
}
class DefaultWidget extends StatelessWidget {
const DefaultWidget({super.key});
@override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
padding: const EdgeInsets.all(10),
child: const Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Please replace the respective Widget here',
style: TextStyle(
fontSize: 18,
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,351 @@
import 'package:flutter/material.dart';
import '../utils/color_constants.dart';
import '../utils/size_utils.dart';
class CustomButton extends StatelessWidget {
CustomButton(
{super.key, this.shape,
this.padding,
this.variant,
this.fontStyle,
this.alignment,
this.margin,
this.onTap,
this.width,
this.height,
this.text,
this.prefixWidget,
this.suffixWidget});
ButtonShape? shape;
ButtonPadding? padding;
ButtonVariant? variant;
ButtonFontStyle? fontStyle;
Alignment? alignment;
EdgeInsetsGeometry? margin;
VoidCallback? onTap;
double? width;
double? height;
String? text;
Widget? prefixWidget;
Widget? suffixWidget;
@override
Widget build(BuildContext context) {
return alignment != null
? Align(
alignment: alignment!,
child: _buildButtonWidget(),
)
: _buildButtonWidget();
}
_buildButtonWidget() {
return Padding(
padding: margin ?? EdgeInsets.zero,
child: TextButton(
onPressed: onTap,
style: _buildTextButtonStyle(),
child: _buildButtonWithOrWithoutIcon(),
),
);
}
_buildButtonWithOrWithoutIcon() {
if (prefixWidget != null || suffixWidget != null) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
prefixWidget ?? const SizedBox(),
Text(
text ?? "",
textAlign: TextAlign.center,
style: _setFontStyle(),
),
suffixWidget ?? const SizedBox(),
],
);
} else {
return Text(
text ?? "",
textAlign: TextAlign.center,
style: _setFontStyle(),
);
}
}
_buildTextButtonStyle() {
return TextButton.styleFrom(
fixedSize: Size(
width ?? double.maxFinite,
height ?? getVerticalSize(40),
),
padding: _setPadding(),
backgroundColor: _setColor(),
side: _setTextButtonBorder(),
shape: RoundedRectangleBorder(
borderRadius: _setBorderRadius(),
),
);
}
_setPadding() {
switch (padding) {
case ButtonPadding.PaddingT14:
return getPadding(
top: 14,
right: 14,
bottom: 14,
);
case ButtonPadding.PaddingT7:
return getPadding(
top: 7,
right: 7,
bottom: 7,
);
case ButtonPadding.PaddingAll7:
return getPadding(
all: 7,
);
case ButtonPadding.PaddingAll3:
return getPadding(
all: 3,
);
default:
return getPadding(
all: 14,
);
}
}
_setColor() {
switch (variant) {
case ButtonVariant.FillBlue50:
return ColorConstant.blue50;
case ButtonVariant.FillDeeporangeA10033:
return ColorConstant.deepOrangeA10033;
case ButtonVariant.FillGray10001:
return ColorConstant.gray10001;
case ButtonVariant.FillLightblue100:
return ColorConstant.lightBlue100;
case ButtonVariant.FillRed200:
return ColorConstant.red200;
case ButtonVariant.FillGreenA100:
return ColorConstant.greenA100;
case ButtonVariant.FillBlueA200:
return ColorConstant.blueA200;
case ButtonVariant.FillBluegray50:
return ColorConstant.blueGray50;
case ButtonVariant.OutlineBlueA700:
return null;
default:
return ColorConstant.blueA700;
}
}
_setTextButtonBorder() {
switch (variant) {
case ButtonVariant.OutlineBlueA700:
return BorderSide(
color: ColorConstant.blueA700,
width: getHorizontalSize(
1.00,
),
);
case ButtonVariant.FillBlueA700:
case ButtonVariant.FillBlue50:
case ButtonVariant.FillDeeporangeA10033:
case ButtonVariant.FillGray10001:
case ButtonVariant.FillLightblue100:
case ButtonVariant.FillRed200:
case ButtonVariant.FillGreenA100:
case ButtonVariant.FillBlueA200:
case ButtonVariant.FillBluegray50:
return null;
default:
return null;
}
}
_setBorderRadius() {
switch (shape) {
case ButtonShape.RoundedBorder2:
return BorderRadius.circular(
getHorizontalSize(
2.00,
),
);
case ButtonShape.RoundedBorder16:
return BorderRadius.circular(
getHorizontalSize(
16.00,
),
);
case ButtonShape.Square:
return BorderRadius.circular(0);
default:
return BorderRadius.circular(
getHorizontalSize(
6.00,
),
);
}
}
_setFontStyle() {
switch (fontStyle) {
case ButtonFontStyle.GilroyMedium14:
return TextStyle(
color: ColorConstant.blueA700,
fontSize: getFontSize(
14,
),
fontFamily: 'Gilroy',
fontWeight: FontWeight.w500,
);
case ButtonFontStyle.GilroyMedium14WhiteA700:
return TextStyle(
color: ColorConstant.whiteA700,
fontSize: getFontSize(
14,
),
fontFamily: 'Gilroy',
fontWeight: FontWeight.w500,
);
case ButtonFontStyle.GilroyMedium16Black900:
return TextStyle(
color: ColorConstant.black900,
fontSize: getFontSize(
16,
),
fontFamily: 'Gilroy',
fontWeight: FontWeight.w500,
);
case ButtonFontStyle.SFUIDisplayBold12:
return TextStyle(
color: ColorConstant.whiteA700,
fontSize: getFontSize(
12,
),
fontFamily: 'SF UI Display',
fontWeight: FontWeight.w700,
);
case ButtonFontStyle.GilroyMedium16BlueA700:
return TextStyle(
color: ColorConstant.blueA700,
fontSize: getFontSize(
16,
),
fontFamily: 'Gilroy',
fontWeight: FontWeight.w500,
);
case ButtonFontStyle.GilroyMedium12:
return TextStyle(
color: ColorConstant.deepOrange400,
fontSize: getFontSize(
12,
),
fontFamily: 'Gilroy',
fontWeight: FontWeight.w500,
);
case ButtonFontStyle.GilroyMedium12Red700:
return TextStyle(
color: ColorConstant.red700,
fontSize: getFontSize(
12,
),
fontFamily: 'Gilroy',
fontWeight: FontWeight.w500,
);
case ButtonFontStyle.InterSemiBold10:
return TextStyle(
color: ColorConstant.black90001,
fontSize: getFontSize(
10,
),
fontFamily: 'Inter',
fontWeight: FontWeight.w600,
);
case ButtonFontStyle.RobotoMedium14:
return TextStyle(
color: ColorConstant.whiteA700,
fontSize: getFontSize(
14,
),
fontFamily: 'Roboto',
fontWeight: FontWeight.w500,
);
case ButtonFontStyle.InterRegular14:
return TextStyle(
color: ColorConstant.blueGray400,
fontSize: getFontSize(
14,
),
fontFamily: 'Inter',
fontWeight: FontWeight.w400,
);
default:
return TextStyle(
color: ColorConstant.whiteA700,
fontSize: getFontSize(
16,
),
fontFamily: 'Gilroy',
fontWeight: FontWeight.w500,
);
}
}
}
enum ButtonShape {
Square,
RoundedBorder6,
RoundedBorder2,
RoundedBorder16,
}
enum ButtonPadding {
PaddingAll14,
PaddingT14,
PaddingT7,
PaddingAll7,
PaddingAll3,
}
enum ButtonVariant {
FillBlueA700,
OutlineBlueA700,
FillBlue50,
FillDeeporangeA10033,
FillGray10001,
FillLightblue100,
FillRed200,
FillGreenA100,
FillBlueA200,
FillBluegray50,
}
enum ButtonFontStyle {
GilroyMedium16,
GilroyMedium14,
GilroyMedium14WhiteA700,
GilroyMedium16Black900,
SFUIDisplayBold12,
GilroyMedium16BlueA700,
GilroyMedium12,
GilroyMedium12Red700,
InterSemiBold10,
RobotoMedium14,
InterRegular14,
}

View File

@@ -0,0 +1,147 @@
import 'package:flutter/material.dart';
import '../utils/color_constants.dart';
import '../utils/size_utils.dart';
class CustomCheckbox extends StatelessWidget {
CustomCheckbox(
{super.key, this.fontStyle,
this.alignment,
this.isRightCheck = false,
this.iconSize,
this.value,
this.onChange,
this.text,
this.width,
this.margin});
CheckboxFontStyle? fontStyle;
Alignment? alignment;
bool? isRightCheck;
double? iconSize;
bool? value;
Function(bool)? onChange;
String? text;
double? width;
EdgeInsetsGeometry? margin;
@override
Widget build(BuildContext context) {
return alignment != null
? Align(
alignment: alignment ?? Alignment.center,
child: _buildCheckboxWidget(),
)
: _buildCheckboxWidget();
}
_buildCheckboxWidget() {
return InkWell(
onTap: () {
value = !(value!);
onChange!(value!);
},
child: Container(
width: width,
margin: margin ?? EdgeInsets.zero,
child: isRightCheck! ? getRightSideCheckbox() : getLeftSideCheckbox(),
),
);
}
Widget getRightSideCheckbox() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.only(
right: 8,
),
child: getTextWidget(),
),
getCheckboxWidget(),
],
);
}
Widget getLeftSideCheckbox() {
return Row(
children: [
getCheckboxWidget(),
Padding(
padding: const EdgeInsets.only(
left: 8,
),
child: getTextWidget(),
),
],
);
}
Widget getTextWidget() {
return Text(
text ?? "",
textAlign: TextAlign.center,
style: _setFontStyle(),
);
}
Widget getCheckboxWidget() {
return SizedBox(
height: iconSize,
width: iconSize,
child: Checkbox(
value: value ?? false,
onChanged: (value) {
onChange!(value!);
},
checkColor: ColorConstant.whiteA700,
visualDensity: const VisualDensity(
vertical: -4,
horizontal: -4,
),
),
);
}
_setFontStyle() {
switch (fontStyle) {
case CheckboxFontStyle.GilroyMedium16:
return TextStyle(
color: ColorConstant.blueGray900,
fontSize: getFontSize(
16,
),
fontFamily: 'Gilroy',
fontWeight: FontWeight.w500,
);
case CheckboxFontStyle.GilroyMedium14:
return TextStyle(
color: ColorConstant.blueGray300,
fontSize: getFontSize(
14,
),
fontFamily: 'Gilroy',
fontWeight: FontWeight.w500,
);
default:
return TextStyle(
color: ColorConstant.blueGray400,
fontSize: getFontSize(
14,
),
fontFamily: 'Gilroy',
fontWeight: FontWeight.w400,
);
}
}
}
enum CheckboxFontStyle { GilroyRegular14, GilroyMedium16, GilroyMedium14 }

View File

@@ -0,0 +1,199 @@
import 'package:flutter/material.dart';
import '../utils/color_constants.dart';
import '../utils/size_utils.dart';
class CustomDropDown extends StatelessWidget {
CustomDropDown(
{super.key, this.shape,
this.padding,
this.variant,
this.fontStyle,
this.alignment,
this.width,
this.margin,
this.focusNode,
this.icon,
this.hintText,
this.prefix,
this.prefixConstraints,
this.items,
this.onChanged,
this.validator});
DropDownShape? shape;
DropDownPadding? padding;
DropDownVariant? variant;
DropDownFontStyle? fontStyle;
Alignment? alignment;
double? width;
EdgeInsetsGeometry? margin;
FocusNode? focusNode;
Widget? icon;
String? hintText;
Widget? prefix;
BoxConstraints? prefixConstraints;
List<String>? items;
Function(String)? onChanged;
FormFieldValidator<String>? validator;
@override
Widget build(BuildContext context) {
return alignment != null
? Align(
alignment: alignment ?? Alignment.center,
child: _buildDropDownWidget(),
)
: _buildDropDownWidget();
}
_buildDropDownWidget() {
return Container(
width: width ?? double.maxFinite,
margin: margin,
child: DropdownButtonFormField(
focusNode: focusNode,
icon: icon,
style: _setFontStyle(),
decoration: _buildDecoration(),
items: items?.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(
value,
overflow: TextOverflow.ellipsis,
),
);
}).toList(),
onChanged: (value) {
onChanged!(value.toString());
},
validator: validator,
),
);
}
_buildDecoration() {
return InputDecoration(
hintText: hintText ?? "",
hintStyle: _setFontStyle(),
border: _setBorderStyle(),
enabledBorder: _setBorderStyle(),
focusedBorder: _setBorderStyle(),
prefixIcon: prefix,
prefixIconConstraints: prefixConstraints,
fillColor: _setFillColor(),
filled: _setFilled(),
isDense: true,
contentPadding: _setPadding(),
);
}
_setFontStyle() {
switch (fontStyle) {
case DropDownFontStyle.GilroyRegular16:
return TextStyle(
color: ColorConstant.blueGray200,
fontSize: getFontSize(
16,
),
fontFamily: 'Gilroy',
fontWeight: FontWeight.w400,
);
default:
return TextStyle(
color: ColorConstant.blueGray900,
fontSize: getFontSize(
16,
),
fontFamily: 'Gilroy',
fontWeight: FontWeight.w600,
);
}
}
_setOutlineBorderRadius() {
switch (shape) {
default:
return BorderRadius.circular(
getHorizontalSize(
6.00,
),
);
}
}
_setBorderStyle() {
switch (variant) {
case DropDownVariant.None:
return InputBorder.none;
default:
return OutlineInputBorder(
borderRadius: _setOutlineBorderRadius(),
borderSide: BorderSide(
color: ColorConstant.blueGray100,
width: 1,
),
);
}
}
_setFillColor() {
switch (variant) {
default:
return ColorConstant.whiteA700;
}
}
_setFilled() {
switch (variant) {
case DropDownVariant.None:
return false;
default:
return true;
}
}
_setPadding() {
switch (padding) {
default:
return getPadding(
left: 10,
top: 10,
bottom: 10,
);
}
}
}
enum DropDownShape {
RoundedBorder6,
}
enum DropDownPadding {
PaddingT10,
}
enum DropDownVariant {
None,
OutlineBluegray100,
}
enum DropDownFontStyle {
GilroySemiBold16,
GilroyRegular16,
}

View File

@@ -0,0 +1,161 @@
import 'package:flutter/material.dart';
import '../utils/color_constants.dart';
import '../utils/size_utils.dart';
import 'custom_text_form_field.dart';
class CustomDropdownFormField extends StatelessWidget {
CustomDropdownFormField({super.key,
this.shape,
this.padding,
this.initialValue,
this.variant,
this.fontStyle,
this.alignment,
this.width,
this.margin,
this.items,
this.value,
this.hintText,
this.onChanged,
this.validator,
this.onSaved,
});
TextFormFieldShape? shape;
TextFormFieldPadding? padding;
String? initialValue;
TextFormFieldVariant? variant;
TextFormFieldFontStyle? fontStyle;
Alignment? alignment;
double? width;
EdgeInsetsGeometry? margin;
List<DropdownMenuItem<String>>? items;
String? value;
String? hintText;
void Function(String?)? onChanged;
FormFieldValidator<String>? validator;
void Function(String?)? onSaved;
@override
Widget build(BuildContext context) {
return alignment != null
? Align(
alignment: alignment ?? Alignment.center,
child: _buildDropdownFormFieldWidget(),
)
: _buildDropdownFormFieldWidget();
}
_buildDropdownFormFieldWidget() {
return Container(
width: width ?? double.maxFinite,
margin: margin,
child: DropdownButtonFormField<String>(
value: value,
items: items,
onChanged: onChanged,
validator: validator,
onSaved: onSaved,
decoration: _buildDecoration(),
),
);
}
_buildDecoration() {
return InputDecoration(
hintText: hintText ?? "",
hintStyle: _setFontStyle(),
border: _setBorderStyle(),
enabledBorder: _setBorderStyle(),
focusedBorder: _setBorderStyle(),
fillColor: _setFillColor(),
filled: _setFilled(),
isDense: true,
contentPadding: _setPadding(),
);
}
_setFontStyle() {
switch (fontStyle) {
// Add cases for different font styles if needed
default:
return TextStyle(
color: ColorConstant.blueGray200,
fontSize: getFontSize(16),
fontFamily: 'Gilroy',
fontWeight: FontWeight.w500,
);
}
}
_setOutlineBorderRadius() {
switch (shape) {
case TextFormFieldShape.CircleBorder16:
return BorderRadius.circular(getHorizontalSize(16.00));
default:
return BorderRadius.circular(getHorizontalSize(6.00));
}
}
_setBorderStyle() {
switch (variant) {
case TextFormFieldVariant.FillBlue50:
return OutlineInputBorder(
borderRadius: _setOutlineBorderRadius(),
borderSide: BorderSide.none,
);
// Add cases for different variants if needed
default:
return OutlineInputBorder(
borderRadius: _setOutlineBorderRadius(),
borderSide: BorderSide(
color: ColorConstant.blueGray100,
width: 1,
),
);
}
}
_setFillColor() {
switch (variant) {
case TextFormFieldVariant.FillBlue50:
return ColorConstant.blue50;
// Add cases for different variants if needed
default:
return ColorConstant.whiteA700;
}
}
_setFilled() {
switch (variant) {
case TextFormFieldVariant.FillBlue50:
return true;
// Add cases for different variants if needed
default:
return true;
}
}
_setPadding() {
switch (padding) {
case TextFormFieldPadding.PaddingAll11:
return getPadding(all: 11);
// Add cases for different paddings if needed
default:
return getPadding(all: 11);
}
}
}

View File

@@ -0,0 +1,48 @@
import 'package:flutter/material.dart';
class CustomDropdownField<T> extends StatelessWidget {
final String? hintText;
final List<DropdownMenuItem<T>> items;
final T? value;
final void Function(T?)? onChanged;
final void Function(T?)? onSaved;
final String? Function(T?)? validator;
const CustomDropdownField(
{super.key, this.hintText,
required this.items,
this.value,
this.onChanged,
this.onSaved,
this.validator});
@override
Widget build(BuildContext context) {
return FormField<T>(
builder: (FormFieldState<T> state) {
return InputDecorator(
decoration: InputDecoration(
enabledBorder: InputBorder.none,
hintStyle: TextStyle(color: Colors.black.withOpacity(.4)),
contentPadding: const EdgeInsets.only(left: 10, right: 10, bottom: 6),
errorText: state.errorText,
),
child: DropdownButtonFormField<T>(
value: value,
items: items,
onChanged: (T? newValue) {
state.didChange(newValue);
if (onChanged != null) {
onChanged!(newValue);
}
},
onSaved: onSaved,
hint: Text(hintText ?? ''),
style: TextStyle(color: Colors.black.withOpacity(.8)),
//isExpanded: true,
validator: validator),
);
},
);
}
}

View File

@@ -0,0 +1,58 @@
// import 'package:flutter/material.dart';
// import '../core/app_export.dart';
// import 'base_button.dart';
// class CustomElevatedButton extends BaseButton {
// const CustomElevatedButton(
// {super.key, Key? key,
// this.decoration,
// this.leftIcon,
// this.rightIcon,
// super.margin,
// super.onPressed,
// super.buttonStyle,
// super.alignment,
// super.buttonTextStyle,
// super.isDisabled,
// super.height,
// super.width,
// required super.text});
// final BoxDecoration? decoration;
// final Widget? leftIcon;
// final Widget? rightIcon;
// @override
// Widget build(BuildContext context) {
// return alignment != null
// ? Align(
// alignment: alignment ?? Alignment.center,
// child: buildElevatedButtonWidget)
// : buildElevatedButtonWidget;
// }
// Widget get buildElevatedButtonWidget => Container(
// height: height ?? 41.v,
// width: width ?? double.maxFinite,
// margin: margin,
// decoration: decoration,
// child: ElevatedButton(
// style: buttonStyle,
// onPressed: isDisabled ?? false ? null : onPressed ?? () {},
// child: Row(
// mainAxisAlignment: MainAxisAlignment.center,
// crossAxisAlignment: CrossAxisAlignment.center,
// children: [
// leftIcon ?? const SizedBox.shrink(),
// Text(
// text,
// style: buttonTextStyle ?? CustomTextStyles.titleMediumWhiteA700,
// ),
// rightIcon ?? const SizedBox.shrink()
// ],
// ),
// ),
// );
// }

View File

@@ -0,0 +1,91 @@
import 'package:flutter/material.dart';
import '../utils/size_utils.dart';
class CustomFloatingButton extends StatelessWidget {
CustomFloatingButton(
{super.key, this.shape,
this.variant,
this.alignment,
this.margin,
this.onTap,
this.width,
this.height,
this.child});
FloatingButtonShape? shape;
FloatingButtonVariant? variant;
Alignment? alignment;
EdgeInsetsGeometry? margin;
VoidCallback? onTap;
double? width;
double? height;
Widget? child;
@override
Widget build(BuildContext context) {
return alignment != null
? Align(
alignment: alignment ?? Alignment.center,
child: _buildFabWidget(),
)
: _buildFabWidget();
}
_buildFabWidget() {
return Padding(
padding: margin ?? EdgeInsets.zero,
child: FloatingActionButton(
backgroundColor: _setColor(),
onPressed: onTap,
child: Container(
alignment: Alignment.center,
width: getSize(width ?? 0),
height: getSize(height ?? 0),
decoration: _buildDecoration(),
child: child,
),
),
);
}
_buildDecoration() {
return BoxDecoration(
color: _setColor(),
borderRadius: _setBorderRadius(),
);
}
_setColor() {
switch (variant) {
default:
return const Color.fromRGBO(253, 202, 101, 1.0);
}
}
_setBorderRadius() {
switch (shape) {
default:
return BorderRadius.circular(
getHorizontalSize(
6.00,
),
);
}
}
}
enum FloatingButtonShape {
RoundedBorder6,
}
enum FloatingButtonVariant {
FillBlueA700,
}

View File

@@ -0,0 +1,152 @@
import 'package:flutter/material.dart';
import '../core/app_export.dart';
class CustomFloatingTextField extends StatelessWidget {
const CustomFloatingTextField(
{super.key,
this.alignment,
this.width,
this.scrollPadding,
this.controller,
this.focusNode,
this.autofocus = false,
this.textStyle,
this.obscureText = false,
this.textInputAction = TextInputAction.next,
this.textInputType = TextInputType.text,
this.maxLines,
this.hintText,
this.hintStyle,
this.labelText,
this.labelStyle,
this.prefix,
this.prefixConstraints,
this.suffix,
this.suffixConstraints,
this.contentPadding,
this.borderDecoration,
this.fillColor,
this.filled = true,
this.validator});
final Alignment? alignment;
final double? width;
final TextEditingController? scrollPadding;
final TextEditingController? controller;
final FocusNode? focusNode;
final bool? autofocus;
final TextStyle? textStyle;
final bool? obscureText;
final TextInputAction? textInputAction;
final TextInputType? textInputType;
final int? maxLines;
final String? hintText;
final TextStyle? hintStyle;
final String? labelText;
final TextStyle? labelStyle;
final Widget? prefix;
final BoxConstraints? prefixConstraints;
final Widget? suffix;
final BoxConstraints? suffixConstraints;
final EdgeInsets? contentPadding;
final InputBorder? borderDecoration;
final Color? fillColor;
final bool? filled;
final FormFieldValidator<String>? validator;
@override
Widget build(BuildContext context) {
return alignment != null
? Align(
alignment: alignment ?? Alignment.center,
child: floatingTextFieldWidget(context))
: floatingTextFieldWidget(context);
}
Widget floatingTextFieldWidget(BuildContext context) => SizedBox(
width: width ?? double.maxFinite,
child: TextFormField(
scrollPadding:
EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
controller: controller,
focusNode: focusNode,
onTapOutside: (event) {
if (focusNode != null) {
focusNode?.unfocus();
} else {
FocusManager.instance.primaryFocus?.unfocus();
}
},
autofocus: autofocus!,
style: textStyle ?? theme.textTheme.bodyMedium,
obscureText: obscureText!,
textInputAction: textInputAction,
keyboardType: textInputType,
maxLines: maxLines ?? 1,
decoration: decoration,
validator: validator,
),
);
InputDecoration get decoration => InputDecoration(
hintText: hintText ?? "",
hintStyle: hintStyle ?? theme.textTheme.bodyMedium,
labelText: labelText ?? "",
labelStyle: labelStyle,
prefixIcon: prefix,
prefixIconConstraints: prefixConstraints,
suffixIcon: suffix,
suffixIconConstraints: suffixConstraints,
isDense: true,
contentPadding:
contentPadding ?? EdgeInsets.fromLTRB(8.h, 24.v, 8.h, 8.v),
fillColor: fillColor ?? appTheme.gray50,
filled: filled,
border: borderDecoration ??
OutlineInputBorder(
borderRadius: BorderRadius.circular(8.h),
borderSide: BorderSide(
color: theme.colorScheme.errorContainer,
width: 1,
),
),
enabledBorder: borderDecoration ??
OutlineInputBorder(
borderRadius: BorderRadius.circular(8.h),
borderSide: BorderSide(
color: theme.colorScheme.errorContainer,
width: 1,
),
),
focusedBorder: borderDecoration ??
OutlineInputBorder(
borderRadius: BorderRadius.circular(8.h),
borderSide: BorderSide(
color: theme.colorScheme.errorContainer,
width: 1,
),
),
);
}

View File

@@ -0,0 +1,398 @@
// ignore_for_file: constant_identifier_names
import 'package:base_project/core/app_export.dart';
import 'package:flutter/material.dart';
import '../utils/color_constants.dart';
import '../utils/size_utils.dart';
extension IconButtonStyleHelper on CustomIconButton {
static BoxDecoration get fillErrorContainer => BoxDecoration(
color: theme.colorScheme.errorContainer,
);
static BoxDecoration get fillDeepOrangeA => BoxDecoration(
color: appTheme.deepOrangeA400,
borderRadius: BorderRadius.circular(26.h),
);
static BoxDecoration get fillLightBlue => BoxDecoration(
color: appTheme.lightBlue900,
borderRadius: BorderRadius.circular(4.h),
);
static BoxDecoration get fillBlue => BoxDecoration(
color: appTheme.blue400,
borderRadius: BorderRadius.circular(25.h),
);
static BoxDecoration get outlineIndigoTL12 => BoxDecoration(
color: appTheme.lightGreenA200,
borderRadius: BorderRadius.circular(12.h),
border: Border.all(
color: appTheme.indigo50,
width: 1.h,
),
);
static BoxDecoration get fillPrimaryContainer => BoxDecoration(
color: theme.colorScheme.primaryContainer,
);
static BoxDecoration get fillPrimary => BoxDecoration(
color: theme.colorScheme.primary,
borderRadius: BorderRadius.circular(16.h),
);
static BoxDecoration get outlineIndigo => BoxDecoration(
color: appTheme.lightGreenA200,
borderRadius: BorderRadius.circular(12.h),
border: Border.all(
color: appTheme.indigo50,
width: 1.h,
),
);
static BoxDecoration get fillBlueA => BoxDecoration(
color: appTheme.blueA20002,
);
static BoxDecoration get gradientLightGreenAToLightGreenA => BoxDecoration(
borderRadius: BorderRadius.circular(32.h),
gradient: LinearGradient(
begin: const Alignment(0.5, 0),
end: const Alignment(0.5, 1),
colors: [appTheme.lightGreenA20001, appTheme.lightGreenA20000],
),
);
}
class CustomIconButton extends StatelessWidget {
final EdgeInsetsGeometry? padding;
CustomIconButton({super.key,
this.shape,
this.padding,
this.variant,
this.alignment,
this.margin,
this.width,
this.height,
this.child,
this.onTap,
this.decoration,
this.padding_f,
});
// file===green color for all boottom bar
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
height: height,
width: width,
padding: padding ?? EdgeInsets.zero,
decoration: decoration ??
BoxDecoration(
color: const Color.fromRGBO(253, 202, 101, 1.0),
borderRadius: BorderRadius.circular(
// Set borderRadius to half of the smallest dimension
width! < height! ? width! / 2 : height! / 2,
), // Adjust as needed
),
child: Center(child: child), // Ensure child is centered
),
);
}
// @override
// Widget build(BuildContext context) {
// return GestureDetector(
// onTap: onTap,
// child: Container(
// height: height,
// width: width,
// padding: padding ?? EdgeInsets.zero,
// decoration: decoration ??
// BoxDecoration(
// color: Colors.blue,
// borderRadius: BorderRadius.circular(16), // Adjust as needed
// ),
// child: Center(child: child), // Ensure child is centered
// ),
// );
// }
IconButtonShape? shape;
// IconButtonPadding? padding;
IconButtonVariant? variant;
Alignment? alignment;
EdgeInsetsGeometry? margin;
double? width;
double? height;
Widget? child;
VoidCallback? onTap;
final BoxDecoration? decoration;
final EdgeInsetsGeometry? padding_f;
// @override
// Widget build(BuildContext context) {
// return alignment != null
// ? Align(
// alignment: alignment ?? Alignment.center,
// child: _buildIconButtonWidget(),
// )
// : _buildIconButtonWidget();
// }
Widget get iconButtonWidget => SizedBox(
height: height ?? 0,
width: width ?? 0,
child: IconButton(
padding: EdgeInsets.zero,
icon: Container(
height: height ?? 0,
width: width ?? 0,
padding: padding_f ?? EdgeInsets.zero,
decoration: decoration ??
BoxDecoration(
color: appTheme.lightGreenA200,
borderRadius: BorderRadius.circular(25.h),
),
child: child,
),
onPressed: onTap,
),
);
_buildIconButtonWidget() {
return Padding(
padding: margin ?? EdgeInsets.zero,
child: IconButton(
visualDensity: const VisualDensity(
vertical: -4,
horizontal: -4,
),
iconSize: getSize(height ?? 0),
padding: const EdgeInsets.all(0),
icon: Container(
alignment: Alignment.center,
width: getSize(width ?? 0),
height: getSize(height ?? 0),
padding: _setPadding(),
decoration: _buildDecoration(),
child: child,
),
onPressed: onTap,
),
);
}
_buildDecoration() {
return BoxDecoration(
color: _setColor(),
border: _setBorder(),
borderRadius: _setBorderRadius(),
boxShadow: _setBoxShadow(),
);
}
EdgeInsets _setPadding() {
if (padding == IconButtonPadding.PaddingAll4) {
return const EdgeInsets.all(4);
} else if (padding == IconButtonPadding.PaddingAll16) {
return const EdgeInsets.all(16);
} else if (padding == IconButtonPadding.PaddingAll8) {
return const EdgeInsets.all(8);
} else {
return const EdgeInsets.all(11);
}
}
// _setPadding() {
// switch (padding) {
// case IconButtonPadding.PaddingAll4:
// return getPadding(
// all: 4,
// );
// case IconButtonPadding.PaddingAll16:
// return getPadding(
// all: 16,
// );
// case IconButtonPadding.PaddingAll8:
// return getPadding(
// all: 8,
// );
// default:
// return getPadding(
// all: 11,
// );
// }
// }
_setColor() {
switch (variant) {
case IconButtonVariant.FillBlueA700:
return ColorConstant.blueA700;
case IconButtonVariant.OutlineGray80049:
return ColorConstant.whiteA700;
case IconButtonVariant.FillGray300:
return ColorConstant.gray300;
case IconButtonVariant.FillGray100:
return ColorConstant.gray100;
case IconButtonVariant.FillBlack90001:
return ColorConstant.black90001;
case IconButtonVariant.OutlineBluegray400:
return ColorConstant.whiteA700;
case IconButtonVariant.FillBlueA200:
return ColorConstant.blueA200;
case IconButtonVariant.OutlineBlueA700:
case IconButtonVariant.OutlineBlue50:
return null;
default:
return ColorConstant.blue50;
}
}
_setBorder() {
switch (variant) {
case IconButtonVariant.OutlineBlueA700:
return Border.all(
color: ColorConstant.blueA700,
width: getHorizontalSize(
1.00,
),
);
case IconButtonVariant.OutlineGray80049:
return Border.all(
color: ColorConstant.gray80049,
width: getHorizontalSize(
1.00,
),
);
case IconButtonVariant.OutlineBlue50:
return Border.all(
color: ColorConstant.blue50,
width: getHorizontalSize(
1.00,
),
);
case IconButtonVariant.OutlineBluegray400:
return Border.all(
color: ColorConstant.blueGray400,
width: getHorizontalSize(
1.00,
),
);
case IconButtonVariant.FillBlue50:
case IconButtonVariant.FillBlueA700:
case IconButtonVariant.FillGray300:
case IconButtonVariant.FillGray100:
case IconButtonVariant.FillBlack90001:
case IconButtonVariant.FillBlueA200:
return null;
default:
return null;
}
}
_setBorderRadius() {
switch (shape) {
case IconButtonShape.CircleBorder15:
return BorderRadius.circular(
getHorizontalSize(
15.00,
),
);
case IconButtonShape.RoundedBorder26:
return BorderRadius.circular(
getHorizontalSize(
26.00,
),
);
case IconButtonShape.CircleBorder10:
return BorderRadius.circular(
getHorizontalSize(
10.00,
),
);
case IconButtonShape.CircleBorder30:
return BorderRadius.circular(
getHorizontalSize(
30.00,
),
);
default:
return BorderRadius.circular(
getHorizontalSize(
6.00,
),
);
}
}
_setBoxShadow() {
switch (variant) {
case IconButtonVariant.OutlineBlueA700:
return [
BoxShadow(
color: ColorConstant.indigoA20033,
spreadRadius: getHorizontalSize(
2.00,
),
blurRadius: getHorizontalSize(
2.00,
),
offset: const Offset(
0,
4,
),
),
];
case IconButtonVariant.FillBlue50:
case IconButtonVariant.FillBlueA700:
case IconButtonVariant.OutlineGray80049:
case IconButtonVariant.FillGray300:
case IconButtonVariant.FillGray100:
case IconButtonVariant.FillBlack90001:
case IconButtonVariant.OutlineBlue50:
case IconButtonVariant.OutlineBluegray400:
case IconButtonVariant.FillBlueA200:
return null;
default:
return null;
}
}
}
enum IconButtonShape {
RoundedBorder6,
CircleBorder15,
RoundedBorder26,
CircleBorder10,
CircleBorder30,
}
enum IconButtonPadding {
PaddingAll4,
PaddingAll16,
PaddingAll8,
PaddingAll11,
}
enum IconButtonVariant {
FillBlue50,
FillBlueA700,
OutlineBlueA700,
OutlineGray80049,
FillGray300,
FillGray100,
FillBlack90001,
OutlineBlue50,
OutlineBluegray400,
FillBlueA200,
}

View File

@@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
import '../core/app_export.dart';
class CustomIconButton_f extends StatelessWidget {
const CustomIconButton_f(
{super.key,
this.alignment,
this.height,
this.width,
this.padding,
this.decoration,
this.child,
this.onTap});
final Alignment? alignment;
final double? height;
final double? width;
final EdgeInsetsGeometry? padding;
final BoxDecoration? decoration;
final Widget? child;
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {
return alignment != null
? Align(
alignment: alignment ?? Alignment.center, child: iconButtonWidget)
: iconButtonWidget;
}
Widget get iconButtonWidget => SizedBox(
height: height ?? 0,
width: width ?? 0,
child: IconButton(
padding: EdgeInsets.zero,
icon: Container(
height: height ?? 0,
width: width ?? 0,
padding: padding ?? EdgeInsets.zero,
decoration: decoration ??
BoxDecoration(
color: appTheme.whiteA700,
borderRadius: BorderRadius.circular(12.h),
border: Border.all(
color: theme.colorScheme.onPrimaryContainer,
width: 1.h,
),
),
child: child,
),
onPressed: onTap,
),
);
}

View File

@@ -0,0 +1,154 @@
// ignore_for_file: must_be_immutable
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
class CustomImageView extends StatelessWidget {
///[url] is required parameter for fetching network image
String? url;
///[imagePath] is required parameter for showing png,jpg,etc image
String? imagePath;
///[svgPath] is required parameter for showing svg image
String? svgPath;
///[file] is required parameter for fetching image file
File? file;
double? height;
double? width;
Color? color;
BoxFit? fit;
final String placeHolder;
Alignment? alignment;
VoidCallback? onTap;
EdgeInsetsGeometry? margin;
BorderRadius? radius;
BoxBorder? border;
///a [CustomImageView] it can be used for showing any type of images
/// it will shows the placeholder image if image is not found on network image
CustomImageView({
super.key,
this.url,
this.imagePath,
this.svgPath,
this.file,
this.height,
this.width,
this.color,
this.fit,
this.alignment,
this.onTap,
this.radius,
this.margin,
this.border,
this.placeHolder = 'assets/images/image_not_found.png',
});
@override
Widget build(BuildContext context) {
return alignment != null
? Align(
alignment: alignment!,
child: _buildWidget(),
)
: _buildWidget();
}
Widget _buildWidget() {
return Padding(
padding: margin ?? EdgeInsets.zero,
child: InkWell(
onTap: onTap,
child: _buildCircleImage(),
),
);
}
///build the image with border radius
_buildCircleImage() {
if (radius != null) {
return ClipRRect(
borderRadius: radius ?? BorderRadius.zero,
child: _buildImageWithBorder(),
);
} else {
return _buildImageWithBorder();
}
}
///build the image with border and border radius style
_buildImageWithBorder() {
if (border != null) {
return Container(
decoration: BoxDecoration(
border: border,
borderRadius: radius,
),
child: _buildImageView(),
);
} else {
return _buildImageView();
}
}
Widget _buildImageView() {
if (svgPath != null && svgPath!.isNotEmpty) {
return SizedBox(
height: height,
width: width,
child: SvgPicture.asset(
svgPath!,
height: height,
width: width,
fit: fit ?? BoxFit.contain,
color: color,
),
);
} else if (file != null && file!.path.isNotEmpty) {
return Image.file(
file!,
height: height,
width: width,
fit: fit ?? BoxFit.cover,
color: color,
);
} else if (url != null && url!.isNotEmpty) {
return CachedNetworkImage(
height: height,
width: width,
fit: fit,
imageUrl: url!,
color: color,
placeholder: (context, url) => SizedBox(
height: 30,
width: 30,
child: LinearProgressIndicator(
color: Colors.grey.shade200,
backgroundColor: Colors.grey.shade100,
),
),
errorWidget: (context, url, error) => Image.asset(
placeHolder,
height: height,
width: width,
fit: fit ?? BoxFit.cover,
),
);
} else if (imagePath != null && imagePath!.isNotEmpty) {
return Image.asset(
imagePath!,
height: height,
width: width,
fit: fit ?? BoxFit.cover,
color: color,
);
}
return const SizedBox();
}
}

Some files were not shown because too many files have changed in this diff Show More