Estado base antes de integración Cognito.
Incluye admin web Spring Boot, API JWT para escáner y app Flutter qrscanner con login y escaneo de pases.
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:qrscanner/config/api_config.dart';
|
||||
import 'package:qrscanner/models/auth_session.dart';
|
||||
import 'package:qrscanner/models/login_response.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class AuthService {
|
||||
static const _sessionKey = 'auth_session';
|
||||
|
||||
AuthSession? _currentSession;
|
||||
|
||||
AuthSession? get currentSession => _currentSession;
|
||||
|
||||
Future<LoginResponse> login({
|
||||
required String username,
|
||||
required String password,
|
||||
bool rememberMe = false,
|
||||
}) async {
|
||||
final response = await http.post(
|
||||
Uri.parse(ApiConfig.loginUrl),
|
||||
headers: const {'Content-Type': 'application/json'},
|
||||
body: jsonEncode({
|
||||
'username': username,
|
||||
'password': password,
|
||||
}),
|
||||
);
|
||||
|
||||
final Map<String, dynamic> body =
|
||||
jsonDecode(response.body) as Map<String, dynamic>;
|
||||
final loginResponse = LoginResponse.fromJson(body);
|
||||
|
||||
if (response.statusCode == 200 &&
|
||||
loginResponse.success &&
|
||||
loginResponse.accessToken != null) {
|
||||
final session = AuthSession.fromLoginResponse(
|
||||
loginResponse.copyWith(username: loginResponse.username ?? username),
|
||||
);
|
||||
_currentSession = session;
|
||||
|
||||
if (rememberMe) {
|
||||
await _saveSession(session);
|
||||
} else {
|
||||
await clearSession();
|
||||
}
|
||||
|
||||
return loginResponse;
|
||||
}
|
||||
|
||||
return LoginResponse(
|
||||
success: false,
|
||||
message: loginResponse.message ??
|
||||
'No se pudo iniciar sesión (${response.statusCode})',
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _saveSession(AuthSession session) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(_sessionKey, jsonEncode(session.toJson()));
|
||||
}
|
||||
|
||||
Future<void> clearSession() async {
|
||||
_currentSession = null;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove(_sessionKey);
|
||||
}
|
||||
|
||||
Future<AuthSession?> loadStoredSession() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final raw = prefs.getString(_sessionKey);
|
||||
if (raw == null || raw.isEmpty) return null;
|
||||
|
||||
final session = AuthSession.fromJson(
|
||||
jsonDecode(raw) as Map<String, dynamic>,
|
||||
);
|
||||
_currentSession = session;
|
||||
return session;
|
||||
}
|
||||
|
||||
Future<bool> hasStoredSession() async {
|
||||
final session = await loadStoredSession();
|
||||
return session != null && session.accessToken.isNotEmpty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import 'package:audioplayers/audioplayers.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:vibration/vibration.dart';
|
||||
|
||||
class FeedbackService {
|
||||
FeedbackService() : _player = AudioPlayer() {
|
||||
_player.setReleaseMode(ReleaseMode.stop);
|
||||
}
|
||||
|
||||
final AudioPlayer _player;
|
||||
|
||||
Future<void> onCodeDetected() async {
|
||||
await Future.wait([
|
||||
_play('sounds/scan.wav'),
|
||||
_vibratePattern(const [0, 40]),
|
||||
]);
|
||||
}
|
||||
|
||||
Future<void> onSuccess() async {
|
||||
await Future.wait([
|
||||
_play('sounds/success.wav'),
|
||||
_vibratePattern(const [0, 90, 70, 140]),
|
||||
]);
|
||||
await HapticFeedback.heavyImpact();
|
||||
}
|
||||
|
||||
Future<void> onWarning() async {
|
||||
await Future.wait([
|
||||
_play('sounds/warning.wav'),
|
||||
_vibratePattern(const [0, 120, 80, 120, 80, 120]),
|
||||
]);
|
||||
await HapticFeedback.mediumImpact();
|
||||
}
|
||||
|
||||
Future<void> _play(String asset) async {
|
||||
try {
|
||||
await _player.stop();
|
||||
await _player.play(AssetSource(asset));
|
||||
} catch (_) {
|
||||
// Ignore audio errors on devices without speaker routing.
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _vibratePattern(List<int> pattern) async {
|
||||
try {
|
||||
final hasVibrator = await Vibration.hasVibrator();
|
||||
if (hasVibrator != true) return;
|
||||
|
||||
final hasAmplitude = await Vibration.hasAmplitudeControl();
|
||||
if (hasAmplitude == true) {
|
||||
final intensities = pattern
|
||||
.asMap()
|
||||
.entries
|
||||
.map((entry) => entry.key.isOdd ? 180 : 0)
|
||||
.toList();
|
||||
await Vibration.vibrate(
|
||||
pattern: pattern,
|
||||
intensities: intensities,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await Vibration.vibrate(pattern: pattern);
|
||||
} catch (_) {
|
||||
await HapticFeedback.selectionClick();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
await _player.dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:qrscanner/config/api_config.dart';
|
||||
import 'package:qrscanner/models/auth_session.dart';
|
||||
import 'package:qrscanner/models/scan_response.dart';
|
||||
|
||||
class PassCodeService {
|
||||
Future<ScanResponse> scan({
|
||||
required AuthSession session,
|
||||
required String code,
|
||||
}) async {
|
||||
final response = await http.post(
|
||||
Uri.parse(ApiConfig.scanUrl),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': session.authorizationHeader,
|
||||
},
|
||||
body: jsonEncode({'code': code}),
|
||||
);
|
||||
|
||||
final Map<String, dynamic> body =
|
||||
jsonDecode(response.body) as Map<String, dynamic>;
|
||||
|
||||
return ScanResponse.fromJson(body);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user