redesigned payment page + a lot of fixes

This commit is contained in:
Arseni
2026-02-21 21:55:20 +03:00
parent a68aa2abff
commit 0c6fa03aba
208 changed files with 4062 additions and 2217 deletions

View File

@@ -12,7 +12,8 @@ import 'package:pweb/generated/i18n/app_localizations.dart';
class AddPaymentMethodButton extends StatelessWidget {
final List<PaymentType> types;
final Set<PaymentType> disabledTypes;
final ValueChanged<PaymentType> onAdd;
final ValueChanged<PaymentType>? onAdd;
final VoidCallback? onPressed;
static const double _borderRadius = 14;
static const double _iconSize = 18;
@@ -20,13 +21,18 @@ class AddPaymentMethodButton extends StatelessWidget {
static const double _menuIconSize = 18;
static const double _menuIconTextSpacing = 8;
static const double _buttonHeight = 70;
static const EdgeInsets _buttonPadding = EdgeInsets.symmetric(horizontal: 14, vertical: 10);
static const EdgeInsets _buttonPadding = EdgeInsets.symmetric(
horizontal: 14,
vertical: 10,
);
static const FontWeight _labelWeight = FontWeight.w600;
const AddPaymentMethodButton({
super.key,
required this.types,
required this.disabledTypes,
required this.onAdd,
this.onAdd,
this.onPressed,
});
@override
@@ -41,6 +47,38 @@ class AddPaymentMethodButton extends StatelessWidget {
? theme.colorScheme.primary
: theme.colorScheme.onSurface.withValues(alpha: 0.4);
final buttonChild = Container(
height: _buttonHeight,
padding: _buttonPadding,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(_borderRadius),
border: Border.all(color: borderColor),
color: theme.colorScheme.onSecondary,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.add, size: _iconSize, color: textColor),
const SizedBox(width: _iconTextSpacing),
Text(
l10n.addPaymentMethod,
style: theme.textTheme.titleSmall?.copyWith(
color: textColor,
fontWeight: _labelWeight,
),
),
],
),
);
final onPressed = this.onPressed;
if (onPressed != null) {
return GestureDetector(
onTap: hasEnabled ? onPressed : null,
child: buttonChild,
);
}
return PopupMenuButton<PaymentType>(
enabled: hasEnabled,
onSelected: onAdd,
@@ -67,29 +105,7 @@ class AddPaymentMethodButton extends StatelessWidget {
);
})
.toList(),
child: Container(
height: _buttonHeight,
padding: _buttonPadding,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(_borderRadius),
border: Border.all(color: borderColor),
color: theme.colorScheme.onSecondary,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.add, size: _iconSize, color: textColor),
const SizedBox(width: _iconTextSpacing),
Text(
l10n.addPaymentMethod,
style: theme.textTheme.titleSmall?.copyWith(
color: textColor,
fontWeight: _labelWeight,
),
),
],
),
),
child: buttonChild,
);
}
}

View File

