baseproject
This commit is contained in:
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
Reference in New Issue
Block a user