This commit is contained in:
Arseni
2026-03-04 17:43:18 +03:00
parent 80b25a8608
commit aff804ec58
46 changed files with 1090 additions and 345 deletions

View File

@@ -13,8 +13,6 @@ import 'package:pweb/pages/dashboard/buttons/balance/config.dart';
import 'package:pweb/pages/dashboard/buttons/balance/header.dart';
import 'package:pweb/widgets/refresh_balance/wallet.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class WalletCard extends StatelessWidget {
final Wallet wallet;
@@ -30,7 +28,6 @@ class WalletCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
final loc = AppLocalizations.of(context)!;
final networkLabel = (wallet.network == null || wallet.network == ChainNetwork.unspecified)
? null
: wallet.network!.localizedName(context);
@@ -53,11 +50,12 @@ class WalletCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
BalanceHeader(
title: loc.paymentTypeCryptoWallet,
title: wallet.name,
subtitle: networkLabel,
badge: (symbol == null || symbol.isEmpty) ? null : symbol,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
BalanceAmount(
wallet: wallet,
@@ -65,12 +63,16 @@ class WalletCard extends StatelessWidget {
context.read<WalletsController>().toggleBalanceMask(wallet.id);
},
),
WalletBalanceRefreshButton(
walletRef: wallet.id,
Column(
children: [
WalletBalanceRefreshButton(
walletRef: wallet.id,
),
BalanceAddFunds(onTopUp: onTopUp),
],
),
],
),
BalanceAddFunds(onTopUp: onTopUp),
],
),
),

View File

@@ -1,22 +0,0 @@
import 'package:flutter/material.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class SourceQuotePanelHeader extends StatelessWidget {
const SourceQuotePanelHeader({
super.key,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final l10n = AppLocalizations.of(context)!;
return Text(
l10n.sourceOfFunds,
style: theme.textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.w600,
),
);
}
}

View File

