zebra_tc 1.0.0
zebra_tc: ^1.0.0 copied to clipboard
Flutter plugin for Zebra TC series built-in barcode scanner via DataWedge.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:zebra_tc/zebra_tc.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Zebra TC Example',
theme: ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange), useMaterial3: true),
home: const ScannerScreen(),
);
}
}
class ScannerScreen extends StatefulWidget {
const ScannerScreen({super.key});
@override
State<ScannerScreen> createState() => _ScannerScreenState();
}
class _ScannerScreenState extends State<ScannerScreen> {
final _zebra = ZebraTc();
final List<ZebraScan> _scans = [];
StreamSubscription<ZebraScan>? _subscription;
bool _isListening = false;
bool _flashOn = true;
String? _statusMessage;
@override
void initState() {
super.initState();
_init();
}
@override
void dispose() {
_subscription?.cancel();
super.dispose();
}
Future<void> _init() async {
try {
await _zebra.createProfile('ZebraTcExample');
_setStatus('Profile created. Tap "Listen" to start scanning.');
} catch (e) {
_setStatus('Init error: $e');
}
}
void _startListening() {
_subscription = _zebra.scanStream.listen((scan) {
setState(() => _scans.insert(0, scan));
}, onError: (e) => _setStatus('Stream error: $e'));
setState(() => _isListening = true);
_setStatus('Listening for scans…');
}
Future<void> _stopListening() async {
await _subscription?.cancel();
_subscription = null;
setState(() => _isListening = false);
_setStatus('Stopped listening.');
}
Future<void> _toggleFlash() async {
final next = !_flashOn;
await _zebra.setFlash(enabled: next);
setState(() => _flashOn = next);
}
void _clearScans() => setState(() => _scans.clear());
void _setStatus(String msg) => setState(() => _statusMessage = msg);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Zebra TC Scanner'),
actions: [
if (_scans.isNotEmpty)
IconButton(icon: const Icon(Icons.clear_all), tooltip: 'Clear results', onPressed: _clearScans),
],
),
body: Column(
children: [
_ControlPanel(
isListening: _isListening,
flashOn: _flashOn,
onToggleListen: _isListening ? _stopListening : _startListening,
onSoftScanStart: () => _zebra.startScan(),
onSoftScanStop: () => _zebra.stopScan(),
onToggleFlash: _toggleFlash,
),
if (_statusMessage != null) _StatusBanner(message: _statusMessage!),
Expanded(
child: _scans.isEmpty
? const _EmptyState()
: ListView.separated(
padding: const EdgeInsets.symmetric(vertical: 8),
itemCount: _scans.length,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (_, i) => _ScanTile(scan: _scans[i], index: _scans.length - i),
),
),
],
),
);
}
}
class _ControlPanel extends StatelessWidget {
const _ControlPanel({
required this.isListening,
required this.flashOn,
required this.onToggleListen,
required this.onSoftScanStart,
required this.onSoftScanStop,
required this.onToggleFlash,
});
final bool isListening;
final bool flashOn;
final VoidCallback onToggleListen;
final VoidCallback onSoftScanStart;
final VoidCallback onSoftScanStop;
final VoidCallback onToggleFlash;
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.all(12),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
FilledButton.icon(
icon: Icon(isListening ? Icons.stop : Icons.play_arrow),
label: Text(isListening ? 'Stop Listening' : 'Start Listening'),
style: isListening ? FilledButton.styleFrom(backgroundColor: Colors.red) : null,
onPressed: onToggleListen,
),
const SizedBox(height: 8),
Row(
children: [
Expanded(
child: OutlinedButton.icon(
icon: const Icon(Icons.qr_code_scanner),
label: const Text('Soft Scan'),
onPressed: onSoftScanStart,
),
),
const SizedBox(width: 8),
Expanded(
child: OutlinedButton.icon(
icon: const Icon(Icons.stop_circle_outlined),
label: const Text('Stop Scan'),
onPressed: onSoftScanStop,
),
),
const SizedBox(width: 8),
IconButton.outlined(
icon: Icon(flashOn ? Icons.flash_on : Icons.flash_off),
tooltip: flashOn ? 'Flash off' : 'Flash on',
onPressed: onToggleFlash,
),
],
),
],
),
),
);
}
}
class _StatusBanner extends StatelessWidget {
const _StatusBanner({required this.message});
final String message;
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
color: Theme.of(context).colorScheme.surfaceContainerHighest,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
child: Text(message, style: Theme.of(context).textTheme.bodySmall),
);
}
}
class _EmptyState extends StatelessWidget {
const _EmptyState();
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.barcode_reader, size: 64, color: Colors.grey.shade400),
const SizedBox(height: 12),
Text('No scans yet', style: Theme.of(context).textTheme.titleMedium?.copyWith(color: Colors.grey)),
const SizedBox(height: 4),
Text(
'Tap "Start Listening", then scan a barcode',
style: Theme.of(context).textTheme.bodySmall?.copyWith(color: Colors.grey),
),
],
),
);
}
}
class _ScanTile extends StatelessWidget {
const _ScanTile({required this.scan, required this.index});
final ZebraScan scan;
final int index;
@override
Widget build(BuildContext context) {
return ListTile(
leading: CircleAvatar(
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
child: Text('$index', style: TextStyle(color: Theme.of(context).colorScheme.onPrimaryContainer, fontSize: 12)),
),
title: Text(scan.barcode, style: const TextStyle(fontFamily: 'monospace', fontSize: 15)),
subtitle: Text(scan.symbology.isEmpty ? '—' : scan.symbology),
trailing: const Icon(Icons.check_circle, color: Colors.green, size: 18),
);
}
}