kz_educa/lib/services/user_state_service.dart

234 lines
7.1 KiB
Dart

import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:image_picker/image_picker.dart';
import 'dart:io';
import 'package:kzeduca_app/models/app_models.dart' as app_models;
import 'package:kzeduca_app/models/transaction.dart' as txn;
import 'package:kzeduca_app/services/hive_service.dart';
enum UserStateStatus {
idle,
loading,
loaded,
error,
}
class UserStateService extends ChangeNotifier {
final FirebaseFirestore _db = FirebaseFirestore.instance;
final FirebaseAuth _auth = FirebaseAuth.instance;
final FirebaseStorage _storage = FirebaseStorage.instance;
final String _appId = 'default-kzeduca-app';
app_models.AppUser? _currentUser;
UserStateStatus _status = UserStateStatus.idle;
String? _errorMessage;
app_models.AppUser? get currentUser => _currentUser;
UserStateStatus get status => _status;
String? get errorMessage => _errorMessage;
FirebaseAuth get auth => _auth;
UserStateService() {
_auth.authStateChanges().listen((user) {
if (user == null) {
_currentUser = null;
_status = UserStateStatus.idle;
// NOTA: A notificação aqui é síncrona, o que é geralmente seguro no construtor
notifyListeners();
} else {
// Inicia a busca de dados quando o estado de autenticação muda
fetchUserData(user.uid);
}
});
}
CollectionReference get _usersCollection {
return _db.collection('artifacts').doc(_appId).collection('users');
}
// MÉTODO AUXILIAR PARA NOTIFICAÇÃO SEGURA (CORREÇÃO DO BUG setState during build)
void _safeNotifyListeners() {
// Garante que a notificação é adiada para a próxima "micropassagem"
// do loop de eventos, evitando o erro durante a fase de build.
if (hasListeners) {
Future.microtask(notifyListeners);
}
}
// ----------------------
// MÉTODO: fetchUserData
// ----------------------
Future<void> fetchUserData(String userId) async {
_status = UserStateStatus.loading;
_safeNotifyListeners(); // Notificação inicial
try {
final userDoc = await _usersCollection.doc(userId).get();
if (userDoc.exists) {
final data = userDoc.data();
if (data != null) {
_currentUser = app_models.AppUser.fromMap(data as Map<String, dynamic>);
_status = UserStateStatus.loaded;
} else {
_status = UserStateStatus.error;
_errorMessage = 'User data is empty.';
}
} else {
// Lida com a criação e atribui o utilizador imediatamente
await createUserDocument(_auth.currentUser!, 'Utilizador');
_status = UserStateStatus.loaded;
}
} catch (e) {
_status = UserStateStatus.error;
_errorMessage = 'Failed to fetch user data: $e';
} finally {
// Notificação final (agora segura)
_safeNotifyListeners();
}
}
// ----------------------
// MÉTODO: updateBalance
// ----------------------
Future<void> updateBalance(txn.Transaction transaction) async {
if (_currentUser == null) {
_errorMessage = 'No user logged in.';
_status = UserStateStatus.error;
_safeNotifyListeners();
return;
}
final newBalance = transaction.type == txn.TransactionType.income
? _currentUser!.currentBalance + transaction.amount
: _currentUser!.currentBalance - transaction.amount;
try {
await _usersCollection.doc(_currentUser!.uid).update({
'currentBalance': newBalance,
});
_currentUser = _currentUser!.copyWith(currentBalance: newBalance);
_status = UserStateStatus.loaded;
} catch (e) {
_status = UserStateStatus.error;
_errorMessage = 'Failed to update user balance: $e';
} finally {
_safeNotifyListeners();
}
}
// ----------------------
// MÉTODO: createUserDocument
// ----------------------
Future<void> createUserDocument(User firebaseUser, String username) async {
final userDocRef = _usersCollection.doc(firebaseUser.uid);
final userDoc = await userDocRef.get();
if (!userDoc.exists) {
final newUser = app_models.AppUser(
uid: firebaseUser.uid,
username: username,
email: firebaseUser.email,
currentBalance: 0.0,
);
await userDocRef.set(newUser.toMap());
_currentUser = newUser;
_status = UserStateStatus.loaded;
_safeNotifyListeners(); // Notificação segura
}
}
// ----------------------
// MÉTODO: recalculateBalanceFromHive
// ----------------------
Future<void> recalculateBalanceFromHive() async {
final hiveService = HiveService();
final transactions = await hiveService.getTransactions();
double balance = 0.0;
for (final t in transactions) {
if (t.type == txn.TransactionType.income) {
balance += t.amount;
} else {
balance -= t.amount;
}
}
if (_currentUser != null) {
_currentUser = _currentUser!.copyWith(currentBalance: balance);
_safeNotifyListeners(); // Notificação segura
}
}
// ----------------------
// MÉTODO: updateUserProfile
// ----------------------
Future<void> updateUserProfile({
String? username,
String? email,
XFile? profileImage,
}) async {
if (_currentUser == null) return;
_status = UserStateStatus.loading;
_safeNotifyListeners();
try {
final updatedData = <String, dynamic>{};
if (username != null) {
updatedData['username'] = username;
}
if (email != null) {
updatedData['email'] = email;
}
// Lógica para fazer o upload da imagem de perfil
if (profileImage != null) {
final storageRef = _storage
.ref('artifacts')
.child(_appId)
.child('users')
.child(_currentUser!.uid)
.child('profileImage.jpg');
await storageRef.putFile(File(profileImage.path));
final imageUrl = await storageRef.getDownloadURL();
updatedData['profileImage'] = imageUrl;
}
if (updatedData.isNotEmpty) {
await _usersCollection.doc(_currentUser!.uid).update(updatedData);
}
// Atualiza o objeto localmente para refletir as mudanças
_currentUser = _currentUser!.copyWith(
username: username ?? _currentUser!.username,
email: email ?? _currentUser!.email,
profileImage: updatedData['profileImage'] ?? _currentUser!.profileImage,
);
_status = UserStateStatus.loaded;
} catch (e) {
_errorMessage = 'Erro ao atualizar perfil: $e';
_status = UserStateStatus.error;
} finally {
_safeNotifyListeners();
}
}
// ----------------------
// MÉTODO: logout
// ----------------------
void logout() {
_auth.signOut();
_currentUser = null;
_status = UserStateStatus.idle;
notifyListeners(); // A notificação síncrona é segura no logout
}
// O método loadUser não precisa de lógica, pois fetchUserData já é chamado pelo listener.
Future<void> loadUser() async {}
}