@@ -2,93 +2,134 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:pshared/controllers/balance_mask/wallets.dart';
import 'package:pshared/controllers/payment/source.dart';
import 'package:pshared/provider/payment/multiple/quotation.dart';
import 'package:pweb/controllers/payouts/multiple_payouts.dart';
import 'package:pweb/controllers/payouts/payout_verification.dart';
import 'package:pweb/models/payment/source_funds.dart';
import 'package:pweb/pages/dashboard/payouts/multiple/actions.dart';
import 'package:pweb/pages/dashboard/payouts/multiple/panels/source_quote/header.dart';
import 'package:pweb/pages/dashboard/payouts/multiple/panels/source_quote/summary.dart';
import 'package:pweb/pages/dashboard/payouts/multiple/widgets/quote_status.dart';
import 'package:pweb/pages/payout_page/send/widgets/send_button.dart';
import 'package:pweb/widgets/payment/source_of_funds_panel.dart';
import 'package:pweb/widgets/payment/source_wallet_selector.dart';
import 'package:pweb/widgets/cooldown_hint.dart';
import 'package:pweb/widgets/refresh_balance/wallet.dart';
import 'package:pweb/models/state/control_state.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class SourceQuotePanel extends StatelessWidget {
const SourceQuotePanel({
super.key,
required this.controller,
required this.walletsController,
});
const SourceQuotePanel({super.key, required this.controller});
final MultiplePayoutsController controller;
final WalletsController walletsController;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final verificationController =
context.watch<PayoutVerificationController>();
final l10n = AppLocalizations.of(context)!;
final sourceController = context.watch<PaymentSourceController>();
final verificationController = context
.watch<PayoutVerificationController>();
final quotationProvider = context.watch<MultiQuotationProvider>();
final verificationContextKey = quotationProvider.quotation?.quoteRef ??
final verificationContextKey =
quotationProvider.quotation?.quoteRef ??
quotationProvider.quotation?.idempotencyKey;
final isCooldownActive = verificationController.isCooldownActiveFor(
verificationContextKey,
);
final canSend = controller.canSend && !isCooldownActive;
return Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: theme.colorScheme.surface,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: theme.colorScheme.outlineVariant),
return SourceOfFundsPanel(
title: l10n.sourceOfFunds,
sourceSelector: SourceWalletSelector(
sourceController: sourceController,
isBusy: controller.isBusy,
),
visibleStates: const <SourceOfFundsVisibleState>{
SourceOfFundsVisibleState.headerAction,
SourceOfFundsVisibleState.summary,
SourceOfFundsVisibleState.quoteStatus,
SourceOfFundsVisibleState.sendAction,
},
stateWidgets: <SourceOfFundsVisibleState, Widget>{
SourceOfFundsVisibleState.headerAction: _MultipleRefreshAction(
sourceController: sourceController,
),
SourceOfFundsVisibleState.summary: SourceQuoteSummary(
controller: controller,
spacing: 12,
),
SourceOfFundsVisibleState.quoteStatus: MultipleQuoteStatusCard(
controller: controller,
),
SourceOfFundsVisibleState.sendAction: _MultipleSendAction(
controller: controller,
canSend: canSend,
isCooldownActive: isCooldownActive,
verificationController: verificationController,
verificationContextKey: verificationContextKey,
),
},
);
}
}
class _MultipleRefreshAction extends StatelessWidget {
const _MultipleRefreshAction({required this.sourceController});
final PaymentSourceController sourceController;
@override
Widget build(BuildContext context) {
final selectedWallet = sourceController.selectedWallet;
if (selectedWallet == null) {
return const SizedBox.shrink();
}
return WalletBalanceRefreshButton(walletRef: selectedWallet.id);
}
}
class _MultipleSendAction extends StatelessWidget {
const _MultipleSendAction({
required this.controller,
required this.canSend,
required this.isCooldownActive,
required this.verificationController,
required this.verificationContextKey,
});
final MultiplePayoutsController controller;
final bool canSend;
final bool isCooldownActive;
final PayoutVerificationController verificationController;
final String? verificationContextKey;
@override
Widget build(BuildContext context) {
return Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
SourceQuotePanelHeader(),
const SizedBox(height: 8),
SourceWalletSelector(
walletsController: walletsController,
isBusy: controller.isBusy,
SendButton(
onPressed: () => handleMultiplePayoutSend(context, controller),
state: controller.isSending
? ControlState.loading
: canSend
? ControlState.enabled
: ControlState.disabled,
),
const SizedBox(height: 12),
const Divider(height: 1),
const SizedBox(height: 12),
SourceQuoteSummary(controller: controller, spacing: 12),
const SizedBox(height: 12),
MultipleQuoteStatusCard(controller: controller),
const SizedBox(height: 12),
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SendButton(
onPressed: () => handleMultiplePayoutSend(context, controller),
state: controller.isSending
? ControlState.loading
: canSend
? ControlState.enabled
: ControlState.disabled,
),
if (isCooldownActive) ...[
const SizedBox(height: 8),
CooldownHint(
seconds: verificationController.cooldownRemainingSecondsFor(
verificationContextKey,
),
),
],
],
if (isCooldownActive) ...[
const SizedBox(height: 8),
CooldownHint(
seconds: verificationController.cooldownRemainingSecondsFor(
verificationContextKey,
),
),
),
],
],
),
);
}
}

View File

