Done Category, Product Catalogue, Customer, Vendor, Purchase Invoice Module

This commit is contained in:
2026-08-28 11:30:18 +05:30
parent 0d13833679
commit 189f49ed07
118 changed files with 12624 additions and 4004 deletions

View File

@@ -23,7 +23,8 @@ class ProfileScreen extends ConsumerStatefulWidget {
ConsumerState<ProfileScreen> createState() => _ProfileScreenState();
}
class _ProfileScreenState extends ConsumerState<ProfileScreen> with SingleTickerProviderStateMixin {
class _ProfileScreenState extends ConsumerState<ProfileScreen>
with SingleTickerProviderStateMixin {
bool _isExporting = false;
String? _profileType;
String _userName = 'Loading...';
@@ -34,8 +35,14 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> with SingleTicker
@override
void initState() {
super.initState();
_animController = AnimationController(vsync: this, duration: const Duration(milliseconds: 800));
_fadeAnim = CurvedAnimation(parent: _animController, curve: Curves.easeOutCubic);
_animController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 800),
);
_fadeAnim = CurvedAnimation(
parent: _animController,
curve: Curves.easeOutCubic,
);
_animController.forward();
_fetchProfileType();
}
@@ -68,7 +75,8 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> with SingleTicker
}
String _getInitials(String name) {
if (name.isEmpty || name == 'Kifi User' || name == 'Loading...') return 'KU';
if (name.isEmpty || name == 'Kifi User' || name == 'Loading...')
return 'KU';
final parts = name.trim().split(' ');
if (parts.length > 1 && parts[1].isNotEmpty) {
return '${parts[0][0]}${parts[1][0]}'.toUpperCase();
@@ -76,11 +84,9 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> with SingleTicker
return name.substring(0, name.length >= 2 ? 2 : 1).toUpperCase();
}
Future<void> _logout(BuildContext context) async {
const storage = FlutterSecureStorage();
await storage.delete(key: 'jwt_token');
if (context.mounted) {
Future<void> _logout() async {
await DioClient().clearToken();
if (mounted) {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (_) => const AuthScreen()),
@@ -97,9 +103,14 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> with SingleTicker
final file = File('${tempDir.path}/transactions_export.csv');
await file.writeAsBytes(bytes);
final xFile = XFile(file.path);
await Share.shareXFiles([xFile], text: 'Here is my Kifi transactions export.');
await Share.shareXFiles([
xFile,
], text: 'Here is my Kifi transactions export.');
} catch (e) {
if (mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Export failed: $e')));
if (mounted)
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Export failed: $e')));
} finally {
if (mounted) setState(() => _isExporting = false);
}
@@ -115,14 +126,19 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> with SingleTicker
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
extendBodyBehindAppBar: true,
appBar: AppBar(
title: const Text('Profile', style: TextStyle(fontWeight: FontWeight.bold, letterSpacing: 1.2)),
title: const Text(
'Profile',
style: TextStyle(fontWeight: FontWeight.bold, letterSpacing: 1.2),
),
backgroundColor: Colors.transparent,
elevation: 0,
centerTitle: true,
flexibleSpace: ClipRect(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
child: Container(color: Theme.of(context).scaffoldBackgroundColor.withOpacity(0.5)),
child: Container(
color: Theme.of(context).scaffoldBackgroundColor.withOpacity(0.5),
),
),
),
),
@@ -130,9 +146,11 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> with SingleTicker
children: [
// Background Decorative Elements
Positioned(
top: -50, right: -50,
top: -50,
right: -50,
child: Container(
width: 200, height: 200,
width: 200,
height: 200,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: primaryColor.withOpacity(isDark ? 0.2 : 0.1),
@@ -140,9 +158,11 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> with SingleTicker
),
),
Positioned(
bottom: -100, left: -50,
bottom: -100,
left: -50,
child: Container(
width: 300, height: 300,
width: 300,
height: 300,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.purple.withOpacity(isDark ? 0.15 : 0.08),
@@ -158,32 +178,45 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> with SingleTicker
constraints: const BoxConstraints(maxWidth: 600),
child: ListView(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 24.0),
padding: const EdgeInsets.symmetric(
horizontal: 24.0,
vertical: 24.0,
),
children: [
// Avatar Section
Center(
child: Hero(
tag: 'profile_avatar',
child: Container(
width: 120, height: 120,
width: 120,
height: 120,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
colors: [
primaryColor.withOpacity(0.8),
primaryColor.withOpacity(0.5)
primaryColor.withOpacity(0.5),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
boxShadow: [
BoxShadow(color: primaryColor.withOpacity(0.3), blurRadius: 20, offset: const Offset(0, 10)),
BoxShadow(
color: primaryColor.withOpacity(0.3),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
child: Center(
child: Text(
_getInitials(_userName),
style: const TextStyle(fontSize: 40, fontWeight: FontWeight.bold, color: Colors.white, letterSpacing: 2),
style: const TextStyle(
fontSize: 40,
fontWeight: FontWeight.bold,
color: Colors.white,
letterSpacing: 2,
),
),
),
),
@@ -192,14 +225,21 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> with SingleTicker
const SizedBox(height: 24),
Text(
_userName,
style: Theme.of(context).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold),
style: Theme.of(context).textTheme.headlineSmall
?.copyWith(fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
if (_userEmail.isNotEmpty && _userEmail != 'Loading...') ...[
if (_userEmail.isNotEmpty &&
_userEmail != 'Loading...') ...[
const SizedBox(height: 8),
Text(
_userEmail,
style: TextStyle(color: isDark ? Colors.grey.shade400 : Colors.grey.shade600, fontSize: 16),
style: TextStyle(
color: isDark
? Colors.grey.shade400
: Colors.grey.shade600,
fontSize: 16,
),
textAlign: TextAlign.center,
),
],
@@ -212,9 +252,16 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> with SingleTicker
filter: ImageFilter.blur(sigmaX: 15, sigmaY: 15),
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).cardColor.withOpacity(isDark ? 0.3 : 0.6),
color: Theme.of(
context,
).cardColor.withOpacity(isDark ? 0.3 : 0.6),
borderRadius: BorderRadius.circular(24),
border: Border.all(color: Colors.white.withOpacity(isDark ? 0.05 : 0.2), width: 1.5),
border: Border.all(
color: Colors.white.withOpacity(
isDark ? 0.05 : 0.2,
),
width: 1.5,
),
),
child: Column(
children: [
@@ -223,9 +270,19 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> with SingleTicker
icon: LucideIcons.downloadCloud,
iconColor: Colors.blue,
title: 'Export Data to CSV',
trailing: _isExporting
? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2))
: const Icon(LucideIcons.chevronRight, size: 20, color: Colors.grey),
trailing: _isExporting
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
: const Icon(
LucideIcons.chevronRight,
size: 20,
color: Colors.grey,
),
onTap: _isExporting ? null : _exportData,
),
_buildDivider(context, isDark),
@@ -236,31 +293,56 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> with SingleTicker
title: 'Appearance',
trailing: SegmentedButton<ThemeMode>(
segments: const [
ButtonSegment(value: ThemeMode.light, icon: Icon(LucideIcons.sun, size: 16)),
ButtonSegment(value: ThemeMode.system, icon: Icon(LucideIcons.monitor, size: 16)),
ButtonSegment(value: ThemeMode.dark, icon: Icon(LucideIcons.moon, size: 16)),
ButtonSegment(
value: ThemeMode.light,
icon: Icon(LucideIcons.sun, size: 16),
),
ButtonSegment(
value: ThemeMode.system,
icon: Icon(
LucideIcons.monitor,
size: 16,
),
),
ButtonSegment(
value: ThemeMode.dark,
icon: Icon(LucideIcons.moon, size: 16),
),
],
selected: {themeMode},
onSelectionChanged: (Set<ThemeMode> newSelection) {
ref.read(themeProvider.notifier).setTheme(newSelection.first);
},
onSelectionChanged:
(Set<ThemeMode> newSelection) {
ref
.read(themeProvider.notifier)
.setTheme(newSelection.first);
},
showSelectedIcon: false,
style: ButtonStyle(
visualDensity: VisualDensity.compact,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
backgroundColor: WidgetStateProperty.resolveWith<Color?>((states) {
if (states.contains(WidgetState.selected)) {
return primaryColor.withOpacity(0.2);
}
return null;
}),
tapTargetSize:
MaterialTapTargetSize.shrinkWrap,
backgroundColor:
WidgetStateProperty.resolveWith<
Color?
>((states) {
if (states.contains(
WidgetState.selected,
)) {
return primaryColor.withOpacity(
0.2,
);
}
return null;
}),
),
),
),
_buildDivider(context, isDark),
Consumer(
builder: (context, ref, child) {
final isBusinessMode = ref.watch(businessModeProvider);
final isBusinessMode = ref.watch(
businessModeProvider,
);
return Column(
children: [
if (_profileType == 'BUSINESS') ...[
@@ -271,7 +353,11 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> with SingleTicker
title: 'Business Mode',
subtitle: 'Inventory and Sales Hub',
value: isBusinessMode,
onChanged: (val) => ref.read(businessModeProvider.notifier).toggleMode(),
onChanged: (val) => ref
.read(
businessModeProvider.notifier,
)
.toggleMode(),
),
if (isBusinessMode) ...[
_buildDivider(context, isDark),
@@ -280,16 +366,27 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> with SingleTicker
icon: LucideIcons.settings2,
iconColor: Colors.grey.shade600,
title: 'Business Settings',
subtitle: 'Configure Taxes, Barcodes, etc.',
trailing: const Icon(LucideIcons.chevronRight, size: 20, color: Colors.grey),
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const BusinessSettingsScreen())),
subtitle:
'Configure Taxes, Barcodes, etc.',
trailing: const Icon(
LucideIcons.chevronRight,
size: 20,
color: Colors.grey,
),
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) =>
const BusinessSettingsScreen(),
),
),
),
],
_buildDivider(context, isDark),
],
],
);
}
},
),
_buildDivider(context, isDark),
_buildSettingsTile(
@@ -297,7 +394,11 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> with SingleTicker
icon: LucideIcons.helpCircle,
iconColor: Colors.green,
title: 'Help & Support',
trailing: const Icon(LucideIcons.chevronRight, size: 20, color: Colors.grey),
trailing: const Icon(
LucideIcons.chevronRight,
size: 20,
color: Colors.grey,
),
onTap: () {},
),
],
@@ -306,7 +407,7 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> with SingleTicker
),
),
const SizedBox(height: 48),
// Logout Button
Container(
width: double.infinity,
@@ -316,20 +417,33 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> with SingleTicker
colors: [Colors.red.shade400, Colors.red.shade600],
),
boxShadow: [
BoxShadow(color: Colors.red.withOpacity(0.3), blurRadius: 15, offset: const Offset(0, 5)),
BoxShadow(
color: Colors.red.withOpacity(0.3),
blurRadius: 15,
offset: const Offset(0, 5),
),
],
),
child: ElevatedButton.icon(
onPressed: () => _logout(context),
onPressed: () => _logout(),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent,
foregroundColor: Colors.white,
shadowColor: Colors.transparent,
padding: const EdgeInsets.symmetric(vertical: 20),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
icon: const Icon(LucideIcons.logOut, size: 22),
label: const Text('Log Out', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, letterSpacing: 1.0)),
label: const Text(
'Log Out',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
letterSpacing: 1.0,
),
),
),
),
const SizedBox(height: 40),
@@ -364,8 +478,19 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> with SingleTicker
),
child: Icon(icon, color: iconColor, size: 24),
),
title: Text(title, style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 16)),
subtitle: subtitle != null ? Text(subtitle, style: TextStyle(fontSize: 13, color: isDark ? Colors.grey.shade400 : Colors.grey.shade600)) : null,
title: Text(
title,
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 16),
),
subtitle: subtitle != null
? Text(
subtitle,
style: TextStyle(
fontSize: 13,
color: isDark ? Colors.grey.shade400 : Colors.grey.shade600,
),
)
: null,
trailing: trailing,
onTap: onTap,
);