Se agrego el sdk para el pda de honeywell
This commit is contained in:
@@ -1,43 +1,92 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:audioplayers/audioplayers.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:vibration/vibration.dart';
|
||||
|
||||
class FeedbackService {
|
||||
FeedbackService() : _player = AudioPlayer() {
|
||||
_player.setReleaseMode(ReleaseMode.stop);
|
||||
FeedbackService() {
|
||||
_configurePlayers();
|
||||
}
|
||||
|
||||
final AudioPlayer _player;
|
||||
final AudioPlayer _scanPlayer = AudioPlayer();
|
||||
final AudioPlayer _successPlayer = AudioPlayer();
|
||||
final AudioPlayer _warningPlayer = AudioPlayer();
|
||||
|
||||
static final AudioContext _loudContext = AudioContext(
|
||||
android: AudioContextAndroid(
|
||||
isSpeakerphoneOn: true,
|
||||
audioMode: AndroidAudioMode.normal,
|
||||
contentType: AndroidContentType.sonification,
|
||||
usageType: AndroidUsageType.alarm,
|
||||
audioFocus: AndroidAudioFocus.gain,
|
||||
),
|
||||
iOS: AudioContextIOS(
|
||||
category: AVAudioSessionCategory.playback,
|
||||
options: {
|
||||
AVAudioSessionOptions.mixWithOthers,
|
||||
AVAudioSessionOptions.duckOthers,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
Future<void> _configurePlayers() async {
|
||||
for (final player in [_scanPlayer, _successPlayer, _warningPlayer]) {
|
||||
await player.setReleaseMode(ReleaseMode.stop);
|
||||
await player.setPlayerMode(PlayerMode.lowLatency);
|
||||
await player.setVolume(1.0);
|
||||
await player.setAudioContext(_loudContext);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> onCodeDetected() async {
|
||||
await Future.wait([
|
||||
_play('sounds/scan.wav'),
|
||||
_vibratePattern(const [0, 40]),
|
||||
_play(_scanPlayer, 'sounds/scan.wav'),
|
||||
_vibratePattern(const [0, 80]),
|
||||
HapticFeedback.mediumImpact(),
|
||||
]);
|
||||
}
|
||||
|
||||
Future<void> onSuccess() async {
|
||||
await Future.wait([
|
||||
_play('sounds/success.wav'),
|
||||
_vibratePattern(const [0, 90, 70, 140]),
|
||||
_playSuccessSequence(),
|
||||
_vibratePattern(const [0, 120, 60, 180]),
|
||||
HapticFeedback.heavyImpact(),
|
||||
]);
|
||||
await HapticFeedback.heavyImpact();
|
||||
}
|
||||
|
||||
Future<void> onWarning() async {
|
||||
await Future.wait([
|
||||
_play('sounds/warning.wav'),
|
||||
_vibratePattern(const [0, 120, 80, 120, 80, 120]),
|
||||
_playWarningSequence(),
|
||||
_vibratePattern(const [0, 180, 80, 180, 80, 220]),
|
||||
HapticFeedback.heavyImpact(),
|
||||
]);
|
||||
await HapticFeedback.mediumImpact();
|
||||
}
|
||||
|
||||
Future<void> _play(String asset) async {
|
||||
Future<void> _playSuccessSequence() async {
|
||||
await _play(_successPlayer, 'sounds/success.wav');
|
||||
if (!kIsWeb && Platform.isAndroid) {
|
||||
await Future<void>.delayed(const Duration(milliseconds: 120));
|
||||
await _play(_successPlayer, 'sounds/success.wav');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _playWarningSequence() async {
|
||||
for (var i = 0; i < 2; i++) {
|
||||
await _play(_warningPlayer, 'sounds/warning.wav');
|
||||
if (i == 0) {
|
||||
await Future<void>.delayed(const Duration(milliseconds: 140));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _play(AudioPlayer player, String asset) async {
|
||||
try {
|
||||
await _player.stop();
|
||||
await _player.play(AssetSource(asset));
|
||||
await player.stop();
|
||||
await player.play(AssetSource(asset));
|
||||
} catch (_) {
|
||||
// Ignore audio errors on devices without speaker routing.
|
||||
await SystemSound.play(SystemSoundType.alert);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +100,7 @@ class FeedbackService {
|
||||
final intensities = pattern
|
||||
.asMap()
|
||||
.entries
|
||||
.map((entry) => entry.key.isOdd ? 180 : 0)
|
||||
.map((entry) => entry.key.isOdd ? 255 : 0)
|
||||
.toList();
|
||||
await Vibration.vibrate(
|
||||
pattern: pattern,
|
||||
@@ -62,11 +111,15 @@ class FeedbackService {
|
||||
|
||||
await Vibration.vibrate(pattern: pattern);
|
||||
} catch (_) {
|
||||
await HapticFeedback.selectionClick();
|
||||
await HapticFeedback.heavyImpact();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
await _player.dispose();
|
||||
await Future.wait([
|
||||
_scanPlayer.dispose(),
|
||||
_successPlayer.dispose(),
|
||||
_warningPlayer.dispose(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:qrscanner/services/scan_launch_service.dart';
|
||||
|
||||
typedef HardwareScanCallback = void Function(String rawValue);
|
||||
|
||||
/// Integración con el escáner físico Honeywell vía Intent API nativa (EDA51).
|
||||
class HardwareScannerService {
|
||||
HardwareScannerService._();
|
||||
|
||||
static final HardwareScannerService instance = HardwareScannerService._();
|
||||
|
||||
final _scanLaunch = ScanLaunchService.instance;
|
||||
|
||||
HardwareScanCallback? _onScan;
|
||||
void Function(Exception error)? _onError;
|
||||
StreamSubscription<String>? _scanSubscription;
|
||||
bool _active = false;
|
||||
|
||||
bool get isActive => _active;
|
||||
|
||||
static Future<bool> isAvailable() => instance._scanLaunch.isSupported();
|
||||
|
||||
Future<bool> activate({
|
||||
required HardwareScanCallback onScan,
|
||||
void Function(Exception error)? onError,
|
||||
}) async {
|
||||
if (kIsWeb || !Platform.isAndroid) return false;
|
||||
|
||||
_onScan = onScan;
|
||||
_onError = onError;
|
||||
|
||||
await _scanLaunch.claimScanner();
|
||||
await _scanSubscription?.cancel();
|
||||
_scanSubscription = _scanLaunch.scanStream.listen(
|
||||
(code) => _onScan?.call(code),
|
||||
onError: (Object error) => _onError?.call(Exception(error.toString())),
|
||||
);
|
||||
|
||||
_active = true;
|
||||
return _scanLaunch.isSupported();
|
||||
}
|
||||
|
||||
Future<void> pause() async {
|
||||
_active = false;
|
||||
}
|
||||
|
||||
Future<void> deactivate() async {
|
||||
await _scanSubscription?.cancel();
|
||||
_scanSubscription = null;
|
||||
_onScan = null;
|
||||
_onError = null;
|
||||
_active = false;
|
||||
}
|
||||
|
||||
Future<void> ensureStopped() async {
|
||||
await deactivate();
|
||||
await _scanLaunch.releaseScanner();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class ScanLaunchService {
|
||||
ScanLaunchService._();
|
||||
|
||||
static final ScanLaunchService instance = ScanLaunchService._();
|
||||
|
||||
static const _methodChannel = MethodChannel('com.example.qrscanner/scan_launch');
|
||||
static const _eventChannel = EventChannel('com.example.qrscanner/scan_events');
|
||||
|
||||
Stream<String>? _scanStream;
|
||||
|
||||
bool get isAndroid => !kIsWeb && Platform.isAndroid;
|
||||
|
||||
Stream<String> get scanStream {
|
||||
_scanStream ??= _eventChannel
|
||||
.receiveBroadcastStream()
|
||||
.where((event) => event is String && event.trim().isNotEmpty)
|
||||
.map((event) => (event as String).trim());
|
||||
return _scanStream!;
|
||||
}
|
||||
|
||||
Future<bool> isSupported() async {
|
||||
if (!isAndroid) return false;
|
||||
try {
|
||||
final supported = await _methodChannel.invokeMethod<bool>('isSupported');
|
||||
return supported ?? false;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> claimScanner() async {
|
||||
if (!isAndroid) return;
|
||||
try {
|
||||
await _methodChannel.invokeMethod<void>('claimScanner');
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
Future<void> releaseScanner() async {
|
||||
if (!isAndroid) return;
|
||||
try {
|
||||
await _methodChannel.invokeMethod<void>('releaseScanner');
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
Future<String?> consumePendingScan() async {
|
||||
if (!isAndroid) return null;
|
||||
try {
|
||||
final code = await _methodChannel.invokeMethod<String>('consumePendingScan');
|
||||
final trimmed = code?.trim();
|
||||
if (trimmed == null || trimmed.isEmpty) return null;
|
||||
return trimmed;
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:qrscanner/models/auth_session.dart';
|
||||
import 'package:qrscanner/screens/home_screen.dart';
|
||||
import 'package:qrscanner/screens/scanner_screen.dart';
|
||||
import 'package:qrscanner/services/auth_service.dart';
|
||||
import 'package:qrscanner/services/scan_launch_service.dart';
|
||||
|
||||
class ScanNavigationService {
|
||||
ScanNavigationService._();
|
||||
|
||||
static final ScanNavigationService instance = ScanNavigationService._();
|
||||
|
||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
final AuthService _authService = AuthService();
|
||||
StreamSubscription<String>? _scanSubscription;
|
||||
String? _pendingCode;
|
||||
bool _handlingScan = false;
|
||||
bool _scannerOpen = false;
|
||||
|
||||
String? get pendingCode => _pendingCode;
|
||||
|
||||
void setPendingCode(String code) {
|
||||
final trimmed = code.trim();
|
||||
if (trimmed.isEmpty) return;
|
||||
_pendingCode = trimmed;
|
||||
}
|
||||
|
||||
String? takePendingCode() {
|
||||
final code = _pendingCode;
|
||||
_pendingCode = null;
|
||||
return code;
|
||||
}
|
||||
|
||||
Future<void> initialize() async {
|
||||
await ScanLaunchService.instance.claimScanner();
|
||||
|
||||
final pending = await ScanLaunchService.instance.consumePendingScan();
|
||||
if (pending != null) {
|
||||
setPendingCode(pending);
|
||||
}
|
||||
|
||||
await _scanSubscription?.cancel();
|
||||
_scanSubscription = ScanLaunchService.instance.scanStream.listen(
|
||||
(code) => unawaited(handleScan(code)),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
await _scanSubscription?.cancel();
|
||||
_scanSubscription = null;
|
||||
}
|
||||
|
||||
Future<void> handleScan(String code, {AuthSession? session}) async {
|
||||
final trimmed = code.trim();
|
||||
if (trimmed.isEmpty || _handlingScan) return;
|
||||
|
||||
_handlingScan = true;
|
||||
try {
|
||||
setPendingCode(trimmed);
|
||||
|
||||
final activeSession = session ??
|
||||
_authService.currentSession ??
|
||||
await _authService.loadStoredSession();
|
||||
|
||||
if (activeSession == null) return;
|
||||
|
||||
final navigator = navigatorKey.currentState;
|
||||
if (navigator == null) return;
|
||||
|
||||
takePendingCode();
|
||||
|
||||
if (_scannerOpen) return;
|
||||
|
||||
_scannerOpen = true;
|
||||
try {
|
||||
await navigator.push(
|
||||
PageRouteBuilder<void>(
|
||||
settings: const RouteSettings(name: '/scanner'),
|
||||
pageBuilder: (context, animation, secondaryAnimation) =>
|
||||
ScannerScreen(
|
||||
session: activeSession,
|
||||
initialCode: trimmed,
|
||||
),
|
||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||
return FadeTransition(
|
||||
opacity: animation,
|
||||
child: SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(0, 0.06),
|
||||
end: Offset.zero,
|
||||
).animate(CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: Curves.easeOutCubic,
|
||||
)),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
},
|
||||
transitionDuration: const Duration(milliseconds: 380),
|
||||
),
|
||||
);
|
||||
} finally {
|
||||
_scannerOpen = false;
|
||||
}
|
||||
} finally {
|
||||
_handlingScan = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> openPendingScanAfterAuth(AuthSession session) async {
|
||||
final code = takePendingCode();
|
||||
if (code == null) return;
|
||||
await handleScan(code, session: session);
|
||||
}
|
||||
|
||||
Future<void> navigateToHome(AuthSession session) async {
|
||||
final navigator = navigatorKey.currentState;
|
||||
if (navigator == null) return;
|
||||
|
||||
await navigator.pushAndRemoveUntil(
|
||||
PageRouteBuilder<void>(
|
||||
pageBuilder: (context, animation, secondaryAnimation) =>
|
||||
HomeScreen(session: session),
|
||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||
return FadeTransition(opacity: animation, child: child);
|
||||
},
|
||||
transitionDuration: const Duration(milliseconds: 400),
|
||||
),
|
||||
(route) => false,
|
||||
);
|
||||
|
||||
await openPendingScanAfterAuth(session);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user