@@ -1,7 +1,5 @@
import 'package:flutter/material.dart';
import 'package:pshared/controllers/balance_mask/wallets.dart';
import 'package:pweb/controllers/payouts/multiple_payouts.dart';
import 'package:pweb/pages/dashboard/payouts/multiple/panels/source_quote/widget.dart';
import 'package:pweb/pages/dashboard/payouts/multiple/panels/upload_panel/widget.dart';
@@ -9,14 +7,9 @@ import 'package:pweb/pages/dashboard/payouts/multiple/sections/upload_csv/panel_
class UploadCsvLayout extends StatelessWidget {
const UploadCsvLayout({
super.key,
required this.controller,
required this.walletsController,
});
const UploadCsvLayout({super.key, required this.controller});
final MultiplePayoutsController controller;
final WalletsController walletsController;
@override
Widget build(BuildContext context) {
@@ -27,28 +20,17 @@ class UploadCsvLayout extends StatelessWidget {
if (!useHorizontal) {
return Column(
children: [
PanelCard(
child: UploadPanel(
controller: controller,
),
),
PanelCard(child: UploadPanel(controller: controller)),
if (hasFile) ...[
const SizedBox(height: 12),
SourceQuotePanel(
controller: controller,
walletsController: walletsController,
),
SourceQuotePanel(controller: controller),
],
],
);
}
if (!hasFile) {
return PanelCard(
child: UploadPanel(
controller: controller,
),
);
return PanelCard(child: UploadPanel(controller: controller));
}
return IntrinsicHeight(
@@ -57,19 +39,12 @@ class UploadCsvLayout extends StatelessWidget {
children: [
Expanded(
flex: 3,
child: PanelCard(
child: UploadPanel(
controller: controller,
),
),
child: PanelCard(child: UploadPanel(controller: controller)),
),
const SizedBox(width: 12),
Expanded(
flex: 5,
child: SourceQuotePanel(
controller: controller,
walletsController: walletsController,
),
child: SourceQuotePanel(controller: controller),
),
],
),

View File

@@ -6,7 +6,6 @@ import 'package:pweb/controllers/payouts/multiple_payouts.dart';
import 'package:pweb/pages/dashboard/payouts/multiple/sections/upload_csv/header.dart';
import 'package:pweb/pages/dashboard/payouts/multiple/sections/upload_csv/layout.dart';
class UploadCSVSection extends StatelessWidget {
const UploadCSVSection({super.key});
@@ -22,10 +21,7 @@ class UploadCSVSection extends StatelessWidget {
children: [
UploadCsvHeader(theme: theme),
const SizedBox(height: _verticalSpacing),
UploadCsvLayout(
controller: controller,
walletsController: context.watch(),
),
UploadCsvLayout(controller: controller),
],
);
}

View File

@@ -4,13 +4,14 @@ import 'package:provider/provider.dart';
import 'package:pshared/controllers/payment/source.dart';
import 'package:pweb/models/payment/source_funds.dart';
import 'package:pweb/pages/payout_page/send/widgets/method_selector.dart';
import 'package:pweb/pages/payout_page/send/widgets/section/title.dart';
import 'package:pweb/pages/payout_page/send/widgets/section/card.dart';
import 'package:pweb/utils/dimensions.dart';
import 'package:pweb/widgets/payment/source_of_funds_panel.dart';
import 'package:pweb/widgets/refresh_balance/ledger.dart';
import 'package:pweb/widgets/refresh_balance/wallet.dart';
class PaymentSourceOfFundsCard extends StatelessWidget {
final AppDimensions dimensions;
final String title;
@@ -23,38 +24,33 @@ class PaymentSourceOfFundsCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return PaymentSectionCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(child: SectionTitle(title)),
Consumer<PaymentSourceController>(
builder: (context, provider, _) {
final selectedWallet = provider.selectedWallet;
if (selectedWallet != null) {
return WalletBalanceRefreshButton(
walletRef: selectedWallet.id,
);
}
return SourceOfFundsPanel(
title: title,
selectorSpacing: dimensions.paddingSmall,
sourceSelector: const PaymentMethodSelector(),
visibleStates: const <SourceOfFundsVisibleState>{
SourceOfFundsVisibleState.headerAction,
},
stateWidgets: <SourceOfFundsVisibleState, Widget>{
SourceOfFundsVisibleState
.headerAction: Consumer<PaymentSourceController>(
builder: (context, provider, _) {
final selectedWallet = provider.selectedWallet;
if (selectedWallet != null) {
return WalletBalanceRefreshButton(walletRef: selectedWallet.id);
}
final selectedLedger = provider.selectedLedgerAccount;
if (selectedLedger != null) {
return LedgerBalanceRefreshButton(
ledgerAccountRef: selectedLedger.ledgerAccountRef,
);
}
final selectedLedger = provider.selectedLedgerAccount;
if (selectedLedger != null) {
return LedgerBalanceRefreshButton(
ledgerAccountRef: selectedLedger.ledgerAccountRef,
);
}
return const SizedBox.shrink();
},
),
],
),
SizedBox(height: dimensions.paddingSmall),
const PaymentMethodSelector(),
],
),
return const SizedBox.shrink();
},
),
},
);
}
}

