Files
Holzleitner-Lieferservice-App/lib/feature/scan/presentation/scan_page.dart
2026-05-11 17:12:05 +02:00

928 lines
31 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:async';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hl_lieferservice/feature/car_selection/bloc/bloc.dart';
import 'package:hl_lieferservice/feature/car_selection/bloc/state.dart';
import 'package:hl_lieferservice/feature/delivery/bloc/tour_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/bloc/tour_event.dart';
import 'package:hl_lieferservice/feature/delivery/bloc/tour_state.dart';
import 'package:hl_lieferservice/feature/delivery/overview/presentation/delivery_fail_page.dart';
import 'package:hl_lieferservice/feature/scan/presentation/scanner.dart';
import 'package:hl_lieferservice/feature/settings/bloc/settings_bloc.dart';
import 'package:hl_lieferservice/feature/settings/bloc/settings_state.dart';
import 'package:hl_lieferservice/model/article.dart';
import 'package:hl_lieferservice/model/component.dart';
import 'package:hl_lieferservice/model/delivery.dart';
import 'package:hl_lieferservice/model/tour.dart';
import 'package:hl_lieferservice/widget/home/bloc/navigation_bloc.dart';
import 'package:hl_lieferservice/widget/home/bloc/navigation_event.dart';
import 'package:hl_lieferservice/widget/operations/bloc/operation_bloc.dart';
import 'package:hl_lieferservice/widget/operations/bloc/operation_event.dart';
// ---------------------------------------------------------------------------
// Data helpers
// ---------------------------------------------------------------------------
class _DeliveryGroup {
final Delivery delivery;
final String? carPlate;
final List<Article> articles;
const _DeliveryGroup({
required this.delivery,
required this.articles,
this.carPlate,
});
int get totalArticles => articles.length;
int get completeArticles => articles
.where((a) => a.isFullyScanned)
.length;
int get totalUnits => articles.fold(0, (sum, a) {
if (a.isParent && a.components.isNotEmpty) {
return sum + a.components.fold(0, (s, c) => s + c.requiredAmount);
}
return sum + a.amount;
});
int get scannedUnits => articles.fold(0, (sum, a) {
if (a.isParent && a.components.isNotEmpty) {
return sum + a.components.fold(0, (s, c) => s + c.scannedAmount);
}
return sum + a.scannedAmount + a.scannedRemovedAmount;
});
bool get isComplete => totalArticles > 0 && completeArticles == totalArticles;
bool get hasAnyScanned => scannedUnits > 0;
bool get isPartial => hasAnyScanned && !isComplete;
}
// ---------------------------------------------------------------------------
// ScanPage
// ---------------------------------------------------------------------------
class ScanPage extends StatefulWidget {
const ScanPage({super.key});
@override
State<ScanPage> createState() => _ScanPageState();
}
class _ScanPageState extends State<ScanPage> with SingleTickerProviderStateMixin {
late final TabController _tabController;
final FocusNode _focusNode = FocusNode();
String _buffer = '';
Timer? _bufferTimer;
int? _selectedCarId;
bool _isScanning = false;
@override
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
WidgetsBinding.instance.addPostFrameCallback((_) => _focusNode.requestFocus());
final carState = context.read<CarSelectBloc>().state;
if (carState is CarSelectComplete) {
_selectedCarId = carState.selectedCar.id;
}
}
@override
void dispose() {
_tabController.dispose();
_focusNode.dispose();
_bufferTimer?.cancel();
super.dispose();
}
// -------------------------------------------------------------------------
// Scanner input
// -------------------------------------------------------------------------
void _handleKey(KeyEvent event) {
if (event is! KeyDownEvent) return;
if (event.logicalKey == LogicalKeyboardKey.enter) {
_bufferTimer?.cancel();
if (_buffer.isNotEmpty) {
_handleBarcodeScanned(_buffer);
_buffer = '';
}
} else {
final character = event.character;
if (character != null && character.isNotEmpty) {
_buffer += character;
_bufferTimer?.cancel();
_bufferTimer = Timer(const Duration(milliseconds: 1000), () {
if (_buffer.isNotEmpty) {
_handleBarcodeScanned(_buffer);
_buffer = '';
}
});
}
}
}
/// Extrahiert die Artikelnummer aus einem Barcode der Form
/// `<artikelnummer>;<kundennummer>;<belegnummer>`.
/// Liefert `null`, wenn der Barcode dem erwarteten Format nicht entspricht.
String? _extractArticleNumber(String barcode) {
debugPrint("QR CODE: $barcode");
final parts = barcode.split(';');
if (parts.length != 3) return null;
final articleNumber = parts[0].trim();
if (articleNumber.isEmpty) return null;
return articleNumber;
}
void _handleBarcodeScanned(String barcode) {
if (!mounted) return;
if (_selectedCarId == null) {
context.read<OperationBloc>().add(
FailOperation(message: "Kein Fahrzeug ausgewählt"),
);
return;
}
final articleNumber = _extractArticleNumber(barcode);
if (articleNumber == null) {
context.read<OperationBloc>().add(
FailOperation(message: "Ungültiger Barcode: $barcode"),
);
return;
}
final tourState = context.read<TourBloc>().state;
if (tourState is! TourLoaded) return;
// ── 1. Try component match first (Stückliste) ──
final componentDeliveries = tourState.tour.deliveries
.where((d) => d.state != DeliveryState.finished)
.where((d) {
final parent = d.findParentOfComponent(articleNumber);
if (parent == null) return false;
final comp = parent.findComponent(articleNumber);
return comp != null && !comp.isFullyScanned;
})
.toList();
if (componentDeliveries.isNotEmpty) {
if (componentDeliveries.length == 1) {
setState(() => _isScanning = true);
context.read<TourBloc>().add(ScanComponentEvent(
componentArticleNumber: articleNumber,
carId: _selectedCarId!.toString(),
deliveryId: componentDeliveries.first.id,
));
return;
}
_showCustomerSelectionSheet(
articleNumber,
componentDeliveries,
tourState.tour,
isComponent: true,
);
return;
}
// ── 2. Regular article scan ──
final needingDeliveries = tourState.tour.deliveries
.where((d) => d.state != DeliveryState.finished)
.where((d) => d.articles.any((a) =>
a.articleNumber == articleNumber &&
!a.isParent &&
a.scannedAmount + a.scannedRemovedAmount < a.amount))
.toList();
if (needingDeliveries.isEmpty) {
setState(() => _isScanning = true);
context.read<TourBloc>().add(ScanArticleEvent(
articleNumber: articleNumber,
carId: _selectedCarId!.toString(),
deliveryId: tourState.tour.deliveries.first.id,
));
return;
}
if (needingDeliveries.length == 1) {
setState(() => _isScanning = true);
context.read<TourBloc>().add(ScanArticleEvent(
articleNumber: articleNumber,
carId: _selectedCarId!.toString(),
deliveryId: needingDeliveries.first.id,
));
return;
}
_showCustomerSelectionSheet(articleNumber, needingDeliveries, tourState.tour);
}
void _showCustomerSelectionSheet(
String articleNumber,
List<Delivery> deliveries,
Tour tour, {
bool isComponent = false,
}) {
final tourBloc = context.read<TourBloc>();
final carId = _selectedCarId!;
showModalBottomSheet(
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
builder: (ctx) {
return SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 8),
Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: Colors.grey.shade300,
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(height: 12),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
const Icon(Icons.help_outline, size: 20),
const SizedBox(width: 8),
Text(
"Für welchen Kunden?",
style: Theme.of(ctx).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
],
),
),
const SizedBox(height: 8),
const Divider(height: 1),
...deliveries.map((delivery) {
final carPlate = _lookupCarPlate(delivery.carId, tour);
return ListTile(
leading: const Icon(Icons.person_outline),
title: Text(delivery.customer.name),
subtitle: Text(
delivery.customer.address.toString(),
style: const TextStyle(fontSize: 12),
),
trailing: carPlate != null ? _carBadge(ctx, carPlate) : null,
onTap: () {
Navigator.pop(ctx);
setState(() => _isScanning = true);
if (isComponent) {
tourBloc.add(ScanComponentEvent(
componentArticleNumber: articleNumber,
carId: carId.toString(),
deliveryId: delivery.id,
));
} else {
tourBloc.add(ScanArticleEvent(
articleNumber: articleNumber,
carId: carId.toString(),
deliveryId: delivery.id,
));
}
},
);
}),
const SizedBox(height: 8),
],
),
);
},
);
}
// -------------------------------------------------------------------------
// Data
// -------------------------------------------------------------------------
String? _lookupCarPlate(int? carId, Tour tour) {
if (carId == null) return null;
return tour.driver.cars.firstWhereOrNull((c) => c.id == carId)?.plate;
}
List<_DeliveryGroup> _buildDeliveryGroups(Tour tour) {
final List<_DeliveryGroup> groups = [];
for (final delivery in tour.deliveries) {
if (delivery.state == DeliveryState.finished) continue;
final scannableArticles =
delivery.articles.where((a) => a.scannable).toList();
if (scannableArticles.isEmpty) continue;
groups.add(_DeliveryGroup(
delivery: delivery,
articles: scannableArticles,
carPlate: _lookupCarPlate(delivery.carId, tour),
));
}
return groups;
}
// -------------------------------------------------------------------------
// Widgets
// -------------------------------------------------------------------------
Widget _carBadge(BuildContext context, String plate) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primaryContainer,
borderRadius: BorderRadius.circular(6),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.local_shipping_outlined,
size: 12,
color: Theme.of(context).colorScheme.onPrimaryContainer,
),
const SizedBox(width: 4),
Text(
plate,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.onPrimaryContainer,
),
),
],
),
);
}
Widget _buildProgressHeader(List<_DeliveryGroup> allGroups) {
final total = allGroups.length;
final done = allGroups.where((g) => g.isComplete).length;
final progress = total > 0 ? done / total : 0.0;
return Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Beladungsfortschritt",
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
Text(
"$done / $total Kunden",
style: Theme.of(context).textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 6),
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: LinearProgressIndicator(
value: progress,
minHeight: 6,
backgroundColor:
Theme.of(context).colorScheme.surfaceContainerHighest,
valueColor: AlwaysStoppedAnimation<Color>(
done == total && total > 0
? Colors.green
: Theme.of(context).primaryColor,
),
),
),
],
),
);
}
Widget _buildDeliveryTile(_DeliveryGroup group) {
final isComplete = group.isComplete;
final isPartial = group.isPartial;
final Color cardColor;
final Color borderColor;
final Color titleColor;
final Color leadingColor;
if (isComplete) {
cardColor = Colors.green.withValues(alpha: 0.07);
borderColor = Colors.green.withValues(alpha: 0.35);
titleColor = Colors.green.shade700;
leadingColor = Colors.green;
} else if (isPartial) {
cardColor = Colors.orange.withValues(alpha: 0.07);
borderColor = Colors.orange.withValues(alpha: 0.35);
titleColor = Colors.orange.shade800;
leadingColor = Colors.orange.shade700;
} else {
cardColor = Theme.of(context).colorScheme.surfaceContainerLow;
borderColor = Colors.transparent;
titleColor = Theme.of(context).colorScheme.onSurface;
leadingColor = Theme.of(context).colorScheme.onSurfaceVariant;
}
return Card(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
elevation: 0,
color: cardColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(color: borderColor),
),
child: ExpansionTile(
shape: const Border(),
collapsedShape: const Border(),
leading: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: isComplete
? Icon(
Icons.check_circle_rounded,
color: leadingColor,
size: 32,
key: const ValueKey('done'),
)
: SizedBox(
width: 36,
key: const ValueKey('progress'),
child: Center(
child: Text(
'${group.completeArticles}/${group.totalArticles}',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: leadingColor,
),
),
),
),
),
title: Text(
group.delivery.customer.name,
style: TextStyle(
fontWeight: FontWeight.w600,
color: titleColor,
),
),
subtitle: Text(
group.delivery.customer.address.toString(),
style: TextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
trailing: group.carPlate != null
? _carBadge(context, group.carPlate!)
: null,
children: [
const Divider(height: 1, indent: 16, endIndent: 16),
...group.articles.map(_buildArticleEntry),
const SizedBox(height: 4),
],
),
);
}
Widget _buildArticleEntry(Article article) {
if (article.isParent && article.components.isNotEmpty) {
return _buildParentArticleEntry(article);
}
final entryDone = article.isFullyScanned;
return ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 2),
leading: Icon(
entryDone ? Icons.check_circle_outline : Icons.inventory_2_outlined,
color: entryDone
? Colors.green
: Theme.of(context).colorScheme.onSurfaceVariant,
size: 20,
),
title: Text(
article.name,
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
),
subtitle: Text(
"Artikelnr. ${article.articleNumber}",
style: TextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
trailing: Text(
'${article.scannedAmount + article.scannedRemovedAmount} / ${article.amount}×',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 13,
color: entryDone
? Colors.green
: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
);
}
/// Renders a parent article (Stückliste) with its components listed below.
Widget _buildParentArticleEntry(Article article) {
final allDone = article.isFullyScanned;
final scannedCount =
article.components.where((c) => c.isFullyScanned).length;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListTile(
contentPadding:
const EdgeInsets.symmetric(horizontal: 24, vertical: 2),
leading: Icon(
allDone
? Icons.check_circle_outline
: Icons.account_tree_outlined,
color: allDone
? Colors.green
: Theme.of(context).colorScheme.primary,
size: 20,
),
title: Text(
article.name,
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
),
subtitle: Text(
"Stückliste · $scannedCount/${article.components.length} Komponenten",
style: TextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
trailing: Icon(
allDone ? Icons.check_circle : Icons.pending_outlined,
color: allDone ? Colors.green : Colors.orange,
size: 18,
),
),
...article.components.map(_buildComponentEntry),
],
);
}
/// Single component row, indented below the parent article.
Widget _buildComponentEntry(Component component) {
final done = component.isFullyScanned;
return Padding(
padding: const EdgeInsets.only(left: 32),
child: ListTile(
dense: true,
contentPadding:
const EdgeInsets.symmetric(horizontal: 24, vertical: 0),
leading: Icon(
done
? Icons.check_circle_outline
: Icons.radio_button_unchecked,
color: done
? Colors.green
: Theme.of(context).colorScheme.onSurfaceVariant,
size: 16,
),
title: Text(
component.name,
style: const TextStyle(fontSize: 13),
),
subtitle: Text(
"Artikelnr. ${component.articleNumber}",
style: TextStyle(
fontSize: 11,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
trailing: Text(
'${component.scannedAmount} / ${component.requiredAmount}×',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12,
color: done
? Colors.green
: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
);
}
// -------------------------------------------------------------------------
// Tab views
// -------------------------------------------------------------------------
Widget _buildOpenTab(
TourLoaded state,
List<_DeliveryGroup> openGroups,
List<_DeliveryGroup> allGroups,
bool useHardwareScanner,
) {
return Column(
children: [
if (_isScanning)
const LinearProgressIndicator(),
if (!useHardwareScanner && openGroups.isNotEmpty)
Stack(
children: [
BarcodeScannerWidget(onBarcodeDetected: _handleBarcodeScanned),
if (state.pendingScanRequests > 0)
Positioned(
top: 8,
right: 8,
child: Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: Colors.black54,
borderRadius: BorderRadius.circular(20),
),
child: const SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation(Colors.white),
),
),
),
),
],
),
_buildProgressHeader(allGroups),
const Divider(height: 1),
Expanded(
child: openGroups.isEmpty
? Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.check_circle_rounded,
size: 64,
color: Colors.green.shade400,
),
const SizedBox(height: 12),
const Text(
"Alle Kunden vollständig beladen!",
style: TextStyle(fontSize: 16),
),
],
),
)
: ListView.builder(
padding: const EdgeInsets.only(top: 8, bottom: 96),
itemCount: openGroups.length,
itemBuilder: (context, index) =>
_buildDeliveryTile(openGroups[index]),
),
),
],
);
}
Widget _buildLoadedTab(List<_DeliveryGroup> loadedGroups) {
if (_selectedCarId == null) {
return const Center(child: Text("Kein Fahrzeug ausgewählt"));
}
if (loadedGroups.isEmpty) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.inventory_2_outlined,
size: 64,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
const SizedBox(height: 12),
Text(
"Noch keine Kunden im Auto",
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
),
);
}
return ListView.builder(
padding: const EdgeInsets.only(top: 8, bottom: 96),
itemCount: loadedGroups.length,
itemBuilder: (context, index) =>
_buildDeliveryTile(loadedGroups[index]),
);
}
@override
Widget build(BuildContext context) {
return BlocConsumer<CarSelectBloc, CarSelectState>(
listener: (context, carState) {
if (carState is CarSelectComplete) {
setState(() => _selectedCarId = carState.selectedCar.id);
}
},
builder: (context, carState) {
return BlocConsumer<TourBloc, TourState>(
listener: (context, tourState) {
if (tourState is TourLoaded && tourState.pendingScanRequests == 0) {
setState(() => _isScanning = false);
}
},
builder: (context, tourState) {
if (tourState is TourLoadingFailed) {
return const DeliveryLoadingFailedPage();
}
if (tourState is! TourLoaded) {
return const Center(child: CircularProgressIndicator());
}
final settingsState = context.read<SettingsBloc>().state;
final useHardwareScanner = settingsState is AppSettingsLoaded &&
settingsState.settings.useHardwareScanner;
if (settingsState is AppSettingsFailed) {
context.read<OperationBloc>().add(FailOperation(
message:
"Einstellungen konnten nicht geladen werden. Nutze Kamera-Scanner.",
));
}
final allGroups = _buildDeliveryGroups(tourState.tour);
// Offen: Lieferung hat noch mindestens einen nicht vollständig
// gescannten Artikel (über alle Autos hinweg).
final openGroups =
allGroups.where((g) => !g.isComplete).toList();
// Im Auto: Lieferung des aktuellen Autos, bei der mindestens ein
// Stück gescannt wurde.
final loadedGroups = allGroups
.where((g) =>
g.delivery.carId == _selectedCarId && g.hasAnyScanned)
.toList();
final allDone = tourState.tour.deliveries.isNotEmpty &&
openGroups.isEmpty;
return Scaffold(
appBar: AppBar(
title: const Text("Beladung"),
backgroundColor: Theme.of(context).primaryColor,
foregroundColor: Theme.of(context).colorScheme.onSecondary,
centerTitle: false,
actions: [
if (carState is CarSelectComplete)
Padding(
padding: const EdgeInsets.only(right: 16),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.local_shipping,
color: Theme.of(context).colorScheme.onSecondary,
size: 20,
),
const SizedBox(width: 6),
Text(
carState.selectedCar.plate,
style: TextStyle(
color:
Theme.of(context).colorScheme.onSecondary,
fontWeight: FontWeight.bold,
),
),
],
),
),
],
bottom: TabBar(
controller: _tabController,
labelColor: Theme.of(context).colorScheme.onSecondary,
unselectedLabelColor: Theme.of(context)
.colorScheme
.onSecondary
.withValues(alpha: 0.6),
indicatorColor: Theme.of(context).colorScheme.onSecondary,
tabs: [
Tab(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.pending_outlined, size: 18),
const SizedBox(width: 6),
const Text("Offen"),
if (openGroups.isNotEmpty) ...[
const SizedBox(width: 6),
_tabBadge(
context,
openGroups.length.toString(),
),
],
],
),
),
Tab(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.local_shipping_outlined, size: 18),
const SizedBox(width: 6),
const Text("Im Auto"),
if (loadedGroups.isNotEmpty) ...[
const SizedBox(width: 6),
_tabBadge(
context,
loadedGroups.length.toString(),
color: Colors.green,
),
],
],
),
),
],
),
),
floatingActionButton: allDone
? FloatingActionButton.extended(
onPressed: () {
context
.read<NavigationBloc>()
.add(NavigateToIndex(index: 1));
},
icon: const Icon(Icons.local_shipping_outlined),
label: const Text("Tour starten"),
backgroundColor: Colors.green,
foregroundColor: Colors.white,
)
: null,
body: KeyboardListener(
focusNode: _focusNode,
onKeyEvent: _handleKey,
child: TabBarView(
controller: _tabController,
children: [
_buildOpenTab(
tourState,
openGroups,
allGroups,
useHardwareScanner,
),
_buildLoadedTab(loadedGroups),
],
),
),
);
},
);
},
);
}
Widget _tabBadge(BuildContext context, String label, {Color? color}) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: (color ?? Theme.of(context).colorScheme.onSecondary)
.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(10),
),
child: Text(
label,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.bold,
color: color ?? Theme.of(context).colorScheme.onSecondary,
),
),
);
}
}