Done Category, Product Catalogue, Customer, Vendor, Purchase Invoice Module
This commit is contained in:
@@ -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,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user