View File

@@ -2,12 +2,9 @@ import 'package:flutter/material.dart';
import 'package:pshared/models/payment/operation.dart';
bool shouldShowToAmount(OperationItem operation) {
if (operation.toCurrency.trim().isEmpty) return false;
if (operation.currency.trim().isEmpty) return true;
if (operation.currency != operation.toCurrency) return true;
return (operation.toAmount - operation.amount).abs() > 0.0001;
return true;
}
String formatOperationTime(BuildContext context, DateTime date) {

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:pshared/models/payment/execution_operation.dart';
import 'package:pshared/models/payment/payment.dart';
import 'package:pweb/pages/report/details/header.dart';
@@ -13,12 +14,17 @@ class PaymentDetailsContent extends StatelessWidget {
final Payment payment;
final VoidCallback onBack;
final VoidCallback? onDownloadAct;
final bool Function(PaymentExecutionOperation operation)?
canDownloadOperationDocument;
final ValueChanged<PaymentExecutionOperation>? onDownloadOperationDocument;
const PaymentDetailsContent({
super.key,
required this.payment,
required this.onBack,
this.onDownloadAct,
this.canDownloadOperationDocument,
this.onDownloadOperationDocument,
});
@override
@@ -29,17 +35,15 @@ class PaymentDetailsContent extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
PaymentDetailsHeader(
title: loc.paymentInfo,
onBack: onBack,
),
PaymentDetailsHeader(title: loc.paymentInfo, onBack: onBack),
const SizedBox(height: 16),
PaymentSummaryCard(
PaymentSummaryCard(payment: payment, onDownloadAct: onDownloadAct),
const SizedBox(height: 16),
PaymentDetailsSections(
payment: payment,
onDownloadAct: onDownloadAct,
canDownloadOperationDocument: canDownloadOperationDocument,
onDownloadOperationDocument: onDownloadOperationDocument,
),
const SizedBox(height: 16),
PaymentDetailsSections(payment: payment),
],
),
);

View File

@@ -19,17 +19,17 @@ import 'package:pweb/generated/i18n/app_localizations.dart';
class PaymentDetailsPage extends StatelessWidget {
final String paymentId;
const PaymentDetailsPage({
super.key,
required this.paymentId,
});
const PaymentDetailsPage({super.key, required this.paymentId});
@override
Widget build(BuildContext context) {
return ChangeNotifierProxyProvider<PaymentsProvider, PaymentDetailsController>(
return ChangeNotifierProxyProvider<
PaymentsProvider,
PaymentDetailsController
>(
create: (_) => PaymentDetailsController(paymentId: paymentId),
update: (_, payments, controller) => controller!
..update(payments, paymentId),
update: (_, payments, controller) =>
controller!..update(payments, paymentId),
child: const _PaymentDetailsView(),
);
}
@@ -67,6 +67,17 @@ class _PaymentDetailsView extends StatelessWidget {
onDownloadAct: controller.canDownload
? () => downloadPaymentAct(context, payment.paymentRef ?? '')
: null,
canDownloadOperationDocument:
controller.canDownloadOperationDocument,
onDownloadOperationDocument: (operation) {
final request = controller.operationDocumentRequest(operation);
if (request == null) return;
downloadPaymentAct(
context,
request.paymentRef,
operationRef: request.operationRef,
);
},
);
},
),

View File

