part of 'dialog.dart'; /// Papan angka desimal untuk produk yang dijual per timbangan. /// /// Satu kali dialog ini ditutup dengan angka = satu penimbangan = satu baris /// keranjang. Untuk mengoreksi salah timbang, buka lagi baris yang sama lewat /// [initialWeight] alih-alih menambah baris baru. class WeightInputDialog extends StatefulWidget { final Product product; final ProductVariant? variant; final double? initialWeight; final ValueChanged onSubmit; const WeightInputDialog({ super.key, required this.product, required this.onSubmit, this.variant, this.initialWeight, }); /// Berat terkecil yang diterima backend; di bawah ini membulat ke nol. static const double minWeight = 0.001; /// Backend membulatkan berat ke 3 desimal. static const int maxDecimals = 3; @override State createState() => _WeightInputDialogState(); } class _WeightInputDialogState extends State { /// Angka disimpan sebagai teks supaya "4," dan "4,20" bisa diketik utuh /// tanpa dinormalisasi di tengah pengetikan. late String _input = _formatInitial(widget.initialWeight); static String _formatInitial(double? weight) { if (weight == null || weight <= 0) return ''; return weight .toStringAsFixed(WeightInputDialog.maxDecimals) .replaceFirst(RegExp(r'\.?0+$'), '') .replaceAll('.', ','); } double get _weight => double.tryParse(_input.replaceAll(',', '.')) ?? 0; bool get _isValid => _weight >= WeightInputDialog.minWeight; double get _unitPrice => widget.product.price + (widget.variant?.priceModifier ?? 0); String get _unitLabel => widget.product.unitLabel; void _tapDigit(String digit) { final separatorIndex = _input.indexOf(','); if (separatorIndex != -1 && _input.length - separatorIndex - 1 >= WeightInputDialog.maxDecimals) { return; } setState(() { // Hindari "007": nol di depan hanya berarti sesuatu sebelum koma. _input = _input == '0' ? digit : '$_input$digit'; }); } void _tapSeparator() { if (_input.contains(',')) return; setState(() => _input = _input.isEmpty ? '0,' : '$_input,'); } void _tapBackspace() { if (_input.isEmpty) return; setState(() => _input = _input.substring(0, _input.length - 1)); } void _tapClear() => setState(() => _input = ''); void _submit() { if (!_isValid) return; final rounded = double.parse( _weight.toStringAsFixed(WeightInputDialog.maxDecimals), ); context.maybePop(); widget.onSubmit(rounded); } @override Widget build(BuildContext context) { return CustomModalDialog( title: widget.product.name, subtitle: _unitLabel.isEmpty ? 'Masukkan berat' : 'Masukkan berat dalam $_unitLabel', minWidth: 380, maxWidth: 420, minHeight: 0, contentPadding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _buildDisplay(), const SpaceHeight(16), _buildKeypad(), const SpaceHeight(16), AppElevatedButton.filled( borderRadius: 12, disabled: !_isValid, onPressed: _submit, label: widget.initialWeight == null ? 'Tambah ke Pesanan' : 'Simpan Berat', ), if (_input.isNotEmpty && !_isValid) ...[ const SpaceHeight(8), Text( 'Berat minimal 0,001${_unitLabel.isEmpty ? '' : ' $_unitLabel'}', textAlign: TextAlign.center, style: AppStyle.sm.copyWith(color: AppColor.error), ), ], ], ), ); } Widget _buildDisplay() { return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: AppColor.primary.withOpacity(0.06), borderRadius: BorderRadius.circular(12), border: Border.all(color: AppColor.primary.withOpacity(0.3)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.baseline, textBaseline: TextBaseline.alphabetic, children: [ Expanded( child: Text( _input.isEmpty ? '0' : _input, textAlign: TextAlign.right, style: AppStyle.xxl.copyWith( fontSize: 40, fontWeight: FontWeight.bold, color: _input.isEmpty ? AppColor.textSecondary : AppColor.black, ), ), ), if (_unitLabel.isNotEmpty) ...[ const SpaceWidth(8), Text( _unitLabel, style: AppStyle.lg.copyWith(color: AppColor.textSecondary), ), ], ], ), const SpaceHeight(4), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '${_unitPrice.currencyFormatRpV2}' '${_unitLabel.isEmpty ? '' : ' / $_unitLabel'}', style: AppStyle.sm.copyWith(color: AppColor.textSecondary), ), Text( (_unitPrice * _weight).currencyFormatRpV2, style: AppStyle.lg.copyWith( fontWeight: FontWeight.bold, color: AppColor.primary, ), ), ], ), ], ), ); } Widget _buildKeypad() { const keys = [ ['1', '2', '3'], ['4', '5', '6'], ['7', '8', '9'], [',', '0', 'hapus'], ]; return Column( children: [ for (final row in keys) Padding( padding: const EdgeInsets.only(bottom: 8), child: Row( children: [ for (final key in row) Expanded( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 4), child: _KeypadButton( label: key, onTap: () { switch (key) { case ',': _tapSeparator(); case 'hapus': _tapBackspace(); default: _tapDigit(key); } }, // Tahan lama untuk mengosongkan angka sekaligus. onLongPress: key == 'hapus' ? _tapClear : null, ), ), ), ], ), ), ], ); } } class _KeypadButton extends StatelessWidget { final String label; final VoidCallback onTap; final VoidCallback? onLongPress; const _KeypadButton({ required this.label, required this.onTap, this.onLongPress, }); @override Widget build(BuildContext context) { final isBackspace = label == 'hapus'; return InkWell( onTap: onTap, onLongPress: onLongPress, borderRadius: BorderRadius.circular(12), child: Container( height: 56, alignment: Alignment.center, decoration: BoxDecoration( color: AppColor.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: AppColor.border), ), child: isBackspace ? const Icon(Icons.backspace_outlined, color: AppColor.primary) : Text( label, style: AppStyle.xxl.copyWith(fontWeight: FontWeight.w600), ), ), ); } }