refactor: extract boxing logic into dedicated modules with tests
- boxing_models.dart: add BoxingMode, BoxingPhase, BoxingPageArguments - boxing_box_mutations.dart: box item add/replace/remove operations - boxing_calculations.dart: pure calculation helpers (quantities, validation) - boxing_status_presenter.dart: status bar text/dot determination - dialogs/cross_paichan_dialog.dart: cross-paichan confirmation dialog - widgets/boxing_notice_banners.dart: notice banner widgets - Add unit tests for mutations, calculations, and status presenter Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
178
lib/pages/boxing/widgets/boxing_notice_banners.dart
Normal file
178
lib/pages/boxing/widgets/boxing_notice_banners.dart
Normal file
@@ -0,0 +1,178 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pad_scanner/pages/boxing/boxing_models.dart';
|
||||
|
||||
class BoxingNoticeBanners extends StatelessWidget {
|
||||
final bool isAutoProcessing;
|
||||
final List<String> autoWarnings;
|
||||
final String? paichanSwitchNotice;
|
||||
final bool isDuplicateBoxNo;
|
||||
final bool quantityTooHigh;
|
||||
final BoxingPhase phase;
|
||||
final String boxNoText;
|
||||
final int remainingQuantity;
|
||||
final String? zongpaiNo;
|
||||
final int? completedJumpBoxNo;
|
||||
final VoidCallback onJumpToCompletedBox;
|
||||
|
||||
const BoxingNoticeBanners({
|
||||
super.key,
|
||||
required this.isAutoProcessing,
|
||||
required this.autoWarnings,
|
||||
required this.paichanSwitchNotice,
|
||||
required this.isDuplicateBoxNo,
|
||||
required this.quantityTooHigh,
|
||||
required this.phase,
|
||||
required this.boxNoText,
|
||||
required this.remainingQuantity,
|
||||
required this.zongpaiNo,
|
||||
required this.completedJumpBoxNo,
|
||||
required this.onJumpToCompletedBox,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final banners = <Widget>[];
|
||||
if (isAutoProcessing) {
|
||||
banners.add(
|
||||
_NoticeBanner(
|
||||
icon: SizedBox(
|
||||
width: 12,
|
||||
height: 12,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 1.5,
|
||||
color: Colors.orange.shade700,
|
||||
),
|
||||
),
|
||||
text: '正在同步转运数据...',
|
||||
background: Colors.orange.shade50,
|
||||
foreground: Colors.orange.shade900,
|
||||
border: Colors.orange.shade100,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (autoWarnings.isNotEmpty) {
|
||||
banners.add(
|
||||
_NoticeBanner(
|
||||
icon: Icon(
|
||||
Icons.warning_amber,
|
||||
size: 14,
|
||||
color: Colors.amber.shade900,
|
||||
),
|
||||
text: autoWarnings.join(';'),
|
||||
background: Colors.amber.shade100,
|
||||
foreground: Colors.amber.shade900,
|
||||
border: Colors.amber.shade200,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (paichanSwitchNotice != null) {
|
||||
banners.add(
|
||||
_NoticeBanner(
|
||||
icon: Icon(Icons.info_outline, size: 14, color: Colors.blue.shade900),
|
||||
text: paichanSwitchNotice!,
|
||||
background: Colors.blue.shade50,
|
||||
foreground: Colors.blue.shade900,
|
||||
border: Colors.blue.shade100,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (isDuplicateBoxNo && phase == BoxingPhase.scanned) {
|
||||
banners.add(
|
||||
_NoticeBanner(
|
||||
icon: Icon(
|
||||
Icons.warning_amber,
|
||||
size: 14,
|
||||
color: Colors.amber.shade900,
|
||||
),
|
||||
text: '箱号 $boxNoText 已存在,请重新输入',
|
||||
background: Colors.amber.shade100,
|
||||
foreground: Colors.amber.shade900,
|
||||
border: Colors.amber.shade200,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (quantityTooHigh && phase == BoxingPhase.scanned) {
|
||||
banners.add(
|
||||
_NoticeBanner(
|
||||
icon: Icon(Icons.close, size: 14, color: Colors.red.shade800),
|
||||
text: '超出可装数量上限(最多可装 $remainingQuantity 件)',
|
||||
background: Colors.red.shade50,
|
||||
foreground: Colors.red.shade800,
|
||||
border: Colors.red.shade100,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (phase == BoxingPhase.scanned &&
|
||||
zongpaiNo != null &&
|
||||
remainingQuantity <= 0) {
|
||||
final jumpBoxNo = completedJumpBoxNo;
|
||||
if (jumpBoxNo != null) {
|
||||
banners.add(
|
||||
GestureDetector(
|
||||
onTap: onJumpToCompletedBox,
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: _NoticeBanner(
|
||||
icon: Icon(Icons.warning, size: 14, color: Colors.amber.shade800),
|
||||
text: '$zongpaiNo 已完成装箱。(P4跳转所在箱号)',
|
||||
background: Colors.amber.shade50,
|
||||
foreground: Colors.amber.shade800,
|
||||
border: Colors.amber.shade200,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
banners.add(
|
||||
_NoticeBanner(
|
||||
icon: Icon(Icons.warning, size: 14, color: Colors.red.shade800),
|
||||
text: '$zongpaiNo 已全部装箱完毕,无需操作',
|
||||
background: Colors.red.shade50,
|
||||
foreground: Colors.red.shade800,
|
||||
border: Colors.red.shade100,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
return Column(mainAxisSize: MainAxisSize.min, children: banners);
|
||||
}
|
||||
}
|
||||
|
||||
class _NoticeBanner extends StatelessWidget {
|
||||
final Widget icon;
|
||||
final String text;
|
||||
final Color background;
|
||||
final Color foreground;
|
||||
final Color border;
|
||||
|
||||
const _NoticeBanner({
|
||||
required this.icon,
|
||||
required this.text,
|
||||
required this.background,
|
||||
required this.foreground,
|
||||
required this.border,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: background,
|
||||
border: Border(bottom: BorderSide(color: border)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
icon,
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(fontSize: 12, color: foreground),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user