@@ -1,36 +1,48 @@
import 'package:flutter/material.dart';
import 'package:pshared/models/payment/execution_operation.dart';
import 'package:pshared/models/payment/payment.dart';
import 'package:pweb/pages/report/details/sections/fx.dart';
import 'package:pweb/pages/report/details/sections/metadata.dart';
import 'package:pweb/pages/report/details/sections/operations/section.dart';
class PaymentDetailsSections extends StatelessWidget {
final Payment payment;
final bool Function(PaymentExecutionOperation operation)?
canDownloadOperationDocument;
final ValueChanged<PaymentExecutionOperation>? onDownloadOperationDocument;
const PaymentDetailsSections({
super.key,
required this.payment,
this.canDownloadOperationDocument,
this.onDownloadOperationDocument,
});
@override
Widget build(BuildContext context) {
final hasFx = _hasFxQuote(payment);
if (!hasFx) {
return PaymentMetadataSection(payment: payment);
}
final hasOperations = payment.operations.isNotEmpty;
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(child: PaymentFxSection(payment: payment)),
const SizedBox(width: 16),
Expanded(child: PaymentMetadataSection(payment: payment)),
if (hasFx) ...[
PaymentFxSection(payment: payment),
const SizedBox(height: 16),
],
if (hasOperations) ...[
PaymentOperationsSection(
payment: payment,
canDownloadDocument: canDownloadOperationDocument,
onDownloadDocument: onDownloadOperationDocument,
),
const SizedBox(height: 16),
],
],
);
}
bool _hasFxQuote(Payment payment) => payment.lastQuote?.fxQuote != null;
}

View File

@@ -0,0 +1,59 @@
import 'package:flutter/material.dart';
import 'package:pshared/models/payment/execution_operation.dart';
import 'package:pshared/models/payment/payment.dart';
import 'package:pweb/pages/report/details/section.dart';
import 'package:pweb/pages/report/details/sections/operations/tile.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class PaymentOperationsSection extends StatelessWidget {
final Payment payment;
final bool Function(PaymentExecutionOperation operation)? canDownloadDocument;
final ValueChanged<PaymentExecutionOperation>? onDownloadDocument;
const PaymentOperationsSection({
super.key,
required this.payment,
this.canDownloadDocument,
this.onDownloadDocument,
});
@override
Widget build(BuildContext context) {
final loc = AppLocalizations.of(context)!;
final operations = payment.operations;
if (operations.isEmpty) {
return const SizedBox.shrink();
}
final children = <Widget>[];
for (var i = 0; i < operations.length; i++) {
final operation = operations[i];
final canDownload = canDownloadDocument?.call(operation) ?? false;
children.add(
OperationHistoryTile(
operation: operation,
canDownloadDocument: canDownload,
onDownloadDocument: canDownload && onDownloadDocument != null
? () => onDownloadDocument!(operation)
: null,
),
);
if (i < operations.length - 1) {
children.addAll([
const SizedBox(height: 8),
Divider(
height: 1,
color: Theme.of(context).dividerColor.withAlpha(20),
),
const SizedBox(height: 8),
]);
}
}
return DetailsSection(title: loc.operationfryTitle, children: children);
}
}

View File

@@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
import 'package:pweb/utils/payment/status_view.dart';
class StepStateChip extends StatelessWidget {
final StatusView view;
const StepStateChip({super.key, required this.view});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: view.backgroundColor,
borderRadius: BorderRadius.circular(999),
),
child: Text(
view.label.toUpperCase(),
style: Theme.of(context).textTheme.labelMedium?.copyWith(
color: view.foregroundColor,
fontWeight: FontWeight.w700,
letterSpacing: 0.2,
),
),
);
}
}

View File

@@ -0,0 +1,70 @@
import 'package:flutter/material.dart';
import 'package:pshared/models/payment/execution_operation.dart';
import 'package:pweb/utils/report/operations/state_mapper.dart';
import 'package:pweb/pages/report/details/sections/operations/state_chip.dart';
import 'package:pweb/utils/report/operations/time_format.dart';
import 'package:pweb/utils/report/operations/title_mapper.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
class OperationHistoryTile extends StatelessWidget {
final PaymentExecutionOperation operation;
final bool canDownloadDocument;
final VoidCallback? onDownloadDocument;
const OperationHistoryTile({
super.key,
required this.operation,
required this.canDownloadDocument,
this.onDownloadDocument,
});
@override
Widget build(BuildContext context) {
final loc = AppLocalizations.of(context)!;
final theme = Theme.of(context);
final title = resolveOperationTitle(loc, operation.code);
final stateView = resolveStepStateView(context, operation.state);
final completedAt = formatCompletedAt(context, operation.completedAt);
final canDownload = canDownloadDocument && onDownloadDocument != null;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
title,
style: theme.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w600,
),
),
),
const SizedBox(width: 8),
StepStateChip(view: stateView),
],
),
const SizedBox(height: 6),
Text(
'${loc.completedAtLabel}: $completedAt',
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
if (canDownload) ...[
const SizedBox(height: 8),
TextButton.icon(
onPressed: onDownloadDocument,
icon: const Icon(Icons.download),
label: Text(loc.downloadAct),
),
],
],
);
}
}