@@ -7,6 +7,8 @@ import 'package:pshared/models/recipient/payment_method_draft.dart';
import 'package:pweb/pages/payment_methods/form.dart';
import 'package:pweb/pages/payment_methods/icon.dart';
import 'package:pweb/widgets/dialogs/confirmation_dialog.dart';
import 'package:pweb/models/state/control_state.dart';
import 'package:pweb/models/state/visibility.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
@@ -17,6 +19,8 @@ class PaymentMethodPanel extends StatelessWidget {
final List<RecipientMethodDraft> entries;
final ValueChanged<int> onRemove;
final void Function(int, PaymentMethodData) onChanged;
final ControlState editState;
final VisibilityState deleteVisibility;
final double padding;
@@ -27,6 +31,8 @@ class PaymentMethodPanel extends StatelessWidget {
required this.entries,
required this.onRemove,
required this.onChanged,
this.editState = ControlState.enabled,
this.deleteVisibility = VisibilityState.visible,
this.padding = 16,
});
@@ -79,7 +85,7 @@ class PaymentMethodPanel extends StatelessWidget {
),
),
),
if (entry != null)
if (entry != null && deleteVisibility == VisibilityState.visible)
TextButton.icon(
onPressed: () => _confirmDelete(context, () => onRemove(selectedIndex)),
icon: Icon(Icons.delete, color: theme.colorScheme.error),
@@ -96,6 +102,7 @@ class PaymentMethodPanel extends StatelessWidget {
key: ValueKey('${selectedType.name}-${entry.existing?.id ?? selectedIndex}-form'),
selectedType: selectedType,
initialData: entry.data,
isEditable: editState == ControlState.enabled,
onChanged: (data) {
if (data == null) return;
onChanged(selectedIndex, data);

View File

@@ -3,8 +3,8 @@ import 'package:flutter/material.dart';
import 'package:pshared/models/payment/type.dart';
import 'package:pshared/models/recipient/payment_method_draft.dart';
import 'package:pweb/models/payment_method_tile/availability.dart';
import 'package:pweb/models/payment_method_tile/selection.dart';
import 'package:pweb/models/payment/method_tile/availability.dart';
import 'package:pweb/models/payment/method_tile/selection.dart';
import 'package:pweb/pages/address_book/form/widgets/payment_methods/add_button.dart';
import 'package:pweb/pages/address_book/form/widgets/payment_methods/tile.dart';
@@ -15,8 +15,10 @@ class PaymentMethodSelectorRow extends StatelessWidget {
final int? selectedIndex;
final Map<PaymentType, List<RecipientMethodDraft>> methods;
final void Function(PaymentType type, int index) onSelected;
final ValueChanged<PaymentType> onAdd;
final ValueChanged<PaymentType>? onAdd;
final VoidCallback? onAddPressed;
final Set<PaymentType> disabledTypes;
final String? Function(RecipientMethodDraft entry)? detailsBuilder;
final double spacing;
final double tilePadding;
@@ -29,8 +31,10 @@ class PaymentMethodSelectorRow extends StatelessWidget {
required this.selectedIndex,
required this.methods,
required this.onSelected,
required this.onAdd,
this.onAdd,
this.onAddPressed,
this.disabledTypes = const {},
this.detailsBuilder,
this.spacing = 12,
this.tilePadding = 10,
this.runSpacing = 12,
@@ -51,12 +55,14 @@ class PaymentMethodSelectorRow extends StatelessWidget {
final availability = isAdded
? PaymentMethodTileAvailability.added
: PaymentMethodTileAvailability.available;
final detailsText = detailsBuilder?.call(entry);
tiles.add(
PaymentMethodTile(
type: type,
selection: selection,
availability: availability,
padding: tilePadding,
detailsText: detailsText,
onTap: () => onSelected(type, index),
),
);
@@ -68,6 +74,7 @@ class PaymentMethodSelectorRow extends StatelessWidget {
types: types,
disabledTypes: disabledTypes,
onAdd: onAdd,
onPressed: onAddPressed,
),
);
@@ -78,4 +85,4 @@ class PaymentMethodSelectorRow extends StatelessWidget {
children: tiles,
);
}
}
}

View File

@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
import 'package:pshared/models/payment/type.dart';
import 'package:pweb/models/payment_method_tile/availability.dart';
import 'package:pweb/models/payment_method_tile/selection.dart';
import 'package:pweb/models/payment/method_tile/availability.dart';
import 'package:pweb/models/payment/method_tile/selection.dart';
import 'package:pweb/pages/payment_methods/icon.dart';
import 'package:pweb/utils/payment/label.dart';
@@ -15,6 +15,7 @@ class PaymentMethodTile extends StatelessWidget {
final PaymentMethodTileSelection selection;
final PaymentMethodTileAvailability availability;
final double padding;
final String? detailsText;
final VoidCallback? onTap;
const PaymentMethodTile({
@@ -22,6 +23,7 @@ class PaymentMethodTile extends StatelessWidget {
required this.selection,
required this.availability,
required this.padding,
this.detailsText,
required this.onTap,
});
@@ -50,6 +52,13 @@ class PaymentMethodTile extends StatelessWidget {
final backgroundColor = isSelected
? theme.colorScheme.primary.withValues(alpha: 0.08)
: theme.colorScheme.onSecondary;
final showDetails =
availability == PaymentMethodTileAvailability.added &&
detailsText != null &&
detailsText!.isNotEmpty;
final detailsColor = isSelected
? theme.colorScheme.primary
: theme.colorScheme.onSurfaceVariant;
return IntrinsicWidth(
child: Opacity(
@@ -68,9 +77,9 @@ class PaymentMethodTile extends StatelessWidget {
border: Border.all(color: borderColor),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
iconForPaymentType(type),
@@ -78,30 +87,44 @@ class PaymentMethodTile extends StatelessWidget {
color: isSelected ? theme.colorScheme.primary : theme.colorScheme.onSurface,
),
const SizedBox(width: 8),
Text(
label,
style: theme.textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.w600,
color: isSelected ? theme.colorScheme.primary : theme.colorScheme.onSurface,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: theme.textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.w600,
color: isSelected ? theme.colorScheme.primary : theme.colorScheme.onSurface,
),
),
const SizedBox(height: 8),
if (showDetails)
Text(
detailsText!,
style: theme.textTheme.labelSmall?.copyWith(
color: detailsColor,
fontWeight: FontWeight.w600,
),
)
else
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: badgeColor,
borderRadius: BorderRadius.circular(999),
),
child: Text(
badgeLabel,
style: theme.textTheme.labelSmall?.copyWith(
color: badgeTextColor,
fontWeight: FontWeight.w600,
),
),
),
],
),
],
),
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: badgeColor,
borderRadius: BorderRadius.circular(999),
),
child: Text(
badgeLabel,
style: theme.textTheme.labelSmall?.copyWith(
color: badgeTextColor,
fontWeight: FontWeight.w600,
),
),
),
],
),
),