recipe-manager/mobile/lib/core/providers.dart

135 lines
3.7 KiB
Dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'api/api_client.dart';
import 'models/recipe.dart';
import 'storage/secure_storage_service.dart';
final secureStorageProvider = Provider<SecureStorageService>((ref) {
return SecureStorageService();
});
final apiClientProvider = Provider<ApiClient>((ref) {
final storage = ref.watch(secureStorageProvider);
return ApiClient(storage);
});
// Authentication State Notifier
class AuthState {
final bool isAuthenticated;
final bool isLoading;
final String? errorMessage;
final Map<String, dynamic>? user;
const AuthState({
required this.isAuthenticated,
this.isLoading = false,
this.errorMessage,
this.user,
});
factory AuthState.initial() => const AuthState(isAuthenticated: false);
factory AuthState.loading() => const AuthState(isAuthenticated: false, isLoading: true);
factory AuthState.authenticated(Map<String, dynamic> user) =>
AuthState(isAuthenticated: true, user: user);
factory AuthState.unauthenticated([String? error]) =>
AuthState(isAuthenticated: false, errorMessage: error);
}
class AuthNotifier extends StateNotifier<AuthState> {
final ApiClient _api;
final SecureStorageService _storage;
AuthNotifier(this._api, this._storage) : super(AuthState.initial()) {
checkInitialAuth();
}
Future<void> checkInitialAuth() async {
final isValid = await _storage.hasValidSession();
if (isValid) {
final user = await _storage.getUser();
state = AuthState.authenticated(user ?? {'username': 'Chef'});
} else {
state = AuthState.unauthenticated();
}
}
Future<bool> login({
required String serverUrl,
required String username,
required String password,
required String deviceName,
}) async {
state = AuthState.loading();
try {
final res = await _api.login(
serverUrl: serverUrl,
username: username,
password: password,
deviceName: deviceName,
);
final user = res['user'] as Map<String, dynamic>;
state = AuthState.authenticated(user);
return true;
} catch (e) {
state = AuthState.unauthenticated(e.toString());
return false;
}
}
Future<void> logout() async {
await _api.logout();
state = AuthState.unauthenticated();
}
}
final authProvider = StateNotifierProvider<AuthNotifier, AuthState>((ref) {
final api = ref.watch(apiClientProvider);
final storage = ref.watch(secureStorageProvider);
return AuthNotifier(api, storage);
});
// Recipe List State Notifier
class RecipeListNotifier extends StateNotifier<AsyncValue<List<Recipe>>> {
final ApiClient _api;
RecipeListNotifier(this._api) : super(const AsyncValue.loading()) {
loadRecipes();
}
Future<void> loadRecipes() async {
state = const AsyncValue.loading();
try {
final recipes = await _api.fetchRecipes();
state = AsyncValue.data(recipes);
} catch (e, st) {
state = AsyncValue.error(e, st);
}
}
Future<void> addOrUpdate(Recipe recipe) async {
try {
final saved = await _api.saveRecipe(recipe);
final current = state.value ?? [];
final idx = current.indexWhere((r) => r.id == saved.id);
if (idx >= 0) {
current[idx] = saved;
state = AsyncValue.data([...current]);
} else {
state = AsyncValue.data([saved, ...current]);
}
} catch (e) {
rethrow;
}
}
Future<void> remove(String id) async {
await _api.deleteRecipe(id);
final current = state.value ?? [];
state = AsyncValue.data(current.where((r) => r.id != id).toList());
}
}
final recipeListProvider =
StateNotifierProvider<RecipeListNotifier, AsyncValue<List<Recipe>>>((ref) {
final api = ref.watch(apiClientProvider);
return RecipeListNotifier(api);
});