Reliability/PoC hardening: dedupe uploads by file_hash, surface editor sync failures instead of a false success SnackBar with a retry-without-re-OCR path, bound the OCR pipeline fetches with timeouts, share a single ApiClient/Dio instance app-wide, tune capture JPEG quality, and add an opt-in docker-compose.demo.yml for a production-mode run ahead of client demos. Translate all Flutter-side user-facing text (screens, validators, SnackBars, the printed delivery receipt, and shared API error messages) to Bahasa Indonesia. Also includes in-progress OCR parser/accuracy-tuning work from the same session: table column/unit normalization fixes, store/customer master data, accuracy history log, and test-image renaming/cleanup. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017eRAsLqN9Sg1b9YPz22Lxz
62 lines
1.6 KiB
Dart
62 lines
1.6 KiB
Dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import '../../core/network/api_client.dart';
|
|
import '../../config/app_config.dart';
|
|
|
|
class AuthNotifier extends StateNotifier<bool> {
|
|
AuthNotifier(this._ref) : super(false);
|
|
|
|
final Ref _ref;
|
|
|
|
Future<bool> checkLoginState() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final token = prefs.getString('auth_token');
|
|
if (token != null) {
|
|
state = true;
|
|
return true;
|
|
}
|
|
state = false;
|
|
return false;
|
|
}
|
|
|
|
Future<void> login(String username, String password) async {
|
|
final apiClient = _ref.read(apiClientProvider);
|
|
try {
|
|
final response = await apiClient.client.post(
|
|
AppConfig.loginEndpoint,
|
|
data: {
|
|
'username': username,
|
|
'password': password,
|
|
},
|
|
);
|
|
|
|
if (response.data is! Map || response.data['data'] is! Map) {
|
|
throw Exception("Format respon server tidak valid (bukan JSON)");
|
|
}
|
|
|
|
final token = response.data['data']['token'];
|
|
if (token == null) {
|
|
throw Exception("Token otentikasi tidak ditemukan dalam respon");
|
|
}
|
|
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setString('auth_token', token);
|
|
|
|
state = true;
|
|
} catch (e) {
|
|
state = false;
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
Future<void> logout() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.remove('auth_token');
|
|
state = false;
|
|
}
|
|
}
|
|
|
|
final authProvider = StateNotifierProvider<AuthNotifier, bool>((ref) {
|
|
return AuthNotifier(ref);
|
|
});
|