Se agrego el sdk para el pda de honeywell
This commit is contained in:
@@ -1,13 +1,16 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
<uses-permission android:name="com.honeywell.decode.permission.DECODE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name=".QrScannerApplication"
|
||||||
android:label="qrscanner"
|
android:label="qrscanner"
|
||||||
android:name="${applicationName}"
|
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:usesCleartextTraffic="true">
|
android:usesCleartextTraffic="true"
|
||||||
|
tools:replace="android:label">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
@@ -17,10 +20,6 @@
|
|||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize">
|
||||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
|
||||||
the Android process has started. This theme is visible to the user
|
|
||||||
while the Flutter UI initializes. After that, this theme continues
|
|
||||||
to determine the Window background behind the Flutter UI. -->
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="io.flutter.embedding.android.NormalTheme"
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
android:resource="@style/NormalTheme"
|
android:resource="@style/NormalTheme"
|
||||||
@@ -29,22 +28,29 @@
|
|||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.example.qrscanner.action.BARCODE_DATA"/>
|
||||||
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<!-- Don't delete the meta-data below.
|
|
||||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
<receiver
|
||||||
|
android:name=".HoneywellScanReceiver"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.example.qrscanner.action.BARCODE_DATA"/>
|
||||||
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="flutterEmbedding"
|
android:name="flutterEmbedding"
|
||||||
android:value="2" />
|
android:value="2" />
|
||||||
</application>
|
</application>
|
||||||
<!-- Required to query activities that can process text, see:
|
|
||||||
https://developer.android.com/training/package-visibility and
|
|
||||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
|
||||||
|
|
||||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
|
||||||
<queries>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||||
<data android:mimeType="text/plain"/>
|
<data android:mimeType="text/plain"/>
|
||||||
</intent>
|
</intent>
|
||||||
</queries>
|
</queries>
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
package com.example.qrscanner
|
||||||
|
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import io.flutter.plugin.common.EventChannel
|
||||||
|
|
||||||
|
object HoneywellScanManager {
|
||||||
|
const val ACTION_BARCODE_DATA = "com.example.qrscanner.action.BARCODE_DATA"
|
||||||
|
const val EXTRA_SCAN_DATA = "scan_data"
|
||||||
|
|
||||||
|
private const val PREFS = "honeywell_scan_bridge"
|
||||||
|
private const val PENDING_KEY = "pending_scan"
|
||||||
|
|
||||||
|
private const val ACTION_CLAIM_SCANNER = "com.honeywell.aidc.action.ACTION_CLAIM_SCANNER"
|
||||||
|
private const val ACTION_RELEASE_SCANNER = "com.honeywell.aidc.action.ACTION_RELEASE_SCANNER"
|
||||||
|
private const val EXTRA_SCANNER = "com.honeywell.aidc.extra.EXTRA_SCANNER"
|
||||||
|
private const val EXTRA_PROPERTIES = "com.honeywell.aidc.extra.EXTRA_PROPERTIES"
|
||||||
|
private const val SCANNER_PACKAGE = "com.intermec.datacollectionservice"
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var eventSink: EventChannel.EventSink? = null
|
||||||
|
|
||||||
|
fun setEventSink(sink: EventChannel.EventSink?) {
|
||||||
|
eventSink = sink
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isSupported(context: Context): Boolean {
|
||||||
|
return try {
|
||||||
|
context.packageManager.getPackageInfo(SCANNER_PACKAGE, 0)
|
||||||
|
true
|
||||||
|
} catch (_: PackageManager.NameNotFoundException) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun claimScanner(context: Context) {
|
||||||
|
if (!isSupported(context)) return
|
||||||
|
|
||||||
|
val properties = Bundle().apply {
|
||||||
|
putBoolean("DPR_DATA_INTENT", true)
|
||||||
|
putString("DPR_DATA_INTENT_ACTION", ACTION_BARCODE_DATA)
|
||||||
|
putBoolean("DPR_LAUNCH_BROWSER", false)
|
||||||
|
putBoolean("DEC_QR_ENABLED", true)
|
||||||
|
putBoolean("DEC_AZTEC_ENABLED", false)
|
||||||
|
putBoolean("DEC_CODABAR_ENABLED", false)
|
||||||
|
putBoolean("DEC_CODE39_ENABLED", false)
|
||||||
|
putBoolean("DEC_CODE93_ENABLED", false)
|
||||||
|
putBoolean("DEC_CODE128_ENABLED", false)
|
||||||
|
putBoolean("DEC_DATAMATRIX_ENABLED", false)
|
||||||
|
putBoolean("DEC_EAN8_ENABLED", false)
|
||||||
|
putBoolean("DEC_EAN13_ENABLED", false)
|
||||||
|
putBoolean("DEC_MAXICODE_ENABLED", false)
|
||||||
|
putBoolean("DEC_PDF417_ENABLED", false)
|
||||||
|
putBoolean("DEC_RSS_14_ENABLED", false)
|
||||||
|
putBoolean("DEC_RSS_EXPANDED_ENABLED", false)
|
||||||
|
putBoolean("DEC_UPCA_ENABLE", false)
|
||||||
|
putBoolean("DEC_UPCE0_ENABLED", false)
|
||||||
|
}
|
||||||
|
|
||||||
|
sendScannerBroadcast(
|
||||||
|
context,
|
||||||
|
Intent(ACTION_CLAIM_SCANNER)
|
||||||
|
.setPackage(SCANNER_PACKAGE)
|
||||||
|
.putExtra(EXTRA_SCANNER, "dcs.scanner.imager")
|
||||||
|
.putExtra(EXTRA_PROPERTIES, properties),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun releaseScanner(context: Context) {
|
||||||
|
if (!isSupported(context)) return
|
||||||
|
sendScannerBroadcast(context, Intent(ACTION_RELEASE_SCANNER))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleBarcodeIntent(context: Context, intent: Intent?) {
|
||||||
|
val data = extractScanData(intent) ?: return
|
||||||
|
deliverScan(context, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deliverScan(context: Context, data: String) {
|
||||||
|
val trimmed = data.trim()
|
||||||
|
if (trimmed.isEmpty()) return
|
||||||
|
|
||||||
|
storePendingScan(context, trimmed)
|
||||||
|
eventSink?.success(trimmed)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun launchAppWithScan(context: Context, data: String) {
|
||||||
|
val trimmed = data.trim()
|
||||||
|
if (trimmed.isEmpty()) return
|
||||||
|
|
||||||
|
storePendingScan(context, trimmed)
|
||||||
|
|
||||||
|
val launchIntent = Intent(context, MainActivity::class.java).apply {
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or
|
||||||
|
Intent.FLAG_ACTIVITY_SINGLE_TOP or
|
||||||
|
Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||||
|
putExtra(EXTRA_SCAN_DATA, trimmed)
|
||||||
|
}
|
||||||
|
context.startActivity(launchIntent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun consumePendingScan(context: Context): String? {
|
||||||
|
val prefs = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||||
|
val stored = prefs.getString(PENDING_KEY, null)?.trim()
|
||||||
|
if (!stored.isNullOrEmpty()) {
|
||||||
|
prefs.edit().remove(PENDING_KEY).apply()
|
||||||
|
return stored
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun storePendingScan(context: Context, data: String) {
|
||||||
|
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||||
|
.edit()
|
||||||
|
.putString(PENDING_KEY, data)
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractScanData(intent: Intent?): String? {
|
||||||
|
if (intent == null) return null
|
||||||
|
|
||||||
|
intent.getStringExtra(EXTRA_SCAN_DATA)?.let { return it }
|
||||||
|
|
||||||
|
if (intent.action == ACTION_BARCODE_DATA) {
|
||||||
|
val version = intent.getIntExtra("version", 0)
|
||||||
|
if (version >= 1) {
|
||||||
|
return intent.getStringExtra("data")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return intent.getStringExtra("data")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendScannerBroadcast(context: Context, intent: Intent) {
|
||||||
|
if (Build.VERSION.SDK_INT < 26) {
|
||||||
|
context.sendBroadcast(intent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val matches = context.packageManager.queryBroadcastReceivers(intent, PackageManager.MATCH_DEFAULT_ONLY)
|
||||||
|
if (matches.isEmpty()) {
|
||||||
|
context.sendBroadcast(intent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (resolveInfo in matches) {
|
||||||
|
val explicit = Intent(intent).apply {
|
||||||
|
component = ComponentName(
|
||||||
|
resolveInfo.activityInfo.applicationInfo.packageName,
|
||||||
|
resolveInfo.activityInfo.name,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
context.sendBroadcast(explicit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.example.qrscanner
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
|
||||||
|
class HoneywellScanReceiver : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
if (intent.action != HoneywellScanManager.ACTION_BARCODE_DATA) return
|
||||||
|
|
||||||
|
val version = intent.getIntExtra("version", 0)
|
||||||
|
if (version < 1) return
|
||||||
|
|
||||||
|
val data = intent.getStringExtra("data")?.trim().orEmpty()
|
||||||
|
if (data.isEmpty()) return
|
||||||
|
|
||||||
|
HoneywellScanManager.launchAppWithScan(context, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,59 @@
|
|||||||
package com.example.qrscanner
|
package com.example.qrscanner
|
||||||
|
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
|
import io.flutter.plugin.common.EventChannel
|
||||||
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
|
||||||
class MainActivity : FlutterActivity()
|
class MainActivity : FlutterActivity() {
|
||||||
|
companion object {
|
||||||
|
private const val METHOD_CHANNEL = "com.example.qrscanner/scan_launch"
|
||||||
|
private const val EVENT_CHANNEL = "com.example.qrscanner/scan_events"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||||
|
super.configureFlutterEngine(flutterEngine)
|
||||||
|
|
||||||
|
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, METHOD_CHANNEL)
|
||||||
|
.setMethodCallHandler { call, result ->
|
||||||
|
when (call.method) {
|
||||||
|
"isSupported" -> result.success(HoneywellScanManager.isSupported(this))
|
||||||
|
"claimScanner" -> {
|
||||||
|
HoneywellScanManager.claimScanner(this)
|
||||||
|
result.success(true)
|
||||||
|
}
|
||||||
|
"releaseScanner" -> {
|
||||||
|
HoneywellScanManager.releaseScanner(this)
|
||||||
|
result.success(true)
|
||||||
|
}
|
||||||
|
"consumePendingScan" -> {
|
||||||
|
result.success(HoneywellScanManager.consumePendingScan(this))
|
||||||
|
}
|
||||||
|
else -> result.notImplemented()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EventChannel(flutterEngine.dartExecutor.binaryMessenger, EVENT_CHANNEL)
|
||||||
|
.setStreamHandler(object : EventChannel.StreamHandler {
|
||||||
|
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
|
||||||
|
HoneywellScanManager.setEventSink(events)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCancel(arguments: Any?) {
|
||||||
|
HoneywellScanManager.setEventSink(null)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNewIntent(intent: android.content.Intent) {
|
||||||
|
super.onNewIntent(intent)
|
||||||
|
setIntent(intent)
|
||||||
|
HoneywellScanManager.handleBarcodeIntent(this, intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
HoneywellScanManager.claimScanner(this)
|
||||||
|
HoneywellScanManager.handleBarcodeIntent(this, intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.example.qrscanner
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
|
||||||
|
class QrScannerApplication : Application() {
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
HoneywellScanManager.claimScanner(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
configurations.maybeCreate("default")
|
||||||
|
artifacts.add("default", file('honeywell.aar'))
|
||||||
Binary file not shown.
@@ -24,3 +24,4 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
include(":app")
|
include(":app")
|
||||||
|
include(":honeywell")
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
+23
-1
@@ -1,20 +1,42 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:qrscanner/screens/home_screen.dart';
|
||||||
import 'package:qrscanner/screens/login_screen.dart';
|
import 'package:qrscanner/screens/login_screen.dart';
|
||||||
|
import 'package:qrscanner/services/scan_navigation_service.dart';
|
||||||
import 'package:qrscanner/theme/app_theme.dart';
|
import 'package:qrscanner/theme/app_theme.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatefulWidget {
|
||||||
const MyApp({super.key});
|
const MyApp({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MyApp> createState() => _MyAppState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MyAppState extends State<MyApp> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
ScanNavigationService.instance.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
ScanNavigationService.instance.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'QR Scanner',
|
title: 'QR Scanner',
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
theme: AppTheme.darkTheme,
|
theme: AppTheme.darkTheme,
|
||||||
|
navigatorKey: ScanNavigationService.instance.navigatorKey,
|
||||||
|
navigatorObservers: [homeRouteObserver],
|
||||||
home: const LoginScreen(),
|
home: const LoginScreen(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,14 @@ import 'package:qrscanner/models/auth_session.dart';
|
|||||||
import 'package:qrscanner/screens/login_screen.dart';
|
import 'package:qrscanner/screens/login_screen.dart';
|
||||||
import 'package:qrscanner/screens/scanner_screen.dart';
|
import 'package:qrscanner/screens/scanner_screen.dart';
|
||||||
import 'package:qrscanner/services/auth_service.dart';
|
import 'package:qrscanner/services/auth_service.dart';
|
||||||
|
import 'package:qrscanner/services/hardware_scanner_service.dart';
|
||||||
|
import 'package:qrscanner/services/scan_launch_service.dart';
|
||||||
import 'package:qrscanner/theme/app_theme.dart';
|
import 'package:qrscanner/theme/app_theme.dart';
|
||||||
|
|
||||||
class HomeScreen extends StatelessWidget {
|
final RouteObserver<ModalRoute<void>> homeRouteObserver =
|
||||||
|
RouteObserver<ModalRoute<void>>();
|
||||||
|
|
||||||
|
class HomeScreen extends StatefulWidget {
|
||||||
const HomeScreen({
|
const HomeScreen({
|
||||||
super.key,
|
super.key,
|
||||||
required this.session,
|
required this.session,
|
||||||
@@ -13,7 +18,29 @@ class HomeScreen extends StatelessWidget {
|
|||||||
|
|
||||||
final AuthSession session;
|
final AuthSession session;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<HomeScreen> createState() => _HomeScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HomeScreenState extends State<HomeScreen> {
|
||||||
|
bool _hardwareAvailable = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_checkHardware();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _checkHardware() async {
|
||||||
|
final available = await ScanLaunchService.instance.isSupported();
|
||||||
|
if (mounted) {
|
||||||
|
setState(() => _hardwareAvailable = available);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _logout(BuildContext context) async {
|
Future<void> _logout(BuildContext context) async {
|
||||||
|
await HardwareScannerService.instance.ensureStopped();
|
||||||
|
await ScanLaunchService.instance.releaseScanner();
|
||||||
await AuthService().clearSession();
|
await AuthService().clearSession();
|
||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
@@ -30,11 +57,12 @@ class HomeScreen extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _openScanner(BuildContext context) {
|
Future<void> _openScanner(BuildContext context) async {
|
||||||
Navigator.of(context).push(
|
await Navigator.of(context).push(
|
||||||
PageRouteBuilder<void>(
|
PageRouteBuilder<void>(
|
||||||
|
settings: const RouteSettings(name: '/scanner'),
|
||||||
pageBuilder: (context, animation, secondaryAnimation) =>
|
pageBuilder: (context, animation, secondaryAnimation) =>
|
||||||
ScannerScreen(session: session),
|
ScannerScreen(session: widget.session),
|
||||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||||
return FadeTransition(
|
return FadeTransition(
|
||||||
opacity: animation,
|
opacity: animation,
|
||||||
@@ -57,7 +85,7 @@ class HomeScreen extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final expiresHours = (session.expiresIn / 3600).round();
|
final expiresHours = (widget.session.expiresIn / 3600).round();
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Stack(
|
body: Stack(
|
||||||
@@ -140,7 +168,7 @@ class HomeScreen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 28),
|
const SizedBox(height: 28),
|
||||||
Text(
|
Text(
|
||||||
'Hola, ${session.username}',
|
'Hola, ${widget.session.username}',
|
||||||
style:
|
style:
|
||||||
Theme.of(context).textTheme.headlineSmall?.copyWith(
|
Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@@ -175,6 +203,40 @@ class HomeScreen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (_hardwareAvailable) ...[
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 14,
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppTheme.primary.withValues(alpha: 0.12),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
border: Border.all(
|
||||||
|
color: AppTheme.primary.withValues(alpha: 0.25),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.barcode_reader,
|
||||||
|
size: 16,
|
||||||
|
color: AppTheme.primary.withValues(alpha: 0.9),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
'Escáner activo · abre la app al escanear',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white.withValues(alpha: 0.7),
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
const SizedBox(height: 48),
|
const SizedBox(height: 48),
|
||||||
_ScanButton(onPressed: () => _openScanner(context)),
|
_ScanButton(onPressed: () => _openScanner(context)),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:qrscanner/config/api_config.dart';
|
import 'package:qrscanner/config/api_config.dart';
|
||||||
import 'package:qrscanner/models/auth_session.dart';
|
import 'package:qrscanner/models/auth_session.dart';
|
||||||
import 'package:qrscanner/screens/home_screen.dart';
|
|
||||||
import 'package:qrscanner/services/auth_service.dart';
|
import 'package:qrscanner/services/auth_service.dart';
|
||||||
|
import 'package:qrscanner/services/scan_launch_service.dart';
|
||||||
|
import 'package:qrscanner/services/scan_navigation_service.dart';
|
||||||
import 'package:qrscanner/theme/app_theme.dart';
|
import 'package:qrscanner/theme/app_theme.dart';
|
||||||
|
|
||||||
class LoginScreen extends StatefulWidget {
|
class LoginScreen extends StatefulWidget {
|
||||||
@@ -47,6 +48,7 @@ class _LoginScreenState extends State<LoginScreen>
|
|||||||
curve: Curves.easeOutCubic,
|
curve: Curves.easeOutCubic,
|
||||||
));
|
));
|
||||||
_animationController.forward();
|
_animationController.forward();
|
||||||
|
ScanLaunchService.instance.claimScanner();
|
||||||
_checkStoredSession();
|
_checkStoredSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +56,7 @@ class _LoginScreenState extends State<LoginScreen>
|
|||||||
final session = await _authService.loadStoredSession();
|
final session = await _authService.loadStoredSession();
|
||||||
if (!mounted || session == null) return;
|
if (!mounted || session == null) return;
|
||||||
|
|
||||||
_goToHome(session);
|
await ScanNavigationService.instance.navigateToHome(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -83,7 +85,7 @@ class _LoginScreenState extends State<LoginScreen>
|
|||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
if (response.success && response.accessToken != null) {
|
if (response.success && response.accessToken != null) {
|
||||||
_goToHome(
|
await ScanNavigationService.instance.navigateToHome(
|
||||||
AuthSession.fromLoginResponse(
|
AuthSession.fromLoginResponse(
|
||||||
response.copyWith(
|
response.copyWith(
|
||||||
username: response.username ?? _usernameController.text.trim(),
|
username: response.username ?? _usernameController.text.trim(),
|
||||||
@@ -109,19 +111,6 @@ class _LoginScreenState extends State<LoginScreen>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _goToHome(AuthSession session) {
|
|
||||||
Navigator.of(context).pushReplacement(
|
|
||||||
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),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
|||||||
+333
-20
@@ -1,25 +1,33 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||||
import 'package:qrscanner/models/auth_session.dart';
|
import 'package:qrscanner/models/auth_session.dart';
|
||||||
import 'package:qrscanner/models/pass_code.dart';
|
import 'package:qrscanner/models/pass_code.dart';
|
||||||
import 'package:qrscanner/models/scan_response.dart';
|
import 'package:qrscanner/models/scan_response.dart';
|
||||||
import 'package:qrscanner/services/feedback_service.dart';
|
import 'package:qrscanner/services/feedback_service.dart';
|
||||||
|
import 'package:qrscanner/services/hardware_scanner_service.dart';
|
||||||
import 'package:qrscanner/services/pass_code_service.dart';
|
import 'package:qrscanner/services/pass_code_service.dart';
|
||||||
import 'package:qrscanner/theme/app_theme.dart';
|
import 'package:qrscanner/theme/app_theme.dart';
|
||||||
|
|
||||||
enum _ScanPhase { scanning, processing, success, warning }
|
enum _ScanPhase { scanning, processing, success, warning }
|
||||||
|
|
||||||
|
enum _ScannerInput { loading, camera, hardware }
|
||||||
|
|
||||||
const double _scanWindowSize = 260;
|
const double _scanWindowSize = 260;
|
||||||
const double _scanWindowRadius = 28;
|
const double _scanWindowRadius = 28;
|
||||||
const Alignment _scanWindowAlignment = Alignment(0, -0.12);
|
const Alignment _scanWindowAlignment = Alignment(0, -0.12);
|
||||||
|
const Duration _resultDisplayDuration = Duration(seconds: 5);
|
||||||
|
|
||||||
class ScannerScreen extends StatefulWidget {
|
class ScannerScreen extends StatefulWidget {
|
||||||
const ScannerScreen({
|
const ScannerScreen({
|
||||||
super.key,
|
super.key,
|
||||||
required this.session,
|
required this.session,
|
||||||
|
this.initialCode,
|
||||||
});
|
});
|
||||||
|
|
||||||
final AuthSession session;
|
final AuthSession session;
|
||||||
|
final String? initialCode;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ScannerScreen> createState() => _ScannerScreenState();
|
State<ScannerScreen> createState() => _ScannerScreenState();
|
||||||
@@ -27,21 +35,21 @@ class ScannerScreen extends StatefulWidget {
|
|||||||
|
|
||||||
class _ScannerScreenState extends State<ScannerScreen>
|
class _ScannerScreenState extends State<ScannerScreen>
|
||||||
with TickerProviderStateMixin {
|
with TickerProviderStateMixin {
|
||||||
final _scannerController = MobileScannerController(
|
MobileScannerController? _cameraController;
|
||||||
detectionSpeed: DetectionSpeed.normal,
|
final _hardwareScanner = HardwareScannerService.instance;
|
||||||
detectionTimeoutMs: 500,
|
|
||||||
facing: CameraFacing.back,
|
|
||||||
formats: const [BarcodeFormat.qrCode],
|
|
||||||
autoZoom: true,
|
|
||||||
);
|
|
||||||
final _passCodeService = PassCodeService();
|
final _passCodeService = PassCodeService();
|
||||||
final _feedbackService = FeedbackService();
|
final _feedbackService = FeedbackService();
|
||||||
|
|
||||||
|
_ScannerInput _inputMode = _ScannerInput.loading;
|
||||||
|
bool _hardwareAvailable = false;
|
||||||
|
bool _isInitialSetup = true;
|
||||||
|
bool _isSwitchingInput = false;
|
||||||
_ScanPhase _phase = _ScanPhase.scanning;
|
_ScanPhase _phase = _ScanPhase.scanning;
|
||||||
bool _isHandlingDetection = false;
|
bool _isHandlingDetection = false;
|
||||||
String? _lastScannedCode;
|
String? _lastScannedCode;
|
||||||
String? _message;
|
String? _message;
|
||||||
PassCode? _passCode;
|
PassCode? _passCode;
|
||||||
|
Timer? _resultAutoDismissTimer;
|
||||||
|
|
||||||
late final AnimationController _scanLineController;
|
late final AnimationController _scanLineController;
|
||||||
late final AnimationController _resultController;
|
late final AnimationController _resultController;
|
||||||
@@ -76,8 +84,158 @@ class _ScannerScreenState extends State<ScannerScreen>
|
|||||||
parent: _resultController,
|
parent: _resultController,
|
||||||
curve: Curves.easeOutBack,
|
curve: Curves.easeOutBack,
|
||||||
));
|
));
|
||||||
|
_initializeScannerInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _initializeScannerInput() async {
|
||||||
|
_hardwareAvailable = await HardwareScannerService.isAvailable();
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
if (_hardwareAvailable) {
|
||||||
|
await _activateHardwareMode(showLoading: true);
|
||||||
|
} else {
|
||||||
|
await _activateCameraMode(showLoading: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
final pendingCode = widget.initialCode?.trim();
|
||||||
|
if (!mounted || pendingCode == null || pendingCode.isEmpty) return;
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (mounted) _processScan(pendingCode);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
MobileScannerController _createCameraController() {
|
||||||
|
return MobileScannerController(
|
||||||
|
autoStart: false,
|
||||||
|
detectionSpeed: DetectionSpeed.normal,
|
||||||
|
detectionTimeoutMs: 500,
|
||||||
|
facing: CameraFacing.back,
|
||||||
|
formats: const [BarcodeFormat.qrCode],
|
||||||
|
autoZoom: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _activateHardwareMode({bool showLoading = false}) async {
|
||||||
|
if (!mounted) return;
|
||||||
|
if (showLoading) {
|
||||||
|
setState(() => _inputMode = _ScannerInput.loading);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
await _cameraController?.stop();
|
||||||
|
} catch (_) {}
|
||||||
|
await _cameraController?.dispose();
|
||||||
|
_cameraController = null;
|
||||||
|
_scanLineController.stop();
|
||||||
|
|
||||||
|
await Future<void>.delayed(const Duration(milliseconds: 300));
|
||||||
|
|
||||||
|
final started = await _hardwareScanner.activate(onScan: _onRawScan);
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
if (started) {
|
||||||
|
setState(() {
|
||||||
|
_inputMode = _ScannerInput.hardware;
|
||||||
|
_isInitialSetup = false;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _activateCameraMode(
|
||||||
|
showLoading: showLoading,
|
||||||
|
fallbackFromHardware: true,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
if (mounted && _inputMode == _ScannerInput.loading) {
|
||||||
|
setState(() {
|
||||||
|
_inputMode = _ScannerInput.hardware;
|
||||||
|
_isInitialSetup = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _activateCameraMode({
|
||||||
|
bool showLoading = false,
|
||||||
|
bool fallbackFromHardware = false,
|
||||||
|
}) async {
|
||||||
|
if (!mounted) return;
|
||||||
|
if (showLoading) {
|
||||||
|
setState(() => _inputMode = _ScannerInput.loading);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await _hardwareScanner.pause();
|
||||||
|
await Future<void>.delayed(const Duration(milliseconds: 300));
|
||||||
|
|
||||||
|
try {
|
||||||
|
await _cameraController?.stop();
|
||||||
|
} catch (_) {}
|
||||||
|
await _cameraController?.dispose();
|
||||||
|
_cameraController = _createCameraController();
|
||||||
|
|
||||||
|
if (!_scanLineController.isAnimating) {
|
||||||
|
_scanLineController.repeat();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {
|
||||||
|
_inputMode = _ScannerInput.camera;
|
||||||
|
_isInitialSetup = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
await Future<void>.delayed(const Duration(milliseconds: 200));
|
||||||
|
|
||||||
|
try {
|
||||||
|
await _cameraController!.start();
|
||||||
|
} catch (_) {
|
||||||
|
if (!mounted) return;
|
||||||
|
if (!fallbackFromHardware) {
|
||||||
|
await _activateHardwareMode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (mounted && _inputMode == _ScannerInput.loading) {
|
||||||
|
setState(() {
|
||||||
|
_inputMode = _ScannerInput.camera;
|
||||||
|
_isInitialSetup = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _toggleScannerInput() async {
|
||||||
|
if (!_hardwareAvailable ||
|
||||||
|
_phase != _ScanPhase.scanning ||
|
||||||
|
_isSwitchingInput ||
|
||||||
|
_inputMode == _ScannerInput.loading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() => _isSwitchingInput = true);
|
||||||
|
try {
|
||||||
|
if (_inputMode == _ScannerInput.hardware) {
|
||||||
|
await _activateCameraMode();
|
||||||
|
} else if (_inputMode == _ScannerInput.camera) {
|
||||||
|
await _activateHardwareMode();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() => _isSwitchingInput = false);
|
||||||
|
} else {
|
||||||
|
_isSwitchingInput = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get _showsScannerInput =>
|
||||||
|
_phase == _ScanPhase.scanning ||
|
||||||
|
_phase == _ScanPhase.processing ||
|
||||||
|
_phase == _ScanPhase.success ||
|
||||||
|
_phase == _ScanPhase.warning;
|
||||||
|
|
||||||
Rect _scanWindowRect(Size size) {
|
Rect _scanWindowRect(Size size) {
|
||||||
final center = _scanWindowAlignment.alongSize(size);
|
final center = _scanWindowAlignment.alongSize(size);
|
||||||
return Rect.fromCenter(
|
return Rect.fromCenter(
|
||||||
@@ -87,23 +245,58 @@ class _ScannerScreenState extends State<ScannerScreen>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _cancelAutoDismiss() {
|
||||||
|
_resultAutoDismissTimer?.cancel();
|
||||||
|
_resultAutoDismissTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _scheduleAutoDismiss() {
|
||||||
|
_cancelAutoDismiss();
|
||||||
|
_resultAutoDismissTimer = Timer(_resultDisplayDuration, () {
|
||||||
|
if (!mounted) return;
|
||||||
|
if (_phase == _ScanPhase.success || _phase == _ScanPhase.warning) {
|
||||||
|
unawaited(_scanAgain());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_cancelAutoDismiss();
|
||||||
_scanLineController.dispose();
|
_scanLineController.dispose();
|
||||||
_resultController.dispose();
|
_resultController.dispose();
|
||||||
_scannerController.dispose();
|
_cameraController?.dispose();
|
||||||
|
_hardwareScanner.deactivate();
|
||||||
_feedbackService.dispose();
|
_feedbackService.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onDetect(BarcodeCapture capture) async {
|
void _onRawScan(String rawValue) {
|
||||||
if (_phase != _ScanPhase.scanning || _isHandlingDetection) return;
|
_processScan(rawValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onDetect(BarcodeCapture capture) async {
|
||||||
final rawValue = capture.barcodes.firstOrNull?.rawValue?.trim();
|
final rawValue = capture.barcodes.firstOrNull?.rawValue?.trim();
|
||||||
if (rawValue == null || rawValue.isEmpty) return;
|
if (rawValue == null || rawValue.isEmpty) return;
|
||||||
|
await _processScan(rawValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _processScan(String rawValue) async {
|
||||||
|
if (_isHandlingDetection || _phase == _ScanPhase.processing) return;
|
||||||
|
|
||||||
final code = _extractCode(rawValue);
|
final code = _extractCode(rawValue);
|
||||||
if (code.isEmpty || code == _lastScannedCode) return;
|
if (code.isEmpty) return;
|
||||||
|
|
||||||
|
final showingResult =
|
||||||
|
_phase == _ScanPhase.success || _phase == _ScanPhase.warning;
|
||||||
|
|
||||||
|
if (showingResult) {
|
||||||
|
_cancelAutoDismiss();
|
||||||
|
_resultController.reset();
|
||||||
|
} else {
|
||||||
|
if (_phase != _ScanPhase.scanning) return;
|
||||||
|
if (code == _lastScannedCode) return;
|
||||||
|
}
|
||||||
|
|
||||||
_isHandlingDetection = true;
|
_isHandlingDetection = true;
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -114,7 +307,9 @@ class _ScannerScreenState extends State<ScannerScreen>
|
|||||||
});
|
});
|
||||||
|
|
||||||
_scanLineController.stop();
|
_scanLineController.stop();
|
||||||
await _scannerController.stop();
|
if (_inputMode == _ScannerInput.camera) {
|
||||||
|
await _cameraController?.stop();
|
||||||
|
}
|
||||||
await _feedbackService.onCodeDetected();
|
await _feedbackService.onCodeDetected();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -166,9 +361,15 @@ class _ScannerScreenState extends State<ScannerScreen>
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _resultController.forward();
|
await _resultController.forward();
|
||||||
|
_scheduleAutoDismiss();
|
||||||
|
|
||||||
|
if (_inputMode == _ScannerInput.camera) {
|
||||||
|
await _cameraController?.start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _scanAgain() async {
|
Future<void> _scanAgain() async {
|
||||||
|
_cancelAutoDismiss();
|
||||||
_resultController.reset();
|
_resultController.reset();
|
||||||
_isHandlingDetection = false;
|
_isHandlingDetection = false;
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -177,8 +378,10 @@ class _ScannerScreenState extends State<ScannerScreen>
|
|||||||
_message = null;
|
_message = null;
|
||||||
_passCode = null;
|
_passCode = null;
|
||||||
});
|
});
|
||||||
_scanLineController.repeat();
|
if (_inputMode == _ScannerInput.camera) {
|
||||||
await _scannerController.start();
|
_scanLineController.repeat();
|
||||||
|
await _cameraController?.start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -203,16 +406,45 @@ class _ScannerScreenState extends State<ScannerScreen>
|
|||||||
),
|
),
|
||||||
title: const Text('Escanear pase'),
|
title: const Text('Escanear pase'),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
|
actions: [
|
||||||
|
if (_hardwareAvailable && _phase == _ScanPhase.scanning)
|
||||||
|
IconButton(
|
||||||
|
tooltip: _inputMode == _ScannerInput.hardware
|
||||||
|
? 'Usar cámara'
|
||||||
|
: 'Usar escáner Honeywell',
|
||||||
|
icon: _isSwitchingInput
|
||||||
|
? const SizedBox(
|
||||||
|
width: 22,
|
||||||
|
height: 22,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Icon(
|
||||||
|
_inputMode == _ScannerInput.hardware
|
||||||
|
? Icons.camera_alt_outlined
|
||||||
|
: Icons.barcode_reader,
|
||||||
|
),
|
||||||
|
onPressed: _isSwitchingInput ? null : _toggleScannerInput,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
body: Stack(
|
body: Stack(
|
||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
children: [
|
children: [
|
||||||
if (_phase == _ScanPhase.scanning || _phase == _ScanPhase.processing)
|
if (_inputMode == _ScannerInput.loading && _isInitialSetup)
|
||||||
|
const Center(
|
||||||
|
child: CircularProgressIndicator(color: AppTheme.primary),
|
||||||
|
),
|
||||||
|
if (_showsScannerInput &&
|
||||||
|
_inputMode == _ScannerInput.camera &&
|
||||||
|
_cameraController != null)
|
||||||
LayoutBuilder(
|
LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
final scanRect = _scanWindowRect(constraints.biggest);
|
final scanRect = _scanWindowRect(constraints.biggest);
|
||||||
return MobileScanner(
|
return MobileScanner(
|
||||||
controller: _scannerController,
|
controller: _cameraController!,
|
||||||
onDetect: _onDetect,
|
onDetect: _onDetect,
|
||||||
scanWindow: scanRect,
|
scanWindow: scanRect,
|
||||||
overlayBuilder: (context, constraints) {
|
overlayBuilder: (context, constraints) {
|
||||||
@@ -236,8 +468,8 @@ class _ScannerScreenState extends State<ScannerScreen>
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (_phase != _ScanPhase.scanning && _phase != _ScanPhase.processing)
|
if (_showsScannerInput && _inputMode == _ScannerInput.hardware)
|
||||||
Container(color: AppTheme.backgroundDark),
|
_buildHardwareScannerView(),
|
||||||
AnimatedContainer(
|
AnimatedContainer(
|
||||||
duration: const Duration(milliseconds: 450),
|
duration: const Duration(milliseconds: 450),
|
||||||
curve: Curves.easeOutCubic,
|
curve: Curves.easeOutCubic,
|
||||||
@@ -256,7 +488,7 @@ class _ScannerScreenState extends State<ScannerScreen>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (_phase == _ScanPhase.scanning)
|
if (_phase == _ScanPhase.scanning && _inputMode != _ScannerInput.loading)
|
||||||
SafeArea(
|
SafeArea(
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.bottomCenter,
|
alignment: Alignment.bottomCenter,
|
||||||
@@ -452,11 +684,92 @@ class _ScannerScreenState extends State<ScannerScreen>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildHardwareScannerView() {
|
||||||
|
return Container(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topCenter,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
|
colors: [
|
||||||
|
AppTheme.backgroundDark,
|
||||||
|
Color(0xFF0D1B2A),
|
||||||
|
Color(0xFF12182B),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 36),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 120,
|
||||||
|
height: 120,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(28),
|
||||||
|
color: AppTheme.primary.withValues(alpha: 0.12),
|
||||||
|
border: Border.all(
|
||||||
|
color: AppTheme.primary.withValues(alpha: 0.35),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.barcode_reader,
|
||||||
|
size: 56,
|
||||||
|
color: AppTheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 28),
|
||||||
|
Text(
|
||||||
|
'Escáner Honeywell',
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Text(
|
||||||
|
'Presiona el botón lateral del EDA51 para leer el código QR del pase.',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||||
|
color: Colors.white.withValues(alpha: 0.6),
|
||||||
|
height: 1.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Container(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withValues(alpha: 0.06),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
border: Border.all(
|
||||||
|
color: Colors.white.withValues(alpha: 0.08),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'Sensor dedicado · Honeywell ScanPal EDA51',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white.withValues(alpha: 0.45),
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildHint() {
|
Widget _buildHint() {
|
||||||
|
final message = _inputMode == _ScannerInput.hardware
|
||||||
|
? 'Presiona el botón lateral para escanear'
|
||||||
|
: 'Apunta la cámara al código QR del pase';
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 32),
|
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Apunta la cámara al código QR del pase',
|
message,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white.withValues(alpha: 0.65),
|
color: Colors.white.withValues(alpha: 0.65),
|
||||||
|
|||||||
@@ -1,43 +1,92 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:audioplayers/audioplayers.dart';
|
import 'package:audioplayers/audioplayers.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:vibration/vibration.dart';
|
import 'package:vibration/vibration.dart';
|
||||||
|
|
||||||
class FeedbackService {
|
class FeedbackService {
|
||||||
FeedbackService() : _player = AudioPlayer() {
|
FeedbackService() {
|
||||||
_player.setReleaseMode(ReleaseMode.stop);
|
_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 {
|
Future<void> onCodeDetected() async {
|
||||||
await Future.wait([
|
await Future.wait([
|
||||||
_play('sounds/scan.wav'),
|
_play(_scanPlayer, 'sounds/scan.wav'),
|
||||||
_vibratePattern(const [0, 40]),
|
_vibratePattern(const [0, 80]),
|
||||||
|
HapticFeedback.mediumImpact(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> onSuccess() async {
|
Future<void> onSuccess() async {
|
||||||
await Future.wait([
|
await Future.wait([
|
||||||
_play('sounds/success.wav'),
|
_playSuccessSequence(),
|
||||||
_vibratePattern(const [0, 90, 70, 140]),
|
_vibratePattern(const [0, 120, 60, 180]),
|
||||||
|
HapticFeedback.heavyImpact(),
|
||||||
]);
|
]);
|
||||||
await HapticFeedback.heavyImpact();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> onWarning() async {
|
Future<void> onWarning() async {
|
||||||
await Future.wait([
|
await Future.wait([
|
||||||
_play('sounds/warning.wav'),
|
_playWarningSequence(),
|
||||||
_vibratePattern(const [0, 120, 80, 120, 80, 120]),
|
_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 {
|
try {
|
||||||
await _player.stop();
|
await player.stop();
|
||||||
await _player.play(AssetSource(asset));
|
await player.play(AssetSource(asset));
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// Ignore audio errors on devices without speaker routing.
|
await SystemSound.play(SystemSoundType.alert);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +100,7 @@ class FeedbackService {
|
|||||||
final intensities = pattern
|
final intensities = pattern
|
||||||
.asMap()
|
.asMap()
|
||||||
.entries
|
.entries
|
||||||
.map((entry) => entry.key.isOdd ? 180 : 0)
|
.map((entry) => entry.key.isOdd ? 255 : 0)
|
||||||
.toList();
|
.toList();
|
||||||
await Vibration.vibrate(
|
await Vibration.vibrate(
|
||||||
pattern: pattern,
|
pattern: pattern,
|
||||||
@@ -62,11 +111,15 @@ class FeedbackService {
|
|||||||
|
|
||||||
await Vibration.vibrate(pattern: pattern);
|
await Vibration.vibrate(pattern: pattern);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
await HapticFeedback.selectionClick();
|
await HapticFeedback.heavyImpact();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> dispose() async {
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -208,6 +208,14 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
honeywell_scanner:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: honeywell_scanner
|
||||||
|
sha256: a2bf8600e8e33887e268df20d300d9341aed708179a3be74bc2acc9f8806a7c1
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "8.0.0"
|
||||||
hooks:
|
hooks:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ dependencies:
|
|||||||
mobile_scanner: ^7.0.1
|
mobile_scanner: ^7.0.1
|
||||||
shared_preferences: ^2.3.3
|
shared_preferences: ^2.3.3
|
||||||
vibration: ^3.1.3
|
vibration: ^3.1.3
|
||||||
|
honeywell_scanner: ^8.0.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user