Files
Kifi/kifi-app/lib/features/auth/presentation/profile_screen.dart

532 lines
21 KiB
Dart

import 'dart:io';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';
import 'package:cross_file/cross_file.dart';
import 'auth_screen.dart';
import '../../../core/network/dio_client.dart';
import '../providers/auth_provider.dart';
import '../../transactions/providers/providers.dart';
import '../../../core/theme/theme_provider.dart';
import '../../business/providers/business_mode_provider.dart';
import '../../projects/providers/project_mode_provider.dart';
import '../../business/presentation/settings/business_settings_screen.dart';
class ProfileScreen extends ConsumerStatefulWidget {
const ProfileScreen({super.key});
@override
ConsumerState<ProfileScreen> createState() => _ProfileScreenState();
}
class _ProfileScreenState extends ConsumerState<ProfileScreen>
with SingleTickerProviderStateMixin {
bool _isExporting = false;
String? _profileType;
String _userName = 'Loading...';
String _userEmail = 'Loading...';
late AnimationController _animController;
late Animation<double> _fadeAnim;
@override
void initState() {
super.initState();
_animController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 800),
);
_fadeAnim = CurvedAnimation(
parent: _animController,
curve: Curves.easeOutCubic,
);
_animController.forward();
_fetchProfileType();
}
@override
void dispose() {
_animController.dispose();
super.dispose();
}
Future<void> _fetchProfileType() async {
try {
final res = await DioClient().dio.get('/account/setup/status');
if (mounted) {
setState(() {
_profileType = res.data['profileType'];
_userName = res.data['name'] ?? 'Kifi User';
if (_userName.isEmpty) _userName = 'Kifi User';
_userEmail = res.data['email'] ?? '';
});
}
} catch (e) {
if (mounted) {
setState(() {
_userName = 'Kifi User';
_userEmail = '';
});
}
}
}
String _getInitials(String name) {
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();
}
return name.substring(0, name.length >= 2 ? 2 : 1).toUpperCase();
}
Future<void> _logout() async {
await DioClient().clearToken();
if (mounted) {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (_) => const AuthScreen()),
(route) => false,
);
}
}
Future<void> _exportData() async {
setState(() => _isExporting = true);
try {
final bytes = await ref.read(apiRepositoryProvider).exportTransactions();
final tempDir = await getTemporaryDirectory();
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.');
} catch (e) {
if (mounted)
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Export failed: $e')));
} finally {
if (mounted) setState(() => _isExporting = false);
}
}
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final primaryColor = Theme.of(context).primaryColor;
final themeMode = ref.watch(themeProvider);
return Scaffold(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
extendBodyBehindAppBar: true,
appBar: AppBar(
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),
),
),
),
),
body: Stack(
children: [
// Background Decorative Elements
Positioned(
top: -50,
right: -50,
child: Container(
width: 200,
height: 200,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: primaryColor.withOpacity(isDark ? 0.2 : 0.1),
),
),
),
Positioned(
bottom: -100,
left: -50,
child: Container(
width: 300,
height: 300,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.purple.withOpacity(isDark ? 0.15 : 0.08),
),
),
),
// Main Content
SafeArea(
child: FadeTransition(
opacity: _fadeAnim,
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 600),
child: ListView(
physics: const BouncingScrollPhysics(),
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,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
colors: [
primaryColor.withOpacity(0.8),
primaryColor.withOpacity(0.5),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
boxShadow: [
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,
),
),
),
),
),
),
const SizedBox(height: 24),
Text(
_userName,
style: Theme.of(context).textTheme.headlineSmall
?.copyWith(fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
if (_userEmail.isNotEmpty &&
_userEmail != 'Loading...') ...[
const SizedBox(height: 8),
Text(
_userEmail,
style: TextStyle(
color: isDark
? Colors.grey.shade400
: Colors.grey.shade600,
fontSize: 16,
),
textAlign: TextAlign.center,
),
],
const SizedBox(height: 40),
// Glassmorphism Card for Settings
ClipRRect(
borderRadius: BorderRadius.circular(24),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 15, sigmaY: 15),
child: Container(
decoration: BoxDecoration(
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,
),
),
child: Column(
children: [
_buildSettingsTile(
context: context,
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,
),
onTap: _isExporting ? null : _exportData,
),
_buildDivider(context, isDark),
_buildSettingsTile(
context: context,
icon: LucideIcons.palette,
iconColor: Colors.purple,
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),
),
],
selected: {themeMode},
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;
}),
),
),
),
_buildDivider(context, isDark),
Consumer(
builder: (context, ref, child) {
final isBusinessMode = ref.watch(
businessModeProvider,
);
return Column(
children: [
if (_profileType == 'BUSINESS') ...[
_buildSwitchTile(
context: context,
icon: LucideIcons.briefcase,
iconColor: Colors.orange,
title: 'Business Mode',
subtitle: 'Inventory and Sales Hub',
value: isBusinessMode,
onChanged: (val) => ref
.read(
businessModeProvider.notifier,
)
.toggleMode(),
),
if (isBusinessMode) ...[
_buildDivider(context, isDark),
_buildSettingsTile(
context: context,
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(),
),
),
),
],
_buildDivider(context, isDark),
],
],
);
},
),
_buildDivider(context, isDark),
_buildSettingsTile(
context: context,
icon: LucideIcons.helpCircle,
iconColor: Colors.green,
title: 'Help & Support',
trailing: const Icon(
LucideIcons.chevronRight,
size: 20,
color: Colors.grey,
),
onTap: () {},
),
],
),
),
),
),
const SizedBox(height: 48),
// Logout Button
Container(
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
gradient: LinearGradient(
colors: [Colors.red.shade400, Colors.red.shade600],
),
boxShadow: [
BoxShadow(
color: Colors.red.withOpacity(0.3),
blurRadius: 15,
offset: const Offset(0, 5),
),
],
),
child: ElevatedButton.icon(
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),
),
),
icon: const Icon(LucideIcons.logOut, size: 22),
label: const Text(
'Log Out',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
letterSpacing: 1.0,
),
),
),
),
const SizedBox(height: 40),
],
),
),
),
),
),
],
),
);
}
Widget _buildSettingsTile({
required BuildContext context,
required IconData icon,
required Color iconColor,
required String title,
String? subtitle,
required Widget trailing,
VoidCallback? onTap,
}) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
leading: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: iconColor.withOpacity(isDark ? 0.2 : 0.1),
borderRadius: BorderRadius.circular(12),
),
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,
trailing: trailing,
onTap: onTap,
);
}
Widget _buildSwitchTile({
required BuildContext context,
required IconData icon,
required Color iconColor,
required String title,
required String subtitle,
required bool value,
required ValueChanged<bool> onChanged,
}) {
return _buildSettingsTile(
context: context,
icon: icon,
iconColor: iconColor,
title: title,
subtitle: subtitle,
trailing: Switch(
value: value,
onChanged: onChanged,
activeColor: iconColor,
),
onTap: () => onChanged(!value),
);
}
Widget _buildDivider(BuildContext context, bool isDark) {
return Divider(
height: 1,
indent: 76,
endIndent: 20,
color: Theme.of(context).dividerColor.withOpacity(isDark ? 0.1 : 0.05),
);
}
}