import 'dart:io'; import 'dart:typed_data'; // <-- ADDED IMPORT import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:image_picker/image_picker.dart'; import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart' as p; import 'package:signature/signature.dart'; // <-- ADDED IMPORT import 'package:environment_monitoring_app/auth_provider.dart'; import 'package:environment_monitoring_app/services/api_service.dart'; class ProfileScreen extends StatefulWidget { const ProfileScreen({super.key}); @override State createState() => _ProfileScreenState(); } class _ProfileScreenState extends State { // FIX: Removed direct instantiation of ApiService bool _isLoading = false; String _errorMessage = ''; File? _profileImageFile; File? _signatureImageFile; // <-- ADDED STATE VARIABLE // FIX: Use late initialization to retrieve the service instance. late ApiService _apiService; // --- ADDED SIGNATURE CONTROLLER --- final SignatureController _signatureController = SignatureController( penStrokeWidth: 3, penColor: Colors.black, exportBackgroundColor: Colors.transparent, // For transparent PNG ); // --- END OF CONTROLLER --- @override void initState() { super.initState(); // FIX: Retrieve the ApiService instance after the context is fully available. WidgetsBinding.instance.addPostFrameCallback((_) { _apiService = Provider.of(context, listen: false); _loadLocalProfileImage().then((_) { // --- MODIFIED: Chain signature loading --- _loadLocalSignatureImage().then((_) { // If no profile data is available at all, trigger a refresh if (Provider.of(context, listen: false).profileData == null) { _refreshProfile(); } }); // --- END MODIFIED --- }); }); } /// Refreshes only the profile data using the dedicated provider method. Future _refreshProfile() async { setState(() { _isLoading = true; _errorMessage = ''; }); try { final auth = Provider.of(context, listen: false); // Call the efficient refreshProfile method instead of the full sync. await auth.refreshProfile(); // After syncing, reload the potentially new profile image await _loadLocalProfileImage(); await _loadLocalSignatureImage(); // <-- ADDED THIS LINE } catch (e) { if (mounted) { setState(() { _errorMessage = 'An unexpected error occurred during sync: ${e.toString()}'; }); } } finally { if (mounted) { setState(() { _isLoading = false; }); } } } /// Loads the profile image from the local cache or downloads it if not present. Future _loadLocalProfileImage() async { final auth = Provider.of(context, listen: false); final String? serverImagePath = auth.profileData?['profile_picture']; if (serverImagePath != null && serverImagePath.isNotEmpty) { final String localFileName = p.basename(serverImagePath); final Directory appDocDir = await getApplicationDocumentsDirectory(); final String localFilePath = p.join(appDocDir.path, 'profile_pictures', localFileName); final File localFile = File(localFilePath); if (await localFile.exists()) { if (mounted) setState(() => _profileImageFile = localFile); } else { final String fullImageUrl = ApiService.imageBaseUrl + serverImagePath; // FIX: Use the injected _apiService instance final downloadedFile = await _apiService.downloadProfilePicture(fullImageUrl, localFilePath); if (downloadedFile != null && mounted) { setState(() => _profileImageFile = downloadedFile); } } } else { if (mounted) setState(() => _profileImageFile = null); } } // --- ADDED NEW FUNCTION --- /// Loads the signature image from the local cache or downloads it if not present. Future _loadLocalSignatureImage() async { final auth = Provider.of(context, listen: false); // Assumes the field name in your DB/profile JSON is 'signature_path' final String? serverSignaturePath = auth.profileData?['signature_path']; if (serverSignaturePath != null && serverSignaturePath.isNotEmpty) { final String localFileName = p.basename(serverSignaturePath); final Directory appDocDir = await getApplicationDocumentsDirectory(); // Store signatures in their own sub-directory final String localFilePath = p.join(appDocDir.path, 'signatures', localFileName); final File localFile = File(localFilePath); if (await localFile.exists()) { if (mounted) setState(() => _signatureImageFile = localFile); } else { final String fullImageUrl = ApiService.imageBaseUrl + serverSignaturePath; // We can re-use the downloadProfilePicture method, just provide a different local path final downloadedFile = await _apiService.downloadProfilePicture(fullImageUrl, localFilePath); if (downloadedFile != null && mounted) { setState(() => _signatureImageFile = downloadedFile); } } } else { if (mounted) setState(() => _signatureImageFile = null); } } // --- END OF NEW FUNCTION --- /// Shows a modal bottom sheet for selecting an image source. Future _showImageSourceSelection() async { showModalBottomSheet( context: context, builder: (BuildContext context) { return SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( leading: const Icon(Icons.camera_alt), title: const Text('Take a photo'), onTap: () { Navigator.pop(context); _pickAndUploadImage(ImageSource.camera); }, ), ListTile( leading: const Icon(Icons.photo_library), title: const Text('Choose from gallery'), onTap: () { Navigator.pop(context); _pickAndUploadImage(ImageSource.gallery); }, ), ], ), ); }, ); } /// Picks an image and initiates the upload process. Future _pickAndUploadImage(ImageSource source) async { final ImagePicker picker = ImagePicker(); final XFile? pickedFile = await picker.pickImage(source: source, imageQuality: 85, maxWidth: 1024); if (pickedFile != null) { setState(() => _isLoading = true); final File imageFile = File(pickedFile.path); // FIX: Use the injected _apiService instance final uploadResult = await _apiService.uploadProfilePicture(imageFile); if (mounted) { if (uploadResult['success']) { // After a successful upload, efficiently refresh only the profile data. await _refreshProfile(); _showSnackBar("Profile picture updated successfully.", isError: false); } else { setState(() { _errorMessage = uploadResult['message'] ?? 'Failed to upload profile picture.'; }); _showSnackBar(_errorMessage, isError: true); } setState(() => _isLoading = false); } } } // --- ADDED THESE 2 NEW FUNCTIONS --- /// Shows the signature pad dialog. Future _showSignaturePad() async { _signatureController.clear(); // Clear any previous drawings final File? signatureFile = await showDialog( context: context, barrierDismissible: false, builder: (BuildContext dialogContext) { return AlertDialog( title: const Text("Provide Your Signature"), content: Container( width: double.maxFinite, height: 300, decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade400), color: Colors.grey.shade200, // Background for the pad ), child: Signature( controller: _signatureController, backgroundColor: Colors.white, ), ), actions: [ TextButton( child: const Text("Clear"), onPressed: () { _signatureController.clear(); }, ), TextButton( child: const Text("Cancel"), onPressed: () { Navigator.pop(dialogContext, null); }, ), ElevatedButton( child: const Text("Save"), onPressed: () async { if (_signatureController.isNotEmpty) { final Uint8List? data = await _signatureController.toPngBytes(); if (data != null) { final tempDir = await getTemporaryDirectory(); final file = File(p.join(tempDir.path, "signature_${DateTime.now().millisecondsSinceEpoch}.png")); await file.writeAsBytes(data); Navigator.pop(dialogContext, file); } } else { // Show a snackbar if the signature is empty ScaffoldMessenger.of(dialogContext).showSnackBar( const SnackBar( content: Text("Please provide a signature first."), duration: Duration(seconds: 2), ), ); } }, ), ], ); }, ); // After dialog is closed, check if we got a file if (signatureFile != null) { await _uploadSignature(signatureFile); } } /// Uploads the signature file and refreshes the profile. Future _uploadSignature(File signatureFile) async { setState(() => _isLoading = true); final uploadResult = await _apiService.uploadSignature(signatureFile); if (mounted) { if (uploadResult['success']) { // Refresh profile to get new signature_path and display it await _refreshProfile(); _showSnackBar("Signature updated successfully.", isError: false); } else { setState(() { _errorMessage = uploadResult['message'] ?? 'Failed to upload signature.'; }); _showSnackBar(_errorMessage, isError: true); } setState(() => _isLoading = false); } // Clean up temp file try { if (await signatureFile.exists()) { await signatureFile.delete(); } } catch (e) { debugPrint("Error deleting temp signature file: $e"); } } // --- END OF NEW FUNCTIONS --- void _showSnackBar(String message, {bool isError = false}) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), backgroundColor: isError ? Theme.of(context).colorScheme.error : Colors.green, ), ); } } @override Widget build(BuildContext context) { final auth = Provider.of(context); final profileData = auth.profileData; return Scaffold( appBar: AppBar( title: const Text("User Profile"), actions: [ IconButton( icon: const Icon(Icons.refresh), onPressed: _isLoading ? null : _refreshProfile, ), ], ), body: Column( children: [ Expanded( child: _isLoading && profileData == null ? const Center(child: CircularProgressIndicator()) : _errorMessage.isNotEmpty ? Center( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.error_outline, color: Theme.of(context).colorScheme.error, size: 50), const SizedBox(height: 16), Text( _errorMessage, textAlign: TextAlign.center, style: TextStyle(color: Theme.of(context).colorScheme.error, fontSize: 16), ), const SizedBox(height: 24), ElevatedButton( onPressed: _isLoading ? null : _refreshProfile, child: const Text('Retry'), ), ], ), ), ) : profileData == null ? const Center(child: Text('No profile data available.')) : RefreshIndicator( onRefresh: _refreshProfile, child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), padding: const EdgeInsets.all(24.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Center( child: GestureDetector( onTap: _isLoading ? null : _showImageSourceSelection, child: Stack( alignment: Alignment.bottomRight, children: [ CircleAvatar( radius: 60, backgroundColor: Theme.of(context).colorScheme.secondary, backgroundImage: _profileImageFile != null ? FileImage(_profileImageFile!) : null, child: _profileImageFile == null ? Icon(Icons.person, size: 80, color: Theme.of(context).colorScheme.onSecondary) : null, ), if (!_isLoading) Positioned( right: 0, bottom: 0, child: CircleAvatar( radius: 20, backgroundColor: Theme.of(context).primaryColor, child: Icon(Icons.edit, color: Theme.of(context).colorScheme.onPrimary, size: 20), ), ), ], ), ), ), const SizedBox(height: 32), _buildProfileSection(context, "Personal Information", [ _buildProfileDetail(context, "Username:", profileData['username']), _buildProfileDetail(context, "Email:", profileData['email']), _buildProfileDetail(context, "First Name:", profileData['first_name']), _buildProfileDetail(context, "Last Name:", profileData['last_name']), _buildProfileDetail(context, "Phone Number:", profileData['phone_number']), ]), const SizedBox(height: 24), _buildProfileSection(context, "Organizational Details", [ _buildProfileDetail(context, "Role:", profileData['role_name']), _buildProfileDetail(context, "Department:", profileData['department_name']), _buildProfileDetail(context, "Company:", profileData['company_name']), _buildProfileDetail(context, "Position:", profileData['position_name']), ]), const SizedBox(height: 24), // --- ADDED THIS SECTION --- _buildSignatureSection(context), const SizedBox(height: 24), // --- END OF ADDED SECTION --- _buildProfileSection(context, "Account Status", [ _buildProfileDetail(context, "Account Status:", profileData['account_status']), _buildProfileDetail(context, "Registered On:", profileData['date_registered']), ]), ], ), ), ), ), Padding( padding: const EdgeInsets.all(24.0), child: Center( child: ElevatedButton.icon( icon: const Icon(Icons.logout), label: const Text("Logout"), onPressed: () { showDialog( context: context, barrierDismissible: false, builder: (dialogContext) => AlertDialog( title: const Text("Confirm Logout"), content: const Text("Are you sure you want to log out?"), actions: [ TextButton( onPressed: () => Navigator.pop(dialogContext), child: const Text("Cancel"), ), ElevatedButton( onPressed: () { Navigator.pop(dialogContext); auth.logout(); Navigator.pushNamedAndRemoveUntil(context, '/', (route) => false); }, style: ElevatedButton.styleFrom( backgroundColor: Colors.red[700], foregroundColor: Colors.white, ), child: const Text("Logout"), ), ], ), ); }, style: ElevatedButton.styleFrom( backgroundColor: Colors.red[700], foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), ), ), ), ), ], ), ); } Widget _buildProfileSection(BuildContext context, String title, List details) { return Card( elevation: 4, margin: const EdgeInsets.symmetric(vertical: 8.0), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: Theme.of(context).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold, color: Theme.of(context).primaryColor), ), const Divider(height: 20, thickness: 1.5), ...details, ], ), ), ); } // --- ADDED NEW WIDGET METHOD --- Widget _buildSignatureSection(BuildContext context) { return Card( elevation: 4, margin: const EdgeInsets.symmetric(vertical: 8.0), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Signature", style: Theme.of(context).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold, color: Theme.of(context).primaryColor), ), const Divider(height: 20, thickness: 1.5), Center( child: Container( width: double.infinity, height: 150, decoration: BoxDecoration( color: Colors.grey.shade200, border: Border.all(color: Colors.grey.shade400), borderRadius: BorderRadius.circular(8), ), child: Padding( padding: const EdgeInsets.all(4.0), child: _signatureImageFile != null ? Image.file( _signatureImageFile!, fit: BoxFit.contain, ) : Center( child: Text( "No signature set.", style: TextStyle(color: Colors.grey.shade700), ), ), ), ), ), const SizedBox(height: 16), Center( child: ElevatedButton.icon( onPressed: _isLoading ? null : _showSignaturePad, icon: const Icon(Icons.edit_note), label: const Text("Update Signature"), style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), ), ), ) ], ), ), ); } // --- END OF NEW WIDGET METHOD --- Widget _buildProfileDetail(BuildContext context, String label, dynamic value) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( flex: 2, child: Text( label, style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold), ), ), const SizedBox(width: 16), Expanded( flex: 3, child: Text( value?.toString() ?? 'N/A', style: Theme.of(context).textTheme.bodyLarge, ), ), ], ), ); } }