baseproject
This commit is contained in:
45
base_project/lib/Reuseable/reusable_date_picker_field.dart
Normal file
45
base_project/lib/Reuseable/reusable_date_picker_field.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
74
base_project/lib/Reuseable/reusable_dropdown_field.dart
Normal file
74
base_project/lib/Reuseable/reusable_dropdown_field.dart
Normal 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;
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
77
base_project/lib/Reuseable/reusable_text_field.dart
Normal file
77
base_project/lib/Reuseable/reusable_text_field.dart
Normal 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(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
69
base_project/lib/commans/widgets/custom_textform_field.dart
Normal file
69
base_project/lib/commans/widgets/custom_textform_field.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
25
base_project/lib/commans/widgets/custome_drawe_item.dart
Normal file
25
base_project/lib/commans/widgets/custome_drawe_item.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
327
base_project/lib/commans/widgets/custome_drawer.dart
Normal file
327
base_project/lib/commans/widgets/custome_drawer.dart
Normal 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);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
6
base_project/lib/core/app_export.dart
Normal file
6
base_project/lib/core/app_export.dart
Normal 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';
|
||||
229
base_project/lib/core/constants/ui_constants.dart
Normal file
229
base_project/lib/core/constants/ui_constants.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
81
base_project/lib/core/network/network_info.dart
Normal file
81
base_project/lib/core/network/network_info.dart
Normal 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;
|
||||
// }
|
||||
// }
|
||||
266
base_project/lib/core/providers/dynamic_theme_provider.dart
Normal file
266
base_project/lib/core/providers/dynamic_theme_provider.dart
Normal 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(),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
20
base_project/lib/core/providers/theme_provider.dart
Normal file
20
base_project/lib/core/providers/theme_provider.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
624
base_project/lib/core/theme/app_theme.dart
Normal file
624
base_project/lib/core/theme/app_theme.dart
Normal 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(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
126
base_project/lib/core/theme/color_scheme.dart
Normal file
126
base_project/lib/core/theme/color_scheme.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
355
base_project/lib/core/theme/dynamic_color_scheme.dart
Normal file
355
base_project/lib/core/theme/dynamic_color_scheme.dart
Normal 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,
|
||||
];
|
||||
}
|
||||
}
|
||||
277
base_project/lib/core/theme/text_theme.dart
Normal file
277
base_project/lib/core/theme/text_theme.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
16
base_project/lib/core/utils/date_time_utils.dart
Normal file
16
base_project/lib/core/utils/date_time_utils.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
93
base_project/lib/core/utils/size_utils.dart
Normal file
93
base_project/lib/core/utils/size_utils.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
13
base_project/lib/core/utils/validation_functions.dart
Normal file
13
base_project/lib/core/utils/validation_functions.dart
Normal 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;
|
||||
}
|
||||
81
base_project/lib/data/exceptions/app_exceptions.dart
Normal file
81
base_project/lib/data/exceptions/app_exceptions.dart
Normal 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: ");
|
||||
}
|
||||
7
base_project/lib/data/network/base_network_service.dart
Normal file
7
base_project/lib/data/network/base_network_service.dart
Normal 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);
|
||||
}
|
||||
157
base_project/lib/data/network/network_api_service.dart
Normal file
157
base_project/lib/data/network/network_api_service.dart
Normal 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
142
base_project/lib/data/network/no-token_network_api_service.dart
Normal file
142
base_project/lib/data/network/no-token_network_api_service.dart
Normal 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
36
base_project/lib/data/notification/notification_service.dart
Normal file
36
base_project/lib/data/notification/notification_service.dart
Normal 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,
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
23
base_project/lib/data/response/api_response.dart
Normal file
23
base_project/lib/data/response/api_response.dart
Normal 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";
|
||||
}
|
||||
}
|
||||
1
base_project/lib/data/response/status.dart
Normal file
1
base_project/lib/data/response/status.dart
Normal file
@@ -0,0 +1 @@
|
||||
enum Status {LOADING,SUCCESS,ERROR}
|
||||
104
base_project/lib/main.dart
Normal file
104
base_project/lib/main.dart
Normal 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!,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
210
base_project/lib/model/system_params_model.dart
Normal file
210
base_project/lib/model/system_params_model.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
57
base_project/lib/model/user/logged_in_user.dart
Normal file
57
base_project/lib/model/user/logged_in_user.dart
Normal 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));
|
||||
}
|
||||
}
|
||||
49
base_project/lib/model/user/user_model_admin.dart
Normal file
49
base_project/lib/model/user/user_model_admin.dart
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
26
base_project/lib/model/user/user_profile.dart
Normal file
26
base_project/lib/model/user/user_profile.dart
Normal 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'],
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
71
base_project/lib/repository/auth_repo.dart
Normal file
71
base_project/lib/repository/auth_repo.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
82
base_project/lib/repository/profile/profile_repo.dart
Normal file
82
base_project/lib/repository/profile/profile_repo.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
80
base_project/lib/repository/system_params_repo.dart
Normal file
80
base_project/lib/repository/system_params_repo.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
26
base_project/lib/resources/api_constants.dart
Normal file
26
base_project/lib/resources/api_constants.dart
Normal 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';
|
||||
}
|
||||
64
base_project/lib/resources/app_colors.dart
Normal file
64
base_project/lib/resources/app_colors.dart
Normal 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)];
|
||||
|
||||
|
||||
}
|
||||
87
base_project/lib/routes/app_routes.dart
Normal file
87
base_project/lib/routes/app_routes.dart
Normal 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();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
base_project/lib/routes/route_names.dart
Normal file
18
base_project/lib/routes/route_names.dart
Normal 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';
|
||||
}
|
||||
221
base_project/lib/shared/widgets/app_bar/modern_app_bar.dart
Normal file
221
base_project/lib/shared/widgets/app_bar/modern_app_bar.dart
Normal 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');
|
||||
}
|
||||
}
|
||||
342
base_project/lib/shared/widgets/buttons/modern_button.dart
Normal file
342
base_project/lib/shared/widgets/buttons/modern_button.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
176
base_project/lib/shared/widgets/buttons/quick_action_button.dart
Normal file
176
base_project/lib/shared/widgets/buttons/quick_action_button.dart
Normal 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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
355
base_project/lib/shared/widgets/cards/dashboard_card.dart
Normal file
355
base_project/lib/shared/widgets/cards/dashboard_card.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
357
base_project/lib/shared/widgets/inputs/modern_image_picker.dart
Normal file
357
base_project/lib/shared/widgets/inputs/modern_image_picker.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
298
base_project/lib/shared/widgets/inputs/modern_text_field.dart
Normal file
298
base_project/lib/shared/widgets/inputs/modern_text_field.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
252
base_project/lib/shared/widgets/navigation/modern_drawer.dart
Normal file
252
base_project/lib/shared/widgets/navigation/modern_drawer.dart
Normal 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,
|
||||
});
|
||||
}
|
||||
275
base_project/lib/shared/widgets/theme_preview.dart
Normal file
275
base_project/lib/shared/widgets/theme_preview.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
120
base_project/lib/shared/widgets/theme_toggle.dart
Normal file
120
base_project/lib/shared/widgets/theme_toggle.dart
Normal 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),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
662
base_project/lib/theme/app_decoration.dart
Normal file
662
base_project/lib/theme/app_decoration.dart
Normal 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;
|
||||
|
||||
1049
base_project/lib/theme/app_style.dart
Normal file
1049
base_project/lib/theme/app_style.dart
Normal file
File diff suppressed because it is too large
Load Diff
143
base_project/lib/theme/custom_button_style.dart
Normal file
143
base_project/lib/theme/custom_button_style.dart
Normal 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)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
492
base_project/lib/theme/custom_text_style.dart
Normal file
492
base_project/lib/theme/custom_text_style.dart
Normal 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;
|
||||
}
|
||||
263
base_project/lib/theme/theme_helper.dart
Normal file
263
base_project/lib/theme/theme_helper.dart
Normal 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);
|
||||
}
|
||||
172
base_project/lib/utils/color_constants.dart
Normal file
172
base_project/lib/utils/color_constants.dart
Normal 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));
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
35
base_project/lib/utils/form_validators.dart
Normal file
35
base_project/lib/utils/form_validators.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
26
base_project/lib/utils/image_constant.dart
Normal file
26
base_project/lib/utils/image_constant.dart
Normal 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';
|
||||
}
|
||||
100
base_project/lib/utils/managers/user_manager.dart
Normal file
100
base_project/lib/utils/managers/user_manager.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
120
base_project/lib/utils/size_utils.dart
Normal file
120
base_project/lib/utils/size_utils.dart
Normal 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 mustn’t.
|
||||
// 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -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 }
|
||||
54
base_project/lib/utils/validator/text_feild_validator.dart
Normal file
54
base_project/lib/utils/validator/text_feild_validator.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
277
base_project/lib/view/auth/admin_reg_view.dart
Normal file
277
base_project/lib/view/auth/admin_reg_view.dart
Normal 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(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
1167
base_project/lib/view/auth/get_otp.dart
Normal file
1167
base_project/lib/view/auth/get_otp.dart
Normal file
File diff suppressed because it is too large
Load Diff
1007
base_project/lib/view/auth/login.dart
Normal file
1007
base_project/lib/view/auth/login.dart
Normal file
File diff suppressed because it is too large
Load Diff
11
base_project/lib/view/auth/register_acc.dart
Normal file
11
base_project/lib/view/auth/register_acc.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
355
base_project/lib/view/auth/sign_up_view.dart
Normal file
355
base_project/lib/view/auth/sign_up_view.dart
Normal 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);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
21
base_project/lib/view/auth/signup.dart
Normal file
21
base_project/lib/view/auth/signup.dart
Normal 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"),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
331
base_project/lib/view/auth/verify_otp.dart
Normal file
331
base_project/lib/view/auth/verify_otp.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
626
base_project/lib/view/dashboard/home.dart
Normal file
626
base_project/lib/view/dashboard/home.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
263
base_project/lib/view/dashboard/profile/account_update.dart
Normal file
263
base_project/lib/view/dashboard/profile/account_update.dart
Normal 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,
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
401
base_project/lib/view/dashboard/profile/change_password.dart
Normal file
401
base_project/lib/view/dashboard/profile/change_password.dart
Normal 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);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
419
base_project/lib/view/dashboard/profile/edit_profile.dart
Normal file
419
base_project/lib/view/dashboard/profile/edit_profile.dart
Normal 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);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
573
base_project/lib/view/dashboard/profile/profile.dart
Normal file
573
base_project/lib/view/dashboard/profile/profile.dart
Normal 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"),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
492
base_project/lib/view/splash_screen.dart
Normal file
492
base_project/lib/view/splash_screen.dart
Normal 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;
|
||||
}
|
||||
@@ -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"),
|
||||
),
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
1399
base_project/lib/view/system_parameters/system_parameters.dart
Normal file
1399
base_project/lib/view/system_parameters/system_parameters.dart
Normal file
File diff suppressed because it is too large
Load Diff
384
base_project/lib/view_model/auth/auth_view_model.dart
Normal file
384
base_project/lib/view_model/auth/auth_view_model.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
209
base_project/lib/view_model/profile/profile_view_model.dart
Normal file
209
base_project/lib/view_model/profile/profile_view_model.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
44
base_project/lib/view_model/splash_view_model.dart
Normal file
44
base_project/lib/view_model/splash_view_model.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
44
base_project/lib/widgets/app_bar/appbar_image.dart
Normal file
44
base_project/lib/widgets/app_bar/appbar_image.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
35
base_project/lib/widgets/app_bar/appbar_title.dart
Normal file
35
base_project/lib/widgets/app_bar/appbar_title.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
56
base_project/lib/widgets/app_bar/custom_app_bar.dart
Normal file
56
base_project/lib/widgets/app_bar/custom_app_bar.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
38
base_project/lib/widgets/base_button.dart
Normal file
38
base_project/lib/widgets/base_button.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
92
base_project/lib/widgets/customFloatingButton.dart
Normal file
92
base_project/lib/widgets/customFloatingButton.dart
Normal 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,
|
||||
}
|
||||
135
base_project/lib/widgets/custom_bottom_bar.dart
Normal file
135
base_project/lib/widgets/custom_bottom_bar.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
351
base_project/lib/widgets/custom_button.dart
Normal file
351
base_project/lib/widgets/custom_button.dart
Normal 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,
|
||||
}
|
||||
147
base_project/lib/widgets/custom_checkbox.dart
Normal file
147
base_project/lib/widgets/custom_checkbox.dart
Normal 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 }
|
||||
199
base_project/lib/widgets/custom_drop_down.dart
Normal file
199
base_project/lib/widgets/custom_drop_down.dart
Normal 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,
|
||||
}
|
||||
161
base_project/lib/widgets/custom_dropdown_field.dart
Normal file
161
base_project/lib/widgets/custom_dropdown_field.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
48
base_project/lib/widgets/custom_dropdownfield.dart
Normal file
48
base_project/lib/widgets/custom_dropdownfield.dart
Normal 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),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
58
base_project/lib/widgets/custom_elevated_button.dart
Normal file
58
base_project/lib/widgets/custom_elevated_button.dart
Normal 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()
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
91
base_project/lib/widgets/custom_floating_button.dart
Normal file
91
base_project/lib/widgets/custom_floating_button.dart
Normal 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,
|
||||
}
|
||||
152
base_project/lib/widgets/custom_floating_text_field.dart
Normal file
152
base_project/lib/widgets/custom_floating_text_field.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
398
base_project/lib/widgets/custom_icon_button.dart
Normal file
398
base_project/lib/widgets/custom_icon_button.dart
Normal 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,
|
||||
}
|
||||
60
base_project/lib/widgets/custom_icon_button_f.dart
Normal file
60
base_project/lib/widgets/custom_icon_button_f.dart
Normal 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
154
base_project/lib/widgets/custom_image_view.dart
Normal file
154
base_project/lib/widgets/custom_image_view.dart
Normal 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
Reference in New Issue
Block a user