View File

@@ -42,7 +42,9 @@ class PaymentSummaryCard extends StatelessWidget {
final feeLabel = formatMoney(fee);
final paymentRef = (payment.paymentRef ?? '').trim();
final showToAmount = toAmountLabel != '-' && toAmountLabel != amountLabel;
final showToAmount = toAmountLabel != '-';
final showFee = payment.lastQuote != null;
final feeText = feeLabel != '-' ? loc.fee(feeLabel) : loc.fee(loc.noFee);
final showPaymentId = paymentRef.isNotEmpty;
final amountParts = splitAmount(amountLabel);
@@ -85,10 +87,10 @@ class PaymentSummaryCard extends StatelessWidget {
icon: Icons.south_east,
text: loc.recipientWillReceive(toAmountLabel),
),
if (feeLabel != '-')
if (showFee)
InfoLine(
icon: Icons.receipt_long_outlined,
text: loc.fee(feeLabel),
text: feeText,
muted: true,
),
if (onDownloadAct != null) ...[

View File

@@ -14,37 +14,34 @@ class OperationStatusBadge extends StatelessWidget {
const OperationStatusBadge({super.key, required this.status});
Color _badgeColor(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return operationStatusView(l10n, status).color;
}
Color _textColor(Color background) {
// computeLuminance returns 0 for black, 1 for white
return background.computeLuminance() > 0.5 ? Colors.black : Colors.white;
}
@override
Widget build(BuildContext context) {
final label = status.localized(context);
final bg = _badgeColor(context);
final fg = _textColor(bg);
final l10n = AppLocalizations.of(context)!;
final view = operationStatusView(
l10n,
Theme.of(context).colorScheme,
status,
);
final label = view.label;
final bg = view.backgroundColor;
final fg = view.foregroundColor;
return badges.Badge(
badgeStyle: badges.BadgeStyle(
shape: badges.BadgeShape.square,
badgeColor: bg,
borderRadius: BorderRadius.circular(12), // fully rounded
borderRadius: BorderRadius.circular(12), // fully rounded
padding: const EdgeInsets.symmetric(
horizontal: 6, vertical: 2 // tighter padding
horizontal: 6,
vertical: 2, // tighter padding
),
),
badgeContent: Text(
label.toUpperCase(), // or keep sentence case
label.toUpperCase(), // or keep sentence case
style: TextStyle(
color: fg,
fontSize: 11, // smaller text
fontWeight: FontWeight.w500, // medium weight
fontSize: 11, // smaller text
fontWeight: FontWeight.w500, // medium weight
),
),
);

View File

@@ -31,9 +31,7 @@ class OperationFilters extends StatelessWidget {
: '${dateToLocalFormat(context, selectedRange!.start)} ${dateToLocalFormat(context, selectedRange!.end)}';
return Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
elevation: 0,
child: Padding(
padding: const EdgeInsets.all(16),
@@ -61,12 +59,12 @@ class OperationFilters extends StatelessWidget {
OutlinedButton.icon(
onPressed: onPickRange,
icon: const Icon(Icons.date_range_outlined, size: 18),
label: Text(
periodLabel,
overflow: TextOverflow.ellipsis,
),
label: Text(periodLabel, overflow: TextOverflow.ellipsis),
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 10,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
@@ -76,11 +74,7 @@ class OperationFilters extends StatelessWidget {
Wrap(
spacing: 10,
runSpacing: 8,
children: const [
OperationStatus.success,
OperationStatus.processing,
OperationStatus.error,
].map((status) {
children: OperationStatus.values.map((status) {
final label = status.localized(context);
final isSelected = selectedStatuses.contains(status);
return FilterChip(